mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-15 04:47:23 +01:00
fix(autoplan): export --public drops owner replies and redacts quoted owner words (GST-1243) (#3967)
* fix(autoplan): export --public drops owner replies and redacts quoted owner words, refusing what it can't redact (GST-1243) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(autoplan): owner words in every deferral rendering, superseded replies, glued punctuation, short replies, echoed plan text (GST-1243 review) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(autoplan): normalize markup, entities, escapes and format characters before matching owner words; each new run in an echoing reply is its own needle (GST-1243 review) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Review-Finding: p1/outside-voice#1 reader=outside-voice Review-Finding: p1/outside-voice#2 reader=outside-voice Review-Finding: p1/outside-voice#3 reader=outside-voice Review-Finding: p1/outside-voice#4 reader=outside-voice Review-Finding: p1/outside-voice#5 reader=outside-voice Review-Finding: p1/outside-voice#6 reader=outside-voice Review-Finding: p1/adversarial-native#1 reader=adversarial-native Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Review-Finding: p2/red-team#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: GST-1243 fragment names the review fixes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9e826b2994
commit
e5366a52b7
13 files changed
+958
-16
No files matched your search
@@ -4800,6 +4800,30 @@ a hit there needs a plan edit: change the path, `revise`, and export again.
|
||||
Generic home-directory paths that are not recorded roots are WARN lines, not
|
||||
errors.
|
||||
|
||||
<a id="artifact-owner-words"></a>
|
||||
### `ARTIFACT_OWNER_WORDS`
|
||||
|
||||
**Meaning.** A public copy of an autoplan run still quotes the owner's own
|
||||
words: a gate reply the private run recorded (on a decision row, the gate or
|
||||
the journal), the words a carried gate reused, or a reason a human gave for
|
||||
deferring a phase, in prose or in one of gstack's renderings of that reason.
|
||||
Nothing a person wrote leaves without their click. A reply of 3 or more words
|
||||
is a quote wherever it appears whole, and from 8 words any 8 consecutive words
|
||||
are one, compared across punctuation, line breaks, JSON escapes, entities, HTML and
|
||||
Markdown markup, invisible format characters, contractions and case; replies
|
||||
of 1 or 2 words are not scanned for, and words the owner echoed from earlier
|
||||
text he didn't write are not his (the words a reply adds to an echo still are). See
|
||||
[the public copy](unattended.md#the-public-copy). `gstack-artifact validate --public <dir> --private
|
||||
<run>/run.json` reports each quote with file:line, and `gstack-autoplan export
|
||||
--public` runs that check on a staging copy and writes nothing when it fails.
|
||||
The error never prints the words themselves.
|
||||
|
||||
**Fix.** Export with `gstack-autoplan export --out <run> --public <dir>`, which
|
||||
drops those fields and replaces each quote or rendering in prose and sealed
|
||||
reviewer files with `[owner words redacted]`. Export never rewrites inline code
|
||||
or fenced blocks in plan.md, so a quote there needs a plan edit: remove it,
|
||||
`revise`, and export again.
|
||||
|
||||
<a id="artifact-public-target-exists"></a>
|
||||
### `ARTIFACT_PUBLIC_TARGET_EXISTS`
|
||||
|
||||
|
||||
+59
-1
@@ -278,7 +278,7 @@ tree no review saw, so /ship runs again;
|
||||
| File | Schema | Contents |
|
||||
|------|--------|----------|
|
||||
| `run.json` | `run` v1 | The manifest: `run` id, `status`, `required_phases`, each phase's `native` and `outside` outcome (`status`, `model`, `provider`), input `snapshot` hashes, `counts`, every artifact's `path` (relative to the run directory) and `sha256`, `session_kind`, `gate_rev`, `deviations`, `consumed_by`. |
|
||||
| `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`<run>-<label>` at gate revision 1, `<run>-r<rev>-<label>` after a reopen, since labels restart at each gate), `title`, `options`, `recommended`, `kind` (`auto`, `approval`, `user_challenge`), `status` (`pending`, `approved`, `overridden`, `rejected`, or `auto_kept` in a [CEO-only run](#ceo-only-runs)), `gate_rev`, and once answered `chosen`, `answered_at`, `reply`, plus how the owner answered: `owner_mode` (`rec`, `rec_except`, `tokens`, `as_is` or `unparsed`, parsed from the owner's own words), `owner_named` (his words named this item), `rec_hash` (the recommendations answered against) and `answered_role` (`human`, or `agent` relaying his words). |
|
||||
| `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`<run>-<label>` at gate revision 1, `<run>-r<rev>-<label>` after a reopen, since labels restart at each gate), `title`, `options`, `recommended`, `kind` (`auto`, `approval`, `user_challenge`), `status` (`pending`, `approved`, `overridden`, `rejected`, or `auto_kept` in a [CEO-only run](#ceo-only-runs)), `gate_rev`, and once answered `chosen`, `answered_at`, `reply`, plus how the owner answered: `owner_mode` (`rec`, `rec_except`, `tokens`, `as_is` or `unparsed`, parsed from the owner's own words), `owner_named` (his words named this item), `rec_hash` (the recommendations answered against) and `answered_role` (`human`, or `agent` relaying his words); an agent-relayed answer adds `owner_reply`, his verbatim words, which never leave in a [public copy](#the-public-copy). |
|
||||
| `shadow/` | private, not an artifact | [Shadow mode](#shadow-mode) only: `ceo-only.r<rev>.jsonl` (one hidden prediction per gate item, written before the answer), `r<rev>.prompt.md` and one `<call>.reply.txt` per model call, the audit `salt`, and `r<rev>.pending` while a call is in flight. Never in `export --public`, the gate render or the owner brief. |
|
||||
| `findings.jsonl` | `findings` v1 | One row per reviewer finding: `id`, `phase`, `voice` (`native`/`outside`), `model`, `severity`, `title`, `disposition`, `plan_items`, `native_counterpart` (string or null), `source`. Rows share the review-log row shape, so `gstack-review-log --findings` derives review status from this file. |
|
||||
| `tasks.jsonl` | `tasks` v2 | Implementation tasks with `id`, `title`, `status`, `depends_on`, `findings` (ids in `findings.jsonl`), `blocked_by`, `acceptance`, optional `pr`/`item`/`tier`. |
|
||||
@@ -292,6 +292,64 @@ only place the run id is declared. `gstack-artifact schema <name>` prints the
|
||||
JSON Schema for any of `findings`, `decisions`, `tasks`, `timing`, `run`,
|
||||
`pregate` and `ship-receipt`; `schema --list` prints the names.
|
||||
|
||||
### The public copy
|
||||
|
||||
`gstack-autoplan export --out <dir> --public <pub>` writes a copy to commit or
|
||||
share. It rewrites the machine paths the run recorded, and it never carries the
|
||||
owner's own words: nothing a person wrote leaves without their click (GST-1243).
|
||||
|
||||
- **Whose words.** Every owner reply the private run ever recorded: on
|
||||
decision rows, on `run.json`'s gate, and in the journal (`attempts.jsonl`
|
||||
keeps a reply that a later answer at the same gate replaced), the words a
|
||||
carried gate reused, and the reason a human gave for `close --deferred --by
|
||||
human`. A reply that parsed as gate grammar is the decision code, not owner
|
||||
prose. An agent's own words (`answer_words`) and an agent's deferral reason
|
||||
stay.
|
||||
- **Dropped fields.** `owner_reply` anywhere, a carry record's `words`, and a
|
||||
human deferral's `reason` (replaced by `[owner words redacted]`). The
|
||||
decision itself stays: `chosen`, `reply` (the gate grammar), `owner_mode`
|
||||
and `owner_reply_parsed`.
|
||||
- **gstack's own renderings.** The human's deferral reason is also replaced
|
||||
where gstack writes it into fixed text: the voice `detail`, the consensus
|
||||
head, the gate item title, a blocker's `why` and the run log; carried words
|
||||
in the `CARRIED:` line too. Each rendering is matched with its delimiters on
|
||||
both sides, so a one-word reason never rewrites a longer one that starts
|
||||
with it.
|
||||
- **Redacted quotes.** Every exported file, sealed reviewer results
|
||||
(`*.bound.md`) included, is scanned for quotes. Before matching, the text is
|
||||
normalized: JSON string escapes (`\n`, `\u2019`) and HTML entities are
|
||||
decoded, format characters are dropped by Unicode category (`\p{Cf}`, such as
|
||||
zero-width spaces, soft hyphens and direction marks, plus variation selectors
|
||||
and the combining grapheme joiner), an HTML tag name reads as a space, and a
|
||||
hyphen-newline wrap is joined. The text is read twice: as rendered (HTML
|
||||
comments, footnote markers, reference definitions and link targets and titles
|
||||
dropped, link text kept) and as written (all of those kept as words), so a
|
||||
quote is found whether markup splits it or hides it. Contractions split
|
||||
("don't" reads "do not", "can't" and "cannot" read "can not", "it's" "it is",
|
||||
"I'd" "I would"). A word is a run of letters and digits, and everything else
|
||||
(spaces, line breaks, dashes, slashes, quote marks, `>` markers, JSON or
|
||||
attribute glue) separates words.
|
||||
Words compare case-insensitively. A reply of 3 or more words is a quote
|
||||
wherever it appears whole; from 8 words, any 8 consecutive words of it are a
|
||||
quote too. Replies of 1 or 2 words ("yes", "do it") are not scanned for:
|
||||
they are codes or grammar, and the field itself is already dropped. A quote
|
||||
extends over the neighboring words that also belong to that reply, and the
|
||||
marker replaces the original characters from its first word to its last.
|
||||
- **Echoes stay.** Words the owner repeated from text recorded before the reply,
|
||||
which he didn't write (the plan at an earlier gate, that gate's items, the
|
||||
reviewer results bound before it), are not his words: they stay, and only the
|
||||
new words around them are redacted. Every run of 3 or more words a reply adds
|
||||
between echoes is matched on its own too (and by any 8 of its words from 8),
|
||||
so the new words are redacted even where they are quoted without the echo.
|
||||
Prose with no quote keeps its bytes. One
|
||||
`OWNER_WORDS: <file> dropped=<n> redacted=<m>` line names each changed file.
|
||||
- **Refusal.** Export never rewrites code in plan.md, so a quote or rendering
|
||||
inside a code span or fence there stops the export with
|
||||
[`ARTIFACT_OWNER_WORDS`](troubleshooting.md#artifact-owner-words), file:line
|
||||
and exit 3, and nothing is written. The error never prints the words.
|
||||
`gstack-artifact validate --public <pub> --private <dir>/run.json` runs the
|
||||
same scan, renderings included, on an existing copy.
|
||||
|
||||
## How a parent consumes a run
|
||||
|
||||
1. **Decide whether the run finished.** For an `/autoplan` run directory,
|
||||
|
||||
Reference in new issue
Block a user