mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 15:57:33 +01:00
Merge origin/main and stamp v1.91.316.0 (train 46 slot 3, #3593)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
commit
99730eaf68
48 files changed
+1749
-174
No files matched your search
+8
-5
@@ -138,8 +138,9 @@ this rule shipped cites this section by name.
|
||||
**What the lane sends.** The plan and its gate in plain language a person outside the
|
||||
project can follow: what changes, why now, what the change does, what it costs, the lane's
|
||||
pick, and the recommended one-word reply. `gstack-owner-brief render --captain --out <run dir>`
|
||||
prints that from the plan's `## Before → after`, `## Why it matters`,
|
||||
`## What caused this work` and `## Big decisions` sections, with the cost lines
|
||||
prints that from the plan's `## ELI5`, `## Before → after`, `## Why it matters`,
|
||||
`## What caused this work` and `## Big decisions` sections (also saved as `eli5` in
|
||||
`gate.json`, so `gstack-gate render` opens with them), with the cost lines
|
||||
(`GATE_SPEND:`, `GATE_REC_COST:`) and a last line for the controller; a plan missing a
|
||||
section exits `CAPTAIN_BRIEF_INCOMPLETE`, so the lane adds the section and renders again
|
||||
([capy/skill/gstack/captain.md](../capy/skill/gstack/captain.md) § 3 lists them). The lane
|
||||
@@ -156,10 +157,12 @@ controller and been answered into the run
|
||||
every accepted plan change has been incorporated (`revise`), every reopened phase has
|
||||
finished, and the run reports `APPROVED` (`status=complete` after `export`). An answered
|
||||
gate that still prints `PLAN_AMEND_PENDING` or `REOPENED:` is not a completed plan, and an
|
||||
imported approval row alone is not completion.
|
||||
imported approval row alone is not completion. A later gate revision that asks nothing new
|
||||
is answered with the owner's recorded words and says `CARRIED:`; that is the owner's word,
|
||||
not a new ask ([carry-forward](unattended.md#carry-forward)).
|
||||
|
||||
**The record.** `gstack-approvals import --autoplan <run dir> --by "<owner>"` writes the
|
||||
approval row (date, the ask, the owner's exact words, the lane that answered as relayer,
|
||||
**The record.** `gstack-approvals import --autoplan <run dir> --by "<owner>"` (with
|
||||
`--stack <b1,b2,...>` for a stacked PR set) writes the approval row (date, the ask, the owner's exact words, the lane that answered as relayer,
|
||||
what it covers); `gstack-approvals render` puts it in the report and the PR body. Before
|
||||
training the PR the controller compares that row's words and `gate_rev` with the ask it
|
||||
relayed and the reply it carried back; a row that does not match is not READY (shape A).
|
||||
|
||||
+10
-1
@@ -155,7 +155,16 @@ and `/proc` start time; elsewhere `ps -o lstart`), because a pid is reused after
|
||||
a reboot or a machine wake. `bin/gstack-upgrade`, and so the session-start
|
||||
self-upgrade, refuses with [UPGRADE_BUSY](troubleshooting.md#upgrade-busy)
|
||||
while a lease held by a live process outside its own chain exists, naming each
|
||||
holder as `pid <p> (<age>m <command>)`. The team-mode SessionStart hook
|
||||
holder as `pid <p> (<age>m <command>)`. The install-mode self-upgrade also
|
||||
defers while its own chain holds a live lease under 120 minutes old. An install
|
||||
run from inside a workflow (/ship's docs step running `install.sh --for
|
||||
document-release`, #3571) prints `SELF_UPGRADE: skipped (a gstack run in this
|
||||
session holds a workflow lease on this installation: ... (UPGRADE_BUSY))`,
|
||||
leaves the checkout where the run started and stays ok with `behind=`. Each
|
||||
`gstack-skill-start` renews the lease. On Capy the leased pid is the machine's
|
||||
long-lived `capy-server`, so the deferral ends when the lease turns 120
|
||||
minutes old, not when the process exits; `bin/gstack-upgrade` run by hand
|
||||
moves the checkout sooner. The team-mode SessionStart hook
|
||||
(`bin/gstack-session-update`) honors the same leases: it logs
|
||||
`SKIP leased pid=<p>/<age>m` to `analytics/session-update.log` and tries again at
|
||||
the next session start, without writing its hourly throttle stamp. A lease whose
|
||||
|
||||
+35
-3
@@ -768,11 +768,13 @@ with a timing flake: never raise the bound.
|
||||
|
||||
```json
|
||||
{ "sealed": {
|
||||
"paths": ["heldout/**"],
|
||||
"paths": ["heldout/**", "**/sealed/**"],
|
||||
"transcripts": ["runs/**/transcript*.jsonl"],
|
||||
"allow_paths": ["**/sealed/*/MANIFEST.json", "**/protected/MANIFEST.json"],
|
||||
"answer_markers": ["SEALED ANSWER:"],
|
||||
"seed_patterns": ["shuffle_seed=\\d+"],
|
||||
"local_paths": true } }
|
||||
"local_paths": true,
|
||||
"case_insensitive": true } }
|
||||
```
|
||||
|
||||
A repo with a `sealed` key gets a configured local check against accidental
|
||||
@@ -792,8 +794,38 @@ gstack-pregate sealed [--range <a..b>] [--remote origin] [--base <branch>] [--js
|
||||
in a later commit); `sealed.answer`, `sealed.seed` and `sealed.local_path`
|
||||
(`/home/<user>/`, `/Users/<user>/`, `C:\Users\<user>\`) check each commit's added
|
||||
lines. Findings print rule, `file[:line]` and commit, never the text.
|
||||
- **Globs.** `paths`, `transcripts` and `allow_paths` match the whole
|
||||
repo-relative path from the repository root. `*` and `?` stay inside one
|
||||
path segment; `**/` spans zero or more directories; a `**` not followed by
|
||||
`/` matches across directories (`heldout/**` covers every depth under
|
||||
`heldout/`); `[` and `{` are literal. So `sealed/**` misses `tasks/x/sealed/a.json`:
|
||||
to guard a `sealed/` directory wherever it sits, write `**/sealed/**`.
|
||||
- **Case.** Matching is case-sensitive. `case_insensitive: true` folds case
|
||||
for `paths` and `transcripts` only, so `**/heldout/**` also catches
|
||||
`HELDOUT/` and `HeldOut/`. Separator and spelling variants (`hold-out/`,
|
||||
`held_out/`, `heldout-v2/`) are not folded: list each as its own glob.
|
||||
The effective value is the base branch's OR the pushed one (it only tightens).
|
||||
- **Exceptions.** `allow_paths` exempts publishable files that sit next to
|
||||
sealed data (a `MANIFEST.json` of hashes and counts) from `sealed.path` and
|
||||
`sealed.transcript`, applied after the deny globs and before waivers. They
|
||||
are read from the destination base branch only: an exception a push adds
|
||||
takes effect once it lands on the base branch, so a branch can never widen
|
||||
its own. They never fold case. An entry whose file name contains `**`, or
|
||||
is only wildcards before its extension (`**`, `sealed/*`, `**/*.json`,
|
||||
`sealed/**.json`), is refused; a partial wildcard such as `MANIFEST*.json`
|
||||
is accepted and matches every file it spells, so name the file. An exempt file is checked by the
|
||||
text rules only, so configure `answer_markers` when you exempt files next to
|
||||
sealed data. Each exempt path is listed on every push (`exempt <rule>
|
||||
<file> commit <sha> (sealed.allow_paths <glob>)`) and counted as
|
||||
`exempt=<n>` on the `SEALED:` line.
|
||||
- **Version skew.** A gstack older than the release whose CHANGELOG adds
|
||||
`sealed.allow_paths` refuses `allow_paths` and `case_insensitive` as
|
||||
unknown keys, so every push and `gstack-pregate run` in the repository
|
||||
fail on that machine. Raise `min_gstack_version` in
|
||||
`.gstack/ship-policy.json` to that release before landing either key.
|
||||
- **Policy.** The union of the destination base branch's `sealed` and the
|
||||
pushed commit's: a push can tighten it, never relax it. A malformed or
|
||||
pushed commit's: a push can tighten it, never relax it (`allow_paths` come
|
||||
from the base branch alone, and a pushed deny glob cannot override one). A malformed or
|
||||
unreadable policy refuses. Waivers come only from the base branch's
|
||||
`.gstack/sealed-allow.tsv` (`rule<TAB>path<TAB>sha<TAB>reason`) and are
|
||||
listed on every push.
|
||||
|
||||
@@ -953,6 +953,22 @@ receipt when the base moves:
|
||||
| a merge of the base whose resolution changed product code | a fresh /ship (`RECEIPT_CARRY_PRODUCT_CHANGED`) |
|
||||
| a rebase or force-push onto a tree the receipt covers, product diff against the new base unchanged | carries (`carry.ancestry: rewritten`) |
|
||||
| any other rebase or force-push | a fresh /ship (`RECEIPT_CARRY_TREE_MISMATCH`) |
|
||||
| a stacked PR whose parent landed (squash or merge), retargeted to the base with the base merged in, HEAD's product diff exactly the one reviewed against the parent branch | carries (reason `stack parent <branch> landed`) |
|
||||
|
||||
**Stacked PRs (#3570).** A child PR's receipt is written against its parent
|
||||
branch, so its `base` names that branch and its `product_patch_id` is the
|
||||
child's own range. After the parent lands, the PR is retargeted to the base, and
|
||||
/ship's carry check runs with `--base <base>`. When that base differs from the
|
||||
receipt's, the carry compares the reviewed range (the recorded
|
||||
`product_patch_id`, or for an older receipt the range against
|
||||
`origin/<parent>` while that branch still resolves) with HEAD's whole product
|
||||
diff against the base, computed the way `write` records it, so a parent's
|
||||
release-metadata change that never reached the base counts too. Equal means
|
||||
the parent fully landed and the child is unchanged, so the receipt carries
|
||||
and from then on names the base it was carried to. A parent that has not landed, a child edited
|
||||
since, or a parent that landed with changes the child never merged all leave a
|
||||
different diff and refuse with `RECEIPT_CARRY_PRODUCT_CHANGED`. An older
|
||||
receipt whose parent branch is gone refuses with `RECEIPT_CARRY_REFUSED`.
|
||||
|
||||
Since the release that added `review_scope`. A `/ship` receipt names the head it
|
||||
reviewed, so a conflict-only merge of the base or a test-only fix used to
|
||||
|
||||
+60
-15
@@ -1566,11 +1566,19 @@ process has exited never blocks, and neither does a stale one: each lease
|
||||
records its process's start identity, so a pid reused after a reboot or a
|
||||
machine wake (GBRA-105's lease named pid 379, which had become dbus-daemon)
|
||||
is reclaimed instead of blocking. The upgrader's own session (the pid chain it
|
||||
runs under) never counts.
|
||||
runs under) never counts for `bin/gstack-upgrade` run by hand. A Capy install
|
||||
in install mode (`bin/gstack-capy-session`) counts it while that lease is under
|
||||
120 minutes old: an install run from inside a workflow that holds the lease
|
||||
(/ship's docs step, #3571) prints `SELF_UPGRADE: skipped (a gstack run in this
|
||||
session holds a workflow lease on this installation: pid <p> (<age>m
|
||||
<command>), so the upgrade waits until the lease is 120 minutes old; fix:
|
||||
<checkout>/bin/gstack-upgrade once the run ends (UPGRADE_BUSY))` and leaves the
|
||||
checkout where the run started. Each `gstack-skill-start` renews the lease.
|
||||
|
||||
**Fix.** Wait for those sessions to finish (`bin/gstack-lease list` shows
|
||||
every lease with its age and `alive|dead|stale`; a holder is named as
|
||||
`pid <p> (<age>m <command>)`), then re-run `bin/gstack-upgrade`.
|
||||
`pid <p> (<age>m <command>)`), then re-run `bin/gstack-upgrade`. For the
|
||||
session's own run, run the printed `bin/gstack-upgrade` after the run ends.
|
||||
When you know they are idle, `GSTACK_UPGRADE_FORCE=1 bin/gstack-upgrade`
|
||||
proceeds. At a Capy session start the same refusal appears as
|
||||
`SELF_UPGRADE: skipped (live gstack sessions hold this installation: ...)`,
|
||||
@@ -1729,8 +1737,11 @@ restate them. Rotate the secret if it was real.
|
||||
|
||||
**Meaning.** The record is kept per branch (one Capy thread ships one branch), and
|
||||
HEAD is detached or the working directory is not a git repository. Exit 3.
|
||||
From `import --autoplan <dir> --stack <b1,b2,...>` it means a listed branch exists
|
||||
neither locally nor on origin; the line names it, and no branch's record was written.
|
||||
|
||||
**Fix.** Check out the branch the work ships from, or pass `--branch <name>`.
|
||||
**Fix.** Check out the branch the work ships from, or pass `--branch <name>`. For
|
||||
`--stack`, correct or fetch the named branch and import again.
|
||||
|
||||
<a id="approval-record-unavailable"></a>
|
||||
### `gstack-approvals: ... (APPROVAL_RECORD_UNAVAILABLE)`
|
||||
@@ -1769,7 +1780,9 @@ import again. Imports are idempotent, so a retry never records an answer twice.
|
||||
### `gstack-owner-brief render --captain: ... (CAPTAIN_BRIEF_INCOMPLETE)`
|
||||
|
||||
**Meaning.** The plan is missing at least one section the brief needs: `## Before → after`,
|
||||
`## Why it matters`, `## What caused this work`, `## Big decisions`. The brief still prints,
|
||||
`## Why it matters`, `## What caused this work`, `## Big decisions`, and `## ELI5` (alias
|
||||
`## In plain words`) on a run whose `gate.json` carries an `eli5` field (gstack 1.91.305 and
|
||||
later; older runs render as before). The brief still prints,
|
||||
but it opens with "Not ready for approval" and has no reply invitation. Exit 1.
|
||||
|
||||
**Fix.** Add the named sections to the plan (`gstack-autoplan revise --plan <file>` if
|
||||
@@ -2474,6 +2487,14 @@ and an E.164 `+14155551234` still report, so the finding means the id stands
|
||||
without a run word (`the id is 40269992077`, `40269992077 failed`) or follows
|
||||
a word that is not one (`see 40323989742`).
|
||||
|
||||
Separately, the scanner skips a bare number that is exactly a power of two
|
||||
from 2^30 through 2^49, one less than such a power, or one of eleven 32-bit
|
||||
hash and PRNG constants (Knuth's multiplicative hash, the golden-ratio
|
||||
constant, the glibc and Numerical Recipes LCGs, FNV-1a, MurmurHash2 and 3, the
|
||||
MT19937 seeder and the largest 32-bit prime; `isNumericConstant` in
|
||||
`lib/redact-patterns.ts`). Only the exact digits with no separator are exempt:
|
||||
the same digits with a `+`, spaces, dashes, dots or parentheses still report.
|
||||
|
||||
**Fix.** Write the id next to a run word (`run 40269992077`) or as an
|
||||
`/actions/runs/` link. For anything else, rerun with `--confirm <token>`
|
||||
using the `TOKEN:` line the refusal printed; MEDIUM findings do not block
|
||||
@@ -2514,6 +2535,15 @@ An address only another remote knows still reports. If the hook also printed
|
||||
`existing-email suppression was limited for this push`, it could not read the
|
||||
history in 5 seconds, so known authors may be listed too.
|
||||
|
||||
A filename with an `@` in the package@version style is not an address and
|
||||
never reports: the part after the `@` is a version or tag (one dotless label
|
||||
or a dotted version, never a domain name) followed by file extensions no
|
||||
top-level domain uses, or a dotted all-digit version followed by
|
||||
an extension that doubles as a top-level domain (`looksLikeAtFilename` in
|
||||
`lib/redact-patterns.ts`). A file named after a real address, an address at an
|
||||
all-digit domain and an address whose top-level domain doubles as a file
|
||||
extension still report.
|
||||
|
||||
**Fix.** Remove the address, or allow it for this repo:
|
||||
`git config --add gstack.redact.allowEmail <address>`. If it is your own
|
||||
address, check `git config user.email`. The allowlist only covers `pii.email`;
|
||||
@@ -3238,7 +3268,10 @@ cannot be downgraded; every other check id in `warn` is honoured only from the
|
||||
base branch's copy or the user-owned file. For the user-owned file, edit it
|
||||
directly at the path the message names. A user file inside the repository, or
|
||||
reached through a symlink into it, is ignored rather than read (`--explain`
|
||||
names it).
|
||||
names it). An unknown `sealed` key that a newer gstack defines (such as
|
||||
`allow_paths` or `case_insensitive`) means this machine's gstack is older than
|
||||
the policy: upgrade gstack here, and raise `min_gstack_version` in the
|
||||
repository's `.gstack/ship-policy.json` before landing such a key.
|
||||
|
||||
<a id="pregate-secrets"></a>
|
||||
### `PREGATE_SECRETS`
|
||||
@@ -3744,7 +3777,11 @@ finding: rule, `file[:line]`, commit. They never print the matched text.
|
||||
|
||||
**Fix.** Remove the output from every published commit before the branch
|
||||
reaches the public repository (a later commit that deletes it still publishes
|
||||
it). A reviewed false positive gets one line in the base branch's
|
||||
it). A publishable file kept next to sealed data (a `MANIFEST.json` of hashes
|
||||
and counts) can be exempted from the path rules with a `sealed.allow_paths`
|
||||
glob in the base branch's `.gstack/pregate.json`; the text rules still read it
|
||||
([docs/pregate.md#sealed](pregate.md#sealed)). A reviewed false positive gets
|
||||
one line in the base branch's
|
||||
`.gstack/sealed-allow.tsv`: `rule<TAB>path<TAB>sha<TAB>reason`. A private
|
||||
destination sets `"visibility": "private"` under `sealed` in the base branch's
|
||||
`.gstack/pregate.json`. Capy's pull-request tool may not run local hooks, so
|
||||
@@ -4819,8 +4856,10 @@ to HEAD: the PR body has no valid receipt, the working tree has uncommitted or
|
||||
untracked changes (a carry certifies a commit, never a tree that differs from
|
||||
it), the receipt's head left HEAD's history and the receipt records no tree to
|
||||
compare (a receipt written before `head_tree`, whose head is not in this
|
||||
clone), or git could not compare the two product diffs or read the working
|
||||
tree's status. It also refuses when an owner exception the receipt records
|
||||
clone), the PR was retargeted to the base after its stack parent landed but
|
||||
the receipt records no `product_patch_id` and `origin/<parent>` no longer
|
||||
resolves, so the reviewed range cannot be recomputed, or git could not
|
||||
compare the two product diffs or read the working tree's status. It also refuses when an owner exception the receipt records
|
||||
no longer stands, and, for a receipt from a gstack that records review
|
||||
depth, when HEAD needs a deeper review than the receipt records, `--head` is
|
||||
not the checked-out HEAD, or the receipt records `gstack_version` without
|
||||
@@ -4831,9 +4870,10 @@ ready` refuses `QUEUE_RECEIPT_STALE`.
|
||||
**Fix.** Commit or stash every working-tree change, then rerun
|
||||
`gstack-ship-receipt carry --pr <n>` at the new head. When the history is
|
||||
already rewritten and the receipt predates `head_tree`, run /ship on the new
|
||||
head; next time merge the base in instead of rebasing. For a depth or
|
||||
exception refusal, run /ship on the new head (or check out `--head` and carry
|
||||
there).
|
||||
head; next time merge the base in instead of rebasing. For a retargeted stacked
|
||||
PR whose older receipt lost its parent branch, or a depth or exception
|
||||
refusal, run /ship on the new head. When `--head` is not the checked-out HEAD,
|
||||
check it out and carry there.
|
||||
|
||||
<a id="review-loop-exception-invalid"></a>
|
||||
### `REVIEW_LOOP_EXCEPTION_INVALID`
|
||||
@@ -4872,13 +4912,18 @@ registered output the pregate's regen check proved (listed under `proven`,
|
||||
checked against the same base registry), nor part of the base merge has
|
||||
a different patch than the reviewed head had. After a rebase it means HEAD has
|
||||
the reviewed tree but the base moved under it, so the diff a reviewer would see
|
||||
is not the reviewed one. The reviewed verdict does not cover HEAD, so the
|
||||
receipt is not carried. The refusal names the changed product paths.
|
||||
is not the reviewed one. For a stacked PR retargeted to the base after its
|
||||
parent landed, it means HEAD's product diff against the base is not the range
|
||||
reviewed against the parent branch: the parent has not fully landed, or the
|
||||
branch changed since. The reviewed verdict does not cover HEAD, so the
|
||||
receipt is not carried. The refusal names the changed product paths, or for a
|
||||
retargeted stacked PR the parent branch and the base.
|
||||
|
||||
**Fix.** Run /ship on the new head. The receipt carries only across a
|
||||
conflict-only base merge, a test-only change, release metadata, a registered
|
||||
output the pregate's regen check proved, or a rebase onto a covered tree with
|
||||
the recorded product diff, which the
|
||||
output the pregate's regen check proved, a rebase onto a covered tree with
|
||||
the recorded product diff, or a stacked PR's retarget once its parent fully
|
||||
landed on the base, which the
|
||||
`carry` object in the carried receipt records with the product patch-id and
|
||||
the range-diff evidence ([unattended.md](unattended.md#the-terminal-line)).
|
||||
|
||||
|
||||
@@ -504,6 +504,53 @@ by its runner. Result codes: `EXECUTION_UNKNOWN`, `RUN_LOCKED`,
|
||||
decision ids are renamed and journaled); one written by a newer gstack is
|
||||
refused.
|
||||
|
||||
### Carry-forward
|
||||
|
||||
A gate revision after the first (`gate_rev` 2 and later, written after a
|
||||
`revise`) is answered with the owner's recorded words from the revision before
|
||||
it when nothing new needs their decision. `gstack-autoplan` prints
|
||||
`CARRIED: gate_rev=<n> <- gate_rev=<m> words="<their words>" by=<relayer> reason=<why>`,
|
||||
then the usual `APPROVED:` lines, and journals `gate_carried`. The carry needs
|
||||
all of: the earlier gate approved (`p1a` or `p1b`) with `rec` or "do it"
|
||||
(not item tokens), no item there left pending, and the owner's words
|
||||
recorded; that gate was not itself carried; every accepted change incorporated
|
||||
or settled; the plan file's bytes equal those recorded at the gate or at the
|
||||
`revise` that took the changes (any later edit refuses, an ELI5-only one
|
||||
included); no urgent or
|
||||
outside-plan finding open; and every new gate item one of the cases below.
|
||||
Anything else prints `CARRY: no (<why>)` (journal `carry_refused`) and the gate
|
||||
goes to the owner as before. `--no-carry-forward` on the first `next` turns it
|
||||
off for the run.
|
||||
|
||||
- **"do it".** When the owner's words were "do it" (their words, not a
|
||||
`captain:` relay of them), a new `auto` item that changes no scope, cost or
|
||||
direction, whose recommended option changes no plan text, and is no round
|
||||
or budget grant takes its recommendation. The
|
||||
decision row gets `carried_under: do it`, the output prints
|
||||
`CARRIED_UNDER_DO_IT:` per item, and the approval record lists them as
|
||||
"taken under "do it", not seen by the owner". `rec` carries no new item.
|
||||
- **Unreviewed post-cap delta.** A "plan changed after <phase> cap" item
|
||||
recommending `a` (accept) is read by one reviewer before it carries. `CARRY_CHECK:` names the attempt and
|
||||
the prompt (the delta against `plan-at-gate.r<m>.md`, the accepted changes,
|
||||
the full plan); the run reserves the estimate in `spend.json` and refuses
|
||||
when it is over `--carry-check-cap <usd>` (default 5) or the run's cap.
|
||||
Dispatch it to a model family other than the plan reviewers', then
|
||||
`check --out <dir> --attempt <id> --result <file> --model <id>`
|
||||
(`--same-family "<why>"` when no other family is available). A High or
|
||||
Critical finding, or `--unavailable "<reason>"`, leaves the gate for the
|
||||
owner. The verdict is saved as `carry_check` on `gate.json`, on the
|
||||
decision rows (`carry_check`) and in `<dir>/decisions.tsv`. A pending
|
||||
check is void, its reservation released and the gate left for the owner,
|
||||
when the owner answers the gate, the plan is revised, a new gate is
|
||||
written, or the delta changed after dispatch.
|
||||
|
||||
Carried rows keep `answered_by: agent:gstack-autoplan` and name their source
|
||||
(`carried_gate_from`, `carried_gate_rev`, `carried_answered_by`);
|
||||
`gstack-approvals import --autoplan` writes the carried gate as its own row,
|
||||
and `gstack-owner-brief render --captain` opens with "Nothing pending.
|
||||
Carried forward from gate_rev <m>", followed by any items taken under "do it"
|
||||
without being shown to the owner.
|
||||
|
||||
## Owner brief
|
||||
|
||||
`gstack-owner-brief render --out <dir>` is what the owner sees at the final
|
||||
|
||||
Reference in new issue
Block a user