From 8af121fd4c8ed0c74856ed5809bfa0449f23822f Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 13:04:44 -0800 Subject: [PATCH 01/10] v1.93.18.0 fix(pregate): a green Free Tests run at the pregate tree clears gstack's remote gate (GST-1024) (#3883) * pregate: gstack declares remote_gate_runs, so a green Free Tests run at the pregate tree clears remote_gate (GST-1024) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (GST-1024: refuse a paid-only selection; validate skipped) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(changelog): GST-1024 entry names all four tests 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> --- .gstack/pregate.json | 5 + CHANGELOG.md | 11 ++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- docs/pregate.md | 33 +++++- lib/pregate/config.ts | 10 +- lib/pregate/index.ts | 16 ++- lib/pregate/run-check.ts | 22 +++- lib/run-evidence.ts | 13 ++- package.json | 2 +- test/pregate-remote-gate-runs.test.ts | 154 ++++++++++++++++++++++++++ 11 files changed, 244 insertions(+), 26 deletions(-) create mode 100644 test/pregate-remote-gate-runs.test.ts diff --git a/.gstack/pregate.json b/.gstack/pregate.json index 6be73a295..5d1caa4f3 100644 --- a/.gstack/pregate.json +++ b/.gstack/pregate.json @@ -22,6 +22,11 @@ "ci-reuse/free-tests": { "workflow": "free-tests.yml", "events": ["pull_request", "workflow_dispatch"], "jobs": ["free-plan", "typecheck", "quick-bun-default", "cso-macos-launcher", "macos-named-regressions", "cso-windows-launcher", "cso-docker-integration", "free-tests"], "artifact": "tested-tree", "os": "Linux" }, "ci-reuse/windows-free-tests": { "workflow": "windows-free-tests.yml", "events": ["pull_request", "workflow_dispatch"], "jobs": ["windows-plan", "windows-free-tests"], "artifact": "tested-tree", "os": "Windows" } }, + "remote_gate_runs": { + "profiles": ["ci-reuse/free-tests"], + "inventory": "bun --no-env-file --config=/dev/null run scripts/test-free-shards.ts --list | sed -n 's/^ //p'", + "skipped": "bun --no-env-file --config=/dev/null -e 'import { isPaidTestFile } from \"./test/helpers/paid-test-set.ts\"; import * as fs from \"node:fs\"; for (const f of fs.readFileSync(process.env.PREGATE_FILES_FILE, \"utf8\").split(/\\r?\\n/)) if (f && isPaidTestFile(f)) console.log(f);'" + }, "warn": [], "strays": { "allow": ["modules/loadouts.json", "modules/loadouts.schema.json"] } } diff --git a/CHANGELOG.md b/CHANGELOG.md index c97693ba1..6bbcc79ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.93.18.0] - 2026-11-02 + +**A green Free Tests run at the pregate tree now clears gstack's remote gate, so a slow Ubicloud VM no longer blocks a PR whose CI already passed (GST-1024).** docs/pregate.md described `gstack-pregate clear --remote pregate.json/remote_gate --run --allow-repo-commands`, but gstack's own `.gstack/pregate.json` declared no `remote_gate_runs`, so the route always refused. On #3869, every check passed at 3f2bcf3 (Free Tests run 41484634592), while two remote gate runs at the same tree failed on bun timeouts in files the PR doesn't touch. + +### Added +- **`.gstack/pregate.json` `remote_gate_runs`:** the `ci-reuse/free-tests` profile, with `scripts/test-free-shards.ts --list` as the inventory (the complete free suite the Free Tests plan shards). +- **`remote_gate_runs.skipped`** (optional, `lib/run-evidence.ts`, `lib/pregate/index.ts`, `lib/pregate/run-check.ts`): a command that reads the selected files from `PREGATE_FILES_FILE` and prints the ones the remote gate itself never runs. Those files need no run, and the receipt records them as `skipped=`. An inventory file stays required even when `skipped` names it, and a failing command refuses with `skipped_failed`. When every selected file is skipped, the clear refuses with `paid_only`, just as the gate's own `tests=0` report never clears. gstack's command prints the selected paid test files, the same split `scripts/ubicloud/pregate-remote.sh` makes. Without it, run 41484634592 still refused: all 71 of the selected files it didn't cover are paid. + +### Tests +- `test/pregate-remote-gate-runs.test.ts`: gstack's inventory equals the Free Tests plan's file set, and its skipped command prints exactly the paid files of a mixed list. In a fixture with gstack's real profile, a green Free Tests `pull_request` run clears the gate. A run at another tree, a run of another workflow, a failed or cancelled run, a fork run, a merge commit behind its base, an uncovered inventory file, a skipped list naming an inventory file, a failing skipped command, a paid-only selection, and a declaration without `skipped` all refuse. Malformed `skipped` values fail config validation. On main, all four tests fail. + ## [1.93.17.0] - 2026-11-02 - `gstack-ship-queue wave plan` now leaves out a READY head that does not merge onto main. It prints `NOT_MERGEABLE: # (...)` with the conflicting files, the same way `NOT_GREEN` works for red CI. A conflict only in the CHANGELOG doesn't count, since the train merges those. Before, such a head was planned into a wave and the wave build stopped on it. diff --git a/VERSION b/VERSION index a194d2ff7..f876ca51e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.17.0 +1.93.18.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 12980b593..6bf1df188 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.17.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.18.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/docs/pregate.md b/docs/pregate.md index 09d1afe7d..9128fa093 100644 --- a/docs/pregate.md +++ b/docs/pregate.md @@ -89,7 +89,7 @@ the byte-pinned `/ship` renders in `test/fixtures/golden/`). | `timeout_s` | seconds each runner process may take (the unit-file group, each serial file, each guard without its own `timeout_s`, and the remote gate's deadline) when `--timeout` is not passed; 1 to 3600, default 600. `gstack-ship-preflight` and the `finish` fallback run pregate without `--timeout`, so this is how a repo with long lanes sets theirs | | `remote_gate` | the repo's remote test gate command; under 8 cores the local test tier becomes a `requires-remote` obligation naming it, and `--remote` runs it (see [remote gate](#remote-gate)) | | `run_profiles` | name → `{ workflow, events, jobs, artifact, max_age_hours?, os? }`: the GitHub Actions runs `clear --run` verifies as evidence on the identical tree; a profile named after a lane (`free-tests.yml/macos-named-regressions`) clears that lane (see [run evidence](#run-evidence)). `--from-run` and the main-push reuse job verify runs against them too ([from-run](#from-run), [CI reuse](#ci-reuse)) | -| `remote_gate_runs` | `{ profiles, inventory }`: the run profiles that together clear `pregate.json/remote_gate`, and the command that prints the remote gate's test files, one per line, which their runs must cover (see [run evidence](#run-evidence)) | +| `remote_gate_runs` | `{ profiles, inventory, skipped? }`: the run profiles that together clear `pregate.json/remote_gate`, the command that prints the remote gate's test files, one per line, which their runs must cover, and an optional command that prints which selected files the gate itself never runs (see [run evidence](#run-evidence)) | | `allowlist` | `[{ finding, paths?, pattern?, reason }]`: benign findings the `secrets` check accepts and names as allowlisted, by gstack-redact finding id or non-secret category, path globs and a pattern anchored at the finding; never `env.value` (see [allowlist](#allowlist)) | Only the `origin/` copy or the user-owned file below is honoured, so a @@ -742,6 +742,16 @@ Then, for the lane: while the working tree is pregate.json's tree (checked before and after it runs); an empty or failing inventory refuses, and it runs under `pipefail`, so a pipeline whose first command fails refuses too. +- When `remote_gate_runs` declares `skipped`, that command runs the same way, + with `PREGATE_FILES_FILE` naming a file of the obligation's selected test + files, one per line, and prints the ones the remote gate itself never runs + (gstack's gate leaves paid test files to the paid PR gate). Those files need + no run, and the receipt counts them as `skipped=`. A file the inventory + prints stays required even when `skipped` names it, and a failing `skipped` + command refuses (`skipped_failed`). When `skipped` names every selected file, + no selected test ran, so the clear refuses (`paid_only`), just as the gate's + own `tests=0` report never clears. Without `skipped`, every selected file + must appear in a run's `files`. - Every run is read once more after the inventory: a run that moved to a new attempt or stopped being a success meanwhile refuses (`run_changed`). @@ -753,13 +763,14 @@ verification is named with its own reason (`fork_run`, `event_mismatch`, `os_mismatch`, `api_error`, …); the clear-level codes are `base_mismatch`, `no_verified_run`, `tree_mismatch` (runs on different trees), `tree_not_stamp_only`, `no_tests`, `inventory_failed`, `inventory_empty`, -`inventory_uncovered` (the remote gate) and `tests_uncovered` (a lane's own +`skipped_failed`, `paid_only`, `inventory_uncovered` (the remote gate) and `tests_uncovered` (a lane's own selected tests). A wrong run count, a repeated run or a lane with no profile is a usage error (exit 2), not a REUSE refusal. Accepted, `clear` prints `REUSE: accepted `, and the receipt reads `remote-gate tree= tests= runs=/,… profiles=

,… -heads= verified=

] [--csv ] [--json] [--cache-dir ] * [--fixtures ] [--now ] * diff --git a/docs/CHANGELOG_STYLE.md b/docs/CHANGELOG_STYLE.md index 4a2194b7f..ce6723fe8 100644 --- a/docs/CHANGELOG_STYLE.md +++ b/docs/CHANGELOG_STYLE.md @@ -1,7 +1,9 @@ # CHANGELOG + VERSION style Moved verbatim from CLAUDE.md (token-load reduction). Read this BEFORE -writing any `## [X.Y.Z]` CHANGELOG entry. +writing a CHANGELOG entry. In gstack the entry is a fragment, +`changelog.d/.md`, and the merge step gives it its `## [X.Y.Z.W]` +heading ([changelog fragments](ship-policy.md#changelog-fragments)). ## Versioning and branch-scoped entries @@ -9,15 +11,15 @@ Moved verbatim from CLAUDE.md (#2096 size limit). The autonomous version policy (choose versions without asking; default to PATCH) stays in CLAUDE.md as the single source. -**Versioning invariant (workspace-aware ship).** VERSION is a monotonic ordered -release identifier, not a strict semver commitment. The bump level -(major/minor/patch/micro) expresses intent at ship time. Queue-advancing past a -claimed version within the same bump level is explicitly permitted — if branch A -claims v1.7.0.0 as a MINOR and branch B is also a MINOR, B lands at v1.8.0.0 -(still a MINOR relative to main). Downstream consumers must NOT rely on -"MINOR = feature-only, PATCH = fix-only" as a strict contract. This is why -`bin/gstack-next-version` advances within the chosen bump level rather than -repicking the level when collisions happen. +**Versioning invariant.** VERSION is a monotonic ordered release identifier, +not a strict semver commitment. The bump level (major/minor/patch/micro) +expresses intent. Versions are assigned at merge, in merge order: each stamp +takes the version before it plus the effective bump, which is the policy's +PATCH raised by any folded fragment's `bump:` front matter. If PR A and PR B +both ask for MINOR and land back to back on v1.6.0.0, A lands at v1.7.0.0 and +B at v1.8.0.0 (still a MINOR relative to the version before it). Downstream +consumers must NOT rely on "MINOR = feature-only, PATCH = fix-only" as a +strict contract. **package.json carries the npm-valid translation, not VERSION verbatim.** VERSION stays the 4-digit source of truth (e.g. `1.67.0.0`); package.json and @@ -26,18 +28,25 @@ translation (`1.67.0`), and lockfile `version` fields sync only when the lockfile already exists. `bin/gstack-version-bump` (via `lib/version-source.ts`) owns the translation and judges drift on translated forms — do NOT "fix" the apparent mismatch by hand, and do not write a 4-digit version into -package.json (npm rejects it). Rationale and translation rules live in the +package.json (npm rejects it). A branch never writes either one: the merge +stamp does, through `bin/gstack-version-bump`. Rationale and translation rules live in the `lib/version-source.ts` header; `test/gstack-version-bump.test.ts` pins the contract. -**VERSION and CHANGELOG are branch-scoped.** Every feature branch that ships gets its -own version bump and CHANGELOG entry. The entry describes what THIS branch adds — -not what was already on main. +**The CHANGELOG entry is branch-scoped; the version is not.** Every feature +branch that ships writes one entry as `changelog.d/.md`: the entry +text only, with no `## [x]` heading, and `bump: minor` or `bump: major` front +matter only when the change warrants more than a PATCH. A branch never edits +VERSION, package.json's version or a released CHANGELOG.md section. At merge, +`train land` (or `gstack-ship-queue land`) assigns the version, folds the +fragment into one `## [] - ` section at the top of CHANGELOG.md +and deletes the file. The entry describes what THIS branch adds — not what was +already on main. **The CHANGELOG entry is the diff between main and the shipping branch — what users get when they upgrade. NOT how the branch got there.** A reader landing on the entry should learn what they can do now that they couldn't before; they should not learn -about the branch's internal version bumps, the bugs we caught and fixed mid-branch, +about versions the branch carried during development, the bugs we caught and fixed mid-branch, the plan reviews we ran, or the commits we squashed. That is branch development narrative. It belongs in PR descriptions and commit messages, not CHANGELOG. @@ -62,29 +71,28 @@ the user gets; the path to that system is invisible to them. **When to write the CHANGELOG entry:** - At `/ship` time (Step 13), not during development or mid-branch. - The entry covers ALL commits on this branch vs the base branch. -- `/ship` lints the lines the branch added with `bin/gstack-prose-lint CHANGELOG.md --base origin/ --gate`, - and the Quality gate workflow gates the `## [Unreleased]` block the same way. Rewrite each `PROSE:` line - it names ([docs/troubleshooting.md](troubleshooting.md#prose-lint-found)). -- Never fold new work into an existing CHANGELOG entry from a prior version that - already landed on main. If main has v0.10.0.0 and your branch adds features, - bump to v0.10.1.0 with a new entry — don't edit the v0.10.0.0 entry. +- `/ship` lints the fragment with `bin/gstack-prose-lint changelog.d/.md --gate`. + Rewrite each `PROSE:` line it names ([docs/troubleshooting.md](troubleshooting.md#prose-lint-found)). +- Never fold new work into a CHANGELOG.md section that already landed on main. + New work goes in this branch's fragment, and the merge stamp gives it its own + section at the next version. **Key questions before writing:** 1. What branch am I on? What did THIS branch change? -2. Is the base branch version already released? (If yes, bump and create new entry.) -3. Does an existing entry on this branch already cover earlier work? (If yes, replace - it with one unified entry for the final version.) +2. Does this change warrant more than a PATCH? (If yes, open the fragment with + `bump: minor` or `bump: major` front matter; otherwise add none.) +3. Does this branch's fragment already cover earlier work? (If yes, rewrite it as + one unified entry.) -**Merging main does NOT mean adopting main's version.** When you merge origin/main into -a feature branch, main may bring new CHANGELOG entries and a higher VERSION. Your branch -still needs its OWN version bump on top. If main is at v0.13.8.0 and your branch adds -features, bump to v0.13.9.0 with a new entry. Never jam your changes into an entry that -already landed on main. Your entry goes on top because your branch lands next. +**Merging main never gives your branch a version.** When you merge origin/main into +a feature branch, main may bring new CHANGELOG sections and a higher VERSION. Take them +exactly as main has them. Your branch still carries only its fragment, and never jams its +changes into a section that already landed on main; the merge stamp puts its section on top. **After merging main, always check:** -- Does CHANGELOG have your branch's own entry separate from main's entries? -- Is VERSION higher than main's VERSION? -- Is your entry the topmost entry in CHANGELOG (above main's latest)? +- Is your fragment still in `changelog.d/`, with its own text, separate from main's sections? +- Are VERSION, package.json's version and every released CHANGELOG.md section exactly + main's? (The Version Gate check requires this of a head the merge step did not stamp.) If any answer is no, fix it before continuing. **After any CHANGELOG edit that moves, adds, or removes entries,** immediately run @@ -93,13 +101,9 @@ order. Gaps between version numbers are fine. A branch that ships at v1.6.4.0 wi a prior v1.5.2.0 or v1.5.3.0 entry on main is correct — those were branch-internal version numbers that never landed. Do not back-fill gaps with placeholder entries. -**Never orphan branch-internal versions.** If your branch bumped VERSION several times -during development (v1.5.1.0 → v1.5.2.0 → v1.6.4.0, say) and those earlier entries were -never released to main, the final ship consolidates ALL of them into a single entry at -the final version (v1.6.4.0). Collapse them — delete the old entries and move their -content into the final entry, re-version table columns accordingly. Readers see one -release, not a branch diary. Gaps are fine (v1.6.3.0 → v1.6.4.0 with no v1.5.x -in between on main is correct). +**Keep one entry per branch.** When the branch's work grows after its fragment was +written, rewrite that fragment as one unified entry instead of adding a second file +beside it. Readers see one release, not a branch diary. CHANGELOG.md is **for users**, not contributors. Write it like product release notes: @@ -122,9 +126,9 @@ we got here. Keep out of the CHANGELOG, always: documents what DID ship, not what MIGHT ship. - Version-bump housekeeping when no user-facing work actually landed. -If the diff between the base branch version and this version has no user-facing change -(only merges, only CHANGELOG edits, only placeholder work), the honest entry is one -sentence: "Version bump for branch-ahead discipline. No user-facing changes yet." Stop +If the diff between the base branch and this branch has no user-facing change +(only merges, only CHANGELOG edits, only placeholder work), the honest fragment is one +sentence: "No user-facing changes yet." Stop there. Do not pad. Do not explain the plan that will ship eventually. Do not narrate the branch's history. When real work lands, the entry will replace this at /ship time. diff --git a/docs/recommended-harness.md b/docs/recommended-harness.md index 6da4b14c2..467b5a1c0 100644 --- a/docs/recommended-harness.md +++ b/docs/recommended-harness.md @@ -263,7 +263,7 @@ line whose input could not be read says `unavailable ()` rather than 0. gstack-factory-scorecard --repo owner/name --since-days 7 \ [--crew login,login] [--gate-workflow evals.yml] [--free-workflow free-tests.yml] \ [--train-prefix train/] [--ready-marker 'READY #\d+|SHIP_RECEIPT:'] [--pass-marker '"passes": (\d+)|passes=(\d+)'] \ - [--pass-cap 3] [--out-dir .context/factory-scorecard] [--now ] + [--pass-cap 2] [--out-dir .context/factory-scorecard] [--now ] ``` `--repo` defaults to the origin remote's owner/name on github.com (or @@ -280,7 +280,7 @@ measured later does not count anything that happened after that week. | Merged PRs | `merged_at` in the window, base = default branch; crew by `--crew` logins, else author association OWNER/MEMBER/COLLABORATOR | several merges a day, every day; outside contributions land in the same week | | Lead time, created → merged | PR `created_at` to `merged_at`, median and p90 | median under 24h, p90 under 3 days (DORA "elite" change lead time is under a day) | | Lead time, READY → merged | first PR comment matching `--ready-marker` at or before `merged_at`; PRs with no such comment are left out and counted | under 2h; every merged PR carries a READY comment | -| Review passes per PR | the largest count `--pass-marker` captures in the PR body (the ship receipt's `"passes": N` once /ship writes it) or a PR comment (`passes=N`, `REVIEW_PASSES: N`), median and p90, plus how many PRs sat at `--pass-cap` (3: pass 1 reviews the whole diff, later passes only the delta, a pass that finds nothing new is the exit) | median 2 passes; none at the cap | +| Review passes per PR | the largest count `--pass-marker` captures in the PR body (the ship receipt's `"passes": N` once /ship writes it) or a PR comment (`passes=N`, `REVIEW_PASSES: N`), median and p90, plus how many PRs reached `--pass-cap` (2, the [review policy](review-policy.md)'s cap of two rounds, a fix-check included: pass 1 reviews the whole diff, pass 2 only the range since, and a pass that finds nothing blocking is the exit) | median 1 pass; few at the cap | | Gate runs green / red / cancelled | completed runs of the gate workflow (`workflow_dispatch` by default; `--gate-event any`); cost from the run's `report-verdict-a*` artifact (`collector-outcomes.json` `.verdict.costUsd`), else the `**$N** rule/judge cost` or `cost=$N` line of the PR's verdict comment | green on 95%+ of runs, cancelled near 0, cost known for every run | | Known gate cost per merged PR | known cost / merged PRs; unknown-cost runs make it a floor | known, stable or falling week over week | | Red-train rate | runs whose head branch starts with `--train-prefix`, cancelled excluded; red = conclusion other than success; red backstops and screens shown beside it | under 5% (each red train is a bisect plus a rebuild, about two gates) | diff --git a/docs/ship-policy.md b/docs/ship-policy.md index 9d013a084..7fd896a28 100644 --- a/docs/ship-policy.md +++ b/docs/ship-policy.md @@ -112,10 +112,16 @@ refuses the file by name (`POLICY_GSTACK_TOO_OLD`). `.gstack/ship-policy.example.json` shows every key set the way a serial-merge repo with a release script, docs mentions and a mirror would set them. -### Queue-side procedure for gstack + +### Queue-side procedure under `stamp-at-merge` without fragments -Who does what under `stamp-at-merge`, for the PR author and for the thread -running the merge queue: +gstack itself no longer follows these steps. Since #3654 (v1.91.342.0) its +policy sets [`changelog_fragments`](#changelog-fragments), so a gstack PR adds +a fragment and never a version, and trains merge through +[`train land`](#train-land). The steps +below are for a repo on `stamp-at-merge` whose policy has no +`changelog_fragments`. Who does what there, for the PR author and for the +thread running the merge queue: 1. **Open.** The branch carries a tentative stamp of the base's version plus PATCH (`gstack-version-bump write --version --regen-digest`) diff --git a/lib/factory-scorecard.ts b/lib/factory-scorecard.ts index ebaac4c48..9be8ce50d 100644 --- a/lib/factory-scorecard.ts +++ b/lib/factory-scorecard.ts @@ -70,7 +70,7 @@ export interface ScorecardOptions { export const DEFAULTS = { base: 'main', sinceDays: 7, gateWorkflow: 'evals.yml', gateEvent: 'workflow_dispatch', freeWorkflow: 'free-tests.yml', trainPrefix: 'train/', readyMarker: 'READY #\\d+|SHIP_RECEIPT:|gstack-ship-receipt', - passMarker: '"passes"\\s*:\\s*(\\d+)|\\bpasses=(\\d+)|\\bREVIEW_PASS(?:ES)?[:= ]\\s*(\\d+)', passCap: 3, maxArtifactRuns: 60, + passMarker: '"passes"\\s*:\\s*(\\d+)|\\bpasses=(\\d+)|\\bREVIEW_PASS(?:ES)?[:= ]\\s*(\\d+)', passCap: 2, maxArtifactRuns: 60, } as const; export const CREW_ASSOCIATIONS = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); @@ -399,7 +399,7 @@ export const BENCHMARKS: Record = { merged: 'several merges a day, every day; outside contributions land in the same week', lead_time: 'median under 24h, p90 under 3 days (DORA elite: change lead time under a day)', ready_to_merge: 'under 2h from READY to merge; every merged PR carries a READY comment', - review_passes: 'median 2 passes, none at the cap (a pass that finds nothing new is the exit; what is left at the cap is named in the PR body)', + review_passes: 'median 1 pass, few at the cap (a pass that finds nothing blocking is the exit; a PR at the cap ships with what is left named in the PR body)', gates: 'green on 95%+ of runs, cancelled near 0, cost known for every run', cost_per_merge: 'known, stable or falling week over week', red_train: 'under 5% of trains red (each red train costs a bisect and a rebuild)', diff --git a/package.json b/package.json index e8c077ccf..0ae64ebdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.19", + "version": "1.93.20", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/factory-scorecard.test.ts b/test/factory-scorecard.test.ts index 3794a333e..bb26faac3 100644 --- a/test/factory-scorecard.test.ts +++ b/test/factory-scorecard.test.ts @@ -59,7 +59,7 @@ describe('factory-scorecard: recorded window', () => { expect(values.merged).toBe('3 (crew 2, outside 1)'); expect(values.lead_time).toBe('median 8.0h, p90 5.0d'); expect(values.ready_to_merge).toBe('median 1.0h, p90 2.0h (2 of 3 PRs with a READY comment)'); - expect(values.review_passes).toBe('median 2, p90 3 (2 of 3 PRs report a pass count; 1 at the cap of 3)'); + expect(values.review_passes).toBe('median 2, p90 3 (2 of 3 PRs report a pass count; 2 at the cap of 2)'); expect(values.gates).toBe('2 / 1 / 1; known cost $43.00 on 2 of 4 runs'); expect(values.cost_per_merge).toBe('$14.33 (2 runs without a known cost)'); expect(values.red_train).toBe('50% (1 red of 2 trains; 0 red backstop, 0 red screens)'); From e0531db79332b5ce77ca8d0a55f7d0592ddd28b1 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 13:39:39 -0800 Subject: [PATCH 04/10] v1.93.21.0 fix(ship): native FIXABLE collects at its own Severity outside gate and risk reviews (GST-987) (#3869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(review-ledger): a finding's Severity field sets its label, body severity words never raise it (GST-976) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review-ledger): Severity counts only in field position, locus-first and emoji-led labels lead, an inline field floors the next (GST-976 review) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review-ledger): linear lead-label prefix, all-caps leads before a space, fields after a parenthesis or without a colon at the line start (GST-976 review) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review-ledger): a lead's file:line has no runaway backtracking and may be a range; a mid-line Severity field must close its clause (GST-976 review) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review-ledger): a field at the line's start needs no closing clause; mid-line fields accept : / and → after the label (GST-976 fix-check) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: changelog fragment and review-policy collect step for GST-976 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review-ledger): a Severity field's label chain (High/Critical) takes its strongest label; mid-line fields keep the clause-closing set (GST-976 scoped check) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(review-ledger): never weaker than main invariant over pinned inputs, fixtures and generated field shapes; a multi-label Severity field counts as none, lowercase fields keep their label (GST-976) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: GST-976 re-audit, multi-label and lowercase Severity fields Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: native FIXABLE raises only on gate and risk review classes (GST-987) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: collect records fixable_raises:false so a later re-derive uses the same class (GST-987) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: GST-987 review fixes: history/floor reasons and FIXABLE-only fields keep v2; manifest must name this ledger Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: GST-987 round-2 fixes: FIXABLE relaxes only beside Blocks: no; raw FIXABLE heading; unreadable manifest keeps v2 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: GST-987 fix-check: items under a FIXABLE heading need Blocks: no; Blocks: no-op is not Blocks: no; wrong-tree collect with an unreadable manifest Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: GST-987 docs audit: depth section notes the native FIXABLE exception Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: a paragraph under a numbered finding heading joins it, so its fields attach (gstack-evals #149, GST-987) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: verify re-derives native labels with the collect's class (GST-987, after #3717) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: #149 join stops at a labeled or located paragraph and after the fields (GST-987) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: GST-987 #149 sibling shapes as named never-weaker invariant fixtures Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: drop the released GST-976 changelog fragment carried by the stack 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> --- CHANGELOG.md | 8 ++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- bin/gstack-review-dispatch | 10 +- docs/review-policy.md | 9 +- lib/review-ledger.ts | 47 +++++++--- package.json | 2 +- .../fixtures/review-ledger/gsta-141-native.md | 6 ++ test/fixtures/review-ledger/main-labels.json | 2 +- test/review-fixable-class.test.ts | 91 +++++++++++++++++++ test/review-ledger-invariant.test.ts | 11 ++- 11 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/review-ledger/gsta-141-native.md create mode 100644 test/review-fixable-class.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 609c1bb6e..d9bcd7f28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.93.21.0] - 2026-11-02 + +**/ship's finding collector lets the native reviewer's own Severity field decide outside gate and risk reviews.** + +### Fixed + +- On a code or docs review with no risk reason (security, secrets, auth, money, user data), no depth error and no history or floor escalation, a native adversarial finding marked FIXABLE that states its own `Severity: Medium` or `Severity: Low` and `Blocks: no` now collects at that severity instead of CRITICAL. Gate and risk reviews, and any collect without this pass's manifest, keep collecting FIXABLE as CRITICAL, and so does a FIXABLE finding without `Blocks: no` or without a Severity field. (GST-987) + ## [1.93.20.0] - 2026-11-02 ### Changed diff --git a/VERSION b/VERSION index 500ba75b7..10f963b21 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.20.0 +1.93.21.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index ca19197ea..ae13f8251 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.20.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.21.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/bin/gstack-review-dispatch b/bin/gstack-review-dispatch index 4697c43b9..3bdc0d919 100755 --- a/bin/gstack-review-dispatch +++ b/bin/gstack-review-dispatch @@ -20,7 +20,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { EXIT, exitTableHelp } from '../lib/headless-artifacts'; import { BRIEF_ROLES, BriefError, briefFile, checkReviewedTree, checkoutRoot, checkoutTree, promptHash, renderBrief, sectionTexts, withWorkspace, type BriefRole, type Slots, type TreeCheck } from '../lib/review-brief'; -import { cleanMarkers, collect, mayBeUnavailable, parseFindings, readJsonl, readLedger, readerKey, recordJudgment, recordSent, appendEvent, verify, type JudgedLine, type MergedLine, type VerifyRow } from '../lib/review-ledger'; +import { cleanMarkers, collect, fixableRaises, mayBeUnavailable, parseFindings, readJsonl, readLedger, readerKey, recordJudgment, recordSent, appendEvent, verify, type JudgedLine, type MergedLine, type VerifyRow } from '../lib/review-ledger'; import { buildManifest, chunkCount, chunkOf, manifestPath, markReader, readManifest, renderStage, writeManifest, STAGES, type Extra, type Manifest, type PassReader, type ReaderState, type Stage } from '../lib/review-pass'; import type { Dispatch } from '../lib/review-depth'; import { runScriptSync } from '../lib/script-invocation'; @@ -329,10 +329,12 @@ function main(): number { process.stdout.write(`COLLECT_UNAVAILABLE: reader=${reader} sha256=${r.sha} reason=${JSON.stringify(why)} (missing outside coverage: unavailable, never clean)\n`); return EXIT.ok; } - const r = collect(ledger, reader, output, merged, path.resolve(outFile!), unavailable, tree); + const mf = flag('--manifest'); + let plan: ReturnType | undefined; + try { plan = mf ? readManifest(mf) : undefined; } catch (e) { process.stderr.write(`gstack-review-dispatch collect: ${(e as Error).message}; native FIXABLE collects as CRITICAL\n`); } + const r = collect(ledger, reader, output, merged, path.resolve(outFile!), unavailable, tree, fixableRaises(plan && path.resolve(plan.ledger) === path.resolve(ledger) ? plan.plan_lines : undefined)); if (tree && !tree.ok) { - const mf = flag('--manifest'); - if (mf) { const m = readManifest(mf); markReader(m, reader, 'failed', { output: outFile, error: `reviewed another tree: ${tree.reported}` }); writeManifest(mf, m); } + if (mf && plan) { markReader(plan, reader, 'failed', { output: outFile, error: `reviewed another tree: ${tree.reported}` }); writeManifest(mf, plan); } process.stdout.write(wrongTree(reader, tree)); return EXIT.fail; } diff --git a/docs/review-policy.md b/docs/review-policy.md index 1a8aa6fc3..b0b8f2358 100644 --- a/docs/review-policy.md +++ b/docs/review-policy.md @@ -326,7 +326,8 @@ fix-checks run at `full` effort (the lane's reasoning); specialists run at batched specialist, which carries the Red Team prompt and stays `full`. The outside voice and the native adversarial pass carry the floor sentence and end each finding with `Severity:` and `Blocks: yes|no`; a finding is CRITICAL when -its label rule says so (native FIXABLE, outside Critical or High) or it says +its label rule says so (native FIXABLE, except as the collect step below +relaxes it on a runtime-only code or docs plan; outside Critical or High) or it says `Blocks: yes`, and `Blocks: no` never lowers one (`gstack-review-dispatch collect`). Work that cannot block (the Step 7 coverage audit, the Step 8 plan-completion audit, the Step 14.5 docs audit, decision-trail reviewers, watchers) runs at `light`: the same model at a lower reasoning effort, never a @@ -564,7 +565,11 @@ ones, and the arm lost blockers. `bin/gstack-review-dispatch` closes both ends: second line for the same field starts another: `Severity` sets its label (a label leading the finding stays its floor), `Blocks: yes` makes it CRITICAL and `Blocks: no` never lowers it; an inline `Blocks: yes` is CRITICAL too, and with no finding above it is one. The native - adversarial voice's FIXABLE collects as CRITICAL (normalizer v2, GSTA-167). An unnumbered, unlabeled bullet with no file:line + adversarial voice's FIXABLE collects as CRITICAL (normalizer v2, GSTA-167) unless collect `--manifest` names this + ledger's pass and its plan is the code or docs class with only `runtime=` depth reasons (no `risk:`, `error:`, + `history:` or `floor:` reason). On such a plan a native finding's own `Severity` field decides and FIXABLE beside it + raises nothing once it also states `Blocks: no`; a native FIXABLE finding without `Blocks: no`, with no `Severity` field, + or whose field names only FIXABLE or INVESTIGATE still collects as CRITICAL (GST-987). An unnumbered, unlabeled bullet with no file:line under a paragraph finding whose Severity or Blocks line has not come yet is that finding's detail, so the field line after it lands on the finding; a numbered item is its own finding. A negated severity word still collects at its label ("found nothing CRITICAL", "No P0/P1 issues"), so a reader's diff --git a/lib/review-ledger.ts b/lib/review-ledger.ts index 4406d59c5..dce6fde5d 100644 --- a/lib/review-ledger.ts +++ b/lib/review-ledger.ts @@ -55,9 +55,11 @@ const ANY_LABEL = new RegExp(`(? ((rank(a) ?? 0) >= (rank(b) ?? 0) ? a : b); /** A prose voice's `Blocks: yes` (GSTA-167 normalizer v2): the finding is CRITICAL whatever its label; `Blocks: no` never lowers one. */ const BLOCKS_YES = /(? { - type Finding = { label: string; raw: string; fields: Set; list: boolean; lead?: string }; +export function parseFindings(reader: string, output: string, fixableRaises = true): Array<{ label: string; raw: string }> { + type Finding = { label: string; raw: string; fields: Set; list: boolean; lead?: string; fixable?: boolean; heading?: boolean }; const out: Finding[] = []; let section: string | null = null; let current: Finding | null = null; @@ -115,9 +119,9 @@ export function parseFindings(reader: string, output: string): Array<{ label: st const named = (l: string) => (role === 'adversarial-native' && l.toUpperCase() === 'FIXABLE' ? 'CRITICAL' : l); const strongestOf = (labels: Array) => labels.reduce((a, b) => (a === undefined ? b : b === undefined ? a : stronger(a, b)), undefined); const strongest = (t: string) => strongestOf([...t.matchAll(ALL_LABELS)].map(m => named(m[1]!))); - const fieldOf = (t: string) => { const ls = [...t.matchAll(SEVERITY_FIELD)].flatMap(m => (m[1] ?? m[2]!).match(FIELD_LABELS)!); return ls.length === 1 ? ls[0] : undefined; }; - const critical = (t: string) => BLOCKS_YES.test(t) || (role === 'adversarial-native' && /(? { const l = LEAD_LABEL.exec(t)?.[1]; return l === undefined ? undefined : named(l); }; + const fieldOf = (t: string) => { const ls = [...t.matchAll(SEVERITY_FIELD)].flatMap(m => (m[1] ?? m[2]!).match(FIELD_LABELS)!); return ls.length === 1 && (fixableRaises || role !== 'adversarial-native' || rank(ls[0]!)) ? ls[0] : undefined; }; + const critical = (t: string) => BLOCKS_YES.test(t) || (fixableRaises && role === 'adversarial-native' && /(? { const l = LEAD_LABEL.exec(t)?.[1]; return l === undefined || !fixableRaises ? l : named(l); }; const labelOf = (t: string) => { const field = fieldOf(t); if (field === undefined) return strongest(t); @@ -150,7 +154,7 @@ export function parseFindings(reader: string, output: string): Array<{ label: st continue; } const heading = LABEL_HEADING.exec(t); - if (heading) { section = named(heading[1]!); current = null; head = ''; continue; } + if (heading) { section = fixableRaises ? named(heading[1]!) : heading[1]!; current = null; head = ''; continue; } if (!indented && HEADING.test(t)) { section = null; current = null; } if (t.startsWith('{')) { try { @@ -160,12 +164,20 @@ export function parseFindings(reader: string, output: string): Array<{ label: st } const detail = listItem && /^[-*+]\s/.test(t) && !locus && !label && !section && !!current && !current.list && !current.fields.size; if (detail && !blocks) { current!.raw += ` | ${t}`; continue; } + if (afterGap && current?.heading && !current.fields.size && !LIST_ITEM.test(t) && !HEADING.test(t) && !NUMBERED_HEAD.test(t) && !LEAD_LABEL.test(t) && !LOCUS.test(t)) { + current.raw += ` | ${t}`; + if (blocks) current.label = 'CRITICAL'; + else if (label) current.label = stronger(current.label, label); + continue; + } const fresh = (listItem && !detail) || (!indented && !detail && (t.startsWith('**') || afterGap)) || (!!current?.fields.size && locus); if (fresh) { current = null; head = t; } const inherited = section && listItem ? section : undefined; const found = label && inherited ? stronger(inherited, label) : label ?? inherited; if (found) { current = push(blocks ? 'CRITICAL' : found, t, [], strongestOf([leadOf(t), inherited, fieldOf(t)])); + current.fixable = inherited?.toUpperCase() === 'FIXABLE'; + current.heading = !indented && NUMBERED_HEAD.test(t); if (!indented) head = t; continue; } @@ -176,7 +188,20 @@ export function parseFindings(reader: string, output: string): Array<{ label: st } if (!fresh && !current && (!LIST_ITEM.test(head) || locus)) head = t; } - return out.map(({ label, raw }) => ({ label, raw })); + const unblocked = (f: Finding) => fixableRaises || role !== 'adversarial-native' || !(f.fixable || f.label.toUpperCase() === 'FIXABLE' || /(? ({ label: unblocked(f) ? f.label : 'CRITICAL', raw: f.raw })); +} + +/** + * Whether the native voice's FIXABLE raises a finding with its own Severity field to CRITICAL (GST-987): true on the gate class, on any + * risk reason (security, secret, auth, money, user data), a depth error, a history or floor escalation, and whenever the plan's + * REVIEW_CLASS or REVIEW_DEPTH reasons cannot be read: only a code or docs plan whose every reason is `runtime=` relaxes it. + */ +export function fixableRaises(planLines?: readonly string[]): boolean { + const cls = planLines?.map(l => /^REVIEW_CLASS: (\S+)/.exec(l)?.[1]).find(Boolean); + const reasons = planLines?.map(l => /^REVIEW_DEPTH: .*?\breasons=(\S+)/.exec(l)?.[1]).find(Boolean); + if (!cls || !reasons) return true; + return !['code', 'docs'].includes(cls) || !reasons.split(',').every(r => /^runtime=\d+$/.test(r)); } /** @@ -201,17 +226,17 @@ const hasCleanMarker = (reader: string, output: string) => output.replace(/\r\n/ * review: false. With unavailable (the outside voice only) the output is its failure text, recorded as missing coverage. With a * failed tree check (GST-482: the reply reviewed another checkout, or names none) nothing is merged and the output is no review. */ -export function collect(ledger: string, reader: string, output: string, mergedFile: string, outputFile?: string, unavailable?: string, tree?: TreeCheck): { sha: string; lines: MergedLine[]; review: boolean } { +export function collect(ledger: string, reader: string, output: string, mergedFile: string, outputFile?: string, unavailable?: string, tree?: TreeCheck, raises = true): { sha: string; lines: MergedLine[]; review: boolean } { const sha = promptHash(output); const prior = readLedger(ledger).filter(e => e.event === 'output' && e.reader === reader).length; const wrongTree = !unavailable && tree !== undefined && !tree.ok; - const lines = unavailable || wrongTree ? [] : parseFindings(reader, output).map((f, i) => { + const lines = unavailable || wrongTree ? [] : parseFindings(reader, output, raises).map((f, i) => { const s = stated(f.raw); return { id: `${reader}#${prior ? `${prior + 1}.` : ''}${i + 1}`, reader, label: f.label, ...(s ? { stated: s } : {}), raw: f.raw, output_sha256: sha }; }); const review = !unavailable && !wrongTree && (lines.length > 0 || hasCleanMarker(reader, output)); fs.appendFileSync(mergedFile, lines.map(l => JSON.stringify(l) + '\n').join(''), { mode: 0o600 }); - appendEvent(ledger, { event: 'output', reader, sha256: sha, file: outputFile ? path.resolve(outputFile) : null, merged: path.resolve(mergedFile), ids: lines.map(l => l.id), review, ...(unavailable ? { unavailable } : {}), + appendEvent(ledger, { event: 'output', reader, sha256: sha, file: outputFile ? path.resolve(outputFile) : null, merged: path.resolve(mergedFile), ids: lines.map(l => l.id), review, ...(unavailable ? { unavailable } : {}), ...(raises ? {} : { fixable_raises: false }), ...(tree && !unavailable ? { tree: tree.expected, reported_tree: tree.reported, tree_ok: tree.ok } : {}) }); return { sha, lines, review }; } @@ -408,7 +433,7 @@ export function verify(ledger: string): { rows: VerifyRow[]; clean: boolean; bri notes.push(typeof o.file === 'string' ? 'output_file_missing' : 'output_file_unrecorded'); differs.push(...ids); } else if (promptHash(text) === o.sha256) { - const parsed = parseFindings(reader, text); + const parsed = parseFindings(reader, text, o.fixable_raises !== false); const off = ids.filter((id, k) => { const m = lines.find(l => l.id === id); return !m || parsed[k]?.label !== m.label || parsed[k]?.raw !== m.raw; }); if (unavailableOutput(o) && parsed.length) { status = 'MISMATCH'; notes.push('unavailable_output_has_findings'); } else if (!unavailableOutput(o)) { diff --git a/package.json b/package.json index 0ae64ebdb..7a34868dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.20", + "version": "1.93.21", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/fixtures/review-ledger/gsta-141-native.md b/test/fixtures/review-ledger/gsta-141-native.md new file mode 100644 index 000000000..5028e1abf --- /dev/null +++ b/test/fixtures/review-ledger/gsta-141-native.md @@ -0,0 +1,6 @@ +# garrytan/gstack-evals#141: native adversarial findings #4 and #6, head a752186 (tree 0ac70e872724875710ccc47f9c93b7b30a58604a) +Copied verbatim from /home/user/.capy/work/ship-141/run/p2/out/adversarial-native.txt + +4. **driver.ts:352, 561 and 618 (the C1 ruling): skill-eval sessions now end with background Agents still running and leave no structured record.** run-host.ts:352 passes no wait. Claude Code 2.1.314 runs Agent calls in the background by default. So a skill-eval `--host claude` session ends and is graded on its "agents launched, waiting" turn, which is the original GST-734 bug. Its record also has no `session_end`, only a line in cli-stderr.log. The CREW rule asks for a per-arm count of sessions that ended with tasks pending, and these records can't give that count. Grades for skills that hand work to Agents may be deflated with nothing marking them. Old cached sessions do not mix in, because adapterSha256 hashes driver.ts. The fix: keep emitting `session_end` (cause `tasks_pending`, `waited_ms` 0) when no wait is set, and leave the hold opt-in. That reverses the C1 test's `"session_end" in r.outcome === false` assertion, so the C1 owner needs to agree. FIXABLE. Severity: Medium. Blocks: no + +6. **driver.ts:404-407: the timeout branch for an idle session sets `overdue = true` without checking `stop`.** Here is the case. A background agent's refused request sets a stop while the main agent is idle, such as `cap_stop`, or an infra failure does. The timer then fires during abort teardown. The record ends up with a `cap_stop` or infra termination but `session_end.cause` "deadline". The `watch` interval already checks `stop`. Fix: `else if (idleSince !== null && !stop)`. FIXABLE. Severity: Low. Blocks: no diff --git a/test/fixtures/review-ledger/main-labels.json b/test/fixtures/review-ledger/main-labels.json index c923a6ceb..889b48974 100644 --- a/test/fixtures/review-ledger/main-labels.json +++ b/test/fixtures/review-ledger/main-labels.json @@ -1 +1 @@ -{"note":"parseFindings labels at main 82e9d7ec (GST-976): every input the review-dispatch and prose-voice-floor tests pass it, every reader fixture here, and generated Severity field shapes. Regenerate only from a frozen main.","main":"82e9d7ec7a2c222f20b04aaf5b965fa50d7b6bdb","entries":[{"source":"pinned","reader":"adversarial-native","output":"FIXABLE: race in a.ts:3\nSeverity: Medium\nBlocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"INVESTIGATE: odd lock in b.ts:9\nSeverity: Low\nBlocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"INVESTIGATE: odd lock in b.ts:9\nBlocks: no","main":["INVESTIGATE"]},{"source":"pinned","reader":"adversarial-native","output":"FIXABLE: race in a.ts:3. Severity: Medium. Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"1. FIXABLE: race in a.ts:3 — Severity: Low, Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"- FIXABLE: race in a.ts:3\n- Severity: Medium\n- Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Drops the last row (q.ts:7). Severity: Critical. Blocks: no","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"1. Cache evicts sessions (c.ts:4)\n Severity: High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry storm (r.ts:2)\n Severity: Medium\n Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Index drift (i.ts:5). Severity: Low. Blocks: Yes, it loses the row","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Naming (n.ts:1)\n Severity: Low\n Blocks: no","main":["Low"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nNO FINDINGS\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"off by one\",\"specialist\":\"testing\"}\n{\"severity\":\"INFORMATIONAL\",\"path\":\"a.ts\",\"line\":9,\"category\":\"style\",\"summary\":\"name\",\"specialist\":\"testing\"}\n","main":["CRITICAL","INFORMATIONAL"]},{"source":"pinned","reader":"outside-voice","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nFindings:\n1. **High**: retry loop never backs off (q.ts:78)\n2. Low - log line is noisy\nRecommendation: Fix the retry because it DoSes the pool\n","main":["High","Low"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"x\",\"specialist\":\"testing\"}\n","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"b.ts\",\"line\":1,\"category\":\"x\",\"summary\":\"y\",\"specialist\":\"red-team\"}\n","main":["CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"x\",\"specialist\":\"testing\"}\n{\"severity\":\"INFORMATIONAL\",\"path\":\"b.ts\",\"line\":1,\"category\":\"n\",\"summary\":\"y\",\"specialist\":\"testing\"}\n","main":["CRITICAL","INFORMATIONAL"]},{"source":"pinned","reader":"r","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"r","output":"## High\n1. The auth check skips webhooks (a.ts:4)\n - Impact: nested evidence\n **Fix:**\n- second one\n## Notes\n- not a finding\n**Critical findings**\n- real one\n**Recommendations**\n- consider renaming foo\n","main":["High","High","Critical"]},{"source":"pinned","reader":"r","output":"1. **Unbounded retry in queue.ts:78** (FIXABLE): loops on 429\n2. `auth.ts:42` skips token expiry check — INVESTIGATE\n### CRITICAL: missing auth check in api.ts:10\n#### [P1] token not expired\n### High: authorization bypass (auth.ts:14)\n- **HIGH**: leak\n| High | race in queue.ts:4 |\nClassification: INVESTIGATE\n7. **Informational: reads every file**","main":["FIXABLE","INVESTIGATE","CRITICAL","P1","High","HIGH","High","INVESTIGATE","Informational"]},{"source":"pinned","reader":"r","output":"FIXABLE: race in a.ts:3\nINVESTIGATE: odd lock\nRecommendation: Fix the race because x\n","main":["FIXABLE","INVESTIGATE"]},{"source":"pinned","reader":"outside-voice","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"REVIEWED_TREE: bedd511efa0fa16c9277821073c68e438dc760d2 /workspace/gstack-evals\n{\"severity\":\"INFORMATIONAL\",\"path\":\"wiki/board/README.md\",\"line\":229,\"summary\":\"Section 9's opening still says every ruling is recorded in the approvals file.\"}\n{\"severity\":\"INFORMATIONAL\",\"path\":\"wiki/board/README.md\",\"line\":113,\"summary\":\"The tstack row still runs on full stacks-r1 and is not labelled raw.\"}\n\nI checked these against the post, the frozen texts and the pinned code, and found nothing CRITICAL. The arithmetic is consistent throughout:\n- The new secondary: 47/60, 15/60, 5/40, 7/40, 4/40 and 11.5/40.\n- The minimum p of 2^-15.","main":["INFORMATIONAL","INFORMATIONAL","CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Found no CRITICAL issues in the fix range.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Nothing CRITICAL or HIGH remains.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"This is not CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"I found zero Critical findings, and none High.","main":["Critical"]},{"source":"pinned","reader":"fix-check","output":"The retry path is safe; without CRITICAL impact.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No new CRITICAL issues introduced by the fix.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No remaining CRITICAL or HIGH findings.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"**No** CRITICAL issues found.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No **CRITICAL** issues found.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No `CRITICAL` issue.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No [P1] issues remain.","main":["P1"]},{"source":"pinned","reader":"fix-check","output":"Nothing at CRITICAL severity remains.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL regressions introduced by the fix.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Nothing CRITICAL to report.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"There is nothing CRITICAL in the fix range.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL — the fix is sound.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Found no CRITICAL or HIGH regressions.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No HIGH-risk changes.","main":["HIGH"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL/HIGH findings.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No P0/P1 issues.","main":["P0"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL, HIGH or MEDIUM issues.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Checked collect() at lib/review-ledger.ts:189; no CRITICAL issues remain.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL issues; served on localhost:3000.","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"Not CRITICAL, but High: the cache drops a row (c.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"No CRITICAL, High: the cache drops a row (c.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"Nothing CRITICAL / HIGH: race in q.ts:7","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"No CRITICAL lock guards the write in Makefile:7\nSeverity: CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"1. auth.ts:3 LOW: log line too verbose\nNo CRITICAL lock guards the write in Makefile:7\nSeverity: CRITICAL","main":["LOW","CRITICAL"]},{"source":"pinned","reader":"r","output":"1. auth.ts:3 LOW: log line too verbose\nNo CRITICAL issues remain.","main":["LOW","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- a.ts:3 race. Severity: Low. Blocks: no\n Separately, nothing CRITICAL about b.ts:9, but it leaks fds.\n Severity: High","main":["Low","CRITICAL"]},{"source":"pinned","reader":"r","output":"1. A CRITICAL race in queue.ts:78 that no lock covers","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"- A divide by zero CRITICAL bug in parse()","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"FIXABLE: no timeout on the fetch in a.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry loop drops the last row (queue.ts:78). Severity: Medium · Blocks: yes\n2. INVESTIGATE: lock order in pool.ts:12\n Blocks: yes\n3. FIXABLE: stale cache key (cache.ts:4)\n Severity: High\n Blocks: no\n4. Token never expires (auth.ts:9)\n Fix: check exp\n **Blocks:** yes\nRecommendation: Fix the retry loop because it loses rows","main":["CRITICAL","CRITICAL","High","CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"1. INVESTIGATE: lock order in pool.ts:12\n Severity: Medium\n Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Low-level retry loop in queue.ts:78 drops writes on 429. Severity: Critical. Blocks: no\n- [P2] cache.ts:40 evicts live sessions. Severity: High. Blocks: no","main":["Critical","High"]},{"source":"pinned","reader":"outside-voice","output":"1. FIXABLE: race in a.ts:3\nWhy it matters: two writers\nSeverity: Low\nBlocks: yes\n2. Unlabeled item about b.ts:9\nSeverity: Medium","main":["CRITICAL","Medium"]},{"source":"pinned","reader":"outside-voice","output":"1. Token never expires (auth.ts:9). Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Token never expires in auth.ts:9 — Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token never expires (auth.ts:9)\nFix: check exp **Blocks:** yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"## Critical findings\n- Severity: Medium — missing owner check at auth.ts:14\n- Severity: Low — token logged at log.ts:3","main":["Critical","Critical"]},{"source":"pinned","reader":"outside-voice","output":"**Finding 1.** a.ts:3 drops rows\nSeverity: Low\nBlocks: no\n**Finding 2.** b.ts:4 leaks\nSeverity: High\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"a.ts:3 drops rows\nSeverity: High\nBlocks: yes\nb.ts:4 leaks\nSeverity: High\nBlocks: yes","main":["CRITICAL","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Medium: cache key drift (c.ts:4)\n Blocks the event loop on every miss - High","main":["Medium","High"]},{"source":"pinned","reader":"outside-voice","output":"2. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"> Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"A in a.ts:1.\nSeverity: Low\n\nB in b.ts:2.\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"A in a.ts:1.\nSeverity: Low\nB in b.ts:2.\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Findings:\n A in a.ts:1.\n Severity: Low\n B in b.ts:2.\n Blocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"A in a.ts:1.\nSeverity: Low\nB in b.ts:2 drops rows. Blocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"**Finding 1.** a.ts:3 races. Severity: Low\n**Finding 2.** b.ts:4 leaks. Blocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"FIXABLE: race in a.ts:3\nSeverity: Medium\nWhy: the writer races\nBlocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- Unlabeled thing in a.ts:3\n- Severity: Medium\n- Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token never expires (auth.ts:9)\nFix: check exp\nBlocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry storm (r.ts:2)\nSeverity High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"CRITICAL: x.ts:4 drops rows (pass 1 had Severity: Low)","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native/adversarial","output":"FIXABLE: race in a.ts:3\nSeverity: Medium\nBlocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"FIXABLE: race in a.ts:3\nSeverity: Medium","main":["Medium"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry loses writes (queue.ts:78). Severity: Medium\n Fix: reject the batch. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"specialist-batched","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nI couldn't run the review because the diff base doesn't exist. `git diff 1b68e637c07cea38f01c01120e02d184575da82c` fails with \"bad object\", and fetching that sha from origin is refused with \"not our ref\", so the commit is neither in this checkout nor on the remote.\n\nThis checkout is on `main` at 047dad7 (v1.91.357.0), tracking origin/main with no uncommitted changes, so there's nothing local to review against either. I'm not returning NO FINDINGS, because that would claim a clean diff I never saw.\n\nTo continue I need one of these: a diff base that exists, or the branch or sha holding the c3-36-p1 change. It may only exist on another machine and was never pushed. I've read all three checklists (testing, security, state-machines) and will apply them as soon as I have a diff.","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nI could not access the diff: git diff failed with \"bad object\", so there is nothing here to review.\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"off by one\",\"specialist\":\"testing\"}\n","main":["CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"specialist-batched","output":"`NO FINDINGS`\n","main":[]},{"source":"pinned","reader":"targeted","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"fix-check","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"red-team","output":"Fixtures were reviewed in summary mode only.\n\nNO FINDINGS\n","main":[]},{"source":"pinned","reader":"adversarial-native","output":"Fixtures were reviewed in summary mode.\n\nRecommendation: Ship as-is because no finding survived tracing\n","main":[]},{"source":"pinned","reader":"adversarial-native","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"No actionable findings in the supplied diff. Static review only.\n\nRecommendation: approve because the diff introduces no demonstrated bypass.\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"Recommendation: ship because it is clean\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"I'm not returning NO FINDINGS, because I never saw the diff.\n","main":[]},{"source":"pinned","reader":"red-team","output":"","main":[]},{"source":"pinned","reader":"fix-check","output":"The fixes look fine to me.\n","main":[]},{"source":"pinned","reader":"adversarial-native","output":"I could not read the diff.\n","main":[]},{"source":"pinned","reader":"adversarial-native","fixture":"review-ledger/gsta-196-native.txt","main":["High","CRITICAL","CRITICAL","Low"]},{"source":"pinned","reader":"outside-voice","output":"1. FIXABLE: race in a.ts:3\n- Unlabeled sibling at b.ts:9\nSeverity: Medium","main":["FIXABLE","Medium"]},{"source":"pinned","reader":"outside-voice","output":"**Retry storm in r.ts:2 (INVESTIGATE).**\n- detail\n- High: a second defect in s.ts:4\nSeverity: Medium","main":["INVESTIGATE","High"]},{"source":"pinned","reader":"outside-voice","output":"Low: cache TTL mismatch (cache.ts:4)\n\n2. Token never expires (auth.ts:9)\nSeverity: High\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"**Race in a.ts:3 (High).**\n- the writer holds no lock\nSeverity: Medium","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"**Race in a.ts:3 (Medium).**\n\n- the writer holds no lock\n\nSeverity: Medium. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"{\"severity\":\"INFORMATIONAL\",\"path\":\"a.ts\",\"line\":3,\"summary\":\"Severity: High is the wrong word here\"}\n","main":["INFORMATIONAL"]},{"source":"pinned","reader":"outside-voice","output":"| 1 | `CRITICAL` | High | token leak in log.ts:3 |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"| log.ts:3 | `CRITICAL` | High | token leak |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None of the writes are atomic and are CRITICAL (queue.ts:78); High confidence","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing guards the write path the failure of which is CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"| a.ts:3 | race | not CRITICAL, Low |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"| a.ts:3 | maps `FIXABLE` to `CRITICAL` in a comment | Low |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Neither path checks auth so this is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None of the inputs are sanitized so this is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing checks the token which is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No bounds check lets CRITICAL data leak (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No defects in parsing but the upload handler is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Without auth this is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Not checking expiry at auth.ts:9 lets stale tokens through. Severity: High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"The lock isn't released on error (pool.ts:12): High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"A retry storm could escalate up to CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Low: the parser now maps `FIXABLE` to `CRITICAL` in a comment (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No input validation is CRITICAL (upload.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No CSRF token is a CRITICAL gap at form.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing escaped here is CRITICAL: stored XSS at view.ts:8","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No timeout here is High (fetch.ts:3)","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"Upgraded: not High, CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Severity: not Medium, CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing else is CRITICAL except the token leak in log.ts:3.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No other issue is CRITICAL besides the auth bypass in auth.ts:9.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None of the other findings are CRITICAL, but the SQL injection in db.ts:12 is.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing here is CRITICAL apart from the unescaped query (db.ts:12).","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing else is CRITICAL except the token leak.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Fix the token leak in log.ts:3 first; nothing else is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 blocks the merge. Nothing else is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token leak in log.ts:3 must be fixed before landing.\n\nNothing else is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak blocks the merge.\nNo other issue is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 blocks the merge. None of the other findings are CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 must block the merge. The other two are nits. Neither finding is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Apart from the token leak in log.ts:3, I found two nits. Neither finding is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Besides the auth bypass at auth.ts:9 there are three nits. None of the three findings are CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 must block the merge. Nothing here is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No auth check guards the admin route, so nothing CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","fixture":"review-ledger/harn15-152-p1-adversarial-native.txt","main":["CRITICAL","INVESTIGATE","INVESTIGATE","CRITICAL","INVESTIGATE","INVESTIGATE","CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"**1. Race in a.ts:3 (`FIXABLE`).**\nSeverity: Low. Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"1. Race in a.ts:3 (FIXABLE).","main":["FIXABLE"]},{"source":"pinned","reader":"adversarial-native","output":"No rate limit is FIXABLE (login.ts:9)","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","fixture":"review-ledger/harn15-ship2-red-team.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"red-team","fixture":"review-ledger/harn15-ship2-p2-red-team.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"fix-check","fixture":"review-ledger/harn15-ship2-p2-fix-check.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"fix-check","fixture":"review-ledger/harn15-152-p2-fix-check.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"fix-check","fixture":"review-ledger/geva-77-fix-check.txt","main":["INFORMATIONAL","INFORMATIONAL","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"There are no CRITICAL findings.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No CRITICAL or High findings to report.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"I found nothing CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"So there's nothing at CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Neither finding is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing here is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Neither fix is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing in this diff is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"This is not CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Severity: Low (not High)","main":["High"]},{"source":"pinned","reader":"adversarial-native","fixture":"review-ledger/harn15-ship2-p2-adversarial-native.txt","main":["CRITICAL","CRITICAL","CRITICAL","CRITICAL","CRITICAL","Low"]},{"source":"pinned","reader":"outside-voice","output":"1. **Race on the lock** (a.ts:3)\n - the writer holds no lock\n\n Fix: take it first. Severity: High. Blocks: no.","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"1. Lock race (a.ts:3)\n\n Fix: take it first. Blocks: yes\n2. Cache drift (c.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n {\"severity\":\"critical\",\"summary\":\"Low-level race in q.ts\"}","main":["critical"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n **Critical**\n2. Token leak in the logger","main":["Critical"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n ## High\n2. Token leak in the logger","main":["High"]},{"source":"pinned","reader":"red-team","output":"1. Race in queue\n- Severity: Low\n this one blocks. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n {\"severity\":\"low\",\"summary\":\"race\"}\n this blocks the merge. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The problems below are about accounting and what the post leaves out. None of them breaks a test, production or custody.\n\n1. **The per-task cost leaves out model spend.** The P0 and bulk go or no-go will re-project cost from these figures, so they will come out too low. Severity: Medium. Blocks: no.\n\n2. **One round-2 K1 pair is not accounted for.** The post sets a precedent for P0 yield math, so that drop should be named. Severity: Low. Blocks: no.\n\n3. **The two probe populations are not reconciled.** The stage table probes 189 candidates. Severity: Low. Blocks: no.\n\n4. **The P0 impact of the shortfall is missing.** The plan's P0 (line 323) needs 6 K4 replays and 2 decoys. The post says the shortfall but not that it blocks P0. Severity: Low. Blocks: no.\n\n6. **Some wording is ambiguous.** Two examples:\n - \"Repositories 4 (3 for build tasks)\" is set against a target of \"at least 5 repositories\".\n\n Severity: Low. Blocks: no.\n\nRecommendation: ship after a one-line fix because finding 1's per-task cost leaves out measured model spend.","main":["P0","P0","Low","P0","Low"]},{"source":"pinned","reader":"outside-voice","output":"[P0] Token leak in log.ts:3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"P0: token leak in log.ts:3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. Token leak in log.ts:3\n Severity: P0","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot drops the last row (q.ts:7). Severity: P0. Blocks: no","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"**Critical**\n- Token leak in log.ts:3","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"## P0\n- Token leak in log.ts:3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"### CRITICAL: missing auth check in api.ts:10","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot loses rows on retry (r.ts:2). Severity: Low. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot loses rows on retry (r.ts:2)\n Severity: Medium\n Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row (t.md:3)","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. api.ts:10 CRITICAL: missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. api.ts:10 CRITICAL: missing auth\n Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. 🔴 CRITICAL: missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token leak in auth.ts:3 lets anyone read sessions. Severity: Critical\n\nSeverity: Low","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"- auth.ts:3 token leak. Severity: Low (pre-exploit), Severity: Critical (once exposed)","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"- Missing auth on DELETE /users lets any user wipe accounts (CRITICAL); the old ticket filed it at severity low.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- api.ts:10 lets any user wipe accounts. Earlier triage said Severity: Low; it is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. CRITICAL missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. CRITICAL, missing auth on DELETE. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. CRITICAL (api.ts:10) missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row (t.md:3) Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\n**Severity** Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. Bypass x.ts:3 is CRITICAL (Severity: Low in pass 1)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Still open: x.ts:3 is CRITICAL, Severity: Low in pass 1 understated it","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. lib/x.ts:42-58 CRITICAL: drops rows. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. lib/x.ts:42:7 CRITICAL: drops rows. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. (lib/x.ts:42, 50) CRITICAL: drops rows. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity: Low: only the pilot","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity: Low because it is internal","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity: Low/Medium","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"Severity: Low: only the P0 pilot table","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot drops a row. Severity: Low/P3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. drops rows\nSeverity: High/Critical","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"Severity: Medium/CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. x.ts:3 drops prod rows. Severity: Low/CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. x.ts:3 is CRITICAL (Severity: Low/Medium in pass 1)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. x.ts:3 is CRITICAL (Severity: Low: pass 1)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Still open: x.ts:3 is P0, Severity: Low: pass 1 understated it","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nI could not access the diff.\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"Codex timed out after 9 minutes and was terminated; this pass produced NO findings.\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"1. **CRITICAL**: token leak in a.ts:3\nCodex then timed out.\n","main":["CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"- **REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt**\nNO FINDINGS\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"1. **CRITICAL**: auth bypass in a.ts:3\nBlocks: yes\n\nRecommendation: request changes\n","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Codex timed out before it reviewed anything.\n","main":[]},{"source":"pinned","reader":"outside-voice","fixture":"review-wrong-tree/gpt-6.1-sol-gst850-p1.out","main":["CRITICAL","CRITICAL","CRITICAL","CRITICAL","CRITICAL"]},{"source":"pinned","reader":"outside-voice","fixture":"review-wrong-tree/gpt-6.1-sol-gst850-p2.out","main":["CRITICAL","CRITICAL","CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"cd '/home/user/.capy/work/gst976/wt' && echo \"REVIEWED_TREE: $(git rev-parse 'HEAD^{tree}') $(git rev-parse --show-toplevel)\"\nprinted: REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nNO FINDINGS\n","main":[]},{"source":"fixture","reader":"outside-voice","fixture":"review-ledger/main-labels.json","main":["CRITICAL"]},{"source":"fixture","reader":"outside-voice","fixture":"review-ledger/gsta-196-native.txt","main":["High","Low","Low","Low"]},{"source":"fixture","reader":"outside-voice","fixture":"review-ledger/gsta-196-refusal.txt","main":[]}],"shapes":{"bodies":["1. The P0 pilot drops rows (a.ts:3)","1. CRITICAL: drops rows in a.ts:3","- High: the cache leaks (c.ts:4)","**Race in q.ts:7** nothing CRITICAL here","FIXABLE: race in r.ts:2","Note on b.ts:4 for the p1 pilot"],"fields":["Severity: Low","Severity: low","Severity: High/Critical","Severity: Medium/CRITICAL","Severity: Low/Medium","Severity: Low → Medium","Severity: Low: only the pilot","Severity: Low because it is internal","Severity: Low in pass 1, P0 now","(Severity: Low in pass 1)","(Severity: Low/Medium in pass 1)","(Severity: Low: pass 1)","**Severity:** Medium","**Severity** Low","Severity Low","SEVERITY: LOW","Severity: P3","Severity: Low (not High)","Severity: Low. Blocks: no","Severity: Low. Blocks: yes"],"joins":[". ","\n","\n "," — ",", "," "],"readers":["outside-voice","adversarial-native"],"generated":[[0,0,0,0,["P0"]],[0,0,0,1,["P0"]],[0,0,1,0,["P0"]],[0,0,1,1,["P0"]],[0,0,2,0,["P0"]],[0,0,2,1,["P0"]],[0,0,3,0,["P0"]],[0,0,3,1,["P0"]],[0,0,4,0,["P0"]],[0,0,4,1,["P0"]],[0,0,5,0,["P0"]],[0,0,5,1,["P0"]],[0,1,0,0,["P0"]],[0,1,0,1,["P0"]],[0,1,1,0,["P0"]],[0,1,1,1,["P0"]],[0,1,2,0,["P0"]],[0,1,2,1,["P0"]],[0,1,3,0,["P0"]],[0,1,3,1,["P0"]],[0,1,4,0,["P0"]],[0,1,4,1,["P0"]],[0,1,5,0,["P0"]],[0,1,5,1,["P0"]],[0,2,0,0,["P0"]],[0,2,0,1,["P0"]],[0,2,1,0,["P0"]],[0,2,1,1,["P0"]],[0,2,2,0,["P0"]],[0,2,2,1,["P0"]],[0,2,3,0,["P0"]],[0,2,3,1,["P0"]],[0,2,4,0,["P0"]],[0,2,4,1,["P0"]],[0,2,5,0,["P0"]],[0,2,5,1,["P0"]],[0,3,0,0,["P0"]],[0,3,0,1,["P0"]],[0,3,1,0,["P0"]],[0,3,1,1,["P0"]],[0,3,2,0,["P0"]],[0,3,2,1,["P0"]],[0,3,3,0,["P0"]],[0,3,3,1,["P0"]],[0,3,4,0,["P0"]],[0,3,4,1,["P0"]],[0,3,5,0,["P0"]],[0,3,5,1,["P0"]],[0,4,0,0,["P0"]],[0,4,0,1,["P0"]],[0,4,1,0,["P0"]],[0,4,1,1,["P0"]],[0,4,2,0,["P0"]],[0,4,2,1,["P0"]],[0,4,3,0,["P0"]],[0,4,3,1,["P0"]],[0,4,4,0,["P0"]],[0,4,4,1,["P0"]],[0,4,5,0,["P0"]],[0,4,5,1,["P0"]],[0,5,0,0,["P0"]],[0,5,0,1,["P0"]],[0,5,1,0,["P0"]],[0,5,1,1,["P0"]],[0,5,2,0,["P0"]],[0,5,2,1,["P0"]],[0,5,3,0,["P0"]],[0,5,3,1,["P0"]],[0,5,4,0,["P0"]],[0,5,4,1,["P0"]],[0,5,5,0,["P0"]],[0,5,5,1,["P0"]],[0,6,0,0,["P0"]],[0,6,0,1,["P0"]],[0,6,1,0,["P0"]],[0,6,1,1,["P0"]],[0,6,2,0,["P0"]],[0,6,2,1,["P0"]],[0,6,3,0,["P0"]],[0,6,3,1,["P0"]],[0,6,4,0,["P0"]],[0,6,4,1,["P0"]],[0,6,5,0,["P0"]],[0,6,5,1,["P0"]],[0,7,0,0,["P0"]],[0,7,0,1,["P0"]],[0,7,1,0,["P0"]],[0,7,1,1,["P0"]],[0,7,2,0,["P0"]],[0,7,2,1,["P0"]],[0,7,3,0,["P0"]],[0,7,3,1,["P0"]],[0,7,4,0,["P0"]],[0,7,4,1,["P0"]],[0,7,5,0,["P0"]],[0,7,5,1,["P0"]],[0,8,0,0,["P0"]],[0,8,0,1,["P0"]],[0,8,1,0,["P0"]],[0,8,1,1,["P0"]],[0,8,2,0,["P0"]],[0,8,2,1,["P0"]],[0,8,3,0,["P0"]],[0,8,3,1,["P0"]],[0,8,4,0,["P0"]],[0,8,4,1,["P0"]],[0,8,5,0,["P0"]],[0,8,5,1,["P0"]],[0,9,0,0,["P0"]],[0,9,0,1,["P0"]],[0,9,1,0,["P0","Low"]],[0,9,1,1,["P0","Low"]],[0,9,2,0,["P0","Low"]],[0,9,2,1,["P0","Low"]],[0,9,3,0,["P0"]],[0,9,3,1,["P0"]],[0,9,4,0,["P0"]],[0,9,4,1,["P0"]],[0,9,5,0,["P0"]],[0,9,5,1,["P0"]],[0,10,0,0,["P0"]],[0,10,0,1,["P0"]],[0,10,1,0,["P0","Medium"]],[0,10,1,1,["P0","Medium"]],[0,10,2,0,["P0","Medium"]],[0,10,2,1,["P0","Medium"]],[0,10,3,0,["P0"]],[0,10,3,1,["P0"]],[0,10,4,0,["P0"]],[0,10,4,1,["P0"]],[0,10,5,0,["P0"]],[0,10,5,1,["P0"]],[0,11,0,0,["P0"]],[0,11,0,1,["P0"]],[0,11,1,0,["P0","Low"]],[0,11,1,1,["P0","Low"]],[0,11,2,0,["P0","Low"]],[0,11,2,1,["P0","Low"]],[0,11,3,0,["P0"]],[0,11,3,1,["P0"]],[0,11,4,0,["P0"]],[0,11,4,1,["P0"]],[0,11,5,0,["P0"]],[0,11,5,1,["P0"]],[0,12,0,0,["P0"]],[0,12,0,1,["P0"]],[0,12,1,0,["P0"]],[0,12,1,1,["P0"]],[0,12,2,0,["P0"]],[0,12,2,1,["P0"]],[0,12,3,0,["P0"]],[0,12,3,1,["P0"]],[0,12,4,0,["P0"]],[0,12,4,1,["P0"]],[0,12,5,0,["P0"]],[0,12,5,1,["P0"]],[0,13,0,0,["P0"]],[0,13,0,1,["P0"]],[0,13,1,0,["P0"]],[0,13,1,1,["P0"]],[0,13,2,0,["P0"]],[0,13,2,1,["P0"]],[0,13,3,0,["P0"]],[0,13,3,1,["P0"]],[0,13,4,0,["P0"]],[0,13,4,1,["P0"]],[0,13,5,0,["P0"]],[0,13,5,1,["P0"]],[0,14,0,0,["P0"]],[0,14,0,1,["P0"]],[0,14,1,0,["P0"]],[0,14,1,1,["P0"]],[0,14,2,0,["P0"]],[0,14,2,1,["P0"]],[0,14,3,0,["P0"]],[0,14,3,1,["P0"]],[0,14,4,0,["P0"]],[0,14,4,1,["P0"]],[0,14,5,0,["P0"]],[0,14,5,1,["P0"]],[0,15,0,0,["P0"]],[0,15,0,1,["P0"]],[0,15,1,0,["P0"]],[0,15,1,1,["P0"]],[0,15,2,0,["P0"]],[0,15,2,1,["P0"]],[0,15,3,0,["P0"]],[0,15,3,1,["P0"]],[0,15,4,0,["P0"]],[0,15,4,1,["P0"]],[0,15,5,0,["P0"]],[0,15,5,1,["P0"]],[0,16,0,0,["P0"]],[0,16,0,1,["P0"]],[0,16,1,0,["P0"]],[0,16,1,1,["P0"]],[0,16,2,0,["P0"]],[0,16,2,1,["P0"]],[0,16,3,0,["P0"]],[0,16,3,1,["P0"]],[0,16,4,0,["P0"]],[0,16,4,1,["P0"]],[0,16,5,0,["P0"]],[0,16,5,1,["P0"]],[0,17,0,0,["P0"]],[0,17,0,1,["P0"]],[0,17,1,0,["P0"]],[0,17,1,1,["P0"]],[0,17,2,0,["P0"]],[0,17,2,1,["P0"]],[0,17,3,0,["P0"]],[0,17,3,1,["P0"]],[0,17,4,0,["P0"]],[0,17,4,1,["P0"]],[0,17,5,0,["P0"]],[0,17,5,1,["P0"]],[0,18,0,0,["P0"]],[0,18,0,1,["P0"]],[0,18,1,0,["P0"]],[0,18,1,1,["P0"]],[0,18,2,0,["P0"]],[0,18,2,1,["P0"]],[0,18,3,0,["P0"]],[0,18,3,1,["P0"]],[0,18,4,0,["P0"]],[0,18,4,1,["P0"]],[0,18,5,0,["P0"]],[0,18,5,1,["P0"]],[0,19,0,0,["CRITICAL"]],[0,19,0,1,["CRITICAL"]],[0,19,1,0,["CRITICAL"]],[0,19,1,1,["CRITICAL"]],[0,19,2,0,["CRITICAL"]],[0,19,2,1,["CRITICAL"]],[0,19,3,0,["CRITICAL"]],[0,19,3,1,["CRITICAL"]],[0,19,4,0,["CRITICAL"]],[0,19,4,1,["CRITICAL"]],[0,19,5,0,["CRITICAL"]],[0,19,5,1,["CRITICAL"]],[1,0,0,0,["CRITICAL"]],[1,0,0,1,["CRITICAL"]],[1,0,1,0,["CRITICAL"]],[1,0,1,1,["CRITICAL"]],[1,0,2,0,["CRITICAL"]],[1,0,2,1,["CRITICAL"]],[1,0,3,0,["CRITICAL"]],[1,0,3,1,["CRITICAL"]],[1,0,4,0,["CRITICAL"]],[1,0,4,1,["CRITICAL"]],[1,0,5,0,["CRITICAL"]],[1,0,5,1,["CRITICAL"]],[1,1,0,0,["CRITICAL"]],[1,1,0,1,["CRITICAL"]],[1,1,1,0,["CRITICAL"]],[1,1,1,1,["CRITICAL"]],[1,1,2,0,["CRITICAL"]],[1,1,2,1,["CRITICAL"]],[1,1,3,0,["CRITICAL"]],[1,1,3,1,["CRITICAL"]],[1,1,4,0,["CRITICAL"]],[1,1,4,1,["CRITICAL"]],[1,1,5,0,["CRITICAL"]],[1,1,5,1,["CRITICAL"]],[1,2,0,0,["CRITICAL"]],[1,2,0,1,["CRITICAL"]],[1,2,1,0,["CRITICAL"]],[1,2,1,1,["CRITICAL"]],[1,2,2,0,["CRITICAL"]],[1,2,2,1,["CRITICAL"]],[1,2,3,0,["CRITICAL"]],[1,2,3,1,["CRITICAL"]],[1,2,4,0,["CRITICAL"]],[1,2,4,1,["CRITICAL"]],[1,2,5,0,["CRITICAL"]],[1,2,5,1,["CRITICAL"]],[1,3,0,0,["CRITICAL"]],[1,3,0,1,["CRITICAL"]],[1,3,1,0,["CRITICAL"]],[1,3,1,1,["CRITICAL"]],[1,3,2,0,["CRITICAL"]],[1,3,2,1,["CRITICAL"]],[1,3,3,0,["CRITICAL"]],[1,3,3,1,["CRITICAL"]],[1,3,4,0,["CRITICAL"]],[1,3,4,1,["CRITICAL"]],[1,3,5,0,["CRITICAL"]],[1,3,5,1,["CRITICAL"]],[1,4,0,0,["CRITICAL"]],[1,4,0,1,["CRITICAL"]],[1,4,1,0,["CRITICAL"]],[1,4,1,1,["CRITICAL"]],[1,4,2,0,["CRITICAL"]],[1,4,2,1,["CRITICAL"]],[1,4,3,0,["CRITICAL"]],[1,4,3,1,["CRITICAL"]],[1,4,4,0,["CRITICAL"]],[1,4,4,1,["CRITICAL"]],[1,4,5,0,["CRITICAL"]],[1,4,5,1,["CRITICAL"]],[1,5,0,0,["CRITICAL"]],[1,5,0,1,["CRITICAL"]],[1,5,1,0,["CRITICAL"]],[1,5,1,1,["CRITICAL"]],[1,5,2,0,["CRITICAL"]],[1,5,2,1,["CRITICAL"]],[1,5,3,0,["CRITICAL"]],[1,5,3,1,["CRITICAL"]],[1,5,4,0,["CRITICAL"]],[1,5,4,1,["CRITICAL"]],[1,5,5,0,["CRITICAL"]],[1,5,5,1,["CRITICAL"]],[1,6,0,0,["CRITICAL"]],[1,6,0,1,["CRITICAL"]],[1,6,1,0,["CRITICAL"]],[1,6,1,1,["CRITICAL"]],[1,6,2,0,["CRITICAL"]],[1,6,2,1,["CRITICAL"]],[1,6,3,0,["CRITICAL"]],[1,6,3,1,["CRITICAL"]],[1,6,4,0,["CRITICAL"]],[1,6,4,1,["CRITICAL"]],[1,6,5,0,["CRITICAL"]],[1,6,5,1,["CRITICAL"]],[1,7,0,0,["CRITICAL"]],[1,7,0,1,["CRITICAL"]],[1,7,1,0,["CRITICAL"]],[1,7,1,1,["CRITICAL"]],[1,7,2,0,["CRITICAL"]],[1,7,2,1,["CRITICAL"]],[1,7,3,0,["CRITICAL"]],[1,7,3,1,["CRITICAL"]],[1,7,4,0,["CRITICAL"]],[1,7,4,1,["CRITICAL"]],[1,7,5,0,["CRITICAL"]],[1,7,5,1,["CRITICAL"]],[1,8,0,0,["CRITICAL"]],[1,8,0,1,["CRITICAL"]],[1,8,1,0,["CRITICAL"]],[1,8,1,1,["CRITICAL"]],[1,8,2,0,["CRITICAL"]],[1,8,2,1,["CRITICAL"]],[1,8,3,0,["CRITICAL"]],[1,8,3,1,["CRITICAL"]],[1,8,4,0,["CRITICAL"]],[1,8,4,1,["CRITICAL"]],[1,8,5,0,["CRITICAL"]],[1,8,5,1,["CRITICAL"]],[1,9,0,0,["CRITICAL"]],[1,9,0,1,["CRITICAL"]],[1,9,1,0,["CRITICAL","Low"]],[1,9,1,1,["CRITICAL","Low"]],[1,9,2,0,["CRITICAL","Low"]],[1,9,2,1,["CRITICAL","Low"]],[1,9,3,0,["CRITICAL"]],[1,9,3,1,["CRITICAL"]],[1,9,4,0,["CRITICAL"]],[1,9,4,1,["CRITICAL"]],[1,9,5,0,["CRITICAL"]],[1,9,5,1,["CRITICAL"]],[1,10,0,0,["CRITICAL"]],[1,10,0,1,["CRITICAL"]],[1,10,1,0,["CRITICAL","Medium"]],[1,10,1,1,["CRITICAL","Medium"]],[1,10,2,0,["CRITICAL","Medium"]],[1,10,2,1,["CRITICAL","Medium"]],[1,10,3,0,["CRITICAL"]],[1,10,3,1,["CRITICAL"]],[1,10,4,0,["CRITICAL"]],[1,10,4,1,["CRITICAL"]],[1,10,5,0,["CRITICAL"]],[1,10,5,1,["CRITICAL"]],[1,11,0,0,["CRITICAL"]],[1,11,0,1,["CRITICAL"]],[1,11,1,0,["CRITICAL","Low"]],[1,11,1,1,["CRITICAL","Low"]],[1,11,2,0,["CRITICAL","Low"]],[1,11,2,1,["CRITICAL","Low"]],[1,11,3,0,["CRITICAL"]],[1,11,3,1,["CRITICAL"]],[1,11,4,0,["CRITICAL"]],[1,11,4,1,["CRITICAL"]],[1,11,5,0,["CRITICAL"]],[1,11,5,1,["CRITICAL"]],[1,12,0,0,["CRITICAL"]],[1,12,0,1,["CRITICAL"]],[1,12,1,0,["CRITICAL"]],[1,12,1,1,["CRITICAL"]],[1,12,2,0,["CRITICAL"]],[1,12,2,1,["CRITICAL"]],[1,12,3,0,["CRITICAL"]],[1,12,3,1,["CRITICAL"]],[1,12,4,0,["CRITICAL"]],[1,12,4,1,["CRITICAL"]],[1,12,5,0,["CRITICAL"]],[1,12,5,1,["CRITICAL"]],[1,13,0,0,["CRITICAL"]],[1,13,0,1,["CRITICAL"]],[1,13,1,0,["CRITICAL"]],[1,13,1,1,["CRITICAL"]],[1,13,2,0,["CRITICAL"]],[1,13,2,1,["CRITICAL"]],[1,13,3,0,["CRITICAL"]],[1,13,3,1,["CRITICAL"]],[1,13,4,0,["CRITICAL"]],[1,13,4,1,["CRITICAL"]],[1,13,5,0,["CRITICAL"]],[1,13,5,1,["CRITICAL"]],[1,14,0,0,["CRITICAL"]],[1,14,0,1,["CRITICAL"]],[1,14,1,0,["CRITICAL"]],[1,14,1,1,["CRITICAL"]],[1,14,2,0,["CRITICAL"]],[1,14,2,1,["CRITICAL"]],[1,14,3,0,["CRITICAL"]],[1,14,3,1,["CRITICAL"]],[1,14,4,0,["CRITICAL"]],[1,14,4,1,["CRITICAL"]],[1,14,5,0,["CRITICAL"]],[1,14,5,1,["CRITICAL"]],[1,15,0,0,["CRITICAL"]],[1,15,0,1,["CRITICAL"]],[1,15,1,0,["CRITICAL"]],[1,15,1,1,["CRITICAL"]],[1,15,2,0,["CRITICAL"]],[1,15,2,1,["CRITICAL"]],[1,15,3,0,["CRITICAL"]],[1,15,3,1,["CRITICAL"]],[1,15,4,0,["CRITICAL"]],[1,15,4,1,["CRITICAL"]],[1,15,5,0,["CRITICAL"]],[1,15,5,1,["CRITICAL"]],[1,16,0,0,["CRITICAL"]],[1,16,0,1,["CRITICAL"]],[1,16,1,0,["CRITICAL"]],[1,16,1,1,["CRITICAL"]],[1,16,2,0,["CRITICAL"]],[1,16,2,1,["CRITICAL"]],[1,16,3,0,["CRITICAL"]],[1,16,3,1,["CRITICAL"]],[1,16,4,0,["CRITICAL"]],[1,16,4,1,["CRITICAL"]],[1,16,5,0,["CRITICAL"]],[1,16,5,1,["CRITICAL"]],[1,17,0,0,["CRITICAL"]],[1,17,0,1,["CRITICAL"]],[1,17,1,0,["CRITICAL"]],[1,17,1,1,["CRITICAL"]],[1,17,2,0,["CRITICAL"]],[1,17,2,1,["CRITICAL"]],[1,17,3,0,["CRITICAL"]],[1,17,3,1,["CRITICAL"]],[1,17,4,0,["CRITICAL"]],[1,17,4,1,["CRITICAL"]],[1,17,5,0,["CRITICAL"]],[1,17,5,1,["CRITICAL"]],[1,18,0,0,["CRITICAL"]],[1,18,0,1,["CRITICAL"]],[1,18,1,0,["CRITICAL"]],[1,18,1,1,["CRITICAL"]],[1,18,2,0,["CRITICAL"]],[1,18,2,1,["CRITICAL"]],[1,18,3,0,["CRITICAL"]],[1,18,3,1,["CRITICAL"]],[1,18,4,0,["CRITICAL"]],[1,18,4,1,["CRITICAL"]],[1,18,5,0,["CRITICAL"]],[1,18,5,1,["CRITICAL"]],[1,19,0,0,["CRITICAL"]],[1,19,0,1,["CRITICAL"]],[1,19,1,0,["CRITICAL"]],[1,19,1,1,["CRITICAL"]],[1,19,2,0,["CRITICAL"]],[1,19,2,1,["CRITICAL"]],[1,19,3,0,["CRITICAL"]],[1,19,3,1,["CRITICAL"]],[1,19,4,0,["CRITICAL"]],[1,19,4,1,["CRITICAL"]],[1,19,5,0,["CRITICAL"]],[1,19,5,1,["CRITICAL"]],[2,0,0,0,["High"]],[2,0,0,1,["High"]],[2,0,1,0,["High"]],[2,0,1,1,["High"]],[2,0,2,0,["High"]],[2,0,2,1,["High"]],[2,0,3,0,["High"]],[2,0,3,1,["High"]],[2,0,4,0,["High"]],[2,0,4,1,["High"]],[2,0,5,0,["High"]],[2,0,5,1,["High"]],[2,1,0,0,["High"]],[2,1,0,1,["High"]],[2,1,1,0,["High"]],[2,1,1,1,["High"]],[2,1,2,0,["High"]],[2,1,2,1,["High"]],[2,1,3,0,["High"]],[2,1,3,1,["High"]],[2,1,4,0,["High"]],[2,1,4,1,["High"]],[2,1,5,0,["High"]],[2,1,5,1,["High"]],[2,2,0,0,["Critical"]],[2,2,0,1,["Critical"]],[2,2,1,0,["Critical"]],[2,2,1,1,["Critical"]],[2,2,2,0,["Critical"]],[2,2,2,1,["Critical"]],[2,2,3,0,["Critical"]],[2,2,3,1,["Critical"]],[2,2,4,0,["Critical"]],[2,2,4,1,["Critical"]],[2,2,5,0,["Critical"]],[2,2,5,1,["Critical"]],[2,3,0,0,["CRITICAL"]],[2,3,0,1,["CRITICAL"]],[2,3,1,0,["CRITICAL"]],[2,3,1,1,["CRITICAL"]],[2,3,2,0,["CRITICAL"]],[2,3,2,1,["CRITICAL"]],[2,3,3,0,["CRITICAL"]],[2,3,3,1,["CRITICAL"]],[2,3,4,0,["CRITICAL"]],[2,3,4,1,["CRITICAL"]],[2,3,5,0,["CRITICAL"]],[2,3,5,1,["CRITICAL"]],[2,4,0,0,["High"]],[2,4,0,1,["High"]],[2,4,1,0,["High"]],[2,4,1,1,["High"]],[2,4,2,0,["High"]],[2,4,2,1,["High"]],[2,4,3,0,["High"]],[2,4,3,1,["High"]],[2,4,4,0,["High"]],[2,4,4,1,["High"]],[2,4,5,0,["High"]],[2,4,5,1,["High"]],[2,5,0,0,["High"]],[2,5,0,1,["High"]],[2,5,1,0,["High"]],[2,5,1,1,["High"]],[2,5,2,0,["High"]],[2,5,2,1,["High"]],[2,5,3,0,["High"]],[2,5,3,1,["High"]],[2,5,4,0,["High"]],[2,5,4,1,["High"]],[2,5,5,0,["High"]],[2,5,5,1,["High"]],[2,6,0,0,["High"]],[2,6,0,1,["High"]],[2,6,1,0,["High"]],[2,6,1,1,["High"]],[2,6,2,0,["High"]],[2,6,2,1,["High"]],[2,6,3,0,["High"]],[2,6,3,1,["High"]],[2,6,4,0,["High"]],[2,6,4,1,["High"]],[2,6,5,0,["High"]],[2,6,5,1,["High"]],[2,7,0,0,["High"]],[2,7,0,1,["High"]],[2,7,1,0,["High"]],[2,7,1,1,["High"]],[2,7,2,0,["High"]],[2,7,2,1,["High"]],[2,7,3,0,["High"]],[2,7,3,1,["High"]],[2,7,4,0,["High"]],[2,7,4,1,["High"]],[2,7,5,0,["High"]],[2,7,5,1,["High"]],[2,8,0,0,["P0"]],[2,8,0,1,["P0"]],[2,8,1,0,["P0"]],[2,8,1,1,["P0"]],[2,8,2,0,["P0"]],[2,8,2,1,["P0"]],[2,8,3,0,["P0"]],[2,8,3,1,["P0"]],[2,8,4,0,["P0"]],[2,8,4,1,["P0"]],[2,8,5,0,["P0"]],[2,8,5,1,["P0"]],[2,9,0,0,["High"]],[2,9,0,1,["High"]],[2,9,1,0,["High","Low"]],[2,9,1,1,["High","Low"]],[2,9,2,0,["High","Low"]],[2,9,2,1,["High","Low"]],[2,9,3,0,["High"]],[2,9,3,1,["High"]],[2,9,4,0,["High"]],[2,9,4,1,["High"]],[2,9,5,0,["High"]],[2,9,5,1,["High"]],[2,10,0,0,["High"]],[2,10,0,1,["High"]],[2,10,1,0,["High","Medium"]],[2,10,1,1,["High","Medium"]],[2,10,2,0,["High","Medium"]],[2,10,2,1,["High","Medium"]],[2,10,3,0,["High"]],[2,10,3,1,["High"]],[2,10,4,0,["High"]],[2,10,4,1,["High"]],[2,10,5,0,["High"]],[2,10,5,1,["High"]],[2,11,0,0,["High"]],[2,11,0,1,["High"]],[2,11,1,0,["High","Low"]],[2,11,1,1,["High","Low"]],[2,11,2,0,["High","Low"]],[2,11,2,1,["High","Low"]],[2,11,3,0,["High"]],[2,11,3,1,["High"]],[2,11,4,0,["High"]],[2,11,4,1,["High"]],[2,11,5,0,["High"]],[2,11,5,1,["High"]],[2,12,0,0,["High"]],[2,12,0,1,["High"]],[2,12,1,0,["High"]],[2,12,1,1,["High"]],[2,12,2,0,["High"]],[2,12,2,1,["High"]],[2,12,3,0,["High"]],[2,12,3,1,["High"]],[2,12,4,0,["High"]],[2,12,4,1,["High"]],[2,12,5,0,["High"]],[2,12,5,1,["High"]],[2,13,0,0,["High"]],[2,13,0,1,["High"]],[2,13,1,0,["High"]],[2,13,1,1,["High"]],[2,13,2,0,["High"]],[2,13,2,1,["High"]],[2,13,3,0,["High"]],[2,13,3,1,["High"]],[2,13,4,0,["High"]],[2,13,4,1,["High"]],[2,13,5,0,["High"]],[2,13,5,1,["High"]],[2,14,0,0,["High"]],[2,14,0,1,["High"]],[2,14,1,0,["High"]],[2,14,1,1,["High"]],[2,14,2,0,["High"]],[2,14,2,1,["High"]],[2,14,3,0,["High"]],[2,14,3,1,["High"]],[2,14,4,0,["High"]],[2,14,4,1,["High"]],[2,14,5,0,["High"]],[2,14,5,1,["High"]],[2,15,0,0,["High"]],[2,15,0,1,["High"]],[2,15,1,0,["High"]],[2,15,1,1,["High"]],[2,15,2,0,["High"]],[2,15,2,1,["High"]],[2,15,3,0,["High"]],[2,15,3,1,["High"]],[2,15,4,0,["High"]],[2,15,4,1,["High"]],[2,15,5,0,["High"]],[2,15,5,1,["High"]],[2,16,0,0,["High"]],[2,16,0,1,["High"]],[2,16,1,0,["High"]],[2,16,1,1,["High"]],[2,16,2,0,["High"]],[2,16,2,1,["High"]],[2,16,3,0,["High"]],[2,16,3,1,["High"]],[2,16,4,0,["High"]],[2,16,4,1,["High"]],[2,16,5,0,["High"]],[2,16,5,1,["High"]],[2,17,0,0,["High"]],[2,17,0,1,["High"]],[2,17,1,0,["High"]],[2,17,1,1,["High"]],[2,17,2,0,["High"]],[2,17,2,1,["High"]],[2,17,3,0,["High"]],[2,17,3,1,["High"]],[2,17,4,0,["High"]],[2,17,4,1,["High"]],[2,17,5,0,["High"]],[2,17,5,1,["High"]],[2,18,0,0,["High"]],[2,18,0,1,["High"]],[2,18,1,0,["High"]],[2,18,1,1,["High"]],[2,18,2,0,["High"]],[2,18,2,1,["High"]],[2,18,3,0,["High"]],[2,18,3,1,["High"]],[2,18,4,0,["High"]],[2,18,4,1,["High"]],[2,18,5,0,["High"]],[2,18,5,1,["High"]],[2,19,0,0,["CRITICAL"]],[2,19,0,1,["CRITICAL"]],[2,19,1,0,["CRITICAL"]],[2,19,1,1,["CRITICAL"]],[2,19,2,0,["CRITICAL"]],[2,19,2,1,["CRITICAL"]],[2,19,3,0,["CRITICAL"]],[2,19,3,1,["CRITICAL"]],[2,19,4,0,["CRITICAL"]],[2,19,4,1,["CRITICAL"]],[2,19,5,0,["CRITICAL"]],[2,19,5,1,["CRITICAL"]],[3,0,0,0,["CRITICAL"]],[3,0,0,1,["CRITICAL"]],[3,0,1,0,["CRITICAL"]],[3,0,1,1,["CRITICAL"]],[3,0,2,0,["CRITICAL"]],[3,0,2,1,["CRITICAL"]],[3,0,3,0,["CRITICAL"]],[3,0,3,1,["CRITICAL"]],[3,0,4,0,["CRITICAL"]],[3,0,4,1,["CRITICAL"]],[3,0,5,0,["CRITICAL"]],[3,0,5,1,["CRITICAL"]],[3,1,0,0,["CRITICAL"]],[3,1,0,1,["CRITICAL"]],[3,1,1,0,["CRITICAL"]],[3,1,1,1,["CRITICAL"]],[3,1,2,0,["CRITICAL"]],[3,1,2,1,["CRITICAL"]],[3,1,3,0,["CRITICAL"]],[3,1,3,1,["CRITICAL"]],[3,1,4,0,["CRITICAL"]],[3,1,4,1,["CRITICAL"]],[3,1,5,0,["CRITICAL"]],[3,1,5,1,["CRITICAL"]],[3,2,0,0,["CRITICAL"]],[3,2,0,1,["CRITICAL"]],[3,2,1,0,["CRITICAL"]],[3,2,1,1,["CRITICAL"]],[3,2,2,0,["CRITICAL"]],[3,2,2,1,["CRITICAL"]],[3,2,3,0,["CRITICAL"]],[3,2,3,1,["CRITICAL"]],[3,2,4,0,["CRITICAL"]],[3,2,4,1,["CRITICAL"]],[3,2,5,0,["CRITICAL"]],[3,2,5,1,["CRITICAL"]],[3,3,0,0,["CRITICAL"]],[3,3,0,1,["CRITICAL"]],[3,3,1,0,["CRITICAL"]],[3,3,1,1,["CRITICAL"]],[3,3,2,0,["CRITICAL"]],[3,3,2,1,["CRITICAL"]],[3,3,3,0,["CRITICAL"]],[3,3,3,1,["CRITICAL"]],[3,3,4,0,["CRITICAL"]],[3,3,4,1,["CRITICAL"]],[3,3,5,0,["CRITICAL"]],[3,3,5,1,["CRITICAL"]],[3,4,0,0,["CRITICAL"]],[3,4,0,1,["CRITICAL"]],[3,4,1,0,["CRITICAL"]],[3,4,1,1,["CRITICAL"]],[3,4,2,0,["CRITICAL"]],[3,4,2,1,["CRITICAL"]],[3,4,3,0,["CRITICAL"]],[3,4,3,1,["CRITICAL"]],[3,4,4,0,["CRITICAL"]],[3,4,4,1,["CRITICAL"]],[3,4,5,0,["CRITICAL"]],[3,4,5,1,["CRITICAL"]],[3,5,0,0,["CRITICAL"]],[3,5,0,1,["CRITICAL"]],[3,5,1,0,["CRITICAL"]],[3,5,1,1,["CRITICAL"]],[3,5,2,0,["CRITICAL"]],[3,5,2,1,["CRITICAL"]],[3,5,3,0,["CRITICAL"]],[3,5,3,1,["CRITICAL"]],[3,5,4,0,["CRITICAL"]],[3,5,4,1,["CRITICAL"]],[3,5,5,0,["CRITICAL"]],[3,5,5,1,["CRITICAL"]],[3,6,0,0,["CRITICAL"]],[3,6,0,1,["CRITICAL"]],[3,6,1,0,["CRITICAL"]],[3,6,1,1,["CRITICAL"]],[3,6,2,0,["CRITICAL"]],[3,6,2,1,["CRITICAL"]],[3,6,3,0,["CRITICAL"]],[3,6,3,1,["CRITICAL"]],[3,6,4,0,["CRITICAL"]],[3,6,4,1,["CRITICAL"]],[3,6,5,0,["CRITICAL"]],[3,6,5,1,["CRITICAL"]],[3,7,0,0,["CRITICAL"]],[3,7,0,1,["CRITICAL"]],[3,7,1,0,["CRITICAL"]],[3,7,1,1,["CRITICAL"]],[3,7,2,0,["CRITICAL"]],[3,7,2,1,["CRITICAL"]],[3,7,3,0,["CRITICAL"]],[3,7,3,1,["CRITICAL"]],[3,7,4,0,["CRITICAL"]],[3,7,4,1,["CRITICAL"]],[3,7,5,0,["CRITICAL"]],[3,7,5,1,["CRITICAL"]],[3,8,0,0,["CRITICAL"]],[3,8,0,1,["CRITICAL"]],[3,8,1,0,["CRITICAL"]],[3,8,1,1,["CRITICAL"]],[3,8,2,0,["CRITICAL"]],[3,8,2,1,["CRITICAL"]],[3,8,3,0,["CRITICAL"]],[3,8,3,1,["CRITICAL"]],[3,8,4,0,["CRITICAL"]],[3,8,4,1,["CRITICAL"]],[3,8,5,0,["CRITICAL"]],[3,8,5,1,["CRITICAL"]],[3,9,0,0,["CRITICAL"]],[3,9,0,1,["CRITICAL"]],[3,9,1,0,["CRITICAL","Low"]],[3,9,1,1,["CRITICAL","Low"]],[3,9,2,0,["CRITICAL","Low"]],[3,9,2,1,["CRITICAL","Low"]],[3,9,3,0,["CRITICAL"]],[3,9,3,1,["CRITICAL"]],[3,9,4,0,["CRITICAL"]],[3,9,4,1,["CRITICAL"]],[3,9,5,0,["CRITICAL"]],[3,9,5,1,["CRITICAL"]],[3,10,0,0,["CRITICAL"]],[3,10,0,1,["CRITICAL"]],[3,10,1,0,["CRITICAL","Medium"]],[3,10,1,1,["CRITICAL","Medium"]],[3,10,2,0,["CRITICAL","Medium"]],[3,10,2,1,["CRITICAL","Medium"]],[3,10,3,0,["CRITICAL"]],[3,10,3,1,["CRITICAL"]],[3,10,4,0,["CRITICAL"]],[3,10,4,1,["CRITICAL"]],[3,10,5,0,["CRITICAL"]],[3,10,5,1,["CRITICAL"]],[3,11,0,0,["CRITICAL"]],[3,11,0,1,["CRITICAL"]],[3,11,1,0,["CRITICAL","Low"]],[3,11,1,1,["CRITICAL","Low"]],[3,11,2,0,["CRITICAL","Low"]],[3,11,2,1,["CRITICAL","Low"]],[3,11,3,0,["CRITICAL"]],[3,11,3,1,["CRITICAL"]],[3,11,4,0,["CRITICAL"]],[3,11,4,1,["CRITICAL"]],[3,11,5,0,["CRITICAL"]],[3,11,5,1,["CRITICAL"]],[3,12,0,0,["CRITICAL"]],[3,12,0,1,["CRITICAL"]],[3,12,1,0,["CRITICAL"]],[3,12,1,1,["CRITICAL"]],[3,12,2,0,["CRITICAL"]],[3,12,2,1,["CRITICAL"]],[3,12,3,0,["CRITICAL"]],[3,12,3,1,["CRITICAL"]],[3,12,4,0,["CRITICAL"]],[3,12,4,1,["CRITICAL"]],[3,12,5,0,["CRITICAL"]],[3,12,5,1,["CRITICAL"]],[3,13,0,0,["CRITICAL"]],[3,13,0,1,["CRITICAL"]],[3,13,1,0,["CRITICAL"]],[3,13,1,1,["CRITICAL"]],[3,13,2,0,["CRITICAL"]],[3,13,2,1,["CRITICAL"]],[3,13,3,0,["CRITICAL"]],[3,13,3,1,["CRITICAL"]],[3,13,4,0,["CRITICAL"]],[3,13,4,1,["CRITICAL"]],[3,13,5,0,["CRITICAL"]],[3,13,5,1,["CRITICAL"]],[3,14,0,0,["CRITICAL"]],[3,14,0,1,["CRITICAL"]],[3,14,1,0,["CRITICAL"]],[3,14,1,1,["CRITICAL"]],[3,14,2,0,["CRITICAL"]],[3,14,2,1,["CRITICAL"]],[3,14,3,0,["CRITICAL"]],[3,14,3,1,["CRITICAL"]],[3,14,4,0,["CRITICAL"]],[3,14,4,1,["CRITICAL"]],[3,14,5,0,["CRITICAL"]],[3,14,5,1,["CRITICAL"]],[3,15,0,0,["CRITICAL"]],[3,15,0,1,["CRITICAL"]],[3,15,1,0,["CRITICAL"]],[3,15,1,1,["CRITICAL"]],[3,15,2,0,["CRITICAL"]],[3,15,2,1,["CRITICAL"]],[3,15,3,0,["CRITICAL"]],[3,15,3,1,["CRITICAL"]],[3,15,4,0,["CRITICAL"]],[3,15,4,1,["CRITICAL"]],[3,15,5,0,["CRITICAL"]],[3,15,5,1,["CRITICAL"]],[3,16,0,0,["CRITICAL"]],[3,16,0,1,["CRITICAL"]],[3,16,1,0,["CRITICAL"]],[3,16,1,1,["CRITICAL"]],[3,16,2,0,["CRITICAL"]],[3,16,2,1,["CRITICAL"]],[3,16,3,0,["CRITICAL"]],[3,16,3,1,["CRITICAL"]],[3,16,4,0,["CRITICAL"]],[3,16,4,1,["CRITICAL"]],[3,16,5,0,["CRITICAL"]],[3,16,5,1,["CRITICAL"]],[3,17,0,0,["CRITICAL"]],[3,17,0,1,["CRITICAL"]],[3,17,1,0,["CRITICAL"]],[3,17,1,1,["CRITICAL"]],[3,17,2,0,["CRITICAL"]],[3,17,2,1,["CRITICAL"]],[3,17,3,0,["CRITICAL"]],[3,17,3,1,["CRITICAL"]],[3,17,4,0,["CRITICAL"]],[3,17,4,1,["CRITICAL"]],[3,17,5,0,["CRITICAL"]],[3,17,5,1,["CRITICAL"]],[3,18,0,0,["CRITICAL"]],[3,18,0,1,["CRITICAL"]],[3,18,1,0,["CRITICAL"]],[3,18,1,1,["CRITICAL"]],[3,18,2,0,["CRITICAL"]],[3,18,2,1,["CRITICAL"]],[3,18,3,0,["CRITICAL"]],[3,18,3,1,["CRITICAL"]],[3,18,4,0,["CRITICAL"]],[3,18,4,1,["CRITICAL"]],[3,18,5,0,["CRITICAL"]],[3,18,5,1,["CRITICAL"]],[3,19,0,0,["CRITICAL"]],[3,19,0,1,["CRITICAL"]],[3,19,1,0,["CRITICAL"]],[3,19,1,1,["CRITICAL"]],[3,19,2,0,["CRITICAL"]],[3,19,2,1,["CRITICAL"]],[3,19,3,0,["CRITICAL"]],[3,19,3,1,["CRITICAL"]],[3,19,4,0,["CRITICAL"]],[3,19,4,1,["CRITICAL"]],[3,19,5,0,["CRITICAL"]],[3,19,5,1,["CRITICAL"]],[4,0,0,0,["Low"]],[4,0,0,1,["CRITICAL"]],[4,0,1,0,["Low"]],[4,0,1,1,["CRITICAL"]],[4,0,2,0,["Low"]],[4,0,2,1,["CRITICAL"]],[4,0,3,0,["Low"]],[4,0,3,1,["CRITICAL"]],[4,0,4,0,["Low"]],[4,0,4,1,["CRITICAL"]],[4,0,5,0,["Low"]],[4,0,5,1,["CRITICAL"]],[4,1,0,0,["FIXABLE"]],[4,1,0,1,["CRITICAL"]],[4,1,1,0,["FIXABLE"]],[4,1,1,1,["CRITICAL"]],[4,1,2,0,["FIXABLE"]],[4,1,2,1,["CRITICAL"]],[4,1,3,0,["FIXABLE"]],[4,1,3,1,["CRITICAL"]],[4,1,4,0,["FIXABLE"]],[4,1,4,1,["CRITICAL"]],[4,1,5,0,["FIXABLE"]],[4,1,5,1,["CRITICAL"]],[4,2,0,0,["Critical"]],[4,2,0,1,["CRITICAL"]],[4,2,1,0,["Critical"]],[4,2,1,1,["CRITICAL"]],[4,2,2,0,["Critical"]],[4,2,2,1,["CRITICAL"]],[4,2,3,0,["Critical"]],[4,2,3,1,["CRITICAL"]],[4,2,4,0,["Critical"]],[4,2,4,1,["CRITICAL"]],[4,2,5,0,["Critical"]],[4,2,5,1,["CRITICAL"]],[4,3,0,0,["CRITICAL"]],[4,3,0,1,["CRITICAL"]],[4,3,1,0,["CRITICAL"]],[4,3,1,1,["CRITICAL"]],[4,3,2,0,["CRITICAL"]],[4,3,2,1,["CRITICAL"]],[4,3,3,0,["CRITICAL"]],[4,3,3,1,["CRITICAL"]],[4,3,4,0,["CRITICAL"]],[4,3,4,1,["CRITICAL"]],[4,3,5,0,["CRITICAL"]],[4,3,5,1,["CRITICAL"]],[4,4,0,0,["Medium"]],[4,4,0,1,["CRITICAL"]],[4,4,1,0,["Medium"]],[4,4,1,1,["CRITICAL"]],[4,4,2,0,["Medium"]],[4,4,2,1,["CRITICAL"]],[4,4,3,0,["Medium"]],[4,4,3,1,["CRITICAL"]],[4,4,4,0,["Medium"]],[4,4,4,1,["CRITICAL"]],[4,4,5,0,["Medium"]],[4,4,5,1,["CRITICAL"]],[4,5,0,0,["Medium"]],[4,5,0,1,["CRITICAL"]],[4,5,1,0,["Medium"]],[4,5,1,1,["CRITICAL"]],[4,5,2,0,["Medium"]],[4,5,2,1,["CRITICAL"]],[4,5,3,0,["Medium"]],[4,5,3,1,["CRITICAL"]],[4,5,4,0,["Medium"]],[4,5,4,1,["CRITICAL"]],[4,5,5,0,["Medium"]],[4,5,5,1,["CRITICAL"]],[4,6,0,0,["Low"]],[4,6,0,1,["CRITICAL"]],[4,6,1,0,["Low"]],[4,6,1,1,["CRITICAL"]],[4,6,2,0,["Low"]],[4,6,2,1,["CRITICAL"]],[4,6,3,0,["Low"]],[4,6,3,1,["CRITICAL"]],[4,6,4,0,["Low"]],[4,6,4,1,["CRITICAL"]],[4,6,5,0,["Low"]],[4,6,5,1,["CRITICAL"]],[4,7,0,0,["Low"]],[4,7,0,1,["CRITICAL"]],[4,7,1,0,["Low"]],[4,7,1,1,["CRITICAL"]],[4,7,2,0,["Low"]],[4,7,2,1,["CRITICAL"]],[4,7,3,0,["Low"]],[4,7,3,1,["CRITICAL"]],[4,7,4,0,["Low"]],[4,7,4,1,["CRITICAL"]],[4,7,5,0,["Low"]],[4,7,5,1,["CRITICAL"]],[4,8,0,0,["P0"]],[4,8,0,1,["CRITICAL"]],[4,8,1,0,["P0"]],[4,8,1,1,["CRITICAL"]],[4,8,2,0,["P0"]],[4,8,2,1,["CRITICAL"]],[4,8,3,0,["P0"]],[4,8,3,1,["CRITICAL"]],[4,8,4,0,["P0"]],[4,8,4,1,["CRITICAL"]],[4,8,5,0,["P0"]],[4,8,5,1,["CRITICAL"]],[4,9,0,0,["Low"]],[4,9,0,1,["CRITICAL"]],[4,9,1,0,["FIXABLE","Low"]],[4,9,1,1,["CRITICAL","Low"]],[4,9,2,0,["FIXABLE","Low"]],[4,9,2,1,["CRITICAL","Low"]],[4,9,3,0,["Low"]],[4,9,3,1,["CRITICAL"]],[4,9,4,0,["Low"]],[4,9,4,1,["CRITICAL"]],[4,9,5,0,["Low"]],[4,9,5,1,["CRITICAL"]],[4,10,0,0,["Medium"]],[4,10,0,1,["CRITICAL"]],[4,10,1,0,["FIXABLE","Medium"]],[4,10,1,1,["CRITICAL","Medium"]],[4,10,2,0,["FIXABLE","Medium"]],[4,10,2,1,["CRITICAL","Medium"]],[4,10,3,0,["Medium"]],[4,10,3,1,["CRITICAL"]],[4,10,4,0,["Medium"]],[4,10,4,1,["CRITICAL"]],[4,10,5,0,["Medium"]],[4,10,5,1,["CRITICAL"]],[4,11,0,0,["Low"]],[4,11,0,1,["CRITICAL"]],[4,11,1,0,["FIXABLE","Low"]],[4,11,1,1,["CRITICAL","Low"]],[4,11,2,0,["FIXABLE","Low"]],[4,11,2,1,["CRITICAL","Low"]],[4,11,3,0,["Low"]],[4,11,3,1,["CRITICAL"]],[4,11,4,0,["Low"]],[4,11,4,1,["CRITICAL"]],[4,11,5,0,["Low"]],[4,11,5,1,["CRITICAL"]],[4,12,0,0,["Medium"]],[4,12,0,1,["CRITICAL"]],[4,12,1,0,["Medium"]],[4,12,1,1,["CRITICAL"]],[4,12,2,0,["Medium"]],[4,12,2,1,["CRITICAL"]],[4,12,3,0,["Medium"]],[4,12,3,1,["CRITICAL"]],[4,12,4,0,["Medium"]],[4,12,4,1,["CRITICAL"]],[4,12,5,0,["Medium"]],[4,12,5,1,["CRITICAL"]],[4,13,0,0,["Low"]],[4,13,0,1,["CRITICAL"]],[4,13,1,0,["Low"]],[4,13,1,1,["CRITICAL"]],[4,13,2,0,["Low"]],[4,13,2,1,["CRITICAL"]],[4,13,3,0,["Low"]],[4,13,3,1,["CRITICAL"]],[4,13,4,0,["Low"]],[4,13,4,1,["CRITICAL"]],[4,13,5,0,["Low"]],[4,13,5,1,["CRITICAL"]],[4,14,0,0,["Low"]],[4,14,0,1,["CRITICAL"]],[4,14,1,0,["Low"]],[4,14,1,1,["CRITICAL"]],[4,14,2,0,["Low"]],[4,14,2,1,["CRITICAL"]],[4,14,3,0,["Low"]],[4,14,3,1,["CRITICAL"]],[4,14,4,0,["Low"]],[4,14,4,1,["CRITICAL"]],[4,14,5,0,["Low"]],[4,14,5,1,["CRITICAL"]],[4,15,0,0,["LOW"]],[4,15,0,1,["CRITICAL"]],[4,15,1,0,["LOW"]],[4,15,1,1,["CRITICAL"]],[4,15,2,0,["LOW"]],[4,15,2,1,["CRITICAL"]],[4,15,3,0,["LOW"]],[4,15,3,1,["CRITICAL"]],[4,15,4,0,["LOW"]],[4,15,4,1,["CRITICAL"]],[4,15,5,0,["LOW"]],[4,15,5,1,["CRITICAL"]],[4,16,0,0,["P3"]],[4,16,0,1,["CRITICAL"]],[4,16,1,0,["P3"]],[4,16,1,1,["CRITICAL"]],[4,16,2,0,["P3"]],[4,16,2,1,["CRITICAL"]],[4,16,3,0,["P3"]],[4,16,3,1,["CRITICAL"]],[4,16,4,0,["P3"]],[4,16,4,1,["CRITICAL"]],[4,16,5,0,["P3"]],[4,16,5,1,["CRITICAL"]],[4,17,0,0,["High"]],[4,17,0,1,["CRITICAL"]],[4,17,1,0,["High"]],[4,17,1,1,["CRITICAL"]],[4,17,2,0,["High"]],[4,17,2,1,["CRITICAL"]],[4,17,3,0,["High"]],[4,17,3,1,["CRITICAL"]],[4,17,4,0,["High"]],[4,17,4,1,["CRITICAL"]],[4,17,5,0,["High"]],[4,17,5,1,["CRITICAL"]],[4,18,0,0,["Low"]],[4,18,0,1,["CRITICAL"]],[4,18,1,0,["Low"]],[4,18,1,1,["CRITICAL"]],[4,18,2,0,["Low"]],[4,18,2,1,["CRITICAL"]],[4,18,3,0,["Low"]],[4,18,3,1,["CRITICAL"]],[4,18,4,0,["Low"]],[4,18,4,1,["CRITICAL"]],[4,18,5,0,["Low"]],[4,18,5,1,["CRITICAL"]],[4,19,0,0,["CRITICAL"]],[4,19,0,1,["CRITICAL"]],[4,19,1,0,["CRITICAL"]],[4,19,1,1,["CRITICAL"]],[4,19,2,0,["CRITICAL"]],[4,19,2,1,["CRITICAL"]],[4,19,3,0,["CRITICAL"]],[4,19,3,1,["CRITICAL"]],[4,19,4,0,["CRITICAL"]],[4,19,4,1,["CRITICAL"]],[4,19,5,0,["CRITICAL"]],[4,19,5,1,["CRITICAL"]],[5,0,0,0,["Low"]],[5,0,0,1,["Low"]],[5,0,1,0,["Low"]],[5,0,1,1,["Low"]],[5,0,2,0,["Low"]],[5,0,2,1,["Low"]],[5,0,3,0,["Low"]],[5,0,3,1,["Low"]],[5,0,4,0,["Low"]],[5,0,4,1,["Low"]],[5,0,5,0,["Low"]],[5,0,5,1,["Low"]],[5,1,0,0,[]],[5,1,0,1,[]],[5,1,1,0,[]],[5,1,1,1,[]],[5,1,2,0,[]],[5,1,2,1,[]],[5,1,3,0,[]],[5,1,3,1,[]],[5,1,4,0,[]],[5,1,4,1,[]],[5,1,5,0,[]],[5,1,5,1,[]],[5,2,0,0,["Critical"]],[5,2,0,1,["Critical"]],[5,2,1,0,["Critical"]],[5,2,1,1,["Critical"]],[5,2,2,0,["Critical"]],[5,2,2,1,["Critical"]],[5,2,3,0,["Critical"]],[5,2,3,1,["Critical"]],[5,2,4,0,["Critical"]],[5,2,4,1,["Critical"]],[5,2,5,0,["Critical"]],[5,2,5,1,["Critical"]],[5,3,0,0,["CRITICAL"]],[5,3,0,1,["CRITICAL"]],[5,3,1,0,["CRITICAL"]],[5,3,1,1,["CRITICAL"]],[5,3,2,0,["CRITICAL"]],[5,3,2,1,["CRITICAL"]],[5,3,3,0,["CRITICAL"]],[5,3,3,1,["CRITICAL"]],[5,3,4,0,["CRITICAL"]],[5,3,4,1,["CRITICAL"]],[5,3,5,0,["CRITICAL"]],[5,3,5,1,["CRITICAL"]],[5,4,0,0,["Medium"]],[5,4,0,1,["Medium"]],[5,4,1,0,["Medium"]],[5,4,1,1,["Medium"]],[5,4,2,0,["Medium"]],[5,4,2,1,["Medium"]],[5,4,3,0,["Medium"]],[5,4,3,1,["Medium"]],[5,4,4,0,["Medium"]],[5,4,4,1,["Medium"]],[5,4,5,0,["Medium"]],[5,4,5,1,["Medium"]],[5,5,0,0,["Medium"]],[5,5,0,1,["Medium"]],[5,5,1,0,["Medium"]],[5,5,1,1,["Medium"]],[5,5,2,0,["Medium"]],[5,5,2,1,["Medium"]],[5,5,3,0,["Medium"]],[5,5,3,1,["Medium"]],[5,5,4,0,["Medium"]],[5,5,4,1,["Medium"]],[5,5,5,0,["Medium"]],[5,5,5,1,["Medium"]],[5,6,0,0,["Low"]],[5,6,0,1,["Low"]],[5,6,1,0,["Low"]],[5,6,1,1,["Low"]],[5,6,2,0,["Low"]],[5,6,2,1,["Low"]],[5,6,3,0,["Low"]],[5,6,3,1,["Low"]],[5,6,4,0,["Low"]],[5,6,4,1,["Low"]],[5,6,5,0,["Low"]],[5,6,5,1,["Low"]],[5,7,0,0,["Low"]],[5,7,0,1,["Low"]],[5,7,1,0,["Low"]],[5,7,1,1,["Low"]],[5,7,2,0,["Low"]],[5,7,2,1,["Low"]],[5,7,3,0,["Low"]],[5,7,3,1,["Low"]],[5,7,4,0,["Low"]],[5,7,4,1,["Low"]],[5,7,5,0,["Low"]],[5,7,5,1,["Low"]],[5,8,0,0,["P0"]],[5,8,0,1,["P0"]],[5,8,1,0,["P0"]],[5,8,1,1,["P0"]],[5,8,2,0,["P0"]],[5,8,2,1,["P0"]],[5,8,3,0,["P0"]],[5,8,3,1,["P0"]],[5,8,4,0,["P0"]],[5,8,4,1,["P0"]],[5,8,5,0,["P0"]],[5,8,5,1,["P0"]],[5,9,0,0,["Low"]],[5,9,0,1,["Low"]],[5,9,1,0,["Low"]],[5,9,1,1,["Low"]],[5,9,2,0,["Low"]],[5,9,2,1,["Low"]],[5,9,3,0,["Low"]],[5,9,3,1,["Low"]],[5,9,4,0,["Low"]],[5,9,4,1,["Low"]],[5,9,5,0,["Low"]],[5,9,5,1,["Low"]],[5,10,0,0,["Medium"]],[5,10,0,1,["Medium"]],[5,10,1,0,["Medium"]],[5,10,1,1,["Medium"]],[5,10,2,0,["Medium"]],[5,10,2,1,["Medium"]],[5,10,3,0,["Medium"]],[5,10,3,1,["Medium"]],[5,10,4,0,["Medium"]],[5,10,4,1,["Medium"]],[5,10,5,0,["Medium"]],[5,10,5,1,["Medium"]],[5,11,0,0,["Low"]],[5,11,0,1,["Low"]],[5,11,1,0,["Low"]],[5,11,1,1,["Low"]],[5,11,2,0,["Low"]],[5,11,2,1,["Low"]],[5,11,3,0,["Low"]],[5,11,3,1,["Low"]],[5,11,4,0,["Low"]],[5,11,4,1,["Low"]],[5,11,5,0,["Low"]],[5,11,5,1,["Low"]],[5,12,0,0,["Medium"]],[5,12,0,1,["Medium"]],[5,12,1,0,["Medium"]],[5,12,1,1,["Medium"]],[5,12,2,0,["Medium"]],[5,12,2,1,["Medium"]],[5,12,3,0,["Medium"]],[5,12,3,1,["Medium"]],[5,12,4,0,["Medium"]],[5,12,4,1,["Medium"]],[5,12,5,0,["Medium"]],[5,12,5,1,["Medium"]],[5,13,0,0,["Low"]],[5,13,0,1,["Low"]],[5,13,1,0,["Low"]],[5,13,1,1,["Low"]],[5,13,2,0,["Low"]],[5,13,2,1,["Low"]],[5,13,3,0,["Low"]],[5,13,3,1,["Low"]],[5,13,4,0,["Low"]],[5,13,4,1,["Low"]],[5,13,5,0,["Low"]],[5,13,5,1,["Low"]],[5,14,0,0,["Low"]],[5,14,0,1,["Low"]],[5,14,1,0,["Low"]],[5,14,1,1,["Low"]],[5,14,2,0,["Low"]],[5,14,2,1,["Low"]],[5,14,3,0,["Low"]],[5,14,3,1,["Low"]],[5,14,4,0,["Low"]],[5,14,4,1,["Low"]],[5,14,5,0,["Low"]],[5,14,5,1,["Low"]],[5,15,0,0,["LOW"]],[5,15,0,1,["LOW"]],[5,15,1,0,["LOW"]],[5,15,1,1,["LOW"]],[5,15,2,0,["LOW"]],[5,15,2,1,["LOW"]],[5,15,3,0,["LOW"]],[5,15,3,1,["LOW"]],[5,15,4,0,["LOW"]],[5,15,4,1,["LOW"]],[5,15,5,0,["LOW"]],[5,15,5,1,["LOW"]],[5,16,0,0,["P3"]],[5,16,0,1,["P3"]],[5,16,1,0,["P3"]],[5,16,1,1,["P3"]],[5,16,2,0,["P3"]],[5,16,2,1,["P3"]],[5,16,3,0,["P3"]],[5,16,3,1,["P3"]],[5,16,4,0,["P3"]],[5,16,4,1,["P3"]],[5,16,5,0,["P3"]],[5,16,5,1,["P3"]],[5,17,0,0,["High"]],[5,17,0,1,["High"]],[5,17,1,0,["High"]],[5,17,1,1,["High"]],[5,17,2,0,["High"]],[5,17,2,1,["High"]],[5,17,3,0,["High"]],[5,17,3,1,["High"]],[5,17,4,0,["High"]],[5,17,4,1,["High"]],[5,17,5,0,["High"]],[5,17,5,1,["High"]],[5,18,0,0,["Low"]],[5,18,0,1,["Low"]],[5,18,1,0,["Low"]],[5,18,1,1,["Low"]],[5,18,2,0,["Low"]],[5,18,2,1,["Low"]],[5,18,3,0,["Low"]],[5,18,3,1,["Low"]],[5,18,4,0,["Low"]],[5,18,4,1,["Low"]],[5,18,5,0,["Low"]],[5,18,5,1,["Low"]],[5,19,0,0,["CRITICAL"]],[5,19,0,1,["CRITICAL"]],[5,19,1,0,["CRITICAL"]],[5,19,1,1,["CRITICAL"]],[5,19,2,0,["CRITICAL"]],[5,19,2,1,["CRITICAL"]],[5,19,3,0,["CRITICAL"]],[5,19,3,1,["CRITICAL"]],[5,19,4,0,["CRITICAL"]],[5,19,4,1,["CRITICAL"]],[5,19,5,0,["CRITICAL"]],[5,19,5,1,["CRITICAL"]]]}} +{"note":"parseFindings labels at main 82e9d7ec (GST-976): every input the review-dispatch and prose-voice-floor tests pass it, every reader fixture here, and generated Severity field shapes. Regenerate only from a frozen main.","main":"82e9d7ec7a2c222f20b04aaf5b965fa50d7b6bdb","entries":[{"source":"pinned","reader":"adversarial-native","output":"FIXABLE: race in a.ts:3\nSeverity: Medium\nBlocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"INVESTIGATE: odd lock in b.ts:9\nSeverity: Low\nBlocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"INVESTIGATE: odd lock in b.ts:9\nBlocks: no","main":["INVESTIGATE"]},{"source":"pinned","reader":"adversarial-native","output":"FIXABLE: race in a.ts:3. Severity: Medium. Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"1. FIXABLE: race in a.ts:3 — Severity: Low, Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"- FIXABLE: race in a.ts:3\n- Severity: Medium\n- Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Drops the last row (q.ts:7). Severity: Critical. Blocks: no","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"1. Cache evicts sessions (c.ts:4)\n Severity: High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry storm (r.ts:2)\n Severity: Medium\n Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Index drift (i.ts:5). Severity: Low. Blocks: Yes, it loses the row","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Naming (n.ts:1)\n Severity: Low\n Blocks: no","main":["Low"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nNO FINDINGS\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"off by one\",\"specialist\":\"testing\"}\n{\"severity\":\"INFORMATIONAL\",\"path\":\"a.ts\",\"line\":9,\"category\":\"style\",\"summary\":\"name\",\"specialist\":\"testing\"}\n","main":["CRITICAL","INFORMATIONAL"]},{"source":"pinned","reader":"outside-voice","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nFindings:\n1. **High**: retry loop never backs off (q.ts:78)\n2. Low - log line is noisy\nRecommendation: Fix the retry because it DoSes the pool\n","main":["High","Low"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"x\",\"specialist\":\"testing\"}\n","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"b.ts\",\"line\":1,\"category\":\"x\",\"summary\":\"y\",\"specialist\":\"red-team\"}\n","main":["CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"x\",\"specialist\":\"testing\"}\n{\"severity\":\"INFORMATIONAL\",\"path\":\"b.ts\",\"line\":1,\"category\":\"n\",\"summary\":\"y\",\"specialist\":\"testing\"}\n","main":["CRITICAL","INFORMATIONAL"]},{"source":"pinned","reader":"r","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"r","output":"## High\n1. The auth check skips webhooks (a.ts:4)\n - Impact: nested evidence\n **Fix:**\n- second one\n## Notes\n- not a finding\n**Critical findings**\n- real one\n**Recommendations**\n- consider renaming foo\n","main":["High","High","Critical"]},{"source":"pinned","reader":"r","output":"1. **Unbounded retry in queue.ts:78** (FIXABLE): loops on 429\n2. `auth.ts:42` skips token expiry check — INVESTIGATE\n### CRITICAL: missing auth check in api.ts:10\n#### [P1] token not expired\n### High: authorization bypass (auth.ts:14)\n- **HIGH**: leak\n| High | race in queue.ts:4 |\nClassification: INVESTIGATE\n7. **Informational: reads every file**","main":["FIXABLE","INVESTIGATE","CRITICAL","P1","High","HIGH","High","INVESTIGATE","Informational"]},{"source":"pinned","reader":"r","output":"FIXABLE: race in a.ts:3\nINVESTIGATE: odd lock\nRecommendation: Fix the race because x\n","main":["FIXABLE","INVESTIGATE"]},{"source":"pinned","reader":"outside-voice","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"No CRITICAL issues except the token leak in log.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"There is no CRITICAL issue other than the SQL injection at db.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"Nothing CRITICAL apart from the auth bypass (auth.ts:9).","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"No new CRITICAL findings, but the token leak in log.ts:3 still blocks.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"None CRITICAL besides the race in queue.ts:40, which loses writes.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"- **No CRITICAL** issues remain except F2 (store.ts:22 corrupts data).","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"REVIEWED_TREE: bedd511efa0fa16c9277821073c68e438dc760d2 /workspace/gstack-evals\n{\"severity\":\"INFORMATIONAL\",\"path\":\"wiki/board/README.md\",\"line\":229,\"summary\":\"Section 9's opening still says every ruling is recorded in the approvals file.\"}\n{\"severity\":\"INFORMATIONAL\",\"path\":\"wiki/board/README.md\",\"line\":113,\"summary\":\"The tstack row still runs on full stacks-r1 and is not labelled raw.\"}\n\nI checked these against the post, the frozen texts and the pinned code, and found nothing CRITICAL. The arithmetic is consistent throughout:\n- The new secondary: 47/60, 15/60, 5/40, 7/40, 4/40 and 11.5/40.\n- The minimum p of 2^-15.","main":["INFORMATIONAL","INFORMATIONAL","CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Found no CRITICAL issues in the fix range.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Nothing CRITICAL or HIGH remains.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"This is not CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"I found zero Critical findings, and none High.","main":["Critical"]},{"source":"pinned","reader":"fix-check","output":"The retry path is safe; without CRITICAL impact.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No new CRITICAL issues introduced by the fix.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No remaining CRITICAL or HIGH findings.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"**No** CRITICAL issues found.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No **CRITICAL** issues found.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No `CRITICAL` issue.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No [P1] issues remain.","main":["P1"]},{"source":"pinned","reader":"fix-check","output":"Nothing at CRITICAL severity remains.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL regressions introduced by the fix.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Nothing CRITICAL to report.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"There is nothing CRITICAL in the fix range.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL — the fix is sound.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Found no CRITICAL or HIGH regressions.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No HIGH-risk changes.","main":["HIGH"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL/HIGH findings.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No P0/P1 issues.","main":["P0"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL, HIGH or MEDIUM issues.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"Checked collect() at lib/review-ledger.ts:189; no CRITICAL issues remain.","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"No CRITICAL issues; served on localhost:3000.","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"Not CRITICAL, but High: the cache drops a row (c.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"No CRITICAL, High: the cache drops a row (c.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"Nothing CRITICAL / HIGH: race in q.ts:7","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"No CRITICAL lock guards the write in Makefile:7\nSeverity: CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"1. auth.ts:3 LOW: log line too verbose\nNo CRITICAL lock guards the write in Makefile:7\nSeverity: CRITICAL","main":["LOW","CRITICAL"]},{"source":"pinned","reader":"r","output":"1. auth.ts:3 LOW: log line too verbose\nNo CRITICAL issues remain.","main":["LOW","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- a.ts:3 race. Severity: Low. Blocks: no\n Separately, nothing CRITICAL about b.ts:9, but it leaks fds.\n Severity: High","main":["Low","CRITICAL"]},{"source":"pinned","reader":"r","output":"1. A CRITICAL race in queue.ts:78 that no lock covers","main":["CRITICAL"]},{"source":"pinned","reader":"r","output":"- A divide by zero CRITICAL bug in parse()","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"FIXABLE: no timeout on the fetch in a.ts:3","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry loop drops the last row (queue.ts:78). Severity: Medium · Blocks: yes\n2. INVESTIGATE: lock order in pool.ts:12\n Blocks: yes\n3. FIXABLE: stale cache key (cache.ts:4)\n Severity: High\n Blocks: no\n4. Token never expires (auth.ts:9)\n Fix: check exp\n **Blocks:** yes\nRecommendation: Fix the retry loop because it loses rows","main":["CRITICAL","CRITICAL","High","CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"1. INVESTIGATE: lock order in pool.ts:12\n Severity: Medium\n Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Low-level retry loop in queue.ts:78 drops writes on 429. Severity: Critical. Blocks: no\n- [P2] cache.ts:40 evicts live sessions. Severity: High. Blocks: no","main":["Critical","High"]},{"source":"pinned","reader":"outside-voice","output":"1. FIXABLE: race in a.ts:3\nWhy it matters: two writers\nSeverity: Low\nBlocks: yes\n2. Unlabeled item about b.ts:9\nSeverity: Medium","main":["CRITICAL","Medium"]},{"source":"pinned","reader":"outside-voice","output":"1. Token never expires (auth.ts:9). Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Token never expires in auth.ts:9 — Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token never expires (auth.ts:9)\nFix: check exp **Blocks:** yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"## Critical findings\n- Severity: Medium — missing owner check at auth.ts:14\n- Severity: Low — token logged at log.ts:3","main":["Critical","Critical"]},{"source":"pinned","reader":"outside-voice","output":"**Finding 1.** a.ts:3 drops rows\nSeverity: Low\nBlocks: no\n**Finding 2.** b.ts:4 leaks\nSeverity: High\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"a.ts:3 drops rows\nSeverity: High\nBlocks: yes\nb.ts:4 leaks\nSeverity: High\nBlocks: yes","main":["CRITICAL","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Medium: cache key drift (c.ts:4)\n Blocks the event loop on every miss - High","main":["Medium","High"]},{"source":"pinned","reader":"outside-voice","output":"2. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"> Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"A in a.ts:1.\nSeverity: Low\n\nB in b.ts:2.\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"A in a.ts:1.\nSeverity: Low\nB in b.ts:2.\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Findings:\n A in a.ts:1.\n Severity: Low\n B in b.ts:2.\n Blocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"A in a.ts:1.\nSeverity: Low\nB in b.ts:2 drops rows. Blocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"**Finding 1.** a.ts:3 races. Severity: Low\n**Finding 2.** b.ts:4 leaks. Blocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"FIXABLE: race in a.ts:3\nSeverity: Medium\nWhy: the writer races\nBlocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- Unlabeled thing in a.ts:3\n- Severity: Medium\n- Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token never expires (auth.ts:9)\nFix: check exp\nBlocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry storm (r.ts:2)\nSeverity High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"CRITICAL: x.ts:4 drops rows (pass 1 had Severity: Low)","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native/adversarial","output":"FIXABLE: race in a.ts:3\nSeverity: Medium\nBlocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"FIXABLE: race in a.ts:3\nSeverity: Medium","main":["Medium"]},{"source":"pinned","reader":"outside-voice","output":"1. Retry loses writes (queue.ts:78). Severity: Medium\n Fix: reject the batch. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"specialist-batched","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nI couldn't run the review because the diff base doesn't exist. `git diff 1b68e637c07cea38f01c01120e02d184575da82c` fails with \"bad object\", and fetching that sha from origin is refused with \"not our ref\", so the commit is neither in this checkout nor on the remote.\n\nThis checkout is on `main` at 047dad7 (v1.91.357.0), tracking origin/main with no uncommitted changes, so there's nothing local to review against either. I'm not returning NO FINDINGS, because that would claim a clean diff I never saw.\n\nTo continue I need one of these: a diff base that exists, or the branch or sha holding the c3-36-p1 change. It may only exist on another machine and was never pushed. I've read all three checklists (testing, security, state-machines) and will apply them as soon as I have a diff.","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nI could not access the diff: git diff failed with \"bad object\", so there is nothing here to review.\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\n{\"severity\":\"CRITICAL\",\"path\":\"a.ts\",\"line\":3,\"category\":\"logic\",\"summary\":\"off by one\",\"specialist\":\"testing\"}\n","main":["CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"specialist-batched","output":"`NO FINDINGS`\n","main":[]},{"source":"pinned","reader":"targeted","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"fix-check","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"red-team","output":"Fixtures were reviewed in summary mode only.\n\nNO FINDINGS\n","main":[]},{"source":"pinned","reader":"adversarial-native","output":"Fixtures were reviewed in summary mode.\n\nRecommendation: Ship as-is because no finding survived tracing\n","main":[]},{"source":"pinned","reader":"adversarial-native","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"No actionable findings in the supplied diff. Static review only.\n\nRecommendation: approve because the diff introduces no demonstrated bypass.\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"NO FINDINGS\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"Recommendation: ship because it is clean\n","main":[]},{"source":"pinned","reader":"specialist/testing","output":"I'm not returning NO FINDINGS, because I never saw the diff.\n","main":[]},{"source":"pinned","reader":"red-team","output":"","main":[]},{"source":"pinned","reader":"fix-check","output":"The fixes look fine to me.\n","main":[]},{"source":"pinned","reader":"adversarial-native","output":"I could not read the diff.\n","main":[]},{"source":"pinned","reader":"adversarial-native","fixture":"review-ledger/gsta-196-native.txt","main":["High","CRITICAL","CRITICAL","Low"]},{"source":"pinned","reader":"outside-voice","output":"1. FIXABLE: race in a.ts:3\n- Unlabeled sibling at b.ts:9\nSeverity: Medium","main":["FIXABLE","Medium"]},{"source":"pinned","reader":"outside-voice","output":"**Retry storm in r.ts:2 (INVESTIGATE).**\n- detail\n- High: a second defect in s.ts:4\nSeverity: Medium","main":["INVESTIGATE","High"]},{"source":"pinned","reader":"outside-voice","output":"Low: cache TTL mismatch (cache.ts:4)\n\n2. Token never expires (auth.ts:9)\nSeverity: High\nBlocks: yes","main":["Low","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"**Race in a.ts:3 (High).**\n- the writer holds no lock\nSeverity: Medium","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"**Race in a.ts:3 (Medium).**\n\n- the writer holds no lock\n\nSeverity: Medium. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"fix-check","output":"{\"severity\":\"INFORMATIONAL\",\"path\":\"a.ts\",\"line\":3,\"summary\":\"Severity: High is the wrong word here\"}\n","main":["INFORMATIONAL"]},{"source":"pinned","reader":"outside-voice","output":"| 1 | `CRITICAL` | High | token leak in log.ts:3 |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"| log.ts:3 | `CRITICAL` | High | token leak |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None of the writes are atomic and are CRITICAL (queue.ts:78); High confidence","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing guards the write path the failure of which is CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"| a.ts:3 | race | not CRITICAL, Low |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"| a.ts:3 | maps `FIXABLE` to `CRITICAL` in a comment | Low |","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Neither path checks auth so this is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None of the inputs are sanitized so this is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing checks the token which is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No bounds check lets CRITICAL data leak (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No defects in parsing but the upload handler is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Without auth this is CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Not checking expiry at auth.ts:9 lets stale tokens through. Severity: High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"The lock isn't released on error (pool.ts:12): High","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"A retry storm could escalate up to CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Low: the parser now maps `FIXABLE` to `CRITICAL` in a comment (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No input validation is CRITICAL (upload.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No CSRF token is a CRITICAL gap at form.ts:12","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing escaped here is CRITICAL: stored XSS at view.ts:8","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No timeout here is High (fetch.ts:3)","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"Upgraded: not High, CRITICAL (a.ts:3)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Severity: not Medium, CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing else is CRITICAL except the token leak in log.ts:3.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No other issue is CRITICAL besides the auth bypass in auth.ts:9.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"None of the other findings are CRITICAL, but the SQL injection in db.ts:12 is.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing here is CRITICAL apart from the unescaped query (db.ts:12).","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing else is CRITICAL except the token leak.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Fix the token leak in log.ts:3 first; nothing else is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 blocks the merge. Nothing else is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token leak in log.ts:3 must be fixed before landing.\n\nNothing else is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak blocks the merge.\nNo other issue is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 blocks the merge. None of the other findings are CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 must block the merge. The other two are nits. Neither finding is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Apart from the token leak in log.ts:3, I found two nits. Neither finding is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Besides the auth bypass at auth.ts:9 there are three nits. None of the three findings are CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The token leak in log.ts:3 must block the merge. Nothing here is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No auth check guards the admin route, so nothing CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"adversarial-native","fixture":"review-ledger/harn15-152-p1-adversarial-native.txt","main":["CRITICAL","INVESTIGATE","INVESTIGATE","CRITICAL","INVESTIGATE","INVESTIGATE","CRITICAL"]},{"source":"pinned","reader":"adversarial-native","output":"**1. Race in a.ts:3 (`FIXABLE`).**\nSeverity: Low. Blocks: no","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"1. Race in a.ts:3 (FIXABLE).","main":["FIXABLE"]},{"source":"pinned","reader":"adversarial-native","output":"No rate limit is FIXABLE (login.ts:9)","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","fixture":"review-ledger/harn15-ship2-red-team.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"red-team","fixture":"review-ledger/harn15-ship2-p2-red-team.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"fix-check","fixture":"review-ledger/harn15-ship2-p2-fix-check.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"fix-check","fixture":"review-ledger/harn15-152-p2-fix-check.txt","main":["CRITICAL","INFORMATIONAL","INFORMATIONAL"]},{"source":"pinned","reader":"fix-check","fixture":"review-ledger/geva-77-fix-check.txt","main":["INFORMATIONAL","INFORMATIONAL","CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"There are no CRITICAL findings.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"No CRITICAL or High findings to report.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"I found nothing CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"So there's nothing at CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Neither finding is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing here is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Neither fix is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Nothing in this diff is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"This is not CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Severity: Low (not High)","main":["High"]},{"source":"pinned","reader":"adversarial-native","fixture":"review-ledger/harn15-ship2-p2-adversarial-native.txt","main":["CRITICAL","CRITICAL","CRITICAL","CRITICAL","CRITICAL","Low"]},{"source":"pinned","reader":"outside-voice","output":"1. **Race on the lock** (a.ts:3)\n - the writer holds no lock\n\n Fix: take it first. Severity: High. Blocks: no.","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"1. Lock race (a.ts:3)\n\n Fix: take it first. Blocks: yes\n2. Cache drift (c.ts:4)","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n {\"severity\":\"critical\",\"summary\":\"Low-level race in q.ts\"}","main":["critical"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n **Critical**\n2. Token leak in the logger","main":["Critical"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n ## High\n2. Token leak in the logger","main":["High"]},{"source":"pinned","reader":"red-team","output":"1. Race in queue\n- Severity: Low\n this one blocks. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"red-team","output":"1. Unescaped query\n {\"severity\":\"low\",\"summary\":\"race\"}\n this blocks the merge. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"The problems below are about accounting and what the post leaves out. None of them breaks a test, production or custody.\n\n1. **The per-task cost leaves out model spend.** The P0 and bulk go or no-go will re-project cost from these figures, so they will come out too low. Severity: Medium. Blocks: no.\n\n2. **One round-2 K1 pair is not accounted for.** The post sets a precedent for P0 yield math, so that drop should be named. Severity: Low. Blocks: no.\n\n3. **The two probe populations are not reconciled.** The stage table probes 189 candidates. Severity: Low. Blocks: no.\n\n4. **The P0 impact of the shortfall is missing.** The plan's P0 (line 323) needs 6 K4 replays and 2 decoys. The post says the shortfall but not that it blocks P0. Severity: Low. Blocks: no.\n\n6. **Some wording is ambiguous.** Two examples:\n - \"Repositories 4 (3 for build tasks)\" is set against a target of \"at least 5 repositories\".\n\n Severity: Low. Blocks: no.\n\nRecommendation: ship after a one-line fix because finding 1's per-task cost leaves out measured model spend.","main":["P0","P0","Low","P0","Low"]},{"source":"pinned","reader":"outside-voice","output":"[P0] Token leak in log.ts:3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"P0: token leak in log.ts:3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. Token leak in log.ts:3\n Severity: P0","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot drops the last row (q.ts:7). Severity: P0. Blocks: no","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"**Critical**\n- Token leak in log.ts:3","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"## P0\n- Token leak in log.ts:3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"### CRITICAL: missing auth check in api.ts:10","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot loses rows on retry (r.ts:2). Severity: Low. Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot loses rows on retry (r.ts:2)\n Severity: Medium\n Blocks: yes","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row (t.md:3)","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. api.ts:10 CRITICAL: missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. api.ts:10 CRITICAL: missing auth\n Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. 🔴 CRITICAL: missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. Token leak in auth.ts:3 lets anyone read sessions. Severity: Critical\n\nSeverity: Low","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"- auth.ts:3 token leak. Severity: Low (pre-exploit), Severity: Critical (once exposed)","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"- Missing auth on DELETE /users lets any user wipe accounts (CRITICAL); the old ticket filed it at severity low.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"- api.ts:10 lets any user wipe accounts. Earlier triage said Severity: Low; it is CRITICAL.","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. CRITICAL missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. CRITICAL, missing auth on DELETE. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. CRITICAL (api.ts:10) missing auth. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row (t.md:3) Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\n**Severity** Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. Bypass x.ts:3 is CRITICAL (Severity: Low in pass 1)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Still open: x.ts:3 is CRITICAL, Severity: Low in pass 1 understated it","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. lib/x.ts:42-58 CRITICAL: drops rows. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. lib/x.ts:42:7 CRITICAL: drops rows. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. (lib/x.ts:42, 50) CRITICAL: drops rows. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity: Low: only the pilot","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity: Low because it is internal","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot table drops a row\nSeverity: Low/Medium","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"Severity: Low: only the P0 pilot table","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. The P0 pilot drops a row. Severity: Low/P3","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"1. drops rows\nSeverity: High/Critical","main":["Critical"]},{"source":"pinned","reader":"outside-voice","output":"Severity: Medium/CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. x.ts:3 drops prod rows. Severity: Low/CRITICAL","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. x.ts:3 is CRITICAL (Severity: Low/Medium in pass 1)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"1. x.ts:3 is CRITICAL (Severity: Low: pass 1)","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Still open: x.ts:3 is P0, Severity: Low: pass 1 understated it","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"============================================================ a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"------------------------------------------------------------ a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"|------------------------------------------------------------| a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴 a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` High-risk. Severity: Low","main":["High"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` CRITICAL findings follow. Severity: Low","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"```````````````````````````````````````````````````````````` a.ts:3 P0 pilot. Severity: Low","main":["P0"]},{"source":"pinned","reader":"outside-voice","output":"---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- P0 pilot table. Severity: Low","main":["P0"]},{"source":"pinned","reader":"specialist/testing","output":"REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nI could not access the diff.\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"Codex timed out after 9 minutes and was terminated; this pass produced NO findings.\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"1. **CRITICAL**: token leak in a.ts:3\nCodex then timed out.\n","main":["CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"- **REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt**\nNO FINDINGS\n","main":[]},{"source":"pinned","reader":"outside-voice","output":"1. **CRITICAL**: auth bypass in a.ts:3\nBlocks: yes\n\nRecommendation: request changes\n","main":["CRITICAL"]},{"source":"pinned","reader":"outside-voice","output":"Codex timed out before it reviewed anything.\n","main":[]},{"source":"pinned","reader":"outside-voice","fixture":"review-wrong-tree/gpt-6.1-sol-gst850-p1.out","main":["CRITICAL","CRITICAL","CRITICAL","CRITICAL","CRITICAL"]},{"source":"pinned","reader":"outside-voice","fixture":"review-wrong-tree/gpt-6.1-sol-gst850-p2.out","main":["CRITICAL","CRITICAL","CRITICAL"]},{"source":"pinned","reader":"specialist/testing","output":"cd '/home/user/.capy/work/gst976/wt' && echo \"REVIEWED_TREE: $(git rev-parse 'HEAD^{tree}') $(git rev-parse --show-toplevel)\"\nprinted: REVIEWED_TREE: d0dd42d6a88fbc2a6abca4f6b2dfde7e35520c5a /home/user/.capy/work/gst976/wt\nNO FINDINGS\n","main":[]},{"source":"fixture","reader":"outside-voice","fixture":"review-ledger/main-labels.json","main":["CRITICAL"]},{"source":"fixture","reader":"outside-voice","fixture":"review-ledger/gsta-196-native.txt","main":["High","Low","Low","Low"]},{"source":"fixture","reader":"outside-voice","fixture":"review-ledger/gsta-196-refusal.txt","main":[]},{"name":"gst987-149-p0-paragraph","reader":"outside-voice","output":"### 1. P2 race in a.ts:3\n\nP0: also data loss in b.ts:9\n\nSeverity: Medium","main":["P2","P0"]},{"name":"gst987-149-critical-paragraph","reader":"outside-voice","output":"### 1. Medium: race in a.ts:3\n\nCritical: data loss in b.ts:9\n\nSeverity: Low","main":["Medium","Critical"]},{"name":"gst987-149-blocks-yes-paragraph","reader":"outside-voice","output":"### 1. Low: race in a.ts:3\n\nData loss in b.ts:9. Blocks: yes\n\nSeverity: Low","main":["Low","CRITICAL"]},{"name":"gst987-149-native-p0-paragraph","reader":"adversarial-native","output":"### 1. FIXABLE: race in a.ts:3\n\nP0: data loss in b.ts:9\n\nSeverity: Medium, Blocks: no","main":["CRITICAL","P0"]},{"name":"gst987-149-native-critical-paragraph","reader":"adversarial-native","output":"**1. Race in a.ts:3** FIXABLE\n\nCRITICAL: token leak in b.ts:9\n\nSeverity: Low, Blocks: no","main":["CRITICAL","CRITICAL"]},{"name":"gst987-149-native-blocks-yes-paragraph","reader":"adversarial-native","output":"### 1. FIXABLE: race in a.ts:3\n\nData loss in b.ts:9. Blocks: yes\n\nSeverity: Low, Blocks: no","main":["CRITICAL","CRITICAL"]}],"shapes":{"bodies":["1. The P0 pilot drops rows (a.ts:3)","1. CRITICAL: drops rows in a.ts:3","- High: the cache leaks (c.ts:4)","**Race in q.ts:7** nothing CRITICAL here","FIXABLE: race in r.ts:2","Note on b.ts:4 for the p1 pilot"],"fields":["Severity: Low","Severity: low","Severity: High/Critical","Severity: Medium/CRITICAL","Severity: Low/Medium","Severity: Low → Medium","Severity: Low: only the pilot","Severity: Low because it is internal","Severity: Low in pass 1, P0 now","(Severity: Low in pass 1)","(Severity: Low/Medium in pass 1)","(Severity: Low: pass 1)","**Severity:** Medium","**Severity** Low","Severity Low","SEVERITY: LOW","Severity: P3","Severity: Low (not High)","Severity: Low. Blocks: no","Severity: Low. Blocks: yes"],"joins":[". ","\n","\n "," — ",", "," "],"readers":["outside-voice","adversarial-native"],"generated":[[0,0,0,0,["P0"]],[0,0,0,1,["P0"]],[0,0,1,0,["P0"]],[0,0,1,1,["P0"]],[0,0,2,0,["P0"]],[0,0,2,1,["P0"]],[0,0,3,0,["P0"]],[0,0,3,1,["P0"]],[0,0,4,0,["P0"]],[0,0,4,1,["P0"]],[0,0,5,0,["P0"]],[0,0,5,1,["P0"]],[0,1,0,0,["P0"]],[0,1,0,1,["P0"]],[0,1,1,0,["P0"]],[0,1,1,1,["P0"]],[0,1,2,0,["P0"]],[0,1,2,1,["P0"]],[0,1,3,0,["P0"]],[0,1,3,1,["P0"]],[0,1,4,0,["P0"]],[0,1,4,1,["P0"]],[0,1,5,0,["P0"]],[0,1,5,1,["P0"]],[0,2,0,0,["P0"]],[0,2,0,1,["P0"]],[0,2,1,0,["P0"]],[0,2,1,1,["P0"]],[0,2,2,0,["P0"]],[0,2,2,1,["P0"]],[0,2,3,0,["P0"]],[0,2,3,1,["P0"]],[0,2,4,0,["P0"]],[0,2,4,1,["P0"]],[0,2,5,0,["P0"]],[0,2,5,1,["P0"]],[0,3,0,0,["P0"]],[0,3,0,1,["P0"]],[0,3,1,0,["P0"]],[0,3,1,1,["P0"]],[0,3,2,0,["P0"]],[0,3,2,1,["P0"]],[0,3,3,0,["P0"]],[0,3,3,1,["P0"]],[0,3,4,0,["P0"]],[0,3,4,1,["P0"]],[0,3,5,0,["P0"]],[0,3,5,1,["P0"]],[0,4,0,0,["P0"]],[0,4,0,1,["P0"]],[0,4,1,0,["P0"]],[0,4,1,1,["P0"]],[0,4,2,0,["P0"]],[0,4,2,1,["P0"]],[0,4,3,0,["P0"]],[0,4,3,1,["P0"]],[0,4,4,0,["P0"]],[0,4,4,1,["P0"]],[0,4,5,0,["P0"]],[0,4,5,1,["P0"]],[0,5,0,0,["P0"]],[0,5,0,1,["P0"]],[0,5,1,0,["P0"]],[0,5,1,1,["P0"]],[0,5,2,0,["P0"]],[0,5,2,1,["P0"]],[0,5,3,0,["P0"]],[0,5,3,1,["P0"]],[0,5,4,0,["P0"]],[0,5,4,1,["P0"]],[0,5,5,0,["P0"]],[0,5,5,1,["P0"]],[0,6,0,0,["P0"]],[0,6,0,1,["P0"]],[0,6,1,0,["P0"]],[0,6,1,1,["P0"]],[0,6,2,0,["P0"]],[0,6,2,1,["P0"]],[0,6,3,0,["P0"]],[0,6,3,1,["P0"]],[0,6,4,0,["P0"]],[0,6,4,1,["P0"]],[0,6,5,0,["P0"]],[0,6,5,1,["P0"]],[0,7,0,0,["P0"]],[0,7,0,1,["P0"]],[0,7,1,0,["P0"]],[0,7,1,1,["P0"]],[0,7,2,0,["P0"]],[0,7,2,1,["P0"]],[0,7,3,0,["P0"]],[0,7,3,1,["P0"]],[0,7,4,0,["P0"]],[0,7,4,1,["P0"]],[0,7,5,0,["P0"]],[0,7,5,1,["P0"]],[0,8,0,0,["P0"]],[0,8,0,1,["P0"]],[0,8,1,0,["P0"]],[0,8,1,1,["P0"]],[0,8,2,0,["P0"]],[0,8,2,1,["P0"]],[0,8,3,0,["P0"]],[0,8,3,1,["P0"]],[0,8,4,0,["P0"]],[0,8,4,1,["P0"]],[0,8,5,0,["P0"]],[0,8,5,1,["P0"]],[0,9,0,0,["P0"]],[0,9,0,1,["P0"]],[0,9,1,0,["P0","Low"]],[0,9,1,1,["P0","Low"]],[0,9,2,0,["P0","Low"]],[0,9,2,1,["P0","Low"]],[0,9,3,0,["P0"]],[0,9,3,1,["P0"]],[0,9,4,0,["P0"]],[0,9,4,1,["P0"]],[0,9,5,0,["P0"]],[0,9,5,1,["P0"]],[0,10,0,0,["P0"]],[0,10,0,1,["P0"]],[0,10,1,0,["P0","Medium"]],[0,10,1,1,["P0","Medium"]],[0,10,2,0,["P0","Medium"]],[0,10,2,1,["P0","Medium"]],[0,10,3,0,["P0"]],[0,10,3,1,["P0"]],[0,10,4,0,["P0"]],[0,10,4,1,["P0"]],[0,10,5,0,["P0"]],[0,10,5,1,["P0"]],[0,11,0,0,["P0"]],[0,11,0,1,["P0"]],[0,11,1,0,["P0","Low"]],[0,11,1,1,["P0","Low"]],[0,11,2,0,["P0","Low"]],[0,11,2,1,["P0","Low"]],[0,11,3,0,["P0"]],[0,11,3,1,["P0"]],[0,11,4,0,["P0"]],[0,11,4,1,["P0"]],[0,11,5,0,["P0"]],[0,11,5,1,["P0"]],[0,12,0,0,["P0"]],[0,12,0,1,["P0"]],[0,12,1,0,["P0"]],[0,12,1,1,["P0"]],[0,12,2,0,["P0"]],[0,12,2,1,["P0"]],[0,12,3,0,["P0"]],[0,12,3,1,["P0"]],[0,12,4,0,["P0"]],[0,12,4,1,["P0"]],[0,12,5,0,["P0"]],[0,12,5,1,["P0"]],[0,13,0,0,["P0"]],[0,13,0,1,["P0"]],[0,13,1,0,["P0"]],[0,13,1,1,["P0"]],[0,13,2,0,["P0"]],[0,13,2,1,["P0"]],[0,13,3,0,["P0"]],[0,13,3,1,["P0"]],[0,13,4,0,["P0"]],[0,13,4,1,["P0"]],[0,13,5,0,["P0"]],[0,13,5,1,["P0"]],[0,14,0,0,["P0"]],[0,14,0,1,["P0"]],[0,14,1,0,["P0"]],[0,14,1,1,["P0"]],[0,14,2,0,["P0"]],[0,14,2,1,["P0"]],[0,14,3,0,["P0"]],[0,14,3,1,["P0"]],[0,14,4,0,["P0"]],[0,14,4,1,["P0"]],[0,14,5,0,["P0"]],[0,14,5,1,["P0"]],[0,15,0,0,["P0"]],[0,15,0,1,["P0"]],[0,15,1,0,["P0"]],[0,15,1,1,["P0"]],[0,15,2,0,["P0"]],[0,15,2,1,["P0"]],[0,15,3,0,["P0"]],[0,15,3,1,["P0"]],[0,15,4,0,["P0"]],[0,15,4,1,["P0"]],[0,15,5,0,["P0"]],[0,15,5,1,["P0"]],[0,16,0,0,["P0"]],[0,16,0,1,["P0"]],[0,16,1,0,["P0"]],[0,16,1,1,["P0"]],[0,16,2,0,["P0"]],[0,16,2,1,["P0"]],[0,16,3,0,["P0"]],[0,16,3,1,["P0"]],[0,16,4,0,["P0"]],[0,16,4,1,["P0"]],[0,16,5,0,["P0"]],[0,16,5,1,["P0"]],[0,17,0,0,["P0"]],[0,17,0,1,["P0"]],[0,17,1,0,["P0"]],[0,17,1,1,["P0"]],[0,17,2,0,["P0"]],[0,17,2,1,["P0"]],[0,17,3,0,["P0"]],[0,17,3,1,["P0"]],[0,17,4,0,["P0"]],[0,17,4,1,["P0"]],[0,17,5,0,["P0"]],[0,17,5,1,["P0"]],[0,18,0,0,["P0"]],[0,18,0,1,["P0"]],[0,18,1,0,["P0"]],[0,18,1,1,["P0"]],[0,18,2,0,["P0"]],[0,18,2,1,["P0"]],[0,18,3,0,["P0"]],[0,18,3,1,["P0"]],[0,18,4,0,["P0"]],[0,18,4,1,["P0"]],[0,18,5,0,["P0"]],[0,18,5,1,["P0"]],[0,19,0,0,["CRITICAL"]],[0,19,0,1,["CRITICAL"]],[0,19,1,0,["CRITICAL"]],[0,19,1,1,["CRITICAL"]],[0,19,2,0,["CRITICAL"]],[0,19,2,1,["CRITICAL"]],[0,19,3,0,["CRITICAL"]],[0,19,3,1,["CRITICAL"]],[0,19,4,0,["CRITICAL"]],[0,19,4,1,["CRITICAL"]],[0,19,5,0,["CRITICAL"]],[0,19,5,1,["CRITICAL"]],[1,0,0,0,["CRITICAL"]],[1,0,0,1,["CRITICAL"]],[1,0,1,0,["CRITICAL"]],[1,0,1,1,["CRITICAL"]],[1,0,2,0,["CRITICAL"]],[1,0,2,1,["CRITICAL"]],[1,0,3,0,["CRITICAL"]],[1,0,3,1,["CRITICAL"]],[1,0,4,0,["CRITICAL"]],[1,0,4,1,["CRITICAL"]],[1,0,5,0,["CRITICAL"]],[1,0,5,1,["CRITICAL"]],[1,1,0,0,["CRITICAL"]],[1,1,0,1,["CRITICAL"]],[1,1,1,0,["CRITICAL"]],[1,1,1,1,["CRITICAL"]],[1,1,2,0,["CRITICAL"]],[1,1,2,1,["CRITICAL"]],[1,1,3,0,["CRITICAL"]],[1,1,3,1,["CRITICAL"]],[1,1,4,0,["CRITICAL"]],[1,1,4,1,["CRITICAL"]],[1,1,5,0,["CRITICAL"]],[1,1,5,1,["CRITICAL"]],[1,2,0,0,["CRITICAL"]],[1,2,0,1,["CRITICAL"]],[1,2,1,0,["CRITICAL"]],[1,2,1,1,["CRITICAL"]],[1,2,2,0,["CRITICAL"]],[1,2,2,1,["CRITICAL"]],[1,2,3,0,["CRITICAL"]],[1,2,3,1,["CRITICAL"]],[1,2,4,0,["CRITICAL"]],[1,2,4,1,["CRITICAL"]],[1,2,5,0,["CRITICAL"]],[1,2,5,1,["CRITICAL"]],[1,3,0,0,["CRITICAL"]],[1,3,0,1,["CRITICAL"]],[1,3,1,0,["CRITICAL"]],[1,3,1,1,["CRITICAL"]],[1,3,2,0,["CRITICAL"]],[1,3,2,1,["CRITICAL"]],[1,3,3,0,["CRITICAL"]],[1,3,3,1,["CRITICAL"]],[1,3,4,0,["CRITICAL"]],[1,3,4,1,["CRITICAL"]],[1,3,5,0,["CRITICAL"]],[1,3,5,1,["CRITICAL"]],[1,4,0,0,["CRITICAL"]],[1,4,0,1,["CRITICAL"]],[1,4,1,0,["CRITICAL"]],[1,4,1,1,["CRITICAL"]],[1,4,2,0,["CRITICAL"]],[1,4,2,1,["CRITICAL"]],[1,4,3,0,["CRITICAL"]],[1,4,3,1,["CRITICAL"]],[1,4,4,0,["CRITICAL"]],[1,4,4,1,["CRITICAL"]],[1,4,5,0,["CRITICAL"]],[1,4,5,1,["CRITICAL"]],[1,5,0,0,["CRITICAL"]],[1,5,0,1,["CRITICAL"]],[1,5,1,0,["CRITICAL"]],[1,5,1,1,["CRITICAL"]],[1,5,2,0,["CRITICAL"]],[1,5,2,1,["CRITICAL"]],[1,5,3,0,["CRITICAL"]],[1,5,3,1,["CRITICAL"]],[1,5,4,0,["CRITICAL"]],[1,5,4,1,["CRITICAL"]],[1,5,5,0,["CRITICAL"]],[1,5,5,1,["CRITICAL"]],[1,6,0,0,["CRITICAL"]],[1,6,0,1,["CRITICAL"]],[1,6,1,0,["CRITICAL"]],[1,6,1,1,["CRITICAL"]],[1,6,2,0,["CRITICAL"]],[1,6,2,1,["CRITICAL"]],[1,6,3,0,["CRITICAL"]],[1,6,3,1,["CRITICAL"]],[1,6,4,0,["CRITICAL"]],[1,6,4,1,["CRITICAL"]],[1,6,5,0,["CRITICAL"]],[1,6,5,1,["CRITICAL"]],[1,7,0,0,["CRITICAL"]],[1,7,0,1,["CRITICAL"]],[1,7,1,0,["CRITICAL"]],[1,7,1,1,["CRITICAL"]],[1,7,2,0,["CRITICAL"]],[1,7,2,1,["CRITICAL"]],[1,7,3,0,["CRITICAL"]],[1,7,3,1,["CRITICAL"]],[1,7,4,0,["CRITICAL"]],[1,7,4,1,["CRITICAL"]],[1,7,5,0,["CRITICAL"]],[1,7,5,1,["CRITICAL"]],[1,8,0,0,["CRITICAL"]],[1,8,0,1,["CRITICAL"]],[1,8,1,0,["CRITICAL"]],[1,8,1,1,["CRITICAL"]],[1,8,2,0,["CRITICAL"]],[1,8,2,1,["CRITICAL"]],[1,8,3,0,["CRITICAL"]],[1,8,3,1,["CRITICAL"]],[1,8,4,0,["CRITICAL"]],[1,8,4,1,["CRITICAL"]],[1,8,5,0,["CRITICAL"]],[1,8,5,1,["CRITICAL"]],[1,9,0,0,["CRITICAL"]],[1,9,0,1,["CRITICAL"]],[1,9,1,0,["CRITICAL","Low"]],[1,9,1,1,["CRITICAL","Low"]],[1,9,2,0,["CRITICAL","Low"]],[1,9,2,1,["CRITICAL","Low"]],[1,9,3,0,["CRITICAL"]],[1,9,3,1,["CRITICAL"]],[1,9,4,0,["CRITICAL"]],[1,9,4,1,["CRITICAL"]],[1,9,5,0,["CRITICAL"]],[1,9,5,1,["CRITICAL"]],[1,10,0,0,["CRITICAL"]],[1,10,0,1,["CRITICAL"]],[1,10,1,0,["CRITICAL","Medium"]],[1,10,1,1,["CRITICAL","Medium"]],[1,10,2,0,["CRITICAL","Medium"]],[1,10,2,1,["CRITICAL","Medium"]],[1,10,3,0,["CRITICAL"]],[1,10,3,1,["CRITICAL"]],[1,10,4,0,["CRITICAL"]],[1,10,4,1,["CRITICAL"]],[1,10,5,0,["CRITICAL"]],[1,10,5,1,["CRITICAL"]],[1,11,0,0,["CRITICAL"]],[1,11,0,1,["CRITICAL"]],[1,11,1,0,["CRITICAL","Low"]],[1,11,1,1,["CRITICAL","Low"]],[1,11,2,0,["CRITICAL","Low"]],[1,11,2,1,["CRITICAL","Low"]],[1,11,3,0,["CRITICAL"]],[1,11,3,1,["CRITICAL"]],[1,11,4,0,["CRITICAL"]],[1,11,4,1,["CRITICAL"]],[1,11,5,0,["CRITICAL"]],[1,11,5,1,["CRITICAL"]],[1,12,0,0,["CRITICAL"]],[1,12,0,1,["CRITICAL"]],[1,12,1,0,["CRITICAL"]],[1,12,1,1,["CRITICAL"]],[1,12,2,0,["CRITICAL"]],[1,12,2,1,["CRITICAL"]],[1,12,3,0,["CRITICAL"]],[1,12,3,1,["CRITICAL"]],[1,12,4,0,["CRITICAL"]],[1,12,4,1,["CRITICAL"]],[1,12,5,0,["CRITICAL"]],[1,12,5,1,["CRITICAL"]],[1,13,0,0,["CRITICAL"]],[1,13,0,1,["CRITICAL"]],[1,13,1,0,["CRITICAL"]],[1,13,1,1,["CRITICAL"]],[1,13,2,0,["CRITICAL"]],[1,13,2,1,["CRITICAL"]],[1,13,3,0,["CRITICAL"]],[1,13,3,1,["CRITICAL"]],[1,13,4,0,["CRITICAL"]],[1,13,4,1,["CRITICAL"]],[1,13,5,0,["CRITICAL"]],[1,13,5,1,["CRITICAL"]],[1,14,0,0,["CRITICAL"]],[1,14,0,1,["CRITICAL"]],[1,14,1,0,["CRITICAL"]],[1,14,1,1,["CRITICAL"]],[1,14,2,0,["CRITICAL"]],[1,14,2,1,["CRITICAL"]],[1,14,3,0,["CRITICAL"]],[1,14,3,1,["CRITICAL"]],[1,14,4,0,["CRITICAL"]],[1,14,4,1,["CRITICAL"]],[1,14,5,0,["CRITICAL"]],[1,14,5,1,["CRITICAL"]],[1,15,0,0,["CRITICAL"]],[1,15,0,1,["CRITICAL"]],[1,15,1,0,["CRITICAL"]],[1,15,1,1,["CRITICAL"]],[1,15,2,0,["CRITICAL"]],[1,15,2,1,["CRITICAL"]],[1,15,3,0,["CRITICAL"]],[1,15,3,1,["CRITICAL"]],[1,15,4,0,["CRITICAL"]],[1,15,4,1,["CRITICAL"]],[1,15,5,0,["CRITICAL"]],[1,15,5,1,["CRITICAL"]],[1,16,0,0,["CRITICAL"]],[1,16,0,1,["CRITICAL"]],[1,16,1,0,["CRITICAL"]],[1,16,1,1,["CRITICAL"]],[1,16,2,0,["CRITICAL"]],[1,16,2,1,["CRITICAL"]],[1,16,3,0,["CRITICAL"]],[1,16,3,1,["CRITICAL"]],[1,16,4,0,["CRITICAL"]],[1,16,4,1,["CRITICAL"]],[1,16,5,0,["CRITICAL"]],[1,16,5,1,["CRITICAL"]],[1,17,0,0,["CRITICAL"]],[1,17,0,1,["CRITICAL"]],[1,17,1,0,["CRITICAL"]],[1,17,1,1,["CRITICAL"]],[1,17,2,0,["CRITICAL"]],[1,17,2,1,["CRITICAL"]],[1,17,3,0,["CRITICAL"]],[1,17,3,1,["CRITICAL"]],[1,17,4,0,["CRITICAL"]],[1,17,4,1,["CRITICAL"]],[1,17,5,0,["CRITICAL"]],[1,17,5,1,["CRITICAL"]],[1,18,0,0,["CRITICAL"]],[1,18,0,1,["CRITICAL"]],[1,18,1,0,["CRITICAL"]],[1,18,1,1,["CRITICAL"]],[1,18,2,0,["CRITICAL"]],[1,18,2,1,["CRITICAL"]],[1,18,3,0,["CRITICAL"]],[1,18,3,1,["CRITICAL"]],[1,18,4,0,["CRITICAL"]],[1,18,4,1,["CRITICAL"]],[1,18,5,0,["CRITICAL"]],[1,18,5,1,["CRITICAL"]],[1,19,0,0,["CRITICAL"]],[1,19,0,1,["CRITICAL"]],[1,19,1,0,["CRITICAL"]],[1,19,1,1,["CRITICAL"]],[1,19,2,0,["CRITICAL"]],[1,19,2,1,["CRITICAL"]],[1,19,3,0,["CRITICAL"]],[1,19,3,1,["CRITICAL"]],[1,19,4,0,["CRITICAL"]],[1,19,4,1,["CRITICAL"]],[1,19,5,0,["CRITICAL"]],[1,19,5,1,["CRITICAL"]],[2,0,0,0,["High"]],[2,0,0,1,["High"]],[2,0,1,0,["High"]],[2,0,1,1,["High"]],[2,0,2,0,["High"]],[2,0,2,1,["High"]],[2,0,3,0,["High"]],[2,0,3,1,["High"]],[2,0,4,0,["High"]],[2,0,4,1,["High"]],[2,0,5,0,["High"]],[2,0,5,1,["High"]],[2,1,0,0,["High"]],[2,1,0,1,["High"]],[2,1,1,0,["High"]],[2,1,1,1,["High"]],[2,1,2,0,["High"]],[2,1,2,1,["High"]],[2,1,3,0,["High"]],[2,1,3,1,["High"]],[2,1,4,0,["High"]],[2,1,4,1,["High"]],[2,1,5,0,["High"]],[2,1,5,1,["High"]],[2,2,0,0,["Critical"]],[2,2,0,1,["Critical"]],[2,2,1,0,["Critical"]],[2,2,1,1,["Critical"]],[2,2,2,0,["Critical"]],[2,2,2,1,["Critical"]],[2,2,3,0,["Critical"]],[2,2,3,1,["Critical"]],[2,2,4,0,["Critical"]],[2,2,4,1,["Critical"]],[2,2,5,0,["Critical"]],[2,2,5,1,["Critical"]],[2,3,0,0,["CRITICAL"]],[2,3,0,1,["CRITICAL"]],[2,3,1,0,["CRITICAL"]],[2,3,1,1,["CRITICAL"]],[2,3,2,0,["CRITICAL"]],[2,3,2,1,["CRITICAL"]],[2,3,3,0,["CRITICAL"]],[2,3,3,1,["CRITICAL"]],[2,3,4,0,["CRITICAL"]],[2,3,4,1,["CRITICAL"]],[2,3,5,0,["CRITICAL"]],[2,3,5,1,["CRITICAL"]],[2,4,0,0,["High"]],[2,4,0,1,["High"]],[2,4,1,0,["High"]],[2,4,1,1,["High"]],[2,4,2,0,["High"]],[2,4,2,1,["High"]],[2,4,3,0,["High"]],[2,4,3,1,["High"]],[2,4,4,0,["High"]],[2,4,4,1,["High"]],[2,4,5,0,["High"]],[2,4,5,1,["High"]],[2,5,0,0,["High"]],[2,5,0,1,["High"]],[2,5,1,0,["High"]],[2,5,1,1,["High"]],[2,5,2,0,["High"]],[2,5,2,1,["High"]],[2,5,3,0,["High"]],[2,5,3,1,["High"]],[2,5,4,0,["High"]],[2,5,4,1,["High"]],[2,5,5,0,["High"]],[2,5,5,1,["High"]],[2,6,0,0,["High"]],[2,6,0,1,["High"]],[2,6,1,0,["High"]],[2,6,1,1,["High"]],[2,6,2,0,["High"]],[2,6,2,1,["High"]],[2,6,3,0,["High"]],[2,6,3,1,["High"]],[2,6,4,0,["High"]],[2,6,4,1,["High"]],[2,6,5,0,["High"]],[2,6,5,1,["High"]],[2,7,0,0,["High"]],[2,7,0,1,["High"]],[2,7,1,0,["High"]],[2,7,1,1,["High"]],[2,7,2,0,["High"]],[2,7,2,1,["High"]],[2,7,3,0,["High"]],[2,7,3,1,["High"]],[2,7,4,0,["High"]],[2,7,4,1,["High"]],[2,7,5,0,["High"]],[2,7,5,1,["High"]],[2,8,0,0,["P0"]],[2,8,0,1,["P0"]],[2,8,1,0,["P0"]],[2,8,1,1,["P0"]],[2,8,2,0,["P0"]],[2,8,2,1,["P0"]],[2,8,3,0,["P0"]],[2,8,3,1,["P0"]],[2,8,4,0,["P0"]],[2,8,4,1,["P0"]],[2,8,5,0,["P0"]],[2,8,5,1,["P0"]],[2,9,0,0,["High"]],[2,9,0,1,["High"]],[2,9,1,0,["High","Low"]],[2,9,1,1,["High","Low"]],[2,9,2,0,["High","Low"]],[2,9,2,1,["High","Low"]],[2,9,3,0,["High"]],[2,9,3,1,["High"]],[2,9,4,0,["High"]],[2,9,4,1,["High"]],[2,9,5,0,["High"]],[2,9,5,1,["High"]],[2,10,0,0,["High"]],[2,10,0,1,["High"]],[2,10,1,0,["High","Medium"]],[2,10,1,1,["High","Medium"]],[2,10,2,0,["High","Medium"]],[2,10,2,1,["High","Medium"]],[2,10,3,0,["High"]],[2,10,3,1,["High"]],[2,10,4,0,["High"]],[2,10,4,1,["High"]],[2,10,5,0,["High"]],[2,10,5,1,["High"]],[2,11,0,0,["High"]],[2,11,0,1,["High"]],[2,11,1,0,["High","Low"]],[2,11,1,1,["High","Low"]],[2,11,2,0,["High","Low"]],[2,11,2,1,["High","Low"]],[2,11,3,0,["High"]],[2,11,3,1,["High"]],[2,11,4,0,["High"]],[2,11,4,1,["High"]],[2,11,5,0,["High"]],[2,11,5,1,["High"]],[2,12,0,0,["High"]],[2,12,0,1,["High"]],[2,12,1,0,["High"]],[2,12,1,1,["High"]],[2,12,2,0,["High"]],[2,12,2,1,["High"]],[2,12,3,0,["High"]],[2,12,3,1,["High"]],[2,12,4,0,["High"]],[2,12,4,1,["High"]],[2,12,5,0,["High"]],[2,12,5,1,["High"]],[2,13,0,0,["High"]],[2,13,0,1,["High"]],[2,13,1,0,["High"]],[2,13,1,1,["High"]],[2,13,2,0,["High"]],[2,13,2,1,["High"]],[2,13,3,0,["High"]],[2,13,3,1,["High"]],[2,13,4,0,["High"]],[2,13,4,1,["High"]],[2,13,5,0,["High"]],[2,13,5,1,["High"]],[2,14,0,0,["High"]],[2,14,0,1,["High"]],[2,14,1,0,["High"]],[2,14,1,1,["High"]],[2,14,2,0,["High"]],[2,14,2,1,["High"]],[2,14,3,0,["High"]],[2,14,3,1,["High"]],[2,14,4,0,["High"]],[2,14,4,1,["High"]],[2,14,5,0,["High"]],[2,14,5,1,["High"]],[2,15,0,0,["High"]],[2,15,0,1,["High"]],[2,15,1,0,["High"]],[2,15,1,1,["High"]],[2,15,2,0,["High"]],[2,15,2,1,["High"]],[2,15,3,0,["High"]],[2,15,3,1,["High"]],[2,15,4,0,["High"]],[2,15,4,1,["High"]],[2,15,5,0,["High"]],[2,15,5,1,["High"]],[2,16,0,0,["High"]],[2,16,0,1,["High"]],[2,16,1,0,["High"]],[2,16,1,1,["High"]],[2,16,2,0,["High"]],[2,16,2,1,["High"]],[2,16,3,0,["High"]],[2,16,3,1,["High"]],[2,16,4,0,["High"]],[2,16,4,1,["High"]],[2,16,5,0,["High"]],[2,16,5,1,["High"]],[2,17,0,0,["High"]],[2,17,0,1,["High"]],[2,17,1,0,["High"]],[2,17,1,1,["High"]],[2,17,2,0,["High"]],[2,17,2,1,["High"]],[2,17,3,0,["High"]],[2,17,3,1,["High"]],[2,17,4,0,["High"]],[2,17,4,1,["High"]],[2,17,5,0,["High"]],[2,17,5,1,["High"]],[2,18,0,0,["High"]],[2,18,0,1,["High"]],[2,18,1,0,["High"]],[2,18,1,1,["High"]],[2,18,2,0,["High"]],[2,18,2,1,["High"]],[2,18,3,0,["High"]],[2,18,3,1,["High"]],[2,18,4,0,["High"]],[2,18,4,1,["High"]],[2,18,5,0,["High"]],[2,18,5,1,["High"]],[2,19,0,0,["CRITICAL"]],[2,19,0,1,["CRITICAL"]],[2,19,1,0,["CRITICAL"]],[2,19,1,1,["CRITICAL"]],[2,19,2,0,["CRITICAL"]],[2,19,2,1,["CRITICAL"]],[2,19,3,0,["CRITICAL"]],[2,19,3,1,["CRITICAL"]],[2,19,4,0,["CRITICAL"]],[2,19,4,1,["CRITICAL"]],[2,19,5,0,["CRITICAL"]],[2,19,5,1,["CRITICAL"]],[3,0,0,0,["CRITICAL"]],[3,0,0,1,["CRITICAL"]],[3,0,1,0,["CRITICAL"]],[3,0,1,1,["CRITICAL"]],[3,0,2,0,["CRITICAL"]],[3,0,2,1,["CRITICAL"]],[3,0,3,0,["CRITICAL"]],[3,0,3,1,["CRITICAL"]],[3,0,4,0,["CRITICAL"]],[3,0,4,1,["CRITICAL"]],[3,0,5,0,["CRITICAL"]],[3,0,5,1,["CRITICAL"]],[3,1,0,0,["CRITICAL"]],[3,1,0,1,["CRITICAL"]],[3,1,1,0,["CRITICAL"]],[3,1,1,1,["CRITICAL"]],[3,1,2,0,["CRITICAL"]],[3,1,2,1,["CRITICAL"]],[3,1,3,0,["CRITICAL"]],[3,1,3,1,["CRITICAL"]],[3,1,4,0,["CRITICAL"]],[3,1,4,1,["CRITICAL"]],[3,1,5,0,["CRITICAL"]],[3,1,5,1,["CRITICAL"]],[3,2,0,0,["CRITICAL"]],[3,2,0,1,["CRITICAL"]],[3,2,1,0,["CRITICAL"]],[3,2,1,1,["CRITICAL"]],[3,2,2,0,["CRITICAL"]],[3,2,2,1,["CRITICAL"]],[3,2,3,0,["CRITICAL"]],[3,2,3,1,["CRITICAL"]],[3,2,4,0,["CRITICAL"]],[3,2,4,1,["CRITICAL"]],[3,2,5,0,["CRITICAL"]],[3,2,5,1,["CRITICAL"]],[3,3,0,0,["CRITICAL"]],[3,3,0,1,["CRITICAL"]],[3,3,1,0,["CRITICAL"]],[3,3,1,1,["CRITICAL"]],[3,3,2,0,["CRITICAL"]],[3,3,2,1,["CRITICAL"]],[3,3,3,0,["CRITICAL"]],[3,3,3,1,["CRITICAL"]],[3,3,4,0,["CRITICAL"]],[3,3,4,1,["CRITICAL"]],[3,3,5,0,["CRITICAL"]],[3,3,5,1,["CRITICAL"]],[3,4,0,0,["CRITICAL"]],[3,4,0,1,["CRITICAL"]],[3,4,1,0,["CRITICAL"]],[3,4,1,1,["CRITICAL"]],[3,4,2,0,["CRITICAL"]],[3,4,2,1,["CRITICAL"]],[3,4,3,0,["CRITICAL"]],[3,4,3,1,["CRITICAL"]],[3,4,4,0,["CRITICAL"]],[3,4,4,1,["CRITICAL"]],[3,4,5,0,["CRITICAL"]],[3,4,5,1,["CRITICAL"]],[3,5,0,0,["CRITICAL"]],[3,5,0,1,["CRITICAL"]],[3,5,1,0,["CRITICAL"]],[3,5,1,1,["CRITICAL"]],[3,5,2,0,["CRITICAL"]],[3,5,2,1,["CRITICAL"]],[3,5,3,0,["CRITICAL"]],[3,5,3,1,["CRITICAL"]],[3,5,4,0,["CRITICAL"]],[3,5,4,1,["CRITICAL"]],[3,5,5,0,["CRITICAL"]],[3,5,5,1,["CRITICAL"]],[3,6,0,0,["CRITICAL"]],[3,6,0,1,["CRITICAL"]],[3,6,1,0,["CRITICAL"]],[3,6,1,1,["CRITICAL"]],[3,6,2,0,["CRITICAL"]],[3,6,2,1,["CRITICAL"]],[3,6,3,0,["CRITICAL"]],[3,6,3,1,["CRITICAL"]],[3,6,4,0,["CRITICAL"]],[3,6,4,1,["CRITICAL"]],[3,6,5,0,["CRITICAL"]],[3,6,5,1,["CRITICAL"]],[3,7,0,0,["CRITICAL"]],[3,7,0,1,["CRITICAL"]],[3,7,1,0,["CRITICAL"]],[3,7,1,1,["CRITICAL"]],[3,7,2,0,["CRITICAL"]],[3,7,2,1,["CRITICAL"]],[3,7,3,0,["CRITICAL"]],[3,7,3,1,["CRITICAL"]],[3,7,4,0,["CRITICAL"]],[3,7,4,1,["CRITICAL"]],[3,7,5,0,["CRITICAL"]],[3,7,5,1,["CRITICAL"]],[3,8,0,0,["CRITICAL"]],[3,8,0,1,["CRITICAL"]],[3,8,1,0,["CRITICAL"]],[3,8,1,1,["CRITICAL"]],[3,8,2,0,["CRITICAL"]],[3,8,2,1,["CRITICAL"]],[3,8,3,0,["CRITICAL"]],[3,8,3,1,["CRITICAL"]],[3,8,4,0,["CRITICAL"]],[3,8,4,1,["CRITICAL"]],[3,8,5,0,["CRITICAL"]],[3,8,5,1,["CRITICAL"]],[3,9,0,0,["CRITICAL"]],[3,9,0,1,["CRITICAL"]],[3,9,1,0,["CRITICAL","Low"]],[3,9,1,1,["CRITICAL","Low"]],[3,9,2,0,["CRITICAL","Low"]],[3,9,2,1,["CRITICAL","Low"]],[3,9,3,0,["CRITICAL"]],[3,9,3,1,["CRITICAL"]],[3,9,4,0,["CRITICAL"]],[3,9,4,1,["CRITICAL"]],[3,9,5,0,["CRITICAL"]],[3,9,5,1,["CRITICAL"]],[3,10,0,0,["CRITICAL"]],[3,10,0,1,["CRITICAL"]],[3,10,1,0,["CRITICAL","Medium"]],[3,10,1,1,["CRITICAL","Medium"]],[3,10,2,0,["CRITICAL","Medium"]],[3,10,2,1,["CRITICAL","Medium"]],[3,10,3,0,["CRITICAL"]],[3,10,3,1,["CRITICAL"]],[3,10,4,0,["CRITICAL"]],[3,10,4,1,["CRITICAL"]],[3,10,5,0,["CRITICAL"]],[3,10,5,1,["CRITICAL"]],[3,11,0,0,["CRITICAL"]],[3,11,0,1,["CRITICAL"]],[3,11,1,0,["CRITICAL","Low"]],[3,11,1,1,["CRITICAL","Low"]],[3,11,2,0,["CRITICAL","Low"]],[3,11,2,1,["CRITICAL","Low"]],[3,11,3,0,["CRITICAL"]],[3,11,3,1,["CRITICAL"]],[3,11,4,0,["CRITICAL"]],[3,11,4,1,["CRITICAL"]],[3,11,5,0,["CRITICAL"]],[3,11,5,1,["CRITICAL"]],[3,12,0,0,["CRITICAL"]],[3,12,0,1,["CRITICAL"]],[3,12,1,0,["CRITICAL"]],[3,12,1,1,["CRITICAL"]],[3,12,2,0,["CRITICAL"]],[3,12,2,1,["CRITICAL"]],[3,12,3,0,["CRITICAL"]],[3,12,3,1,["CRITICAL"]],[3,12,4,0,["CRITICAL"]],[3,12,4,1,["CRITICAL"]],[3,12,5,0,["CRITICAL"]],[3,12,5,1,["CRITICAL"]],[3,13,0,0,["CRITICAL"]],[3,13,0,1,["CRITICAL"]],[3,13,1,0,["CRITICAL"]],[3,13,1,1,["CRITICAL"]],[3,13,2,0,["CRITICAL"]],[3,13,2,1,["CRITICAL"]],[3,13,3,0,["CRITICAL"]],[3,13,3,1,["CRITICAL"]],[3,13,4,0,["CRITICAL"]],[3,13,4,1,["CRITICAL"]],[3,13,5,0,["CRITICAL"]],[3,13,5,1,["CRITICAL"]],[3,14,0,0,["CRITICAL"]],[3,14,0,1,["CRITICAL"]],[3,14,1,0,["CRITICAL"]],[3,14,1,1,["CRITICAL"]],[3,14,2,0,["CRITICAL"]],[3,14,2,1,["CRITICAL"]],[3,14,3,0,["CRITICAL"]],[3,14,3,1,["CRITICAL"]],[3,14,4,0,["CRITICAL"]],[3,14,4,1,["CRITICAL"]],[3,14,5,0,["CRITICAL"]],[3,14,5,1,["CRITICAL"]],[3,15,0,0,["CRITICAL"]],[3,15,0,1,["CRITICAL"]],[3,15,1,0,["CRITICAL"]],[3,15,1,1,["CRITICAL"]],[3,15,2,0,["CRITICAL"]],[3,15,2,1,["CRITICAL"]],[3,15,3,0,["CRITICAL"]],[3,15,3,1,["CRITICAL"]],[3,15,4,0,["CRITICAL"]],[3,15,4,1,["CRITICAL"]],[3,15,5,0,["CRITICAL"]],[3,15,5,1,["CRITICAL"]],[3,16,0,0,["CRITICAL"]],[3,16,0,1,["CRITICAL"]],[3,16,1,0,["CRITICAL"]],[3,16,1,1,["CRITICAL"]],[3,16,2,0,["CRITICAL"]],[3,16,2,1,["CRITICAL"]],[3,16,3,0,["CRITICAL"]],[3,16,3,1,["CRITICAL"]],[3,16,4,0,["CRITICAL"]],[3,16,4,1,["CRITICAL"]],[3,16,5,0,["CRITICAL"]],[3,16,5,1,["CRITICAL"]],[3,17,0,0,["CRITICAL"]],[3,17,0,1,["CRITICAL"]],[3,17,1,0,["CRITICAL"]],[3,17,1,1,["CRITICAL"]],[3,17,2,0,["CRITICAL"]],[3,17,2,1,["CRITICAL"]],[3,17,3,0,["CRITICAL"]],[3,17,3,1,["CRITICAL"]],[3,17,4,0,["CRITICAL"]],[3,17,4,1,["CRITICAL"]],[3,17,5,0,["CRITICAL"]],[3,17,5,1,["CRITICAL"]],[3,18,0,0,["CRITICAL"]],[3,18,0,1,["CRITICAL"]],[3,18,1,0,["CRITICAL"]],[3,18,1,1,["CRITICAL"]],[3,18,2,0,["CRITICAL"]],[3,18,2,1,["CRITICAL"]],[3,18,3,0,["CRITICAL"]],[3,18,3,1,["CRITICAL"]],[3,18,4,0,["CRITICAL"]],[3,18,4,1,["CRITICAL"]],[3,18,5,0,["CRITICAL"]],[3,18,5,1,["CRITICAL"]],[3,19,0,0,["CRITICAL"]],[3,19,0,1,["CRITICAL"]],[3,19,1,0,["CRITICAL"]],[3,19,1,1,["CRITICAL"]],[3,19,2,0,["CRITICAL"]],[3,19,2,1,["CRITICAL"]],[3,19,3,0,["CRITICAL"]],[3,19,3,1,["CRITICAL"]],[3,19,4,0,["CRITICAL"]],[3,19,4,1,["CRITICAL"]],[3,19,5,0,["CRITICAL"]],[3,19,5,1,["CRITICAL"]],[4,0,0,0,["Low"]],[4,0,0,1,["CRITICAL"]],[4,0,1,0,["Low"]],[4,0,1,1,["CRITICAL"]],[4,0,2,0,["Low"]],[4,0,2,1,["CRITICAL"]],[4,0,3,0,["Low"]],[4,0,3,1,["CRITICAL"]],[4,0,4,0,["Low"]],[4,0,4,1,["CRITICAL"]],[4,0,5,0,["Low"]],[4,0,5,1,["CRITICAL"]],[4,1,0,0,["FIXABLE"]],[4,1,0,1,["CRITICAL"]],[4,1,1,0,["FIXABLE"]],[4,1,1,1,["CRITICAL"]],[4,1,2,0,["FIXABLE"]],[4,1,2,1,["CRITICAL"]],[4,1,3,0,["FIXABLE"]],[4,1,3,1,["CRITICAL"]],[4,1,4,0,["FIXABLE"]],[4,1,4,1,["CRITICAL"]],[4,1,5,0,["FIXABLE"]],[4,1,5,1,["CRITICAL"]],[4,2,0,0,["Critical"]],[4,2,0,1,["CRITICAL"]],[4,2,1,0,["Critical"]],[4,2,1,1,["CRITICAL"]],[4,2,2,0,["Critical"]],[4,2,2,1,["CRITICAL"]],[4,2,3,0,["Critical"]],[4,2,3,1,["CRITICAL"]],[4,2,4,0,["Critical"]],[4,2,4,1,["CRITICAL"]],[4,2,5,0,["Critical"]],[4,2,5,1,["CRITICAL"]],[4,3,0,0,["CRITICAL"]],[4,3,0,1,["CRITICAL"]],[4,3,1,0,["CRITICAL"]],[4,3,1,1,["CRITICAL"]],[4,3,2,0,["CRITICAL"]],[4,3,2,1,["CRITICAL"]],[4,3,3,0,["CRITICAL"]],[4,3,3,1,["CRITICAL"]],[4,3,4,0,["CRITICAL"]],[4,3,4,1,["CRITICAL"]],[4,3,5,0,["CRITICAL"]],[4,3,5,1,["CRITICAL"]],[4,4,0,0,["Medium"]],[4,4,0,1,["CRITICAL"]],[4,4,1,0,["Medium"]],[4,4,1,1,["CRITICAL"]],[4,4,2,0,["Medium"]],[4,4,2,1,["CRITICAL"]],[4,4,3,0,["Medium"]],[4,4,3,1,["CRITICAL"]],[4,4,4,0,["Medium"]],[4,4,4,1,["CRITICAL"]],[4,4,5,0,["Medium"]],[4,4,5,1,["CRITICAL"]],[4,5,0,0,["Medium"]],[4,5,0,1,["CRITICAL"]],[4,5,1,0,["Medium"]],[4,5,1,1,["CRITICAL"]],[4,5,2,0,["Medium"]],[4,5,2,1,["CRITICAL"]],[4,5,3,0,["Medium"]],[4,5,3,1,["CRITICAL"]],[4,5,4,0,["Medium"]],[4,5,4,1,["CRITICAL"]],[4,5,5,0,["Medium"]],[4,5,5,1,["CRITICAL"]],[4,6,0,0,["Low"]],[4,6,0,1,["CRITICAL"]],[4,6,1,0,["Low"]],[4,6,1,1,["CRITICAL"]],[4,6,2,0,["Low"]],[4,6,2,1,["CRITICAL"]],[4,6,3,0,["Low"]],[4,6,3,1,["CRITICAL"]],[4,6,4,0,["Low"]],[4,6,4,1,["CRITICAL"]],[4,6,5,0,["Low"]],[4,6,5,1,["CRITICAL"]],[4,7,0,0,["Low"]],[4,7,0,1,["CRITICAL"]],[4,7,1,0,["Low"]],[4,7,1,1,["CRITICAL"]],[4,7,2,0,["Low"]],[4,7,2,1,["CRITICAL"]],[4,7,3,0,["Low"]],[4,7,3,1,["CRITICAL"]],[4,7,4,0,["Low"]],[4,7,4,1,["CRITICAL"]],[4,7,5,0,["Low"]],[4,7,5,1,["CRITICAL"]],[4,8,0,0,["P0"]],[4,8,0,1,["CRITICAL"]],[4,8,1,0,["P0"]],[4,8,1,1,["CRITICAL"]],[4,8,2,0,["P0"]],[4,8,2,1,["CRITICAL"]],[4,8,3,0,["P0"]],[4,8,3,1,["CRITICAL"]],[4,8,4,0,["P0"]],[4,8,4,1,["CRITICAL"]],[4,8,5,0,["P0"]],[4,8,5,1,["CRITICAL"]],[4,9,0,0,["Low"]],[4,9,0,1,["CRITICAL"]],[4,9,1,0,["FIXABLE","Low"]],[4,9,1,1,["CRITICAL","Low"]],[4,9,2,0,["FIXABLE","Low"]],[4,9,2,1,["CRITICAL","Low"]],[4,9,3,0,["Low"]],[4,9,3,1,["CRITICAL"]],[4,9,4,0,["Low"]],[4,9,4,1,["CRITICAL"]],[4,9,5,0,["Low"]],[4,9,5,1,["CRITICAL"]],[4,10,0,0,["Medium"]],[4,10,0,1,["CRITICAL"]],[4,10,1,0,["FIXABLE","Medium"]],[4,10,1,1,["CRITICAL","Medium"]],[4,10,2,0,["FIXABLE","Medium"]],[4,10,2,1,["CRITICAL","Medium"]],[4,10,3,0,["Medium"]],[4,10,3,1,["CRITICAL"]],[4,10,4,0,["Medium"]],[4,10,4,1,["CRITICAL"]],[4,10,5,0,["Medium"]],[4,10,5,1,["CRITICAL"]],[4,11,0,0,["Low"]],[4,11,0,1,["CRITICAL"]],[4,11,1,0,["FIXABLE","Low"]],[4,11,1,1,["CRITICAL","Low"]],[4,11,2,0,["FIXABLE","Low"]],[4,11,2,1,["CRITICAL","Low"]],[4,11,3,0,["Low"]],[4,11,3,1,["CRITICAL"]],[4,11,4,0,["Low"]],[4,11,4,1,["CRITICAL"]],[4,11,5,0,["Low"]],[4,11,5,1,["CRITICAL"]],[4,12,0,0,["Medium"]],[4,12,0,1,["CRITICAL"]],[4,12,1,0,["Medium"]],[4,12,1,1,["CRITICAL"]],[4,12,2,0,["Medium"]],[4,12,2,1,["CRITICAL"]],[4,12,3,0,["Medium"]],[4,12,3,1,["CRITICAL"]],[4,12,4,0,["Medium"]],[4,12,4,1,["CRITICAL"]],[4,12,5,0,["Medium"]],[4,12,5,1,["CRITICAL"]],[4,13,0,0,["Low"]],[4,13,0,1,["CRITICAL"]],[4,13,1,0,["Low"]],[4,13,1,1,["CRITICAL"]],[4,13,2,0,["Low"]],[4,13,2,1,["CRITICAL"]],[4,13,3,0,["Low"]],[4,13,3,1,["CRITICAL"]],[4,13,4,0,["Low"]],[4,13,4,1,["CRITICAL"]],[4,13,5,0,["Low"]],[4,13,5,1,["CRITICAL"]],[4,14,0,0,["Low"]],[4,14,0,1,["CRITICAL"]],[4,14,1,0,["Low"]],[4,14,1,1,["CRITICAL"]],[4,14,2,0,["Low"]],[4,14,2,1,["CRITICAL"]],[4,14,3,0,["Low"]],[4,14,3,1,["CRITICAL"]],[4,14,4,0,["Low"]],[4,14,4,1,["CRITICAL"]],[4,14,5,0,["Low"]],[4,14,5,1,["CRITICAL"]],[4,15,0,0,["LOW"]],[4,15,0,1,["CRITICAL"]],[4,15,1,0,["LOW"]],[4,15,1,1,["CRITICAL"]],[4,15,2,0,["LOW"]],[4,15,2,1,["CRITICAL"]],[4,15,3,0,["LOW"]],[4,15,3,1,["CRITICAL"]],[4,15,4,0,["LOW"]],[4,15,4,1,["CRITICAL"]],[4,15,5,0,["LOW"]],[4,15,5,1,["CRITICAL"]],[4,16,0,0,["P3"]],[4,16,0,1,["CRITICAL"]],[4,16,1,0,["P3"]],[4,16,1,1,["CRITICAL"]],[4,16,2,0,["P3"]],[4,16,2,1,["CRITICAL"]],[4,16,3,0,["P3"]],[4,16,3,1,["CRITICAL"]],[4,16,4,0,["P3"]],[4,16,4,1,["CRITICAL"]],[4,16,5,0,["P3"]],[4,16,5,1,["CRITICAL"]],[4,17,0,0,["High"]],[4,17,0,1,["CRITICAL"]],[4,17,1,0,["High"]],[4,17,1,1,["CRITICAL"]],[4,17,2,0,["High"]],[4,17,2,1,["CRITICAL"]],[4,17,3,0,["High"]],[4,17,3,1,["CRITICAL"]],[4,17,4,0,["High"]],[4,17,4,1,["CRITICAL"]],[4,17,5,0,["High"]],[4,17,5,1,["CRITICAL"]],[4,18,0,0,["Low"]],[4,18,0,1,["CRITICAL"]],[4,18,1,0,["Low"]],[4,18,1,1,["CRITICAL"]],[4,18,2,0,["Low"]],[4,18,2,1,["CRITICAL"]],[4,18,3,0,["Low"]],[4,18,3,1,["CRITICAL"]],[4,18,4,0,["Low"]],[4,18,4,1,["CRITICAL"]],[4,18,5,0,["Low"]],[4,18,5,1,["CRITICAL"]],[4,19,0,0,["CRITICAL"]],[4,19,0,1,["CRITICAL"]],[4,19,1,0,["CRITICAL"]],[4,19,1,1,["CRITICAL"]],[4,19,2,0,["CRITICAL"]],[4,19,2,1,["CRITICAL"]],[4,19,3,0,["CRITICAL"]],[4,19,3,1,["CRITICAL"]],[4,19,4,0,["CRITICAL"]],[4,19,4,1,["CRITICAL"]],[4,19,5,0,["CRITICAL"]],[4,19,5,1,["CRITICAL"]],[5,0,0,0,["Low"]],[5,0,0,1,["Low"]],[5,0,1,0,["Low"]],[5,0,1,1,["Low"]],[5,0,2,0,["Low"]],[5,0,2,1,["Low"]],[5,0,3,0,["Low"]],[5,0,3,1,["Low"]],[5,0,4,0,["Low"]],[5,0,4,1,["Low"]],[5,0,5,0,["Low"]],[5,0,5,1,["Low"]],[5,1,0,0,[]],[5,1,0,1,[]],[5,1,1,0,[]],[5,1,1,1,[]],[5,1,2,0,[]],[5,1,2,1,[]],[5,1,3,0,[]],[5,1,3,1,[]],[5,1,4,0,[]],[5,1,4,1,[]],[5,1,5,0,[]],[5,1,5,1,[]],[5,2,0,0,["Critical"]],[5,2,0,1,["Critical"]],[5,2,1,0,["Critical"]],[5,2,1,1,["Critical"]],[5,2,2,0,["Critical"]],[5,2,2,1,["Critical"]],[5,2,3,0,["Critical"]],[5,2,3,1,["Critical"]],[5,2,4,0,["Critical"]],[5,2,4,1,["Critical"]],[5,2,5,0,["Critical"]],[5,2,5,1,["Critical"]],[5,3,0,0,["CRITICAL"]],[5,3,0,1,["CRITICAL"]],[5,3,1,0,["CRITICAL"]],[5,3,1,1,["CRITICAL"]],[5,3,2,0,["CRITICAL"]],[5,3,2,1,["CRITICAL"]],[5,3,3,0,["CRITICAL"]],[5,3,3,1,["CRITICAL"]],[5,3,4,0,["CRITICAL"]],[5,3,4,1,["CRITICAL"]],[5,3,5,0,["CRITICAL"]],[5,3,5,1,["CRITICAL"]],[5,4,0,0,["Medium"]],[5,4,0,1,["Medium"]],[5,4,1,0,["Medium"]],[5,4,1,1,["Medium"]],[5,4,2,0,["Medium"]],[5,4,2,1,["Medium"]],[5,4,3,0,["Medium"]],[5,4,3,1,["Medium"]],[5,4,4,0,["Medium"]],[5,4,4,1,["Medium"]],[5,4,5,0,["Medium"]],[5,4,5,1,["Medium"]],[5,5,0,0,["Medium"]],[5,5,0,1,["Medium"]],[5,5,1,0,["Medium"]],[5,5,1,1,["Medium"]],[5,5,2,0,["Medium"]],[5,5,2,1,["Medium"]],[5,5,3,0,["Medium"]],[5,5,3,1,["Medium"]],[5,5,4,0,["Medium"]],[5,5,4,1,["Medium"]],[5,5,5,0,["Medium"]],[5,5,5,1,["Medium"]],[5,6,0,0,["Low"]],[5,6,0,1,["Low"]],[5,6,1,0,["Low"]],[5,6,1,1,["Low"]],[5,6,2,0,["Low"]],[5,6,2,1,["Low"]],[5,6,3,0,["Low"]],[5,6,3,1,["Low"]],[5,6,4,0,["Low"]],[5,6,4,1,["Low"]],[5,6,5,0,["Low"]],[5,6,5,1,["Low"]],[5,7,0,0,["Low"]],[5,7,0,1,["Low"]],[5,7,1,0,["Low"]],[5,7,1,1,["Low"]],[5,7,2,0,["Low"]],[5,7,2,1,["Low"]],[5,7,3,0,["Low"]],[5,7,3,1,["Low"]],[5,7,4,0,["Low"]],[5,7,4,1,["Low"]],[5,7,5,0,["Low"]],[5,7,5,1,["Low"]],[5,8,0,0,["P0"]],[5,8,0,1,["P0"]],[5,8,1,0,["P0"]],[5,8,1,1,["P0"]],[5,8,2,0,["P0"]],[5,8,2,1,["P0"]],[5,8,3,0,["P0"]],[5,8,3,1,["P0"]],[5,8,4,0,["P0"]],[5,8,4,1,["P0"]],[5,8,5,0,["P0"]],[5,8,5,1,["P0"]],[5,9,0,0,["Low"]],[5,9,0,1,["Low"]],[5,9,1,0,["Low"]],[5,9,1,1,["Low"]],[5,9,2,0,["Low"]],[5,9,2,1,["Low"]],[5,9,3,0,["Low"]],[5,9,3,1,["Low"]],[5,9,4,0,["Low"]],[5,9,4,1,["Low"]],[5,9,5,0,["Low"]],[5,9,5,1,["Low"]],[5,10,0,0,["Medium"]],[5,10,0,1,["Medium"]],[5,10,1,0,["Medium"]],[5,10,1,1,["Medium"]],[5,10,2,0,["Medium"]],[5,10,2,1,["Medium"]],[5,10,3,0,["Medium"]],[5,10,3,1,["Medium"]],[5,10,4,0,["Medium"]],[5,10,4,1,["Medium"]],[5,10,5,0,["Medium"]],[5,10,5,1,["Medium"]],[5,11,0,0,["Low"]],[5,11,0,1,["Low"]],[5,11,1,0,["Low"]],[5,11,1,1,["Low"]],[5,11,2,0,["Low"]],[5,11,2,1,["Low"]],[5,11,3,0,["Low"]],[5,11,3,1,["Low"]],[5,11,4,0,["Low"]],[5,11,4,1,["Low"]],[5,11,5,0,["Low"]],[5,11,5,1,["Low"]],[5,12,0,0,["Medium"]],[5,12,0,1,["Medium"]],[5,12,1,0,["Medium"]],[5,12,1,1,["Medium"]],[5,12,2,0,["Medium"]],[5,12,2,1,["Medium"]],[5,12,3,0,["Medium"]],[5,12,3,1,["Medium"]],[5,12,4,0,["Medium"]],[5,12,4,1,["Medium"]],[5,12,5,0,["Medium"]],[5,12,5,1,["Medium"]],[5,13,0,0,["Low"]],[5,13,0,1,["Low"]],[5,13,1,0,["Low"]],[5,13,1,1,["Low"]],[5,13,2,0,["Low"]],[5,13,2,1,["Low"]],[5,13,3,0,["Low"]],[5,13,3,1,["Low"]],[5,13,4,0,["Low"]],[5,13,4,1,["Low"]],[5,13,5,0,["Low"]],[5,13,5,1,["Low"]],[5,14,0,0,["Low"]],[5,14,0,1,["Low"]],[5,14,1,0,["Low"]],[5,14,1,1,["Low"]],[5,14,2,0,["Low"]],[5,14,2,1,["Low"]],[5,14,3,0,["Low"]],[5,14,3,1,["Low"]],[5,14,4,0,["Low"]],[5,14,4,1,["Low"]],[5,14,5,0,["Low"]],[5,14,5,1,["Low"]],[5,15,0,0,["LOW"]],[5,15,0,1,["LOW"]],[5,15,1,0,["LOW"]],[5,15,1,1,["LOW"]],[5,15,2,0,["LOW"]],[5,15,2,1,["LOW"]],[5,15,3,0,["LOW"]],[5,15,3,1,["LOW"]],[5,15,4,0,["LOW"]],[5,15,4,1,["LOW"]],[5,15,5,0,["LOW"]],[5,15,5,1,["LOW"]],[5,16,0,0,["P3"]],[5,16,0,1,["P3"]],[5,16,1,0,["P3"]],[5,16,1,1,["P3"]],[5,16,2,0,["P3"]],[5,16,2,1,["P3"]],[5,16,3,0,["P3"]],[5,16,3,1,["P3"]],[5,16,4,0,["P3"]],[5,16,4,1,["P3"]],[5,16,5,0,["P3"]],[5,16,5,1,["P3"]],[5,17,0,0,["High"]],[5,17,0,1,["High"]],[5,17,1,0,["High"]],[5,17,1,1,["High"]],[5,17,2,0,["High"]],[5,17,2,1,["High"]],[5,17,3,0,["High"]],[5,17,3,1,["High"]],[5,17,4,0,["High"]],[5,17,4,1,["High"]],[5,17,5,0,["High"]],[5,17,5,1,["High"]],[5,18,0,0,["Low"]],[5,18,0,1,["Low"]],[5,18,1,0,["Low"]],[5,18,1,1,["Low"]],[5,18,2,0,["Low"]],[5,18,2,1,["Low"]],[5,18,3,0,["Low"]],[5,18,3,1,["Low"]],[5,18,4,0,["Low"]],[5,18,4,1,["Low"]],[5,18,5,0,["Low"]],[5,18,5,1,["Low"]],[5,19,0,0,["CRITICAL"]],[5,19,0,1,["CRITICAL"]],[5,19,1,0,["CRITICAL"]],[5,19,1,1,["CRITICAL"]],[5,19,2,0,["CRITICAL"]],[5,19,2,1,["CRITICAL"]],[5,19,3,0,["CRITICAL"]],[5,19,3,1,["CRITICAL"]],[5,19,4,0,["CRITICAL"]],[5,19,4,1,["CRITICAL"]],[5,19,5,0,["CRITICAL"]],[5,19,5,1,["CRITICAL"]]]}} \ No newline at end of file diff --git a/test/review-fixable-class.test.ts b/test/review-fixable-class.test.ts new file mode 100644 index 000000000..010781330 --- /dev/null +++ b/test/review-fixable-class.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, test } from 'bun:test'; +import { spawnSync } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { collect, fixableRaises, parseFindings, verify } from '../lib/review-ledger'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const NATIVE = fs.readFileSync(path.join(ROOT, 'test/fixtures/review-ledger/gsta-141-native.md'), 'utf8').split('\n').filter(l => /^\d+\. /.test(l)).join('\n\n'); +const plan = (cls: string, reasons: string) => [`REVIEW_CLASS: ${cls} (x)`, `REVIEW_DEPTH: full runtime_lines=9 rules=r reasons=${reasons} codes=none`]; +const CODE = plan('code', 'runtime=9'); + +describe('native FIXABLE by review class (GST-987)', () => { + // Value: protects=outside the gate class with no risk reason a native finding's own Severity field decides, so gstack-evals #141's + // eval-driver findings (FIXABLE. Severity: Medium/Low. Blocks: no) collect as Medium and Low, while gate, risk and unreadable plans + // keep normalizer v2; fails_when=FIXABLE raises a fielded finding on a code-class plan, or a gate, risk, error or missing plan stops + // raising it; why_new=GEVA-2 reported #141 collecting both as CRITICAL; seam=fixableRaises, parseFindings and collect --manifest; + // no_claim=which class a diff belongs to (lib/review-depth.ts decides) + test('#141 native #4 and #6 collect at their own Severity on a code-class plan', () => { + expect(parseFindings('adversarial-native', NATIVE, fixableRaises(CODE)).map(f => f.label)).toEqual(['Medium', 'Low']); + expect(parseFindings('adversarial-native', NATIVE).map(f => f.label)).toEqual(['CRITICAL', 'CRITICAL']); + }); + test('gate class, any risk reason, a depth error or an unreadable plan keeps normalizer v2', () => { + for (const lines of [plan('gate', 'gate-class,runtime=9'), plan('code', 'risk:money,runtime=9'), plan('code', 'risk:data,runtime=9'), plan('code', 'error:DIFF,runtime=9'), plan('code', 'runtime=9,history:fixed-blocking'), plan('code', 'runtime=9,floor:full'), plan('code', ''), plan('unknown', 'runtime=9'), [CODE[0]!], [], undefined]) + expect(fixableRaises(lines)).toBe(true); + expect(fixableRaises(CODE)).toBe(false); + expect(fixableRaises(plan('docs', 'runtime=0'))).toBe(false); + }); + test('on a code-class plan FIXABLE without a Severity field still collects CRITICAL, and Blocks: yes still raises', () => { + expect(parseFindings('adversarial-native', '1. Race in a.ts:3. FIXABLE', false).map(f => f.label)).toEqual(['CRITICAL']); + expect(parseFindings('adversarial-native', 'FIXABLE: race in a.ts:3\nSeverity: Medium\nBlocks: no', false).map(f => f.label)).toEqual(['Medium']); + expect(parseFindings('adversarial-native', 'FIXABLE: race in a.ts:3\nSeverity: Medium\nBlocks: yes', false).map(f => f.label)).toEqual(['CRITICAL']); + expect(parseFindings('adversarial-native', '1. Race in a.ts:3. Severity: FIXABLE', false).map(f => f.label)).toEqual(['CRITICAL']); + expect(parseFindings('adversarial-native', '1. FIXABLE: race in store.ts:40. Severity: High', false).map(f => f.label)).toEqual(['CRITICAL']); + expect(parseFindings('adversarial-native', '1. FIXABLE: race in store.ts:40\nSeverity: High\nBlocks: no', false).map(f => f.label)).toEqual(['High']); + expect(parseFindings('adversarial-native', '## FIXABLE\n1. Leak in a.ts:3. Severity: Low. Blocks: no\n2. Race in b.ts:4\n3. Drift in c.ts:5. Severity: Low', false).map(f => f.label)).toEqual(['Low', 'CRITICAL', 'CRITICAL']); + expect(parseFindings('adversarial-native', '**FIXABLE**\n- Leak in a.ts:3\n Severity: Medium', false).map(f => f.label)).toEqual(['CRITICAL']); + expect(parseFindings('adversarial-native', 'FIXABLE: race in a.ts:3. Severity: Low. Blocks: no-op path only', false).map(f => f.label)).toEqual(['CRITICAL']); + }); + // Value: protects=a paragraph under a numbered finding heading joins that heading's finding, so the reader's later Severity and Blocks + // fields attach to it instead of splitting it into a CRITICAL heading plus a body item (gstack-evals #149); fails_when=any of the three + // shapes collects 4 findings or the heading alone keeps CRITICAL on a code-class plan; why_new=#149 split native findings in both + // classes; seam=parseFindings; no_claim=how unnumbered headings group + test.each([ + ['### 1. FIXABLE: race in a.ts:3\n\nThe body explains it.\n\nSeverity: Medium, Blocks: no\n\n### 2. FIXABLE: drift in b.ts:9\n\nMore body.\n\nSeverity: Low, Blocks: no'], + ['**1. Race in a.ts:3** FIXABLE\n\nBody.\n\nSeverity: Medium, Blocks: no\n\n**2. Drift in b.ts:9** FIXABLE\n\nBody.\n\nSeverity: Low, Blocks: no'], + ['### 1. Race in a.ts:3 — FIXABLE\n\nBody.\n\n**Severity:** Medium\n**Blocks:** no\n\n### 2. Drift in b.ts:9 — FIXABLE\n\nBody.\n\n**Severity:** Low\n**Blocks:** no'], + ])('a numbered finding heading keeps its body paragraph and fields (#149) %#', (output) => { + expect(parseFindings('adversarial-native', output, false).map(f => f.label)).toEqual(['Medium', 'Low']); + expect(parseFindings('adversarial-native', output, true).map(f => f.label)).toEqual(['CRITICAL', 'CRITICAL']); + }); + test('a labeled or located paragraph after a numbered heading stays its own finding (#149 joins only plain body text)', () => { + for (const raises of [true, false]) { + expect(parseFindings('outside-voice', '### 1. P2 race in a.ts:3\n\nP0: also data loss in b.ts:9\n\nSeverity: Medium', raises).map(f => f.label)).toEqual(['P2', 'P0']); + expect(parseFindings('outside-voice', '### 1. P2 race in a.ts:3\n\nBody.\n\nP1: second issue in b.ts:9', raises).map(f => f.label)).toEqual(['P2', 'P1']); + } + }); + test('collect reads the class from --manifest and defaults to the gate class without one', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gst987-')); + const out = path.join(dir, 'native.txt'); + fs.writeFileSync(out, NATIVE + '\n'); + const manifest = path.join(dir, 'pass-manifest.json'); + const ledger = path.join(dir, 'ledger.jsonl'); + fs.writeFileSync(manifest, JSON.stringify({ version: 1, ledger, plan_lines: CODE, readers: [] })); + const foreign = path.join(dir, 'foreign.json'); + fs.writeFileSync(foreign, JSON.stringify({ version: 1, ledger: path.join(dir, 'other.jsonl'), plan_lines: CODE, readers: [] })); + let n = 0; + const labels = (extra: string[]) => { + fs.rmSync(ledger, { force: true }); + const merged = path.join(dir, `${n++}.merged.jsonl`); + const r = spawnSync(path.join(ROOT, 'bin/gstack-review-dispatch'), ['collect', '--ledger', ledger, '--role', 'adversarial-native', '--output', out, '--merged', merged, ...extra], { cwd: ROOT, encoding: 'utf8', timeout: 60_000 }); + expect(r.stderr).toBe(''); + return fs.readFileSync(merged, 'utf8').trim().split('\n').map(l => JSON.parse(l).label); + }; + expect(labels(['--manifest', manifest])).toEqual(['Medium', 'Low']); + expect(fs.readFileSync(ledger, 'utf8')).toContain('"fixable_raises":false'); + expect(labels([])).toEqual(['CRITICAL', 'CRITICAL']); + expect(fs.readFileSync(ledger, 'utf8')).not.toContain('fixable_raises'); + expect(labels(['--manifest', foreign])).toEqual(['CRITICAL', 'CRITICAL']); + fs.rmSync(dir, { recursive: true, force: true }); + }); + test('verify re-derives a code-class collect with the class it was collected under', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gst987v-')); + const out = path.join(dir, 'native.txt'); + fs.writeFileSync(out, NATIVE + '\n'); + const c = collect(path.join(dir, 'l.jsonl'), 'adversarial-native', NATIVE + '\n', path.join(dir, 'm.jsonl'), out, undefined, undefined, false); + expect(c.lines.map(l => l.label)).toEqual(['Medium', 'Low']); + expect(JSON.stringify(verify(path.join(dir, 'l.jsonl')))).not.toContain('merged_line_differs'); + fs.rmSync(dir, { recursive: true, force: true }); + }); +}); diff --git a/test/review-ledger-invariant.test.ts b/test/review-ledger-invariant.test.ts index f2edd39d3..18b2f6744 100644 --- a/test/review-ledger-invariant.test.ts +++ b/test/review-ledger-invariant.test.ts @@ -11,6 +11,7 @@ const FIELD = new RegExp(`(?:(?:^|\\| )(?:[-*+>]\\s+|\\d+[.)]\\s+)?\\**\\s*Sever const LEAD = new RegExp(`(?:^|\\| )[^\\p{L}\\p{N}]*(?:\\d+[.)]\\s*[^\\p{L}\\p{N}]*)?(?:\\w[\\w./-]*:\\d+(?:[-–:,]\\s*\\d+)*\\S*\\s+[^\\p{L}\\p{N}]*)?(?:${LABELS})(?![a-z])`, 'u'); const HEADING = new RegExp(`^(?:#{1,6}\\s*|\\*\\*)(?:${LABELS})\\b`, 'm'); const LABEL = new RegExp(`${LABELS}`, 'gi'); +const LEAD_NO_FIXABLE = new RegExp(LEAD.source.replace('|FIXABLE|INVESTIGATE', ''), 'u'); describe('parseFindings is never weaker than main (GST-976 invariant)', () => { // Value: protects=no input the suite pins, no reader fixture and no Severity field shape collects below the label main (82e9d7ec) @@ -18,8 +19,10 @@ describe('parseFindings is never weaker than main (GST-976 invariant)', () => { // leading label, a label heading, Blocks: yes or the native voice's FIXABLE raises it; fails_when=a field chain (High/Critical) keeps // its weaker label (b3ddd6c) or a field whose clause runs on stops counting and a body P0 raises the finding (f3810d4); // why_new=three GST-976 review rounds each found a shape the row tables missed; seam=parseFindings; no_claim=that main's label was - // right (it is the floor), or coverage of stored private reader outputs (checked outside the repo) - test('every pinned input, reader fixture and generated field shape', () => { + // right (it is the floor), or coverage of stored private reader outputs (checked outside the repo). GST-987: the gate class (and every + // risk class) keeps this rule; outside it the native voice's FIXABLE beside Blocks: no no longer counts as a raise, so such a fielded + // finding collects at its field unless a lead, a label heading or Blocks: yes raises it + test.each([['gate', true], ['code', false]] as const)('every pinned input, reader fixture and generated field shape (%s class)', (_cls, raises) => { const { entries, shapes } = JSON.parse(fs.readFileSync(path.join(ROOT, 'test/fixtures/review-ledger/main-labels.json'), 'utf8')); const { bodies, fields: tails, joins, readers, generated } = shapes; for (const [b, f, j, r, main] of generated) entries.push({ reader: readers[r], output: bodies[b] + joins[j] + tails[f], main }); @@ -27,14 +30,14 @@ describe('parseFindings is never weaker than main (GST-976 invariant)', () => { let findings = 0; for (const e of entries as Array<{ reader: string; output?: string; fixture?: string; main: string[] }>) { const output = e.output ?? fs.readFileSync(path.join(ROOT, 'test/fixtures', e.fixture!), 'utf8'); - const got = parseFindings(e.reader, output); + const got = parseFindings(e.reader, output, raises); if (!e.main.length) continue; if (got.length !== e.main.length) { bad.push(`count ${got.length}/${e.main.length} ${JSON.stringify(output.slice(0, 80))}`); continue; } got.forEach((f, i) => { findings++; const fields = [...f.raw.matchAll(FIELD)].map(m => (m[1] ?? m[2])!); const single = fields.length === 1 && fields[0]!.match(LABEL)!.length === 1 ? fields[0]!.match(LABEL)![0] : undefined; - const raised = /Blocks:?\**:?\s*\**\s*yes/i.test(f.raw) || (e.reader.startsWith('adversarial-native') && /FIXABLE/.test(f.raw)) || LEAD.test(f.raw) || HEADING.test(output); + const raised = /Blocks:?\**:?\s*\**\s*yes/i.test(f.raw) || (e.reader.startsWith('adversarial-native') && /FIXABLE/.test(f.raw) && (raises || !/Blocks:?\**:?\s*\**\s*no\b/i.test(f.raw))) || (raises ? LEAD : LEAD_NO_FIXABLE).test(f.raw) || HEADING.test(output); const ok = single === undefined ? (rank(f.label) ?? 0) >= (rank(e.main[i]) ?? 0) : (rank(f.label) ?? 0) >= (rank(single) ?? 0) && (raised || (rank(f.label) ?? 0) === (rank(single) ?? 0)); if (!ok) bad.push(`${e.reader} main=${e.main[i]} got=${f.label} field=${single ?? '-'} ${JSON.stringify(f.raw.slice(0, 100))}`); From 82db89ab3ea3c64185538b8af33da2ffd1a18b57 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 13:53:30 -0800 Subject: [PATCH 05/10] v1.93.22.0 fix(ship): targeted record binds to the commit it read; write/carry run the targeted verdict; fix-check ledgers count (GST-986) (#3887) * test: GST-986 failing-first cases for the targeted record, receipt write/carry and fix-check counters Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): GST-986 targeted record binds to the read commit; write/carry run the targeted verdict; fix-check ledgers count Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): GST-986 write/carry report fail-closed targeted classes; docs, changelog, adversarial repin (brief bodies unchanged) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: GST-986 review round 1 failing-first: a docs/TODOS commit after the targeted read binds; verify clears a stale --targeted-out Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): GST-986 review round 1: a record binds across class-free commits after the read; verify removes a stale targeted record Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: GST-986 fix-check failing-first: test skip after the read, re-raised blocker, verify early-return cleanup, carry wording Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): GST-986 scoped fix-check batch: record binds only when nothing after the read carries a class (gate scan run), a re-raised blocker is not fixed, verify always clears the stale record, carry names #3859 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: GST-986 carry case runs receiptDepthAtHead under the fixture's private HOME (pregate real-HOME tripwire) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: GST-986 classify .gstack/tmp/targeted.json as a /ship producer (gstack-clean, from the main merge) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: GST-986 bind a targeted record only to the product patch the reader read The targeted gate reader at 0715327 found that the classifier-only binding rule kept a stale record bound when a post-read edit removed an enforcing line with no money token. Restore main's equal-product-patch check and keep the gate-scan refusal on top of it. Docs-only binding moves to GST-1031. 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> --- CHANGELOG.md | 9 + VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- bin/gstack-review-dispatch | 20 +- bin/gstack-review-scope | 4 +- bin/gstack-ship-receipt | 20 +- docs/review-policy.md | 19 +- docs/troubleshooting.md | 20 +- lib/review-pass.ts | 4 +- lib/review-scope.ts | 37 ++- lib/review-targeted.ts | 6 + lib/ship-receipt-depth.ts | 48 ++- package.json | 2 +- ship/DRIVER.md | 10 +- ship/DRIVER.md.tmpl | 10 +- ship/sections/adversarial.md | 2 +- ship/sections/adversarial.md.tmpl | 2 +- ship/sections/handoff.md | 1 + ship/sections/handoff.md.tmpl | 1 + ship/sections/pr-body.md | 2 +- ship/sections/pr-body.md.tmpl | 2 +- test/gstack-clean-producers.test.ts | 1 + test/review-packets-removed.test.ts | 32 +- test/review-scope-ledger-count.test.ts | 52 +++ test/review-targeted-record.test.ts | 295 ++++++++++++++++++ test/review-targeted.test.ts | 21 +- test/ship-receipt-depth.test.ts | 8 +- .../ship/gst-986-targeted-recorder.json | 4 + 28 files changed, 571 insertions(+), 65 deletions(-) create mode 100644 test/review-targeted-record.test.ts create mode 100644 test/size-ledger/ship/gst-986-targeted-recorder.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d9bcd7f28..7618fa598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.93.22.0] - 2026-11-02 + +### Fixed + +- **The targeted reader's record is honest from verify to the receipt.** `gstack-review-dispatch verify` no longer marks a completed targeted reader `status=failed findings=0` because a Step 11 fix moved HEAD after it read (gstack-evals#140, C5-36 and C6-36). The record now says `done` with its real finding count and names the commit it read. `gstack-ship-receipt write --review-targeted` binds it only while that commit's product patch is the receipt's and nothing since it carries a money, gate, auth or secret change (a test edit that skips a case counts as gate), so a record can no longer vouch for code it never read. +- **`write` and `carry` refuse a receipt the queue refuses (#3854, #3859).** When the head touches money, gate, auth or secret code and no done targeted pass covers it, `write` refuses with `RECEIPT_INVALID` and `carry` with `RECEIPT_CARRY_REFUSED`, instead of publishing a receipt `ready` turns away. Legacy receipts still carry. +- **/ship passes the targeted record to the receipt (#3853).** Step 11's verify writes `.gstack/tmp/targeted.json`, and the Step 19, handoff and driver receipt writes pass it with `--review-targeted`. +- **Fix-check rounds count without hand edits (#3862).** `gstack-review-scope summary --run` counts fix-checks from the dispatch ledgers when no `review-fix-check` row was logged, and counts the blockers those fix-checks followed as fixed. + ## [1.93.21.0] - 2026-11-02 **/ship's finding collector lets the native reviewer's own Severity field decide outside gate and risk reviews.** diff --git a/VERSION b/VERSION index 10f963b21..3e56411d5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.21.0 +1.93.22.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index ae13f8251..dbef6f88c 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.21.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.22.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/bin/gstack-review-dispatch b/bin/gstack-review-dispatch index 3bdc0d919..6ca52ac1a 100755 --- a/bin/gstack-review-dispatch +++ b/bin/gstack-review-dispatch @@ -24,7 +24,7 @@ import { cleanMarkers, collect, fixableRaises, mayBeUnavailable, parseFindings, import { buildManifest, chunkCount, chunkOf, manifestPath, markReader, readManifest, renderStage, writeManifest, STAGES, type Extra, type Manifest, type PassReader, type ReaderState, type Stage } from '../lib/review-pass'; import type { Dispatch } from '../lib/review-depth'; import { runScriptSync } from '../lib/script-invocation'; -import { TARGETED_RULES, parseClassList, targetedBriefSha, targetedPatchId } from '../lib/review-targeted'; +import { TARGETED_RULES, parseClassList, targetedBriefSha, targetedRead } from '../lib/review-targeted'; import { resolveBaseBranch } from '../lib/ship-policy'; const HELP = `gstack-review-dispatch — verbatim reviewer briefs and loss-free collection for /ship @@ -130,7 +130,7 @@ function render(): number { fs.writeFileSync(file, text, { mode: 0o600 }); const ledger = flag('--ledger') ?? path.join(out, 'review-ledger.jsonl'); const sha = promptHash(text); - const targeted = brief === undefined ? {} : { classes: parseClassList(flag('--classes')!), rules: TARGETED_RULES, brief_sha: targetedBriefSha(checklist), diff_base: diffBase ?? null, patch_id: diffBase ? targetedPatchId(process.cwd(), diffBase) : 'none' }; + const targeted = brief === undefined ? {} : { classes: parseClassList(flag('--classes')!), rules: TARGETED_RULES, brief_sha: targetedBriefSha(checklist), diff_base: diffBase ?? null, ...(diffBase ? targetedRead(process.cwd(), diffBase) : { patch_id: 'none', head: null }) }; appendEvent(ledger, { event: 'rendered', reader: readerKey(role, name), role, name: name ?? null, file, sha256: sha, bytes: Buffer.byteLength(text), workdir, tree, ...targeted }); process.stdout.write(`BRIEF: reader=${readerKey(role, name)} sha256=${sha} bytes=${Buffer.byteLength(text)} file=${file}\n`); return EXIT.ok; @@ -279,19 +279,22 @@ function program(): number { /** * The receipt's review_targeted record from the newest targeted dispatch: done only when its brief was sent verbatim, its output - * collected, and the committed diff it was rendered on is still the one at verify (targetedPatchId; uncommitted never proves it). + * collected, and it was rendered on a committed diff (uncommitted never proves what was read). `head` names the commit it read: + * a later commit does not undo the read, and gstack-ship-receipt write binds the record only while that commit's product patch is + * the receipt's (GST-986: Step 11 fixes after the read printed status=failed findings=0 for a completed reader). */ -function targetedRecord(ledger: string, rows: VerifyRow[]): { classes: string[]; rules: string; brief_sha: string; status: 'done' | 'failed'; findings: number; raised_by: string } | null { +function targetedRecord(ledger: string, rows: VerifyRow[]): { classes: string[]; rules: string; brief_sha: string; status: 'done' | 'failed'; findings: number; raised_by: string; head: string | null } | null { const events = readLedger(ledger); const render = events.filter(e => e.event === 'rendered' && e.reader === 'targeted').at(-1); if (!render) return null; const label = rows.filter(r => r.kind === 'BRIEF' && r.reader.split('@')[0] === 'targeted').at(-1)?.reader; const brief = rows.filter(r => r.kind === 'BRIEF' && r.reader === label).at(-1); const output = rows.filter(r => r.kind === 'OUTPUT' && r.reader === label).at(-1); - const read = typeof render.patch_id === 'string' && !['uncommitted', 'none'].includes(render.patch_id) && typeof render.diff_base === 'string' && targetedPatchId(process.cwd(), render.diff_base) === render.patch_id; - const done = brief?.status === 'MATCH' && output?.status === 'MATCH' && read; + const head = typeof render.head === 'string' && /^[0-9a-f]{40,64}$/.test(render.head) ? render.head : null; + const committed = typeof render.patch_id === 'string' && !['uncommitted', 'none'].includes(render.patch_id) && head !== null; + const done = brief?.status === 'MATCH' && output?.status === 'MATCH' && committed; const ids = events.filter(e => e.event === 'output' && e.reader === 'targeted').at(-1)?.ids; - return { classes: (render.classes as string[]) ?? [], rules: String(render.rules), brief_sha: String(render.brief_sha), status: done ? 'done' : 'failed', findings: done && Array.isArray(ids) ? ids.length : 0, raised_by: 'targeted' }; + return { classes: (render.classes as string[]) ?? [], rules: String(render.rules), brief_sha: String(render.brief_sha), status: done ? 'done' : 'failed', findings: Array.isArray(ids) ? ids.length : 0, raised_by: 'targeted', head }; } function main(): number { @@ -356,12 +359,13 @@ function main(): number { return r.ok ? EXIT.ok : EXIT.fail; } if (cmd === 'verify') { + if (flag('--targeted-out')) fs.rmSync(flag('--targeted-out')!, { force: true }); const v = verify(ledger); for (const r of v.rows) process.stdout.write(`${r.kind} ${r.reader} ${r.status} ${r.detail}\n`); if (!v.briefs) { process.stdout.write('VERIFY: no brief records (this run\'s ledger kept no prompts; dispatched-vs-rendered cannot be checked)\n'); return EXIT.refused; } const record = targetedRecord(ledger, v.rows); if (record) { - process.stdout.write(`TARGETED_RECORD: classes=${record.classes.join(',')} status=${record.status} findings=${record.findings} brief=${record.brief_sha.slice(0, 12)} rules=${record.rules}\n`); + process.stdout.write(`TARGETED_RECORD: classes=${record.classes.join(',')} status=${record.status} findings=${record.findings} brief=${record.brief_sha.slice(0, 12)} rules=${record.rules} read=${record.head?.slice(0, 12) ?? 'none'}\n`); if (flag('--targeted-out')) fs.writeFileSync(flag('--targeted-out')!, JSON.stringify(record) + '\n'); } process.stdout.write(`VERIFY: ${v.clean ? 'clean' : 'dirty'} briefs=${v.briefs} outputs=${v.rows.filter(r => r.kind === 'OUTPUT').length} voided=${v.voided}${v.unavailable ? ` unavailable=${v.unavailable}` : ''}\n`); diff --git a/bin/gstack-review-scope b/bin/gstack-review-scope index 402090911..bd8280e29 100755 --- a/bin/gstack-review-scope +++ b/bin/gstack-review-scope @@ -27,7 +27,7 @@ import { runScriptSync } from '../lib/script-invocation'; import { isUnavailable, loadPolicyFromBase, policyUnavailableLine, resolveBaseBranch } from '../lib/ship-policy'; import { EXIT as POST_EXIT, runPost } from '../lib/gstack-post'; import { - parseHunks, renderDeferredIssue, renderDeferredSection, reviewLoopLine, scopeFindings, scopeLine, summarizeReviewLoop, + ledgerPass, parseHunks, renderDeferredIssue, renderDeferredSection, reviewLoopLine, scopeFindings, scopeLine, summarizeReviewLoop, ALREADY_FILED_REASON, DEFERRED_MARKER, findingIdentity, findingWhere, REVIEW_SCOPE_POLICIES, type DeferredRecord, type ReviewScopePolicy, type ScopeFinding, type ScopedFinding, } from '../lib/review-scope'; import { ledgerBlockers, type LedgerBlocker } from '../lib/review-ledger'; @@ -269,7 +269,7 @@ function cmdSummary(rest: string[], json: boolean): number { if (!noLedger && !files.length) return refuse('summary', 'REVIEW_SCOPE_LEDGER', 'name the review\'s dispatch ledgers (--run or --ledger ), or --no-ledger when no reader ran through gstack-review-dispatch'); let ledgers: LedgerBlocker[][]; try { ledgers = files.map(ledgerBlockers); } catch (e: any) { return refuse('summary', 'REVIEW_SCOPE_LEDGER', e.message); } - const summary = summarizeReviewLoop(rows, since, ledgers); + const summary = summarizeReviewLoop(rows, since, ledgers, files.map(ledgerPass)); const line = reviewLoopLine(summary); // A /ship invocation always runs Step 9 once; zero matching rows means the wrong --since or a review log written elsewhere, never a loop to record. if (summary.passes === 0) return refuse('summary', 'REVIEW_SCOPE_INPUT', `no review-log row of this branch is a via:ship review at or after ${since}; check REVIEW_SINCE (${line})`); diff --git a/bin/gstack-ship-receipt b/bin/gstack-ship-receipt index 773fb19c1..79c64da9e 100755 --- a/bin/gstack-ship-receipt +++ b/bin/gstack-ship-receipt @@ -49,7 +49,7 @@ import { } from '../lib/ship-receipt'; import { checkDocsCaps, docsCapLine, docsExceptionLine, parseReceiptDocs } from '../lib/ship-receipt-docs'; import { comparePath, contextPacket, PACKET_SECTION_CAP, readScopedReview, touchedPaths } from '../lib/ship-patch'; -import { deeper, depthBelow, exceptionProblems, parseReviewLoopException, parseReviewTargeted, receiptDepthAtHead, reviewLoopExceptionLine } from '../lib/ship-receipt-depth'; +import { deeper, depthBelow, exceptionProblems, parseReviewLoopException, parseReviewTargeted, receiptDepthAtHead, receiptTargetedAtHead, reviewLoopExceptionLine, targetedClassesSince } from '../lib/ship-receipt-depth'; import { checkReceiptProse, proseLine } from '../lib/ship-receipt-prose'; import { resolveStateRoot } from '../lib/state-root'; import { readPause, receiptBaseRed } from '../lib/base-red'; @@ -96,9 +96,11 @@ write flags: --pr --head --base --version --gated-tree --predecessor --gate ({classes, rules, brief_sha, status, findings, raised_by}: gstack-review-dispatch verify - --targeted-out); recorded as review_targeted bound to the receipt's product_patch_id. Without it the receipt has no - review_targeted, which the queue reports as legacy. + --review-targeted ({classes, rules, brief_sha, status, findings, raised_by, head}: gstack-review-dispatch verify + --targeted-out); recorded as review_targeted bound to the receipt's product_patch_id, and refused (RECEIPT_INVALID) when the + commit the reader read (head) is not an ancestor or a change since it carries a targeted class (gate scan included). write and carry + refuse a receipt whose head needs a targeted class (money, gate, auth, secret) that no done pass covers, as the queue's + targeted verdict does (#3859) (RECEIPT_INVALID, RECEIPT_CARRY_REFUSED). --approval-run (repeatable; resolves plan-sha citations) (W2: a class declared in origin/'s .gstack/approval-classes.json with hits needs a citation that resolves and binds to its effects: APPROVAL_CITATION_MISSING) --out --body . Defaults: head=HEAD, base=origin/HEAD's branch, version=the @@ -222,7 +224,7 @@ function cmdWrite(rest: string[], json: boolean): number { const product = reviewedProduct(repoRoot, base, head, pregateFile); let targeted: ReturnType | undefined; if (flag(rest, '--review-targeted') !== undefined) { - try { targeted = parseReviewTargeted(jsonArg(flag(rest, '--review-targeted')), product.product_patch_id ?? 'none'); } catch (e: any) { targeted = { ok: false, message: `--review-targeted: ${e.message}` }; } + try { targeted = parseReviewTargeted(jsonArg(flag(rest, '--review-targeted')), product.product_patch_id ?? 'none', read => reviewedProduct(repoRoot, base, read, pregateFile).product_patch_id, read => targetedClassesSince(repoRoot, read, headSha ?? head)); } catch (e: any) { targeted = { ok: false, message: `--review-targeted: ${e.message}` }; } } if (targeted && !targeted.ok) { process.stderr.write(resultLine('gstack-ship-receipt write', 'RECEIPT_INVALID', `${targeted.message}; nothing written`) + '\n'); return EXIT.fail; } const approval = approvalForReceipt({ repoRoot, base, head, citationText: [bodyText, ...flags(rest, '--approval')].join('\n'), runDirs: flags(rest, '--approval-run') }); @@ -267,6 +269,12 @@ function cmdWrite(rest: string[], json: boolean): number { process.stderr.write('gstack-ship-receipt write: nothing written\n'); return EXIT.fail; } + const targetedVerdict = receiptTargetedAtHead(repoRoot, `origin/${base.replace(/^origin\//, '')}`, receipt, receipt.head); + if (targetedVerdict.line.includes('(unchecked: ')) process.stderr.write(targetedVerdict.line + '\n'); + if (!targetedVerdict.ok) { + process.stderr.write(`${targetedVerdict.line}\n${resultLine('gstack-ship-receipt write', 'RECEIPT_INVALID', `${targetedVerdict.message}; run the targeted reader on HEAD and pass its record (--review-targeted @); nothing written`)}\n`); + return EXIT.fail; + } const body = bodyFile; const missing = body && receipt.issues ? missingIssueLines(fs.existsSync(body) ? fs.readFileSync(body, 'utf8') : '', receipt.issues) : []; if (missing.length) { @@ -734,6 +742,8 @@ function cmdCarry(rest: string[], json: boolean): number { const carried = carryReceipt(rebound, head, plan.carry, { tree: headTree, fromTree, productPatchId: headPatch }); const errors = validateReceipt(carried); if (errors.length) return refused('RECEIPT_CARRY_REFUSED', `the carried receipt does not validate: ${errors[0]!.message} at ${errors[0]!.path}`); + const targetedVerdict = receiptTargetedAtHead(repoRoot, baseRef, carried, head); + if (!targetedVerdict.ok) return refused('RECEIPT_CARRY_REFUSED', `${targetedVerdict.line}: ${targetedVerdict.message}, so the queue refuses it too (#3859); run /ship on ${head.slice(0, 12)}`); const newBody = placeReceiptFirst(body, carried); const line = receiptSummaryLine(carried); if (scopedDryRun) return refused('RECEIPT_CARRY_SCOPED_REVIEW', scopedDryRun); diff --git a/docs/review-policy.md b/docs/review-policy.md index b0b8f2358..3362090f0 100644 --- a/docs/review-policy.md +++ b/docs/review-policy.md @@ -400,8 +400,19 @@ The brief states the blocking floor in one sentence, and its money block cites the checklist's Forced-CRITICAL rule on uncapped spend, so a money finding can block without new floor words. The reader is read-only, and `NO FINDINGS` is a result. `gstack-review-dispatch render --role targeted --classes ` -renders its brief; `verify --targeted-out ` writes the record that -`gstack-ship-receipt write --review-targeted @` puts in the receipt. +renders its brief; `verify --targeted-out .gstack/tmp/targeted.json` writes the record that +`gstack-ship-receipt write --review-targeted @.gstack/tmp/targeted.json` puts in the receipt +(Step 19, the handoff and the driver pass it when the file exists). The record is `done` when +the reader got its brief verbatim and its reply was collected on a committed diff, whatever +happened to HEAD after, and it names the commit it read (`head`). `write` binds it only while +that commit is an ancestor of HEAD, its product patch is the receipt's, and nothing changed +after it carries a targeted class, with the gate scan run over that range: any product change +after the read refuses with `RECEIPT_INVALID`, docs and TODOS.md commits included, and so +does a test edit the gate scan tags (a skipped case). Every verify +removes `--targeted-out` before it writes a record, so an earlier run's record never reaches +Step 19. `write` and `carry` (which rescans the gate at the new head) also +refuse a receipt whose head needs a class no `done` record covers (`RECEIPT_INVALID`, +`RECEIPT_CARRY_REFUSED`). The queue refuses the same receipts at `ready` (#3859). `gstack-ship-queue ready`, `train build` and `land` print `REVIEW_TARGETED: required= receipt= verdict=ok|missing|failed` and refuse @@ -430,11 +441,11 @@ puts in the receipt: | `review_loop.noted` | below-floor findings listed in the PR body | | `review_loop.fixed` | findings fixed in this PR | | `review_loop.scope` | the `review_scope` the loop ran under | -| `review_loop.fix_checks` | fix-checks this ship ran (never passes) | +| `review_loop.fix_checks` | fix-checks this ship ran (never passes): the larger of the `review-fix-check` rows and the ledgers holding a fix-check reader's review. When fix-checks ran without their rows, a blocker no row disposes counts as fixed once a later fix-check ledger followed it (decision 3.5 runs one only after the fixes) | | `review_loop.rounds` | passes plus fix-checks (R51 caps it at 2; the queue's `REVIEW_DEPTH:` line prints it) | | `review_loop.dispatches` | reviewer dispatches by role: `specialist`, `red-team`, `adversarial-native`, `outside-voice`, `fix-check`, `targeted` | | `review_loop.est_input_tokens` | prompt plus diff bytes the dispatches read, divided by 4: a relative estimate for comparing runs, not a bill | -| `review_targeted` | the [targeted pass](#targeted-pass-on-money-gate-auth-and-secret-code): the `classes` it covered, `rules`, `brief_sha`, the receipt's own `product_patch_id`, `status` (`done` only with its output collected and the committed diff it was rendered on still the one at verify, else `failed`; absent when write got no record, which the queue reports as `legacy`), `findings` and `raised_by`; a carry keeps it while the product patch is unchanged | +| `review_targeted` | the [targeted pass](#targeted-pass-on-money-gate-auth-and-secret-code): the `classes` it covered, `rules`, `brief_sha`, the receipt's own `product_patch_id`, `status` (`done` only with its brief sent verbatim and its output collected on a committed diff, else `failed`; `write` refuses a record when a change after its read commit carries a targeted class or the read commit is not an ancestor, and refuses to write without a record when the head needs a class), `findings` and `raised_by`; a carry keeps it while the product patch is unchanged | Each review row may carry `dispatches: [{role, effort, prompt_bytes, diff_bytes, status}]`, and each finding `raised_by` (the reviewer that raised it, diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 5dfa5ed39..d78dfb536 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -3317,10 +3317,19 @@ for an older PR, write it once by hand with the same command. **Meaning.** The block exists but does not validate against the `ship-receipt` schema (`gstack-artifact schema ship-receipt`). `gstack-ship-receipt write` also refuses with this code, and writes nothing, when `--head` is not the -checked-out HEAD: the receipt binds the tree the review read. +checked-out HEAD: the receipt binds the tree the review read. It refuses the +same way when the head touches money, gate, auth or secret code and no done +targeted record covers those classes (`REVIEW_TARGETED: … verdict=missing|failed`), +or when `--review-targeted` names a read commit whose product patch is not +the receipt's and a change after it carries a targeted class (money, gate, +auth or secret code changed after the targeted reader read it, a test edit the +gate scan tags included), or that is not an ancestor of HEAD. **Fix.** Regenerate it with `gstack-ship-receipt write`; never hand-edit the -block. For the `--head` refusal, check out that commit and write there. +block. For the `--head` refusal, check out that commit and write there. For a +targeted refusal, run the targeted reader on HEAD, `gstack-review-dispatch verify +--targeted-out .gstack/tmp/targeted.json`, and write again with +`--review-targeted @.gstack/tmp/targeted.json`. ### `EXECUTION_UNKNOWN` @@ -5818,7 +5827,10 @@ working tree's status. It also refuses when an owner exception the receipt recor 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 -`review_depth` ([ship-policy.md](ship-policy.md#ship-receipt)). The receipt in +`review_depth` ([ship-policy.md](ship-policy.md#ship-receipt)). It refuses, too, when +HEAD needs a targeted class (money, gate, auth, secret) that the receipt's +`review_targeted` does not cover with a done pass, since the queue would refuse +that receipt. The receipt in the body is unchanged and still names the old head, so `gstack-ship-queue ready` refuses `QUEUE_RECEIPT_STALE`. @@ -5826,7 +5838,7 @@ ready` refuses `QUEUE_RECEIPT_STALE`. `gstack-ship-receipt carry --pr ` 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 retargeted stacked -PR whose older receipt lost its parent branch, or a depth or exception +PR whose older receipt lost its parent branch, or a depth, targeted or exception refusal, run /ship on the new head. When `--head` is not the checked-out HEAD, check it out and carry there. diff --git a/lib/review-pass.ts b/lib/review-pass.ts index 365069720..69689091f 100644 --- a/lib/review-pass.ts +++ b/lib/review-pass.ts @@ -16,7 +16,7 @@ import * as path from 'node:path'; import { BriefError, briefFile, checkoutRoot, checkoutTree, promptHash, renderBrief, withWorkspace, type BriefRole, type SectionTexts, type Slots } from './review-brief'; import { appendEvent, readerKey } from './review-ledger'; import { diffCommand, type Dispatch } from './review-depth'; -import { TARGETED_RULES, parseClassList, targetedBriefSha, targetedPatchId } from './review-targeted'; +import { TARGETED_RULES, parseClassList, targetedBriefSha, targetedRead } from './review-targeted'; export const STAGES = ['first', 'specialists', 'red-team', 'voices'] as const; export type Stage = typeof STAGES[number]; @@ -179,7 +179,7 @@ export function renderStage(m: Manifest, stage: Stage, t: SectionTexts, diffFor: let brief: string; try { brief = fs.readFileSync(checklist, 'utf8'); } catch { throw new BriefError(`cannot read the targeted brief ${checklist}`); } Object.assign(slots, { checklist, brief, classes: (r.classes ?? []).join(','), diff_base: r.diff_base ?? m.diff_base }); - targeted = { classes: parseClassList(slots.classes!), rules: TARGETED_RULES, brief_sha: targetedBriefSha(checklist), diff_base: slots.diff_base, patch_id: targetedPatchId(process.cwd(), slots.diff_base!) }; + targeted = { classes: parseClassList(slots.classes!), rules: TARGETED_RULES, brief_sha: targetedBriefSha(checklist), diff_base: slots.diff_base, ...targetedRead(process.cwd(), slots.diff_base!) }; } const text = withWorkspace(renderBrief(role, slots, t), workdir); const file = path.join(briefs, briefFile(role, r.name)); diff --git a/lib/review-scope.ts b/lib/review-scope.ts index 6ab96923f..620c3c11a 100644 --- a/lib/review-scope.ts +++ b/lib/review-scope.ts @@ -14,7 +14,7 @@ * Pure functions here; bin/gstack-review-scope runs git and gh. */ import { SPECIALISTS } from './review-depth'; -import { rank, type LedgerBlocker } from './review-ledger'; +import { rank, readLedger, type LedgerBlocker } from './review-ledger'; import { parseSpecialistOutput } from './state-matrix'; export type Locus = 'diff' | 'fix-range' | 'outside'; @@ -370,6 +370,18 @@ function rowLocus(f: Record): string | null { return m ? `${m[1]!.replace(/^\.\//, '')}:${m[2]}` : null; } +/** + * When a dispatch ledger started (its first event) and whether it holds a fix-check reader's review: a fix-check run through + * gstack-review-dispatch (the pass program's `fix-check` step) leaves this ledger even when no review-fix-check row was logged. + */ +export interface LedgerPass { started: number; fixCheck: boolean } +export function ledgerPass(ledger: string): LedgerPass { + const events = readLedger(ledger); + const started = Date.parse(String(events[0]?.at ?? '')); + const fixCheck = events.some(e => e.event === 'output' && e.reader === 'fix-check' && e.tree_ok !== false && ((Array.isArray(e.ids) && e.ids.length > 0) || e.review === true)); + return { started: Number.isFinite(started) ? started : NaN, fixCheck }; +} + /** * The loop summary. blocking_found counts every blocker found (GST-951). Each CRITICAL the dispatch ledgers hold * (`ledgerBlockers`, one list per ledger) is paired with at most one row finding per ledger at the same path:line, raised @@ -380,8 +392,13 @@ function rowLocus(f: Record): string | null { * A ledger CRITICAL with no locus, or a second one at a paired line, counts on its own. A different defect the same reviewer * raises at the exact line of a finding a row classified below the floor pairs with that finding: path:line is the only * identity rows and ledgers share until rows carry the ledger id. + * Fix-checks (GST-986, #3862; `ledgerPasses`, one per ledger): fix_checks is the larger of the review-fix-check rows and the + * fix-check ledgers, so one logged both ways counts once. When fix-checks ran without their review-fix-check rows (more fix-check + * ledgers than rows), a blocker no row disposes counts as fixed if a fix-check ledger started after the last ledger that raised it: + * decision 3.5 runs a fix-check only after the loop applied that pass's fixes, and a blocker the loop does not fix is deferred + * or noted by a row. A blocker the last pass found stays unrecorded, and with every fix-check logged the rows decide. */ -export function summarizeReviewLoop(rows: readonly Record[], since: string, ledgers: readonly (readonly LedgerBlocker[])[] = []): ReviewLoopSummary { +export function summarizeReviewLoop(rows: readonly Record[], since: string, ledgers: readonly (readonly LedgerBlocker[])[] = [], ledgerPasses: readonly LedgerPass[] = []): ReviewLoopSummary { const sinceMs = Date.parse(since); const inRun = (r: Record) => r && r.via === 'ship' && Number.isFinite(rowTime(r)) && (!Number.isFinite(sinceMs) || rowTime(r) >= sinceMs); const loopRows = rows.filter(r => inRun(r) && (r.skill === 'review' || r.skill === LATE_DEFER_SKILL || r.skill === FIX_CHECK_SKILL)); @@ -414,20 +431,28 @@ export function summarizeReviewLoop(rows: readonly Record[], since: } const paired = new Set(); let ledgerOnly = 0; + let ledgerOnlyFixed = 0; const pairedIds = new Set(); + const followedIds = new Set(); + const lastPaired = new Map(); + const fixCheckStarts = ledgerPasses.filter(p => p.fixCheck && Number.isFinite(p.started)).map(p => p.started); + const rowFixChecks = loopRows.filter(r => r.skill === FIX_CHECK_SKILL).length; + const followed = (n: number) => fixCheckStarts.length > rowFixChecks && fixCheckStarts.some(t => t > (ledgerPasses[n]?.started ?? Infinity)); for (const [n, list] of ledgers.entries()) { for (const b of list) { const by = b.id.startsWith('fix-check') ? null : raiserOf(b.id); const id = b.locus ? [...seen].find(([k, s]) => s.candidate && s.locus === b.locus && (!s.raiser || !by || s.raiser === by) && !paired.has(`${n}\0${k}`))?.[0] : undefined; - if (id) { paired.add(`${n}\0${id}`); pairedIds.add(id); } else ledgerOnly++; + if (id) { paired.add(`${n}\0${id}`); pairedIds.add(id); if (!((ledgerPasses[lastPaired.get(id) ?? -1]?.started ?? -Infinity) > (ledgerPasses[n]?.started ?? -Infinity))) lastPaired.set(id, n); } else { ledgerOnly++; if (followed(n)) ledgerOnlyFixed++; } } } + for (const [id, n] of lastPaired) if (followed(n)) followedIds.add(id); const blockers = [...seen].filter(([k, s]) => s.blocks || (pairedIds.has(k) && !s.classified)); - const unrecorded = ledgerOnly + blockers.filter(([, s]) => !s.disposed).length; - const out: ReviewLoopSummary = { passes: passes.length, blocking_found: blockers.length + ledgerOnly, deferred: deferred.size, noted: noted.size, fixed: fixed.size, scope }; + for (const [k, s] of blockers) if (!s.disposed && followedIds.has(k)) { s.disposed = true; fixed.add(k); } + const unrecorded = ledgerOnly - ledgerOnlyFixed + blockers.filter(([, s]) => !s.disposed).length; + const out: ReviewLoopSummary = { passes: passes.length, blocking_found: blockers.length + ledgerOnly, deferred: deferred.size, noted: noted.size, fixed: fixed.size + ledgerOnlyFixed, scope }; if (unrecorded) out.unrecorded = unrecorded; if (ledgers.length) out.ledgers_read = ledgers.length; - const fixChecks = loopRows.filter(r => r.skill === FIX_CHECK_SKILL).length; + const fixChecks = Math.max(rowFixChecks, fixCheckStarts.length); if (fixChecks) out.fix_checks = fixChecks; out.rounds = passes.length + fixChecks; const byRole: Record = {}; diff --git a/lib/review-targeted.ts b/lib/review-targeted.ts index 7fe8c2fe7..3af9e4a55 100644 --- a/lib/review-targeted.ts +++ b/lib/review-targeted.ts @@ -82,6 +82,12 @@ export function targetedPatchId(root: string, mergeBase: string): string { return git(['patch-id', '--verbatim'], diff.stdout).stdout?.trim().split(/\s+/)[0] || 'none'; } +/** What a targeted render records: the patch the reader reads and the commit it sits on, which receipt write binds to its product patch. */ +export function targetedRead(root: string, mergeBase: string): { patch_id: string; head: string | null } { + const head = spawnSync('git', ['rev-parse', '--verify', '--quiet', 'HEAD^{commit}'], { cwd: root, encoding: 'utf8', timeout: 30_000 }).stdout?.trim(); + return { patch_id: targetedPatchId(root, mergeBase), head: head || null }; +} + /** A class list as printed and recorded: comma-joined, or `none`. */ export const classList = (classes: readonly string[]) => (classes.length ? classes.join(',') : 'none'); diff --git a/lib/ship-receipt-depth.ts b/lib/ship-receipt-depth.ts index 66adef3d7..134698330 100644 --- a/lib/ship-receipt-depth.ts +++ b/lib/ship-receipt-depth.ts @@ -131,17 +131,26 @@ export function exceptionProblems(body: string, r: ShipReceipt, isAncestor?: (co export type ReviewTargeted = NonNullable; -/** `write --review-targeted` input (gstack-review-dispatch verify --targeted-out), bound to the receipt's product patch-id here. */ -export function parseReviewTargeted(value: unknown, productPatchId: string): { ok: true; targeted: ReviewTargeted } | { ok: false; message: string } { +/** + * `write --review-targeted` input (gstack-review-dispatch verify --targeted-out), bound to the receipt's product patch-id here. + * `head` is the commit the reader read; `productPatchOf` gives that commit's product patch-id by the receipt's own rules. The + * record binds only on an equal product patch, and with `classesSince` also only when nothing changed after the read carries a + * targeted class (a test change the gate scan tags is gate class though the product patch leaves tests out), so a record never + * vouches for work it did not read (GST-986). + */ +export function parseReviewTargeted(value: unknown, productPatchId: string, productPatchOf: (head: string) => string | undefined, classesSince?: (read: string) => readonly TargetedClass[] | null): { ok: true; targeted: ReviewTargeted } | { ok: false; message: string } { const bad = (message: string) => ({ ok: false as const, message }); - if (!value || typeof value !== 'object' || Array.isArray(value)) return bad('review_targeted must be { classes, rules, brief_sha, status, findings, raised_by }'); + if (!value || typeof value !== 'object' || Array.isArray(value)) return bad('review_targeted must be { classes, rules, brief_sha, status, findings, raised_by, head }'); const v = value as Record; - for (const key of Object.keys(v)) if (!['classes', 'rules', 'brief_sha', 'status', 'findings', 'raised_by'].includes(key)) return bad(`review_targeted: unknown key ${JSON.stringify(key)}${key === 'product_patch_id' ? ' (write binds the pass to the receipt\'s product_patch_id)' : ''}`); + for (const key of Object.keys(v)) if (!['classes', 'rules', 'brief_sha', 'status', 'findings', 'raised_by', 'head'].includes(key)) return bad(`review_targeted: unknown key ${JSON.stringify(key)}${key === 'product_patch_id' ? ' (write binds the pass to the receipt\'s product_patch_id)' : ''}`); if (!Array.isArray(v.classes) || !v.classes.every(c => (TARGETED_CLASSES as readonly unknown[]).includes(c))) return bad(`review_targeted.classes must list ${TARGETED_CLASSES.join(', ')}`); if (!text(v.rules) || !text(v.raised_by)) return bad('review_targeted.rules and raised_by must be non-empty one-line strings'); if (typeof v.brief_sha !== 'string' || !/^[0-9a-f]{64}$/.test(v.brief_sha)) return bad('review_targeted.brief_sha must be the sha256 of review/specialists/targeted.md'); if (!['done', 'failed', 'not-run'].includes(v.status as string)) return bad('review_targeted.status must be done, failed or not-run'); if (typeof v.findings !== 'number' || !Number.isInteger(v.findings) || v.findings < 0) return bad('review_targeted.findings must be an integer >= 0'); + if (typeof v.head !== 'string' || !/^[0-9a-f]{40,64}$/.test(v.head)) return bad('review_targeted.head must be the commit the targeted reader read (rerun gstack-review-dispatch verify --targeted-out with this gstack)'); + const read = productPatchOf(v.head); + if (read !== productPatchId || (classesSince && classesSince(v.head)?.length !== 0)) return bad(`the targeted reader read ${v.head.slice(0, 12)}, whose product patch ${read?.slice(0, 12) ?? 'cannot be read'} is not this receipt's ${productPatchId.slice(0, 12)}: the product changed after the read, so run the targeted reader on HEAD and verify again`); const classes = TARGETED_CLASSES.filter(c => (v.classes as string[]).includes(c)); return { ok: true, targeted: { classes, rules: v.rules as string, brief_sha: v.brief_sha, product_patch_id: productPatchId, status: v.status as ReviewTargeted['status'], findings: v.findings, raised_by: (v.raised_by as string).trim() } }; } @@ -219,3 +228,34 @@ export function bindShippedReceipt(root: string, baseRef: string, body: string, return { ...b, depthLine: d.line, targetedLine: t.line }; } +/** + * The targeted classes of what changed from `from` to the checked-out `head`, with gstack-gate-diff run over that range whenever + * a non-docs path changed (a test edit that skips a case is gate class); null when `from` is not an ancestor of `head`, the gate + * scan did not run, or a class input is unreadable. + */ +export function targetedClassesSince(root: string, from: string, head: string): readonly TargetedClass[] | null { + if (!git(root, ['merge-base', '--is-ancestor', from, head]).ok) return null; + const at = headInputs(root, from, head); + let gateTagged: number | null = 0; + if (gateScanNeeded(at.c.paths, at.c.root)) { + const g = runScriptSync(path.join(BIN_DIR, 'gstack-gate-diff'), [from], { cwd: root, encoding: 'utf8', timeout: 120_000, maxBuffer: 16 * 1024 * 1024 }); + const m = /GATE_SUMMARY: .*?tagged=\{([^}]*)\}/.exec(g.stdout ?? ''); + if (g.status !== 0 || !m) return null; + gateTagged = m[1]!.split(',').reduce((n, kv) => n + (Number(kv.split(':')[1]) || 0), 0); + } + const t = targetedClasses({ ...at.c, scope: at.scope, gateTagged }); + return t.errors.length ? null : t.classes; +} + +/** + * receiptTargetedVerdict at the receipt's head, with the classes the queue computes: what write and carry check before they + * publish (#3854). Classes that failed closed on an input this checkout cannot read (no origin base, no gate scan) are reported, + * never refused on here: the queue reads its own inputs at ready and refuses there if the head needs the reader. + */ +export function receiptTargetedAtHead(root: string, baseRef: string, r: ShipReceipt, head: string): DepthVerdict { + const at = headInputs(root, baseRef, head); + const gateTagged = boundScan(r, head) ?? (gateScanNeeded(at.c.paths, at.c.root) ? null : 0); + const t = targetedClasses({ ...at.c, scope: at.scope, gateTagged }); + const v = receiptTargetedVerdict(t.classes, r); + return v.ok || !t.errors.length ? v : { ok: true, line: `${v.line} (unchecked: ${t.errors.join('; ')}; the queue decides at ready)` }; +} diff --git a/package.json b/package.json index 7a34868dc..720d53155 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.21", + "version": "1.93.22", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/ship/DRIVER.md b/ship/DRIVER.md index ca721a499..41293bab8 100644 --- a/ship/DRIVER.md +++ b/ship/DRIVER.md @@ -88,8 +88,8 @@ Step 8.1, 9.2.1's probes, 8.2 (`Scope Check:`) on the todos and commits files, t one program. Prints `Pre-Landing Review:`, `Gate edits:`. `NEXT: stop` stops; `full` or `scoped`: one program runs the failing test, fix, `--tests-only` and Phase 3, each gated (`diffBase` RANGE_BASE on `full` only; scoped fix-check: Step 3.5); `fix-check`: the pass with `fixCheck: {fix_list, hunks, tests, callers}`; `done` or `voices`: that program runs Step 11 from -`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (neither -prints `ADVERSARIAL: skipped (review plan)`). +`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (none: +`ADVERSARIAL: skipped (review plan)`). ## 5. Voices, bind and version: Steps 11, 11.5, 12 @@ -101,7 +101,7 @@ approvals; `VERSION: skipped|kept`: no write. ## 6. Notes and documentation: Steps 13, 14, 14.5 -Step 13: one user-facing entry from the commits file, where `NEXT:` says; +Step 13: one user-facing entry from the commits file where `NEXT:` says; `gstack-prose-lint --base origin/ --gate` until `PROSE_LINT: ok`. Step 14: TODOS.md deferrals and completions, edited from the todos file. Step 14.5, every ship: read `sections/documentation.md` and run its audit as written, as one program (above), then `gstack-ship-receipt docs-caps --base `; print `Documentation:`. @@ -118,8 +118,8 @@ STALE or MISSING reruns once), `PREGATE:`; print them, the last `Gate edits:` an ## 8. Publish: Steps 18, 19, 19-C, 20, 21, 22 - Step 18: the open-PR lookup; a title file starting `v `. `UNATTENDED_SHIP:` with no open PR at HEAD takes Step 18-H. -- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" --run && gstack-ship-receipt write --pr --base --gate @ --pregate --issues @ --review-loop @ --docs @ --body `; `gstack-post pr-body` and `pr-title` (a new PR via the host's PR tool; draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report. -- Step 20: the metrics row via `gstack-review-log`. +- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" --run && gstack-ship-receipt write --pr --base --gate @ --pregate --issues @ --review-loop @ --docs @ [--review-targeted @] --body `; `gstack-post pr-body` and `pr-title` (new PR: host PR tool; draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report. +- Step 20: a metrics row via `gstack-review-log`. - Step 22: `gstack-ship-budget close --reading R --credits C` (report `BUDGET_FINAL:`, `BUDGET_TIMELINE:` (tokens, credits), any `METER:` line), then `gstack-ship-receipt result --pr `: `GSTACK_RESULT: skill=ship status=complete run=`. Under a merge queue, `gstack-ship-queue ready --pr [--screen|--no-screen]` at the pushed head; paste its output. ## Where each STOP sends you (paths under `/ship/`) diff --git a/ship/DRIVER.md.tmpl b/ship/DRIVER.md.tmpl index a0f0d8ac1..48726e977 100644 --- a/ship/DRIVER.md.tmpl +++ b/ship/DRIVER.md.tmpl @@ -86,8 +86,8 @@ Step 8.1, 9.2.1's probes, 8.2 (`Scope Check:`) on the todos and commits files, t one program. Prints `Pre-Landing Review:`, `Gate edits:`. `NEXT: stop` stops; `full` or `scoped`: one program runs the failing test, fix, `--tests-only` and Phase 3, each gated (`diffBase` RANGE_BASE on `full` only; scoped fix-check: Step 3.5); `fix-check`: the pass with `fixCheck: {fix_list, hunks, tests, callers}`; `done` or `voices`: that program runs Step 11 from -`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (neither -prints `ADVERSARIAL: skipped (review plan)`). +`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (none: +`ADVERSARIAL: skipped (review plan)`). ## 5. Voices, bind and version: Steps 11, 11.5, 12 @@ -99,7 +99,7 @@ approvals; `VERSION: skipped|kept`: no write. ## 6. Notes and documentation: Steps 13, 14, 14.5 -Step 13: one user-facing entry from the commits file, where `NEXT:` says; +Step 13: one user-facing entry from the commits file where `NEXT:` says; `gstack-prose-lint --base origin/ --gate` until `PROSE_LINT: ok`. Step 14: TODOS.md deferrals and completions, edited from the todos file. Step 14.5, every ship: read `sections/documentation.md` and run its audit as written, as one program (above), then `gstack-ship-receipt docs-caps --base `; print `Documentation:`. @@ -116,8 +116,8 @@ STALE or MISSING reruns once), `PREGATE:`; print them, the last `Gate edits:` an ## 8. Publish: Steps 18, 19, 19-C, 20, 21, 22 - Step 18: the open-PR lookup; a title file starting `v `. `UNATTENDED_SHIP:` with no open PR at HEAD takes Step 18-H. -- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" --run && gstack-ship-receipt write --pr --base --gate @ --pregate --issues @ --review-loop @ --docs @ --body `; `gstack-post pr-body` and `pr-title` (a new PR via the host's PR tool; draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report. -- Step 20: the metrics row via `gstack-review-log`. +- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" --run && gstack-ship-receipt write --pr --base --gate @ --pregate --issues @ --review-loop @ --docs @ [--review-targeted @] --body `; `gstack-post pr-body` and `pr-title` (new PR: host PR tool; draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report. +- Step 20: a metrics row via `gstack-review-log`. - Step 22: `gstack-ship-budget close --reading R --credits C` (report `BUDGET_FINAL:`, `BUDGET_TIMELINE:` (tokens, credits), any `METER:` line), then `gstack-ship-receipt result --pr `: `GSTACK_RESULT: skill=ship status=complete run=`. Under a merge queue, `gstack-ship-queue ready --pr [--screen|--no-screen]` at the pushed head; paste its output. ## Where each STOP sends you (paths under `/ship/`) diff --git a/ship/sections/adversarial.md b/ship/sections/adversarial.md index 97f66a3d8..396fa3245 100644 --- a/ship/sections/adversarial.md +++ b/ship/sections/adversarial.md @@ -5,7 +5,7 @@ `DISPATCH: outside-voice`. Without the native line (light depth at runtime 0; above 0 the plan lists it beside the batched specialist), print `ADVERSARIAL: skipped (review plan)`; with neither line, take decision 3 of Finish the adversarial phase. On `DISPATCH: targeted`, also run the targeted reader -(`gstack-review-dispatch render --role targeted --classes --base --diff-base `, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out` feeds the receipt's `--review-targeted`. Under a token cap, +(`gstack-review-dispatch render --role targeted --classes --base --diff-base `, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out .gstack/tmp/targeted.json` feeds the receipt's `--review-targeted`. Under a token cap, reserve the planned voices as one batch (`gstack-ship-budget check --next dispatch:`) and add `--reservation ` to the `--start adversarial-review` call below. After `NEXT: voices` (gate class: Step 11 before any stop), Finish the adversarial phase takes neither diff --git a/ship/sections/adversarial.md.tmpl b/ship/sections/adversarial.md.tmpl index 53bd90695..b4b739e0b 100644 --- a/ship/sections/adversarial.md.tmpl +++ b/ship/sections/adversarial.md.tmpl @@ -3,7 +3,7 @@ `DISPATCH: outside-voice`. Without the native line (light depth at runtime 0; above 0 the plan lists it beside the batched specialist), print `ADVERSARIAL: skipped (review plan)`; with neither line, take decision 3 of Finish the adversarial phase. On `DISPATCH: targeted`, also run the targeted reader -(`gstack-review-dispatch render --role targeted --classes --base --diff-base `, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out` feeds the receipt's `--review-targeted`. Under a token cap, +(`gstack-review-dispatch render --role targeted --classes --base --diff-base `, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out .gstack/tmp/targeted.json` feeds the receipt's `--review-targeted`. Under a token cap, reserve the planned voices as one batch (`gstack-ship-budget check --next dispatch:`) and add `--reservation ` to the `--start adversarial-review` call below. After `NEXT: voices` (gate class: Step 11 before any stop), Finish the adversarial phase takes neither diff --git a/ship/sections/handoff.md b/ship/sections/handoff.md index 55ea33fba..83f85320f 100644 --- a/ship/sections/handoff.md +++ b/ship/sections/handoff.md @@ -13,6 +13,7 @@ unattended run may do is docs/unattended.md#what-an-unattended-session-may-do. ~/.claude/skills/gstack/bin/gstack-issue-links --base --out .gstack/tmp/issues.json ~/.claude/skills/gstack/bin/gstack-ship-receipt write --base --gate @.gstack/tmp/ship-gate.json --issues @.gstack/tmp/issues.json --out .gstack/tmp/ship-receipt.md ``` + When Step 11 saved `.gstack/tmp/targeted.json`, add `--review-targeted @.gstack/tmp/targeted.json` to that write. 2. Send the completion message, identifiers first, in this order: the `SHIP_RECEIPT:` line and receipt block; `branch:` and `sha:` (`git rev-parse HEAD`), pushed or `not pushed`; `inputs:` the plan or issue, the base SHA and the spend diff --git a/ship/sections/handoff.md.tmpl b/ship/sections/handoff.md.tmpl index 273bad052..2c38144fc 100644 --- a/ship/sections/handoff.md.tmpl +++ b/ship/sections/handoff.md.tmpl @@ -11,6 +11,7 @@ unattended run may do is docs/unattended.md#what-an-unattended-session-may-do. ~/.claude/skills/gstack/bin/gstack-issue-links --base --out .gstack/tmp/issues.json ~/.claude/skills/gstack/bin/gstack-ship-receipt write --base --gate @.gstack/tmp/ship-gate.json --issues @.gstack/tmp/issues.json --out .gstack/tmp/ship-receipt.md ``` + When Step 11 saved `.gstack/tmp/targeted.json`, add `--review-targeted @.gstack/tmp/targeted.json` to that write. 2. Send the completion message, identifiers first, in this order: the `SHIP_RECEIPT:` line and receipt block; `branch:` and `sha:` (`git rev-parse HEAD`), pushed or `not pushed`; `inputs:` the plan or issue, the base SHA and the spend diff --git a/ship/sections/pr-body.md b/ship/sections/pr-body.md index 79af0c5d9..4b2a9120f 100644 --- a/ship/sections/pr-body.md +++ b/ship/sections/pr-body.md @@ -221,7 +221,7 @@ per-lane counts (`{"":{"pass":N,"fail":N,"skip":N}}`) to ```bash rm -f .gstack/tmp/review-loop.json ~/.claude/skills/gstack/bin/gstack-review-scope summary --since "$REVIEW_SINCE" --no-ledger --out .gstack/tmp/review-loop.json && -~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr --base --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json --body "$PR_BODY_FILE" +~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr --base --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json $([ -s .gstack/tmp/targeted.json ] && echo --review-targeted @.gstack/tmp/targeted.json) --body "$PR_BODY_FILE" ``` A Step 5 red-base pause adds `--base-red ` (a conditional receipt the queue diff --git a/ship/sections/pr-body.md.tmpl b/ship/sections/pr-body.md.tmpl index 611c1e553..c8d0cd9e7 100644 --- a/ship/sections/pr-body.md.tmpl +++ b/ship/sections/pr-body.md.tmpl @@ -204,7 +204,7 @@ per-lane counts (`{"":{"pass":N,"fail":N,"skip":N}}`) to ```bash rm -f .gstack/tmp/review-loop.json ~/.claude/skills/gstack/bin/gstack-review-scope summary --since "$REVIEW_SINCE" --no-ledger --out .gstack/tmp/review-loop.json && -~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr --base --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json --body "$PR_BODY_FILE" +~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr --base --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json $([ -s .gstack/tmp/targeted.json ] && echo --review-targeted @.gstack/tmp/targeted.json) --body "$PR_BODY_FILE" ``` A Step 5 red-base pause adds `--base-red ` (a conditional receipt the queue diff --git a/test/gstack-clean-producers.test.ts b/test/gstack-clean-producers.test.ts index 58781cd0b..daad0df41 100644 --- a/test/gstack-clean-producers.test.ts +++ b/test/gstack-clean-producers.test.ts @@ -36,6 +36,7 @@ const KEPT: Record = { 'ship-gate.json': '/ship handoff gate record', 'issues.json': '/ship issue links', 'docs-audit.json': '/ship documentation audit record', + 'targeted.json': '/ship Step 11 targeted reader record (gstack-review-dispatch verify --targeted-out)', 'tree-receipt.json': 'gstack-tree-receipt output', 'base-red.json': '/ship base-red record', 'review-*': '/ship review loop records', diff --git a/test/review-packets-removed.test.ts b/test/review-packets-removed.test.ts index fa40c3db5..039228592 100644 --- a/test/review-packets-removed.test.ts +++ b/test/review-packets-removed.test.ts @@ -8,7 +8,9 @@ import { describe, test, expect } from 'bun:test'; import { spawnSync } from 'child_process'; import { createHash } from 'crypto'; import * as fs from 'fs'; +import * as os from 'os'; import * as path from 'path'; +import { sectionTexts } from '../lib/review-brief'; const ROOT = path.join(import.meta.dir, '..'); const REMOVED = [ @@ -57,6 +59,7 @@ const NO_LOSS_REPLAYS = { 'L': "GSTA-167 variant G Arm L: live no-loss replay of one batched reader (testing and security plus scoped checklists) and the Red Team on its findings, 2 passes, scored L∘G1 against G1 on the paired rule (part 2)", 'GST-257': "GST-257 PR 1 dispatch-superset no-loss check (test/review-targeted.test.ts): on every fixture diff the branch dispatches main's reviewers at 8be7f21 unchanged plus only the targeted reader, and over the last 100 non-release commits on main it lost none; adds the targeted role and brief to the G1 and L bytes, whose existing reviewer text is unchanged", 'GST-850': "GST-850 dispatch-superset no-loss check: on the 26 GSTA-167 no-loss units the branch plan (wider security and money classes, write-up text in the batched diff) drops no reviewer role main dispatches, so no saved blocker can be lost, and over the last 100 main commits no commit with a gate or risk class drops depth (test/review-risk-routing.test.ts, test/review-risk-linear.test.ts); reviewer text is unchanged", + 'GST-986': "GST-986 brief-identity no-loss check (the brief-body test below): every reviewer brief lib/review-brief.ts cuts from ship/sections/adversarial.md is byte-identical to the bodies at the GST-257 pin; only the lead's Step 11 instruction naming the targeted record's path changed, outside the cut spans", 'GST-911-redos': "GST-911 ReDoS fix verdict-identity no-loss check (wave review-e7b551ebd865): SVG_REFUSED's url() entry, rewritten to run in linear time, refuses exactly the strings #3838's pattern refused (test/review-risk-redos.test.ts: 60,000 seeded strings and the hand-picked edges, and every SVG blob in the repository's history, with zero differences); SVG_MARKUP now refuses a DOCTYPE naming any document but W3C's SVG DTD (test/review-planner-misreads.test.ts), which only keeps more SVGs code and so drops no reviewer; GST-911's no-loss replay holds unchanged; reviewer text is unchanged", 'GST-911': "GST-911 no-loss replay (GSTA-249/13): the planner over the last 100 first-parent main commits to f5cb6ec1e, origin/main's code against this branch, gate scan fixed at 0, changes no commit's depth, class, risk classes, targeted classes or SCOPE_AUTH; the three misreads (a drawing-only SVG, a session-named data file, a bare base) are proven on fixtures (test/review-planner-misreads.test.ts), where every SVG that can run script stays code with the security class; reviewer text is unchanged", 'R51': "R51 risk-tier routing no-loss re-score (GSTA-167): the saved panel findings (349 records over 24 of the 26 no-loss units) re-scored against the branch's plan lose no blocker whose only finders are roles it drops, and every gate, security, user-data and money fixture keeps main's reviewers plus the full panel (test/review-targeted.test.ts, test/review-risk-routing.test.ts); reviewer text is unchanged", @@ -66,7 +69,7 @@ const NO_LOSS_REPLAYS = { type Replay = keyof typeof NO_LOSS_REPLAYS; const REVIEWER_BRIEFS: Record = { 'ship/sections/review-army.md': { sha: '7c58dfd1bf1f16f967186a4872191cb16ebff3be81a50ebbf19067020af3ec53', replay: 'GST-967' }, - 'ship/sections/adversarial.md': { sha: '5d712bb568573931108a79b1bd40b74db6189610f47c547099c13098ef67bef8', replay: 'GST-967' }, + 'ship/sections/adversarial.md': { sha: 'f492e2c1663b81f503872543e1ba616ff092ffeed23e7d993bd2f4d1bfcd5d9e', replay: 'GST-986' }, 'review/sections/review-army.md': { sha: '78329b64640260cd8831ca81a9189f3f8dcade9c406431631d3f2cd798cd3967', replay: '#3638' }, 'review/sections/adversarial.md': { sha: 'e6962fa25b78f362654f2954f79683e524d2ebd452d1494f8a2b099888e8e255', replay: 'G1' }, 'review/checklist.md': { sha: '607788afeb2c83f2dff1dccd58d212616ec72393d28cda2fa18690c1a83df699', replay: '#3638' }, @@ -87,7 +90,34 @@ const REVIEWER_BRIEFS: Record = { 'lib/review-risk.ts': { sha: '126eff0f1de6982e03dc24d8017c67e6807c91dfa518b18d2db3aa910aac911f', replay: 'GST-911' }, }; +/** The reviewer brief bodies lib/review-brief.ts cuts out of the ship sections, at the bytes replays L and GST-257 admitted. */ +const BRIEF_BODIES: Record = { + specialist: '1ebe47ddb3291a9e4fe90b5a260eb57e328144959549bc95c119b68d23652cb5', + framing: 'aad667b5ea4c6d23d930dafffa38178c04be388cd4364fb73759fc92af17bb63', + fixtures: '9517c86befe773e76acbf30a0642ed828e211b8ecb524325702072b26429ef16', + redTeam: '1cca7957d13f9da08434870290b92958f37750207047b92c9aa52a802da789e8', + native: 'ddb1041ec1e4dd373c3e4ab9017e49316e78c230055c1ef3db1bbba518b143a2', + outside: 'bd96406da28b03742384560425a333b6d6d7ed2be95bd2026c90e0039632cee2', +}; + describe('reviewer briefs stay at their no-loss-tested bytes (GSTA-167)', () => { + // Value: protects=the GST-986 repin of ship/sections/adversarial.md admits only a lead-instruction edit: every brief a reviewer + // reads renders byte-identical to the no-loss-tested bodies; fails_when=an edit inside a cut span changes a brief while the + // section pin moves; seam=sectionTexts, the function every render uses; no_claim=the checklists (pinned by file hash below) + test('every reviewer brief cut from the ship sections is byte-identical to its pinned body', () => { + const hashes = (root: string) => Object.fromEntries(Object.entries(sectionTexts(root)).map(([k, v]) => [k, createHash('sha256').update(v).digest('hex')])); + expect(hashes(ROOT)).toEqual(BRIEF_BODIES); + const copy = fs.mkdtempSync(path.join(os.tmpdir(), 'brief-bodies-')); + try { + fs.cpSync(path.join(ROOT, 'ship', 'sections'), path.join(copy, 'ship', 'sections'), { recursive: true }); + const adversarial = path.join(copy, 'ship', 'sections', 'adversarial.md'); + fs.writeFileSync(adversarial, fs.readFileSync(adversarial, 'utf8').replace('Recommendation:', 'Recommendation (short):')); + expect(hashes(copy)).not.toEqual(BRIEF_BODIES); + } finally { + fs.rmSync(copy, { recursive: true, force: true }); + } + }); + test('every pinned reviewer brief, checklist and depth rule hashes to its pin', () => { const drift = Object.entries(REVIEWER_BRIEFS).filter(([file, pin]) => { const text = fs.readFileSync(path.join(ROOT, file), 'utf8').replace(/\r\n/g, '\n'); diff --git a/test/review-scope-ledger-count.test.ts b/test/review-scope-ledger-count.test.ts index 955d57a61..b18d962f6 100644 --- a/test/review-scope-ledger-count.test.ts +++ b/test/review-scope-ledger-count.test.ts @@ -247,3 +247,55 @@ describe('GST-951: the receipt counts every blocker the readers found', () => { expect(both.err).toContain('(REVIEW_SCOPE_INPUT;'); }); }); + +describe('GST-986 item 5: fix-check passes and their findings count from the ledgers', () => { + // #3862 (reconstruction from its PR body; GST-963, pin 0ead1ce50): round 1's red-team CRITICAL sat in pass 1's row unclassified, + // fixed in be9480829; the first granted fix-check found a CRITICAL, fixed in 0ead1ce50; the second fix-check was clean; the + // targeted reader then raised 2 informational findings. Only pass 1 had a review-log row, so the receipt's tool counters read + // blocking_found=0 fixed=0 fix_checks=0 and the lane hand-set blocking_found=2 fixed=2 fix_checks=2 (its third, the docs + // re-check, ran outside the pass program). The outside voice's findings, which GStack Minder ruled below the floor, are left out. + // Value: protects=a fix-check that ran through gstack-review-dispatch counts as a round, and a blocker a later fix-check + // followed counts as fixed, without a review-log row; fails_when=summary counts fix_checks only from rows, or reports such + // blockers as unrecorded; seam=gstack-review-scope summary --run; no_claim=noted (a floor ruling lives only in rows) + test('#3862: two fix-check ledgers count as fix_checks=2, and the blockers they followed count as fixed', () => { + const repo = branchWith([review(0, 1, [{ fingerprint: 'lib/tree-receipt.ts:120:logic', raised_by: 'red-team' }])]); + const run = path.join(repo.root, 'run'); + const step = (dir: string, readers: Array<[string, string]>) => { pass(path.join(run, dir), readers); Bun.sleepSync(5); }; + step('p1', [['specialist-batched', CLEAN], ['red-team', '[CRITICAL] lib/tree-receipt.ts:120 the tree receipt floors the branch\'s own stamp row at the gated VERSION']]); + step('fc1', [['fix-check', '[CRITICAL] lib/ship-queue-train-stamp.ts:88 the train stamp and verify checks pass no baseRef']]); + step('fc2', [['fix-check', CLEAN]]); + step('s11', [['targeted', '- [INFORMATIONAL] lib/stamp-only.ts:14 a comment names the old bound\n- [INFORMATIONAL] lib/stamp-only.ts:30 the side argument has no default test']]); + const s = loop(summary(repo, '--run', run)); + expect(s).toMatchObject({ passes: 1, blocking_found: 2, fixed: 2, fix_checks: 2, rounds: 3, ledgers_read: 4 }); + expect(s.unrecorded).toBeUndefined(); + }); + + // Value: protects=a fix-check with both a row and a ledger counts once, and once every fix-check has its row the rows decide + // dispositions (a blocker no row disposes stays unrecorded); fails_when=the ledger count adds to the row count, or a logged + // fix-check turns undisposed blockers fixed; seam=gstack-review-scope summary --run + test('a fix-check counted by row and ledger counts once; with its row logged, undisposed blockers stay unrecorded', () => { + const repo = branchWith([ + review(0, 1, [{ fingerprint: 'lib/a.ts:5:logic', severity: 'CRITICAL', blocking: true, action: 'deferred', raised_by: 'red-team' }]), + fixCheck(30, []), + ]); + const run = path.join(repo.root, 'run'); + const step = (dir: string, readers: Array<[string, string]>) => { pass(path.join(run, dir), readers); Bun.sleepSync(5); }; + step('p1', [['red-team', '[CRITICAL] lib/a.ts:5 a deferred defect outside the diff\n[CRITICAL] lib/b.ts:9 a fixed defect']]); + step('fc1', [['fix-check', '[CRITICAL] lib/c.ts:3 the fix broke the caller']]); + const s = loop(summary(repo, '--run', run)); + expect(s).toMatchObject({ passes: 1, blocking_found: 3, deferred: 1, fixed: 0, fix_checks: 1, rounds: 2, unrecorded: 2 }); + }); + + // Value: protects=a blocker the last fix-check raises again is not fixed: only a fix-check after its last occurrence follows it + // (round 2 outside voice on this PR: the re-raised blocker read blocking_found=1 fixed=1 with nothing unrecorded); + // fails_when=an earlier occurrence's follow-up marks it fixed; seam=gstack-review-scope summary --run + test('a blocker the last fix-check raises again stays unrecorded', () => { + const repo = branchWith([review(0, 1, [{ fingerprint: 'lib/a.ts:10:logic', raised_by: 'red-team' }])]); + const run = path.join(repo.root, 'run'); + const step = (dir: string, readers: Array<[string, string]>) => { pass(path.join(run, dir), readers); Bun.sleepSync(5); }; + step('p1', [['red-team', '[CRITICAL] lib/a.ts:10 the cap is never enforced']]); + step('fc1', [['fix-check', '[CRITICAL] lib/a.ts:10 the cap is still never enforced']]); + const s = loop(summary(repo, '--run', run)); + expect(s).toMatchObject({ passes: 1, blocking_found: 1, fixed: 0, fix_checks: 1, unrecorded: 1 }); + }); +}); diff --git a/test/review-targeted-record.test.ts b/test/review-targeted-record.test.ts new file mode 100644 index 000000000..3eeb5909f --- /dev/null +++ b/test/review-targeted-record.test.ts @@ -0,0 +1,295 @@ +/** + * GST-986 (GST-966 part 2): the targeted reader's record from verify to the receipt. + * verify records what the reader did and the commit it read; write binds that record only to a receipt whose product patch + * is the one the reader read; write and carry refuse a receipt the queue's targeted verdict would refuse; /ship's receipt + * writes pass the record. Cases come from gstack-evals#140, C5-36/C6-36 item 5 and GSTA-234's outside-voice fixture on #3859. + */ +import { afterEach, describe, expect, setDefaultTimeout, test } from 'bun:test'; +import { spawnSync } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { EXIT } from '../lib/headless-artifacts'; +import { checkoutTree } from '../lib/review-brief'; +import { TARGETED_RULES, targetedBriefSha } from '../lib/review-targeted'; +import { extractReceipt, placeReceiptFirst } from '../lib/ship-receipt'; +import { receiptDepthAtHead } from '../lib/ship-receipt-depth'; +import { cleanup, makeFixtureRepo, runBin, type FixtureRepo } from './helpers/restamp-fixture'; + +setDefaultTimeout(180_000); +const ROOT = path.resolve(import.meta.dir, '..'); +const repos: FixtureRepo[] = []; +afterEach(() => { while (repos.length) cleanup(repos.pop()!); }); + +function moneyBranch(): { repo: FixtureRepo; base: string; head: string } { + const repo = makeFixtureRepo({ prefix: 'gst986-' }); + repos.push(repo); + repo.git('checkout', '-q', '-b', 'feat'); + fs.mkdirSync(path.join(repo.work, 'src'), { recursive: true }); + fs.writeFileSync(path.join(repo.work, 'src', 'charge.ts'), 'export const spend_cap_usd = 5;\n'); + repo.git('add', '-A'); repo.git('commit', '-q', '-m', 'charge'); + return { repo, base: repo.git('merge-base', 'origin/main', 'HEAD'), head: repo.git('rev-parse', 'HEAD') }; +} +const commit = (repo: FixtureRepo, file: string, text: string, msg: string) => { + fs.mkdirSync(path.dirname(path.join(repo.work, file)), { recursive: true }); + fs.writeFileSync(path.join(repo.work, file), text); + repo.git('add', '-A'); repo.git('commit', '-q', '-m', msg); + return repo.git('rev-parse', 'HEAD'); +}; + +/** One targeted read through the real dispatch steps; `reply` is the reader's raw output after its REVIEWED_TREE line. */ +function targetedRead(repo: FixtureRepo, base: string, dir: string, reply: string | null, sent = true, classes = 'money'): string { + const out = path.join(repo.root, dir); + const ledger = path.join(out, 'review-ledger.jsonl'); + const render = runBin(repo, 'gstack-review-dispatch', ['render', '--role', 'targeted', '--classes', classes, '--diff-base', base, '--out', out]); + expect(render.status, render.err).toBe(0); + const file = /file=(\S+)/.exec(render.out)![1]!; + if (sent) expect(runBin(repo, 'gstack-review-dispatch', ['sent', '--ledger', ledger, '--role', 'targeted', '--sent', file]).status).toBe(0); + if (reply === null) return ledger; + const output = path.join(out, 'targeted.out'); + fs.writeFileSync(output, `REVIEWED_TREE: ${checkoutTree(repo.work)}\n${reply}\n`); + const merged = path.join(out, 'merged.jsonl'); + expect(runBin(repo, 'gstack-review-dispatch', ['collect', '--ledger', ledger, '--role', 'targeted', '--output', output, '--merged', merged]).status).toBe(0); + const judged = path.join(out, 'judged.jsonl'); + fs.writeFileSync(judged, fs.readFileSync(merged, 'utf8').split('\n').filter(Boolean).map(l => JSON.stringify({ id: JSON.parse(l).id, severity: JSON.parse(l).label }) + '\n').join('')); + expect(runBin(repo, 'gstack-review-dispatch', ['judge', '--ledger', ledger, '--merged', merged, '--judged', judged]).status).toBe(0); + return ledger; +} +const verify = (repo: FixtureRepo, ledger: string, record?: string) => + runBin(repo, 'gstack-review-dispatch', ['verify', '--ledger', ledger, ...(record ? ['--targeted-out', record] : [])]); +const recordLine = (out: string) => out.split('\n').find(l => l.startsWith('TARGETED_RECORD:')) ?? ''; +const finding = (line: number, summary: string) => JSON.stringify({ severity: 'INFORMATIONAL', confidence: 7, path: 'src/charge.ts', line, category: 'money', summary, fingerprint: `src/charge.ts:${line}:money`, specialist: 'targeted' }); +const write = (repo: FixtureRepo, args: string[]) => runBin(repo, 'gstack-ship-receipt', ['write', '--base', 'main', '--session-kind', 'unattended', ...args, '--json']); + +describe('verify records the targeted reader\'s outcome and the commit it read (item 1)', () => { + // Value: protects=a targeted reader that completed reads as done, with its findings counted, however the head moves after it + // (gstack-evals#140, C5-36 and C6-36 p2: Step 11 fixes moved the head and the clean or 2-finding read printed status=failed + // findings=0); a reader that did not complete stays failed; fails_when=verify fails a completed read because HEAD moved, zeroes + // its findings, or marks a read with no matching dispatch or output done; seam=gstack-review-dispatch verify --targeted-out; + // no_claim=binding the record to the receipt (write, below) + test('a clean read and a 2-finding read stay done after a later fix commit; an unsent or uncollected read is failed', () => { + const { repo, base, head } = moneyBranch(); + const clean = targetedRead(repo, base, 'clean', 'NO FINDINGS'); + const found = targetedRead(repo, base, 'found', `${finding(1, 'the cap is never enforced')}\n${finding(1, 'the cap has no unit test')}`); + const unsent = targetedRead(repo, base, 'unsent', 'NO FINDINGS', false); + const silent = targetedRead(repo, base, 'silent', null); + commit(repo, 'src/charge.ts', 'export const spend_cap_usd = 5; // enforced in run()\n', 'Step 11 fix after the reader'); + + const record = path.join(repo.root, 'targeted.json'); + const v = verify(repo, clean, record); + expect(recordLine(v.out)).toStartWith(`TARGETED_RECORD: classes=money status=done findings=0 brief=${targetedBriefSha().slice(0, 12)} rules=${TARGETED_RULES}`); + expect(JSON.parse(fs.readFileSync(record, 'utf8'))).toEqual({ classes: ['money'], rules: TARGETED_RULES, brief_sha: targetedBriefSha(), status: 'done', findings: 0, raised_by: 'targeted', head }); + expect(recordLine(verify(repo, found).out)).toStartWith('TARGETED_RECORD: classes=money status=done findings=2 '); + expect(recordLine(verify(repo, unsent).out)).toStartWith('TARGETED_RECORD: classes=money status=failed '); + expect(recordLine(verify(repo, silent).out)).toStartWith('TARGETED_RECORD: classes=money status=failed findings=0 '); + }); + + // Value: protects=a read rendered on uncommitted tracked edits never reads as done (nothing proves which patch it read); + // fails_when=the item 1 repair accepts it; seam=gstack-review-dispatch render + verify + test('a read rendered on uncommitted tracked edits is failed', () => { + const { repo, base } = moneyBranch(); + fs.writeFileSync(path.join(repo.work, 'src', 'charge.ts'), 'export const spend_cap_usd = 6;\n'); + const dirty = targetedRead(repo, base, 'dirty', 'NO FINDINGS'); + repo.git('checkout', '--', 'src/charge.ts'); + expect(recordLine(verify(repo, dirty).out)).toStartWith('TARGETED_RECORD: classes=money status=failed '); + }); + + // Value: protects=a run whose ledger holds no targeted render leaves no record for Step 19 to pass, so an earlier run's + // .gstack/tmp/targeted.json never reaches write (pass 1 Red Team note on this PR); fails_when=verify leaves the stale file; + // seam=gstack-ship-receipt write's input via gstack-review-dispatch verify --targeted-out + test('verify with no targeted render removes a stale --targeted-out file', () => { + const { repo, base } = moneyBranch(); + const out = path.join(repo.root, 'plain'); + const render = runBin(repo, 'gstack-review-dispatch', ['render', '--role', 'specialist', '--name', 'testing', '--diff-base', base, '--out', out]); + expect(render.status, render.err).toBe(0); + const record = path.join(repo.root, 'targeted.json'); + fs.writeFileSync(record, '{"stale":true}\n'); + const v = verify(repo, path.join(out, 'review-ledger.jsonl'), record); + expect(recordLine(v.out)).toBe(''); + expect(fs.existsSync(record)).toBe(false); + }); + + // Value: protects=a verify that stops early (an empty ledger: no brief records) still removes the stale record (round 2 Red + // Team and adversarial on this PR); fails_when=the early return leaves it; seam=gstack-review-dispatch verify --targeted-out + test('verify on a ledger with no brief records removes a stale --targeted-out file too', () => { + const { repo } = moneyBranch(); + const ledger = path.join(repo.root, 'empty-ledger.jsonl'); + fs.writeFileSync(ledger, ''); + const record = path.join(repo.root, 'targeted.json'); + fs.writeFileSync(record, '{"stale":true}\n'); + expect(verify(repo, ledger, record).status).toBe(EXIT.refused); + expect(fs.existsSync(record)).toBe(false); + }); +}); + +describe('write binds a targeted record only to the product patch the reader read (item 2)', () => { + // Value: protects=a record verified at H1 never vouches for H2's new product code (GSTA-234's outside-voice CRITICAL on #3859: + // write rebound the H1 record to H2's product patch-id and ready printed verdict=ok); a later commit that leaves the product + // patch alone (a test, the version stamp) still binds; fails_when=write accepts the stale record, rejects the unchanged + // product, or accepts a record that names no commit; seam=gstack-ship-receipt write --review-targeted + test('GSTA-234\'s fixture: an auth commit after verify is refused; a test-only commit binds', () => { + const { repo, base, head } = moneyBranch(); + const record = path.join(repo.root, 'targeted.json'); + expect(verify(repo, targetedRead(repo, base, 'h1', 'NO FINDINGS'), record).status).toBe(0); + + commit(repo, 'test/charge.test.ts', 'test("cap", () => {});\n', 'test only'); + const testOnly = write(repo, ['--review-targeted', `@${record}`]); + expect(testOnly.status, testOnly.err).toBe(EXIT.ok); + expect(JSON.parse(testOnly.out).review_targeted).toMatchObject({ classes: ['money'], status: 'done' }); + + commit(repo, 'src/session.ts', 'export function checkSession(token: string) { return token.length > 0; }\n', 'auth change after verify'); + const body = path.join(repo.root, 'body.md'); + const stale = write(repo, ['--review-targeted', `@${record}`, '--body', body]); + expect(stale.status).toBe(EXIT.fail); + expect(stale.err).toContain(`the targeted reader read ${head.slice(0, 12)}`); + expect(stale.err).toContain('(RECEIPT_INVALID'); + expect(fs.existsSync(body)).toBe(false); + + const { head: _drop, ...headless } = JSON.parse(fs.readFileSync(record, 'utf8')); + const old = write(repo, ['--review-targeted', JSON.stringify(headless)]); + expect(old.status).toBe(EXIT.fail); + expect(old.err).toContain('review_targeted.head'); + }); + + // Value: protects=a record binds only to the product patch the reader read, so a docs or TODOS.md commit after the read (which + // changes the product patch) is refused like a money edit; binding docs-only commits is planned under GST-1031 with a full + // review; fails_when=write binds a record whose read product patch is not the receipt's; seam=gstack-ship-receipt write --review-targeted + test('a docs and TODOS.md commit after the read is refused; a money edit after it is refused', () => { + const { repo, base, head } = moneyBranch(); + const record = path.join(repo.root, 'targeted.json'); + expect(verify(repo, targetedRead(repo, base, 'h1', 'NO FINDINGS'), record).status).toBe(0); + + commit(repo, 'docs/charges.md', '# Charges\n\nThe cap is enforced per run.\n', 'Step 14.5 docs'); + commit(repo, 'TODOS.md', '# TODOS\n\n- [x] enforce the cap\n', 'Step 14 TODOS'); + const docs = write(repo, ['--review-targeted', `@${record}`]); + expect(docs.status).toBe(EXIT.fail); + expect(docs.err).toContain(`the targeted reader read ${head.slice(0, 12)}`); + + commit(repo, 'src/charge.ts', 'export const spend_cap_usd = 50;\n', 'money edit after verify'); + const stale = write(repo, ['--review-targeted', `@${record}`]); + expect(stale.status).toBe(EXIT.fail); + expect(stale.err).toContain(`the targeted reader read ${head.slice(0, 12)}`); + }); + + // Value: protects=a code edit after the read that drops a cap's enforcing line carries no money token, so the classifier tags + // nothing, but the product patch the reader read is gone and the record must not bind (targeted gate reader on this PR at + // 071532752, CRITICAL lib/ship-receipt-depth.ts:153); fails_when=write binds a record whose read product patch is not the + // receipt's; seam=gstack-ship-receipt write --review-targeted + test('a code edit after the read that removes an enforcing line with no money token is refused', () => { + const { repo, base } = moneyBranch(); + commit(repo, 'src/runner.ts', 'export function run(spent: number, limit: number) {\n if (spent > limit) throw new Error("over");\n}\n', 'enforce the limit'); + const head = repo.git('rev-parse', 'HEAD'); + const record = path.join(repo.root, 'targeted.json'); + expect(verify(repo, targetedRead(repo, base, 'h1', 'NO FINDINGS'), record).status).toBe(0); + expect(write(repo, ['--review-targeted', `@${record}`]).status).toBe(EXIT.ok); + commit(repo, 'src/runner.ts', 'export function run(spent: number, limit: number) {\n}\n', 'drop the enforcing line'); + const dropped = write(repo, ['--review-targeted', `@${record}`]); + expect(dropped.status).toBe(EXIT.fail); + expect(dropped.err).toContain(`the targeted reader read ${head.slice(0, 12)}`); + }); + + // Value: protects=a test edit after the read that skips a case is gate-class work the reader never saw, so the record does not + // bind though the product patch (which leaves tests out) is unchanged (round 2 outside voice and adversarial on this PR); + // a record whose read commit is not an ancestor of the head is refused; fails_when=write binds either; + // seam=gstack-ship-receipt write --review-targeted + test('a test skipped after the read is refused though the product patch is unchanged; a rewritten read is refused', () => { + const { repo, base } = moneyBranch(); + const cases = (cap: string) => `import { test, expect } from "bun:test";\ntest.skip("unit", () => { expect(1).toBe(1); });\n${cap}("cap", () => { expect(5).toBe(5); });\n`; + commit(repo, 'test/charge.test.ts', cases('test'), 'cap test, unit case skipped'); + const head = repo.git('rev-parse', 'HEAD'); + const record = path.join(repo.root, 'targeted.json'); + expect(verify(repo, targetedRead(repo, base, 'h1', 'NO FINDINGS', true, 'money,gate'), record).status).toBe(0); + expect(write(repo, ['--review-targeted', `@${record}`]).status).toBe(EXIT.ok); + commit(repo, 'test/charge.test.ts', cases('test.skip'), 'skip the cap test'); + const skipped = write(repo, ['--review-targeted', `@${record}`]); + expect(skipped.status).toBe(EXIT.fail); + expect(skipped.err).toContain(`the targeted reader read ${head.slice(0, 12)}`); + + repo.git('reset', '-q', '--hard', base); + commit(repo, 'src/charge.ts', 'export const spend_cap_usd = 75;\n', 'rewritten money change'); + const rewritten = write(repo, ['--review-targeted', `@${record}`]); + expect(rewritten.status).toBe(EXIT.fail); + expect(rewritten.err).toContain(`the targeted reader read ${head.slice(0, 12)}`); + }); +}); + +describe('write and carry refuse a receipt the queue\'s targeted verdict refuses (item 4, #3854)', () => { + // Value: protects=/ship never publishes a receipt whose head needs a targeted class it did not cover (gstack-evals#140 wrote + // gstack_version with receipt=legacy); a head that needs none writes as before; fails_when=write records no or a failed pass + // on a money head, or refuses a head without classes; seam=gstack-ship-receipt write + receiptTargetedVerdict + test('write: missing and failed records are refused on a money head; a covered or classless head writes', () => { + const { repo, base } = moneyBranch(); + const missing = write(repo, []); + expect(missing.status).toBe(EXIT.fail); + expect(missing.err).toContain('REVIEW_TARGETED: required=money receipt=legacy verdict=missing'); + expect(missing.err).toMatch(/requires the targeted reader on money, but the receipt records no targeted pass.*\(RECEIPT_INVALID/); + const record = path.join(repo.root, 'targeted.json'); + verify(repo, targetedRead(repo, base, 'h1', 'NO FINDINGS'), record); + const failed = write(repo, ['--review-targeted', JSON.stringify({ ...JSON.parse(fs.readFileSync(record, 'utf8')), status: 'failed' })]); + expect(failed.status).toBe(EXIT.fail); + expect(failed.err).toContain('targeted pass failed'); + expect(write(repo, ['--review-targeted', `@${record}`]).status).toBe(EXIT.ok); + + const plain = makeFixtureRepo({ prefix: 'gst986-plain-' }); + repos.push(plain); + plain.git('checkout', '-q', '-b', 'feat'); + commit(plain, 'docs/notes.md', '# notes\n', 'docs'); + const none = write(plain, []); + expect(none.status, none.err).toBe(EXIT.ok); + expect(JSON.parse(none.out)).not.toHaveProperty('review_targeted'); + }); + + // Value: protects=carry never rebinds a receipt the queue would refuse on targeted coverage (#3854: carry checked depth only); + // a legacy receipt and a covered one carry as before; fails_when=carry prints CARRY: carried for an uncovered money head, or + // refuses a covered or legacy one; seam=gstack-ship-receipt carry + test('carry: an uncovered versioned receipt is refused; a covered or legacy one carries', () => { + const { repo, head } = moneyBranch(); + const saved = { HOME: process.env.HOME, GSTACK_HOME: process.env.GSTACK_HOME, GSTACK_STATE_DIR: process.env.GSTACK_STATE_DIR }; + Object.assign(process.env, { HOME: repo.home, GSTACK_HOME: repo.home, GSTACK_STATE_DIR: repo.home }); + let depth: ReturnType; + try { depth = receiptDepthAtHead(repo.work, 'main', path.join(ROOT, 'bin'), head); } finally { + for (const [k, v] of Object.entries(saved)) { if (v === undefined) delete process.env[k]; else process.env[k] = v; } + } + const versioned = { schema_version: 1, pr: 5, head, base: 'main', gstack_version: '1.92.75.0', review_depth: depth }; + const covered = { ...versioned, review_targeted: { classes: ['money'], rules: TARGETED_RULES, brief_sha: targetedBriefSha(), product_patch_id: 'p', status: 'done', findings: 0, raised_by: 'targeted' } }; + commit(repo, 'test/charge.test.ts', 'test("cap", () => {});\n', 'test only'); + let n = 0; + const carry = (receipt: object) => { + const body = path.join(repo.root, `body-${++n}.md`); + fs.writeFileSync(body, placeReceiptFirst('## Summary\n\nProse.\n', receipt as any)); + return { ...runBin(repo, 'gstack-ship-receipt', ['carry', '--body', body], { GSTACK_STATE_ROOT: path.join(repo.root, 'state') }), body }; + }; + const uncovered = carry(versioned); + expect(uncovered.status).toBe(EXIT.refused); + expect(uncovered.err).toMatch(/requires the targeted reader on money, but the receipt records no targeted pass.*\(RECEIPT_CARRY_REFUSED/); + expect(uncovered.err).toContain('the queue refuses it too (#3859)'); + expect(extractReceipt(fs.readFileSync(uncovered.body, 'utf8')).receipt!.head).toBe(head); + const ok = carry(covered); + expect(ok.status, ok.err).toBe(EXIT.ok); + expect(ok.out).toContain('CARRY: carried from'); + const legacy = carry({ schema_version: 1, pr: 5, head, base: 'main' }); + expect(legacy.status, legacy.err).toBe(EXIT.ok); + }); +}); + +describe('/ship\'s receipt writes pass the targeted record (item 3, #3853)', () => { + // Value: protects=the record Step 11's verify writes reaches every receipt write /ship runs (Step 19, the handoff, the driver); + // fails_when=a write command omits --review-targeted or reads another path than verify writes; seam=the generated sections' + // command text expanded by bash; no_claim=the agent following it (prose) + test('verify writes .gstack/tmp/targeted.json and the Step 19, handoff and driver writes read it', () => { + const section = (name: string) => fs.readFileSync(path.join(ROOT, 'ship', 'sections', `${name}.md`), 'utf8'); + const recordPath = /verify --targeted-out (\S+?)`/.exec(section('adversarial'))?.[1]; + expect(recordPath).toBe('.gstack/tmp/targeted.json'); + const writeLine = section('pr-body').split('\n').find(l => l.includes('gstack-ship-receipt write --pr'))!; + const optional = /\$\(\[ -s (\S+) \] && echo (--review-targeted @\S+)\)/.exec(writeLine); + expect(optional?.slice(1)).toEqual([recordPath!, `--review-targeted @${recordPath}`]); + expect(section('handoff')).toContain(`When Step 11 saved \`${recordPath}\`, add \`--review-targeted @${recordPath}\` to that write.`); + expect(fs.readFileSync(path.join(ROOT, 'ship', 'DRIVER.md'), 'utf8')).toContain('[--review-targeted @] --body '); + + const { repo } = moneyBranch(); + const expand = () => spawnSync('bash', ['-c', `printf '%s\\n' ${optional![0]}`], { cwd: repo.work, encoding: 'utf8', timeout: 10_000 }).stdout.split('\n').filter(Boolean); + expect(expand()).toEqual([]); + fs.mkdirSync(path.join(repo.work, '.gstack', 'tmp'), { recursive: true }); + fs.writeFileSync(path.join(repo.work, recordPath!), '{}\n'); + expect(expand()).toEqual(['--review-targeted', `@${recordPath}`]); + }); +}); diff --git a/test/review-targeted.test.ts b/test/review-targeted.test.ts index 8cf6ca497..6c64e288d 100644 --- a/test/review-targeted.test.ts +++ b/test/review-targeted.test.ts @@ -298,14 +298,14 @@ describe('the targeted role end to end on a fixture repository', () => { expect(verify.status, verify.out).toBe(0); expect(verify.out).toContain(`TARGETED_RECORD: classes=money status=done findings=1 brief=${targetedBriefSha().slice(0, 12)} rules=${TARGETED_RULES}`); expect(verify.out).toContain('VERIFY: clean'); + expect(JSON.parse(fs.readFileSync(record, 'utf8')).head).toBe(head); fs.writeFileSync(path.join(repo.work, 'src', 'charge.ts'), 'export const spend_cap_usd = 50;\n'); repo.git('commit', '-qam', 'later product change the reader never read'); - expect(runBin(repo, 'gstack-review-dispatch', ['verify', '--ledger', ledger]).out).toContain('TARGETED_RECORD: classes=money status=failed findings=0'); + expect(runBin(repo, 'gstack-review-dispatch', ['verify', '--ledger', ledger]).out).toContain(`TARGETED_RECORD: classes=money status=done findings=1 brief=${targetedBriefSha().slice(0, 12)} rules=${TARGETED_RULES} read=${head.slice(0, 12)}`); + const later = runBin(repo, 'gstack-ship-receipt', ['write', '--base', 'main', '--session-kind', 'unattended', '--review-targeted', `@${record}`, '--json']); + expect(later.status).not.toBe(0); + expect(later.err).toContain(`the targeted reader read ${head.slice(0, 12)}`); repo.git('reset', '-q', '--hard', head); - fs.writeFileSync(path.join(repo.work, 'src', 'charge.ts'), 'export const spend_cap_usd = 7;\n'); - expect(runBin(repo, 'gstack-review-dispatch', ['verify', '--ledger', ledger]).out).toContain('TARGETED_RECORD: classes=money status=failed findings=0'); - repo.git('checkout', '--', 'src/charge.ts'); - expect(runBin(repo, 'gstack-review-dispatch', ['verify', '--ledger', ledger]).out).toContain('TARGETED_RECORD: classes=money status=done findings=1'); const body = path.join(repo.root, 'body.md'); const write = runBin(repo, 'gstack-ship-receipt', ['write', '--base', 'main', '--session-kind', 'unattended', '--review-targeted', `@${record}`, '--body', body, '--json']); @@ -316,8 +316,8 @@ describe('the targeted role end to end on a fixture repository', () => { expect(rebound.status).not.toBe(0); expect(rebound.err).toContain("write binds the pass to the receipt's product_patch_id"); const plain = runBin(repo, 'gstack-ship-receipt', ['write', '--base', 'main', '--session-kind', 'unattended', '--json']); - expect(plain.status, plain.err).toBe(0); - expect(JSON.parse(plain.out)).not.toHaveProperty('review_targeted'); + expect(plain.status).not.toBe(0); + expect(plain.err).toContain('REVIEW_TARGETED: required=money receipt=legacy verdict=missing'); const slug = runBin(repo, 'gstack-slug', ['--get', 'SLUG']).out.trim(); const logDir = path.join(repo.home, 'projects', slug); @@ -342,8 +342,11 @@ describe('the targeted role end to end on a fixture repository', () => { describe('the receipt record and the queue line', () => { const record = { classes: ['gate', 'money'], rules: TARGETED_RULES, brief_sha: 'a'.repeat(64), status: 'done', findings: 0, raised_by: 'targeted' }; test('parseReviewTargeted binds the receipt\'s product patch, orders the classes and refuses a malformed record', () => { - expect(parseReviewTargeted(record, 'p1')).toEqual({ ok: true, targeted: { ...record, classes: ['money', 'gate'], product_patch_id: 'p1' } as any }); - for (const bad of [{ ...record, classes: ['cost'] }, { ...record, status: 'skipped' }, { ...record, findings: -1 }, { ...record, brief_sha: 'abc' }, { ...record, extra: 1 }, null]) expect(parseReviewTargeted(bad, 'p1').ok).toBe(false); + const read = { ...record, head: 'c'.repeat(40) }; + const patchOf = (head: string) => (head === read.head ? 'p1' : undefined); + expect(parseReviewTargeted(read, 'p1', patchOf)).toEqual({ ok: true, targeted: { ...record, classes: ['money', 'gate'], product_patch_id: 'p1' } as any }); + for (const bad of [{ ...read, classes: ['cost'] }, { ...read, status: 'skipped' }, { ...read, findings: -1 }, { ...read, brief_sha: 'abc' }, { ...read, extra: 1 }, record, { ...read, head: 'abc' }, null]) expect(parseReviewTargeted(bad, 'p1', patchOf).ok).toBe(false); + expect(parseReviewTargeted(read, 'p2', patchOf)).toMatchObject({ ok: false, message: expect.stringContaining(`the targeted reader read ${'c'.repeat(12)}, whose product patch p1 is not this receipt's p2`) }); }); // Value: protects=the queue refuses an uncovered required class (GST-966); fails_when=the verdict misreads a legacy, failed or partial // receipt as covered, or a receipt without classes as required; seam=receiptTargetedVerdict (what ready, train build and land refuse on) diff --git a/test/ship-receipt-depth.test.ts b/test/ship-receipt-depth.test.ts index 2fd996f70..a3052bd06 100644 --- a/test/ship-receipt-depth.test.ts +++ b/test/ship-receipt-depth.test.ts @@ -19,6 +19,8 @@ afterEach(() => { while (repos.length) cleanup(repos.pop()!); }); const receiptIn = (body: string) => extractReceipt(fs.readFileSync(body, 'utf8')).receipt!; const writeReceipt = (repo: FixtureRepo, body: string, extra: string[] = []) => runBin(repo, 'gstack-ship-receipt', ['write', '--pr', '7', '--base', 'main', '--version', '1.0.0', '--session-kind', 'unattended', '--body', body, ...extra]); +/** A done targeted record read at HEAD on every class: since GST-986, write refuses a money, gate, auth or secret head without one. */ +const targetedAtHead = (repo: FixtureRepo) => ['--review-targeted', JSON.stringify({ classes: ['money', 'gate', 'auth', 'secret'], rules: 'r', brief_sha: 'a'.repeat(64), status: 'done', findings: 0, raised_by: 'targeted', head: repo.git('rev-parse', 'HEAD') })]; const lines = (n: number) => Array.from({ length: n }, (_, i) => `exports.f${i} = () => ${i};`).join('\n') + '\n'; const rewrite = (body: string, edit: (r: Record) => void) => { const text = fs.readFileSync(body, 'utf8'); @@ -62,11 +64,11 @@ describe('the queue refuses a receipt below the depth its head needs', () => { const repo = makeFixtureRepo(); repos.push(repo); const head = commit(repo, { 'lib/billing.js': lines(80) }); const body = path.join(repo.root, 'body.md'); - expect(writeReceipt(repo, body).status).toBe(EXIT.ok); + expect(writeReceipt(repo, body, targetedAtHead(repo)).status).toBe(EXIT.ok); expect(receiptIn(body).review_depth).toMatchObject({ depth: 'full', runtime_lines: 80, risk: ['money'] }); const base = repo.git('rev-parse', 'origin/main').slice(0, 12); const bind = (text: string) => bindShippedReceipt(repo.work, 'origin/main', text, '7', [head]); - const untargeted = bind(fs.readFileSync(body, 'utf8')); + const untargeted = bind(rewrite(body, r => { delete r.review_targeted; })); expect(!untargeted.ok && untargeted.code).toBe('QUEUE_RECEIPT_TARGETED_MISSING'); expect(!untargeted.ok && untargeted.depthLine).toContain('REVIEW_DEPTH: receipt=full required=full'); const good = bind(rewrite(body, r => { r.review_targeted = { classes: ['money'], rules: 'r', brief_sha: 'a'.repeat(64), product_patch_id: r.product_patch_id, status: 'done', findings: 0, raised_by: 'targeted' }; })); @@ -103,7 +105,7 @@ describe('the queue refuses a receipt below the depth its head needs', () => { const repo = makeFixtureRepo(); repos.push(repo); const head = commit(repo, { 'lib/auth.js': lines(3) }); const body = path.join(repo.root, 'body.md'); - expect(writeReceipt(repo, body).status).toBe(EXIT.ok); + expect(writeReceipt(repo, body, targetedAtHead(repo)).status).toBe(EXIT.ok); expect(receiptIn(body).review_depth).toMatchObject({ depth: 'full', class: 'gate' }); const bind = (text: string) => bindShippedReceipt(repo.work, 'origin/main', text, '7', [head]); const light = bind(rewrite(body, r => { r.review_depth.depth = 'light'; })); diff --git a/test/size-ledger/ship/gst-986-targeted-recorder.json b/test/size-ledger/ship/gst-986-targeted-recorder.json new file mode 100644 index 000000000..14644dd14 --- /dev/null +++ b/test/size-ledger/ship/gst-986-targeted-recorder.json @@ -0,0 +1,4 @@ +{ + "bytes": 232, + "why": "GST-986: Step 11 verify writes the targeted record to .gstack/tmp/targeted.json (adversarial.md lead instruction); merged over GST-967" +} From 8965b915aec61c686900890b0144e21575267476 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 15:05:47 -0800 Subject: [PATCH 06/10] v1.93.23.0 GSTA-253 flaky pass: plan-design focus header, shared-libs revalidation sources, .capy leak-scan skip, fixture state root (#3878) * fix(flaky): plan-design focus header, shared-libs revalidation sources, .capy leak-scan skip, fixture state root (GSTA-253) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: register the GSTA-253 focus capture in FREE_FIXTURES 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> --- CHANGELOG.md | 13 ++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- package.json | 2 +- .../design-ui-focus-capture-41414111284.json | 115 ++++++++++++++++++ test/git-fixture-template.test.ts | 18 +-- test/helpers/free-fixtures-data.ts | 1 + test/helpers/git-fixture-template.ts | 9 +- test/helpers/pregate-fixture.ts | 2 +- test/helpers/restamp-fixture.ts | 2 +- test/helpers/shared-libs-checker-evidence.ts | 2 +- test/plan-design-with-ui-fixture.test.ts | 32 ++++- test/plan-reality-receipts.test.ts | 15 ++- test/shared-libs-cancellation.test.ts | 2 +- ...hecker-interface-evidence-declared.test.ts | 4 +- ...ed-libs-checker-interface-evidence.test.ts | 41 ++++++- test/skill-e2e-plan-design-with-ui.test.ts | 7 +- test/skill-e2e-shared-libs.test.ts | 2 + 18 files changed, 240 insertions(+), 31 deletions(-) create mode 100644 test/fixtures/design-ui-focus-capture-41414111284.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 7618fa598..63d0b33e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.93.23.0] - 2026-11-02 + +**Four tests from the 2026-11-02 flaky pass stop failing correct runs, and one more names its cause when it fails.** + +### Fixed +- **`test/skill-e2e-plan-design-with-ui.test.ts`**: the Step 0D focus detector accepts the native question header "Focus" with the design rating and an all-7 option, instead of needing one more label spelling. Train gate 41414111284 offered "Full review, all 7 (recommended)", which the old label rule missed. All ten captured focus questions carry that header, and no other captured question does. The capture is saved as a fixture, and eight negative controls cover a wrong header, a missing rating, a 4/7 rating, issue-numbered options, a single option and no all-7 option. +- **`test/skill-e2e-shared-libs.test.ts`**: the `shared-libs-review-revalidation` callback passes the advisory's source files to the checker, so a batched read that exits nonzero but prints the whole file counts, as the checker already allows elsewhere. Train screen 40737741454 failed this way: its one read of `src/retry-worker.ts` was `cat …; echo ===ROUTE; …`, which exited 1 after printing the file. A truncated output and a single-command errored read still fail. +- **`test/helpers/git-fixture-template.ts`**: the template leak scan skips every `.capy` directory, not only `.capy/git-shim.log`. Capy's git shim creates `$HOME/.capy` in whatever home it runs under, so a fixture home that ran git holds the machine's files, not gstack's. A file named `.capy` and look-alike directories (`.capy-old`, `capy`) are still scanned. +- **`test/helpers/restamp-fixture.ts`, `test/helpers/pregate-fixture.ts`**: fixture repositories pin `GSTACK_STATE_ROOT` to their own home, so a value exported by the gstack launch file no longer redirects their state when bun runs without the test preload (`--config=/dev/null`). `test/review-targeted.test.ts`'s end-to-end case failed every time under that setup and passes with the fix. + +### Changed +- `test/plan-reality-receipts.test.ts` checks each git step's exit status and prints its stderr, so a red like Free Tests 41316085384 and 41375226438 (an empty commit sha) names the git step that failed. + ## [1.93.22.0] - 2026-11-02 ### Fixed diff --git a/VERSION b/VERSION index 3e56411d5..f6f8c5632 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.22.0 +1.93.23.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index dbef6f88c..068816677 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.22.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.23.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/package.json b/package.json index 720d53155..13d24a6c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.22", + "version": "1.93.23", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/fixtures/design-ui-focus-capture-41414111284.json b/test/fixtures/design-ui-focus-capture-41414111284.json new file mode 100644 index 000000000..f75db6a85 --- /dev/null +++ b/test/fixtures/design-ui-focus-capture-41414111284.json @@ -0,0 +1,115 @@ +{ + "calls": [ + { + "preReview": true, + "observedAtMs": 76003, + "call": { + "sessionId": "4767bbfe-f301-4d0c-9cfb-eeff94eb0850", + "toolUseId": "toolu_01S2K6zpLEy8wZXVMiG7teSA", + "questions": [ + { + "question": "D1 — Review focus for the dashboard plan\nProject/branch/task: main, design review of the User Dashboard Page plan (review-input.md).\nELI10: I've rated this plan 4/10 on design completeness. The data and safety rules are solid, but it never says what the user sees first, how the three panels sit on phone vs desktop, or what the empty/error/toast/modal moments actually say and do. I'll generate visual mockups next, then review all 7 design dimensions. You can narrow that to specific areas instead.\nStakes if we pick wrong: Narrowing too early leaves gaps like toast accessibility or mobile layout for the implementer to guess at, and they ship as generic defaults.\nRecommendation: A because the gaps span hierarchy, states, responsive and a11y, so a partial pass would miss real issues.\nCompleteness: A=10/10, B=6/10\nPros / cons:\nA) Full review, all 7 passes (recommended)\n ✅ Covers hierarchy, states, journey, slop risk, design system, responsive and a11y\n ✅ Every gap gets its own decision so nothing is silently assumed\n ❌ More questions to answer, one per real gap found across the passes\nB) Focus on specific areas only\n ✅ Fewer questions if you only care about, say, states and mobile layout\n ✅ Faster path to an amended plan for the areas you name\n ❌ Unreviewed dimensions stay at their current low rating and ship as guesses\nNet: Full coverage with more decisions, versus speed with known blind spots.", + "header": "Focus", + "multiSelect": false, + "options": [ + { + "label": "Full review, all 7 (recommended)", + "description": "Mockups, then all 7 passes. ✅ Covers every dimension. ✅ Each gap decided individually. ❌ More questions overall." + }, + { + "label": "Focus on specific areas", + "description": "Name the areas in a note. ✅ Fewer questions. ✅ Faster. ❌ Other dimensions remain unreviewed guesses." + } + ] + } + ], + "answered": true, + "failed": false, + "answers": { + "D1 — Review focus for the dashboard plan\nProject/branch/task: main, design review of the User Dashboard Page plan (review-input.md).\nELI10: I've rated this plan 4/10 on design completeness. The data and safety rules are solid, but it never says what the user sees first, how the three panels sit on phone vs desktop, or what the empty/error/toast/modal moments actually say and do. I'll generate visual mockups next, then review all 7 design dimensions. You can narrow that to specific areas instead.\nStakes if we pick wrong: Narrowing too early leaves gaps like toast accessibility or mobile layout for the implementer to guess at, and they ship as generic defaults.\nRecommendation: A because the gaps span hierarchy, states, responsive and a11y, so a partial pass would miss real issues.\nCompleteness: A=10/10, B=6/10\nPros / cons:\nA) Full review, all 7 passes (recommended)\n ✅ Covers hierarchy, states, journey, slop risk, design system, responsive and a11y\n ✅ Every gap gets its own decision so nothing is silently assumed\n ❌ More questions to answer, one per real gap found across the passes\nB) Focus on specific areas only\n ✅ Fewer questions if you only care about, say, states and mobile layout\n ✅ Faster path to an amended plan for the areas you name\n ❌ Unreviewed dimensions stay at their current low rating and ship as guesses\nNet: Full coverage with more decisions, versus speed with known blind spots.": "Full review, all 7 (recommended)" + }, + "unansweredQuestionIndices": [], + "answeredAt": "2026-11-02T08:39:03.879Z" + } + }, + { + "preReview": true, + "observedAtMs": 106367, + "call": { + "sessionId": "4767bbfe-f301-4d0c-9cfb-eeff94eb0850", + "toolUseId": "toolu_01AqE7XDKvovVJAvKF31kq8z", + "questions": [ + { + "question": "Want outside design voices before the detailed review?\nD2 — Outside design voices\nProject/branch/task: main, design review of the User Dashboard Page plan.\nELI10: Optionally, two other reviewers read the plan before my 7 passes: Codex checks hard design rules and a litmus scorecard, and an independent Claude subagent checks completeness. Their disagreements get raised in the relevant pass.\nStakes if we pick wrong: Skipping means one reviewer's eye; running costs a few minutes and may only get one voice if Codex is missing.\nRecommendation: No, proceed without — this is a taste call, no strong preference either way\nNote: options differ in kind, not coverage — no completeness score.\nPros / cons: see option descriptions.\nNet: A cross-model check versus the shortest path to a fixed plan.", + "header": "Voices", + "multiSelect": false, + "options": [ + { + "label": "Yes, run outside design voices", + "description": "✅ Codex hard-rule + litmus scorecard plus an independent Claude completeness read. ✅ Disagreements surface with both perspectives in the relevant pass. ❌ Adds a few minutes; Codex may be missing here, in which case only the Claude voice runs and it's tagged single-model." + }, + { + "label": "No, proceed without (recommended)", + "description": "✅ Straight into the 7 passes with the approved mockup as anchor. ✅ Fewest steps to a fixed plan. ❌ Findings rest on one reviewer; no cross-model check on slop risk or hierarchy." + } + ] + } + ], + "answered": true, + "failed": false, + "answers": { + "Want outside design voices before the detailed review?\nD2 — Outside design voices\nProject/branch/task: main, design review of the User Dashboard Page plan.\nELI10: Optionally, two other reviewers read the plan before my 7 passes: Codex checks hard design rules and a litmus scorecard, and an independent Claude subagent checks completeness. Their disagreements get raised in the relevant pass.\nStakes if we pick wrong: Skipping means one reviewer's eye; running costs a few minutes and may only get one voice if Codex is missing.\nRecommendation: No, proceed without — this is a taste call, no strong preference either way\nNote: options differ in kind, not coverage — no completeness score.\nPros / cons: see option descriptions.\nNet: A cross-model check versus the shortest path to a fixed plan.": "No, proceed without (recommended)" + }, + "unansweredQuestionIndices": [], + "answeredAt": "2026-11-02T08:39:34.676Z" + } + }, + { + "preReview": false, + "observedAtMs": 132955, + "call": { + "sessionId": "4767bbfe-f301-4d0c-9cfb-eeff94eb0850", + "toolUseId": "toolu_01PDy4qdecNenP7T1QhxRkFR", + "questions": [ + { + "question": "D3 — 1: What does a just-logged-in user see first, second, third?\nProject/branch/task: main, User Dashboard plan, Pass 1 Information Architecture.\nELI10: The plan has three panels but no order. If we don't pick, the engineer puts them in a grid of three equal boxes and nobody knows where to look. Only notifications ask the user to do something, so they're the natural anchor.\nStakes if we pick wrong: Users skim past the one thing needing attention, or the page feels like a generic widget grid.\nRecommendation: 1A because unread notifications are the only actionable content, and Krug says each screen needs one obvious next step.\nNote: options differ in kind, not coverage — no completeness score.\nPros / cons:\nA) Action row, then Notifications primary, Activity secondary (recommended)\n ✅ The one actionable thing (unread items) gets the wide main column and first scan\n ✅ Quick actions sit in the page header as a compact row, one primary button\n ❌ Activity feed is visually demoted; heavy activity users scroll or look right\nB) Activity primary, Notifications in a side rail\n ✅ Suits products where 'what changed' matters more than inbox triage\n ✅ Notifications rail stays visible while scanning activity history\n ❌ Unread items compete in a narrow rail and are easier to ignore\nNet: Anchor on what needs action (notifications) or on what changed (activity).", + "header": "Hierarchy", + "multiSelect": false, + "options": [ + { + "label": "1A Notifications primary (recommended)", + "description": "Header + quick-action row first, Notifications wide main column second, Activity secondary column third.", + "preview": "lg (≥1024px)\n+------------------------------------------------+\n| Dashboard [Open workspace] [Review notifications (3)] |\n+-------------------------------+----------------+\n| Notifications 3 unread | Recent activity|\n| [Mark all as read] | • edited ... |\n| ● New comment on Q4 plan 2m | • shared ... |\n| ● ... | • ... |\n| ... | |\n+-------------------------------+----------------+\n 1st: page title + primary action\n 2nd: unread notifications\n 3rd: recent activity" + }, + { + "label": "1B Activity primary", + "description": "Header + quick-action row first, Activity wide main column second, Notifications side rail third.", + "preview": "lg (≥1024px)\n+------------------------------------------------+\n| Dashboard [Open workspace] [Review notifications (3)] |\n+-------------------------------+----------------+\n| Recent activity | Notifications |\n| • You edited Q4 roadmap 2m | 3 unread |\n| • ... | [Mark all read]|\n| • ... | ● ... |\n+-------------------------------+----------------+\n 1st: page title + primary action\n 2nd: recent activity\n 3rd: notifications rail" + } + ] + } + ], + "answered": true, + "failed": false, + "answers": { + "D3 — 1: What does a just-logged-in user see first, second, third?\nProject/branch/task: main, User Dashboard plan, Pass 1 Information Architecture.\nELI10: The plan has three panels but no order. If we don't pick, the engineer puts them in a grid of three equal boxes and nobody knows where to look. Only notifications ask the user to do something, so they're the natural anchor.\nStakes if we pick wrong: Users skim past the one thing needing attention, or the page feels like a generic widget grid.\nRecommendation: 1A because unread notifications are the only actionable content, and Krug says each screen needs one obvious next step.\nNote: options differ in kind, not coverage — no completeness score.\nPros / cons:\nA) Action row, then Notifications primary, Activity secondary (recommended)\n ✅ The one actionable thing (unread items) gets the wide main column and first scan\n ✅ Quick actions sit in the page header as a compact row, one primary button\n ❌ Activity feed is visually demoted; heavy activity users scroll or look right\nB) Activity primary, Notifications in a side rail\n ✅ Suits products where 'what changed' matters more than inbox triage\n ✅ Notifications rail stays visible while scanning activity history\n ❌ Unread items compete in a narrow rail and are easier to ignore\nNet: Anchor on what needs action (notifications) or on what changed (activity).": "1A Notifications primary (recommended)" + }, + "unansweredQuestionIndices": [], + "answeredAt": "2026-11-02T08:40:01.258Z" + } + } + ], + "provenance": { + "kind": "public native AskUserQuestion calls and answers only (observation.json fingerprints[].nativeCall, byte-exact)", + "runId": 41414111284, + "artifact": "paid-slice-6-a1", + "prNumber": null, + "headSha": "4a280637792b46576474f3349a1ba2e977c5f5c9", + "testedSha": "4a280637792b46576474f3349a1ba2e977c5f5c9", + "recordedOutcome": "ceiling_reached", + "recordedReviewCount": 1, + "recordedStep0Count": 2, + "recordedPassed": false, + "note": "Step 0D titled \"Review focus\" with header Focus, the 4/10 design-completeness rating in its ELI10 body, and an all-7 option that does not lead with the count (\"Full review, all 7 (recommended)\")" + } +} diff --git a/test/git-fixture-template.test.ts b/test/git-fixture-template.test.ts index f63a6c002..29b31e1ae 100644 --- a/test/git-fixture-template.test.ts +++ b/test/git-fixture-template.test.ts @@ -71,21 +71,23 @@ describe('git fixture template', () => { expect(() => leaky.copy()).toThrow(/still references its template [^\n]*\n.*config/); }); - test("Capy's git shim log in a fixture home (home/.capy/git-shim.log) is not a leak; any other file naming the template still is", () => { + test("Capy's directory in a fixture home (home/.capy, the git shim's log and anything else under it) is not a leak; any other file naming the template still is", () => { const plant = (rel: string) => gitFixtureTemplate({ prefix: 'gstack-fixture-template-shim-', build: (root) => { const meta = build(root); fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); fs.writeFileSync(path.join(root, rel), '# capy git shim v2: ts cwd ppid parent verb\n2026-11-02T02:15:30Z\t' + path.join(root, 'work') + '\t1\tbun\tcat-file\n'); return meta; }, rebind: (copy, tpl) => rebindGitDir(path.join(copy, 'work', '.git'), tpl, copy) }); - const shim = plant(path.join('home', '.capy', 'git-shim.log')); - const other = plant(path.join('home', '.capy', 'state.json')); + const skipped = [path.join('home', '.capy', 'git-shim.log'), path.join('home', '.capy', 'git-shim.log.1'), path.join('home', '.capy', 'state', 'run.json')].map(plant); + const leaks = [path.join('home', 'state.json'), path.join('home', '.capy-old', 'state.json'), path.join('home', '.config', '.capy'), path.join('home', 'capy', 'git-shim.log')].map(rel => ({ rel, t: plant(rel) })); try { - const c = shim.copy(); - roots.push(c.root); - expect(git(path.join(c.root, 'work'), 'remote', 'get-url', 'origin')).toBe(path.join(c.root, 'origin.git')); - expect(() => other.copy()).toThrow(/still references its template [^\n]*\n.*state\.json/); - } finally { shim.dispose(); other.dispose(); } + for (const s of skipped) { + const c = s.copy(); + roots.push(c.root); + expect(git(path.join(c.root, 'work'), 'remote', 'get-url', 'origin')).toBe(path.join(c.root, 'origin.git')); + } + for (const { rel, t } of leaks) expect(() => t.copy()).toThrow(new RegExp('still references its template [^\\n]*\\n.*' + rel.replace(/[.\\/]/g, m => '\\' + m) + '$')); + } finally { for (const s of skipped) s.dispose(); for (const { t } of leaks) t.dispose(); } }); test('native, forward-slash, backslash-escaped and Git Bash spellings are each rewritten, and each one left behind is found', () => { diff --git a/test/helpers/free-fixtures-data.ts b/test/helpers/free-fixtures-data.ts index a78b3ffca..6baccf554 100644 --- a/test/helpers/free-fixtures-data.ts +++ b/test/helpers/free-fixtures-data.ts @@ -92,6 +92,7 @@ export const FREE_FIXTURES: Readonly> = { 'test/fixtures/design-ui-focus-capture-40664336141.json': ['test/plan-design-with-ui-fixture.test.ts'], 'test/fixtures/design-ui-focus-capture-41178131219.json': ['test/plan-design-with-ui-fixture.test.ts'], 'test/fixtures/design-ui-focus-capture-41186376825.json': ['test/plan-design-with-ui-fixture.test.ts'], + 'test/fixtures/design-ui-focus-capture-41414111284.json': ['test/plan-design-with-ui-fixture.test.ts'], 'test/fixtures/design-ui-focus-capture.json': ['test/plan-design-with-ui-fixture.test.ts'], 'test/fixtures/detector-corpora/auto-decide-preserved/**': ['test/detector-corpus-auto-decide-preserved.test.ts'], 'test/fixtures/detector-corpora/plan-ceo-section-loading/**': ['test/detector-corpus-plan-ceo-section-loading.test.ts'], diff --git a/test/helpers/git-fixture-template.ts b/test/helpers/git-fixture-template.ts index 5fb9f72eb..f296ea648 100644 --- a/test/helpers/git-fixture-template.ts +++ b/test/helpers/git-fixture-template.ts @@ -54,8 +54,9 @@ export function rebindGitDir(gitDir: string, from: string, to: string): void { /** * Files and symlinks under dir (and the template's meta value) that still name templateRoot. Skipped: zlib-compressed object - * storage, and the log Capy's /usr/bin/git shim appends to in whatever HOME it runs under (.capy/git-shim.log), which names - * the template whenever a fixture home ran git during build; nothing reads it back. + * storage, and every .capy directory: Capy's /usr/bin/git shim creates $HOME/.capy in whatever HOME it runs under and logs + * there, so a fixture home that ran git during build holds the machine's writes, not gstack's (install-fixture's tree() skips + * it for the same reason); nothing reads them back. */ export function templateReferences(dir: string, templateRoot: string, meta?: unknown): string[] { const needles = pathSpellings(templateRoot).map(s => Buffer.from(s)); @@ -65,8 +66,8 @@ export function templateReferences(dir: string, templateRoot: string, meta?: unk for (const entry of fs.readdirSync(d, { withFileTypes: true })) { const p = path.join(d, entry.name); if (entry.isSymbolicLink()) { if (names(Buffer.from(fs.readlinkSync(p)))) hits.push(p); } - else if (entry.isDirectory()) { if (!(path.basename(d) === 'objects' && /^(pack|[0-9a-f]{2})$/.test(entry.name))) walk(p); } - else if (entry.isFile() && !(entry.name === 'git-shim.log' && path.basename(d) === '.capy') && names(fs.readFileSync(p))) hits.push(p); + else if (entry.isDirectory()) { if (entry.name !== '.capy' && !(path.basename(d) === 'objects' && /^(pack|[0-9a-f]{2})$/.test(entry.name))) walk(p); } + else if (entry.isFile() && names(fs.readFileSync(p))) hits.push(p); } }; walk(dir); diff --git a/test/helpers/pregate-fixture.ts b/test/helpers/pregate-fixture.ts index baef6855e..e7f0462fc 100644 --- a/test/helpers/pregate-fixture.ts +++ b/test/helpers/pregate-fixture.ts @@ -107,7 +107,7 @@ function fixtureRepoAt(root: string): FixtureRepo { const origin = path.join(root, 'origin.git'); const work = path.join(root, 'work'); const home = path.join(root, 'home'); - const env = { GSTACK_HOME: home, GSTACK_STATE_DIR: home, HOME: home, GIT_CONFIG_GLOBAL: path.join(root, 'gitconfig'), GIT_CONFIG_NOSYSTEM: '1' }; + const env = { GSTACK_STATE_ROOT: home, GSTACK_HOME: home, GSTACK_STATE_DIR: home, HOME: home, GIT_CONFIG_GLOBAL: path.join(root, 'gitconfig'), GIT_CONFIG_NOSYSTEM: '1' }; const git = (...args: string[]): string => { const r = gitArgvIn(work, args, 30_000, { ...process.env, ...env }); if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: status=${r.status} signal=${r.signal} error=${r.error?.message ?? ''} stderr=${r.stderr?.toString() ?? ''}`); diff --git a/test/helpers/restamp-fixture.ts b/test/helpers/restamp-fixture.ts index 6ef5b188f..a2ed17620 100644 --- a/test/helpers/restamp-fixture.ts +++ b/test/helpers/restamp-fixture.ts @@ -46,7 +46,7 @@ export function fixtureRepoAt(root: string): FixtureRepo { const origin = path.join(root, 'origin.git'); const work = path.join(root, 'work'); const home = path.join(root, 'home'); - const env = { GSTACK_HOME: home, GSTACK_STATE_DIR: home, HOME: home, GIT_CONFIG_GLOBAL: path.join(root, 'gitconfig'), GIT_CONFIG_NOSYSTEM: '1' }; + const env = { GSTACK_STATE_ROOT: home, GSTACK_HOME: home, GSTACK_STATE_DIR: home, HOME: home, GIT_CONFIG_GLOBAL: path.join(root, 'gitconfig'), GIT_CONFIG_NOSYSTEM: '1' }; const git = (...args: string[]): string => { const r = gitArgvIn(work, args, 30_000, { ...process.env, ...env }); if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${r.stderr?.toString()}`); diff --git a/test/helpers/shared-libs-checker-evidence.ts b/test/helpers/shared-libs-checker-evidence.ts index b20dab453..ee56a0c8e 100644 --- a/test/helpers/shared-libs-checker-evidence.ts +++ b/test/helpers/shared-libs-checker-evidence.ts @@ -99,7 +99,7 @@ export async function capture(change: string, prepared?: PathEligibilityFixture, branch: fixtureGit(f, 'symbolic-ref', '--quiet', '--short', 'HEAD'), wtree: fixtureWorkingTree(f), startedAt: receipt.review_start.started_at, finding: current, reusable: change === 'unchanged', coveredPaths: receipt.snapshot.covered_paths, - ...(prepared ? { sources: Object.fromEntries(current!.evidence_paths.map((file: string) => [file, fs.readFileSync(path.join(f.repo, file), 'utf8')])) } : {}) }; + sources: Object.fromEntries(current!.evidence_paths.map((file: string) => [file, fs.readFileSync(path.join(f.repo, file), 'utf8')])) }; return { f, current, token, receipt, events, checkAt, finishAt, expected, prepared, resumed, prerequisites, ...(declared ? { questions } : {}) }; } diff --git a/test/plan-design-with-ui-fixture.test.ts b/test/plan-design-with-ui-fixture.test.ts index 5cba9eac2..e54ca47d0 100644 --- a/test/plan-design-with-ui-fixture.test.ts +++ b/test/plan-design-with-ui-fixture.test.ts @@ -103,6 +103,10 @@ const captured40664336141 = JSON.parse(fs.readFileSync(${JSON.stringify(path.joi // rated in its ELI10 body, and lettered its options ("A) All 7 passes", "A) All 7 dimensions"). const [captured41178131219, captured41186376825] = ['design-ui-focus-capture-41178131219.json', 'design-ui-focus-capture-41186376825.json'].map(name => JSON.parse(fs.readFileSync(${JSON.stringify(ROOT)} + '/test/fixtures/' + name, 'utf8')) .calls.map(({ preReview, observedAtMs, call }) => nativePlanCallFingerprint(call, observedAtMs, preReview))); +// Run 41414111284 slice 6 (train 4a280637): header "Focus", rated in its ELI10 body, and the all-7 option +// put the count second ("Full review, all 7 (recommended)"). +const captured41414111284 = JSON.parse(fs.readFileSync(${JSON.stringify(path.join(ROOT, 'test/fixtures/design-ui-focus-capture-41414111284.json'))}, 'utf8')) + .calls.map(({ preReview, observedAtMs, call }) => nativePlanCallFingerprint(call, observedAtMs, preReview)); const unnumberedFinding = fp('unnumbered-finding', "Which region is the dashboard's primary anchor, and how is the page composed?", false); unnumberedFinding.nativeCall.questions[0].options = nativeFinding.nativeCall.questions[0].options; const finding = fp('finding', 'Which loading feedback should Save show?', false); @@ -241,6 +245,28 @@ mock.module(${JSON.stringify(path.join(ROOT, 'test/helpers/claude-pty-runner.ts' expect([0, 2].some(index => opts.isLastStep0AUQ(capture[index]) || opts.isReviewAUQ(capture[index]))).toBe(false); expect(opts.isReviewAUQ(capture[3])).toBe(true); } + expect(opts.isLastStep0AUQ(captured41414111284[0])).toBe(true); + expect(opts.isReviewAUQ(captured41414111284[0])).toBe(false); + expect(opts.isLastStep0AUQ(captured41414111284[1]) || opts.isReviewAUQ(captured41414111284[1])).toBe(false); + expect(opts.isReviewAUQ(captured41414111284[2])).toBe(true); + const headed = (id, header, question, labels) => { + const call = labeled(id, question, labels); + call.nativeCall.questions[0].header = header; + return call; + }; + const focusBody = 'D1 — Review focus\\nProject/branch/task: main.\\nELI10: I rated this plan 4/10 on design completeness.'; + const fullFirst = ['Full review, all 7 (recommended)', 'Focus on specific areas']; + expect(opts.isLastStep0AUQ(headed('focus-header', 'Focus', focusBody, fullFirst))).toBe(true); + for (const [label, header, question, labels] of [ + ['focus-header-unrated', 'Focus', 'D1 — Review focus\\nELI10: I will review every dimension unless you narrow it.', fullFirst], + ['focus-header-out-of-seven', 'Focus', focusBody.replace('4/10', '4/7'), fullFirst], + ['focus-header-issue-labeled', 'Focus', focusBody, ['1A Full review, all 7', '1B Focus on hierarchy']], + ['focus-header-single-option', 'Focus', focusBody, fullFirst.slice(0, 1)], + ['focus-header-no-all-seven', 'Focus', focusBody, ['Every dimension (recommended)', 'States + hierarchy only']], + ['focus-header-all-four', 'Focus', focusBody, ['Full review, all 4 (recommended)', 'Focus on specific areas']], + ['other-header', 'Hierarchy', focusBody, fullFirst], + ['header-names-more', 'Focus order', focusBody, fullFirst], + ]) expect(opts.isLastStep0AUQ(headed(label, header, question, labels)), label).toBe(false); const letteredFocus = 'D2 — Review focus\\nProject/branch/task: main.\\nELI10: I rated this plan 4/10 on design completeness.'; for (const labels of [['A) All 7 dimensions (recommended)', 'B) Focus on specific areas'], ['A. All seven passes (recommended)', 'B. Hierarchy+states only'], ['A: All 7 areas (recommended)', 'B: Only specific areas']]) { @@ -344,6 +370,8 @@ mock.module(${JSON.stringify(path.join(ROOT, 'test/helpers/claude-pty-runner.ts' : mode === 'captured-41178131219-no-focus' ? captured41178131219.filter((_, index) => index !== 1) : mode === 'captured-41186376825' ? captured41186376825 : mode === 'captured-41186376825-no-focus' ? captured41186376825.filter((_, index) => index !== 1) + : mode === 'captured-41414111284' ? captured41414111284 + : mode === 'captured-41414111284-no-focus' ? captured41414111284.filter((_, index) => index !== 0) : mode === 'captured-focus-after-finding' ? [...captured.filter((_, index) => index !== 1), captured[1]] : mode.startsWith('target-menu') ? [target, fp('other', 'Which artifact should I inspect?', false)] : mode === 'early-exit' ? [] : mode === 'focus-only' ? [chosenFocus] @@ -388,12 +416,12 @@ await import(${JSON.stringify(path.join(ROOT, 'test/skill-e2e-plan-design-with-u } finally { fs.rmSync(directory, { recursive: true, force: true }); } } -test.each(['source', 'paraphrase', 'native-sequence', 'native-unnumbered', 'native-passes', 'captured', 'captured-rated', 'captured-39298269902', 'captured-39363705692', 'captured-39852003387', 'captured-40664336141', 'captured-41178131219', 'captured-41186376825'])('UI gate seeds the exact target and accepts Design progress with an unselected no-UI alternative (%s)', mode => { +test.each(['source', 'paraphrase', 'native-sequence', 'native-unnumbered', 'native-passes', 'captured', 'captured-rated', 'captured-39298269902', 'captured-39363705692', 'captured-39852003387', 'captured-40664336141', 'captured-41178131219', 'captured-41186376825', 'captured-41414111284'])('UI gate seeds the exact target and accepts Design progress with an unselected no-UI alternative (%s)', mode => { const result = exercise(mode); expect(result.code, result.output).toBe(0); }, 20_000); -test.each(['target-menu', 'target-menu-design-system', 'focus-only', 'captured-no-focus', 'captured-focus-after-finding', 'captured-rated-no-focus', 'captured-39298269902-no-focus', 'captured-39298269902-focus-only', 'captured-39363705692-no-focus', 'captured-39852003387-no-focus', 'captured-40664336141-no-focus', 'captured-41178131219-no-focus', 'captured-41186376825-no-focus', 'native-setup-only', 'native-voices-only', 'early-exit', 'timeout', 'exited', 'unanswered-finding', 'failed-finding', 'no-native-finding'])('UI gate rejects %s and removes its fixture', mode => { +test.each(['target-menu', 'target-menu-design-system', 'focus-only', 'captured-no-focus', 'captured-focus-after-finding', 'captured-rated-no-focus', 'captured-39298269902-no-focus', 'captured-39298269902-focus-only', 'captured-39363705692-no-focus', 'captured-39852003387-no-focus', 'captured-40664336141-no-focus', 'captured-41178131219-no-focus', 'captured-41186376825-no-focus', 'captured-41414111284-no-focus', 'native-setup-only', 'native-voices-only', 'early-exit', 'timeout', 'exited', 'unanswered-finding', 'failed-finding', 'no-native-finding'])('UI gate rejects %s and removes its fixture', mode => { const result = exercise(mode); expect(result.code, result.output).toBe(1); expect(result.output).toContain('plan-design-review with UI scope FAILED'); diff --git a/test/plan-reality-receipts.test.ts b/test/plan-reality-receipts.test.ts index 35acb9980..0d1c2239c 100644 --- a/test/plan-reality-receipts.test.ts +++ b/test/plan-reality-receipts.test.ts @@ -16,11 +16,17 @@ import { receiptTokens, resolveReceipts } from '../lib/plan-reality-receipts'; const ROOT = path.resolve(import.meta.dir, '..'); const BIN = path.join(ROOT, 'bin', 'gstack-plan-reality'); +function gitOk(cwd: string, ...args: string[]) { + const r = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 10_000 }); + if (r.status !== 0) throw new Error(`git ${args.join(' ')} exited ${r.status} (signal ${r.signal}, error ${r.error?.message ?? 'none'}): ${r.stderr}`); + return r; +} + function fixtureRepo(): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'reality-receipts-')); fs.mkdirSync(path.join(dir, 'lib')); fs.writeFileSync(path.join(dir, 'lib', 'a.ts'), 'one\ntwo\nthree\n'); - spawnSync('git', ['init', '-q'], { cwd: dir, timeout: 10_000 }); + gitOk(dir, 'init', '-q'); return dir; } @@ -99,10 +105,9 @@ describe('resolveReceipts', () => { test('a commit sha in the repository resolves; an absent one is unverifiable', () => { const dir = fixtureRepo(); - const git = (...a: string[]) => spawnSync('git', a, { cwd: dir, encoding: 'utf8', timeout: 10_000 }); - git('add', '.'); - git('-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-qm', 'seed'); - const sha = git('rev-parse', 'HEAD').stdout.trim(); + gitOk(dir, 'add', '.'); + gitOk(dir, '-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-qm', 'seed'); + const sha = gitOk(dir, 'rev-parse', 'HEAD').stdout.trim(); const out = resolveReceipts(parseRealityLines(`REALITY: numbers pass ok ${sha.slice(0, 10)} abcdef1234`), { roots: [dir] }); expect(out.map(r => r.outcome)).toEqual(['resolved', 'unverifiable']); }); diff --git a/test/shared-libs-cancellation.test.ts b/test/shared-libs-cancellation.test.ts index 0cecc1868..916540a15 100644 --- a/test/shared-libs-cancellation.test.ts +++ b/test/shared-libs-cancellation.test.ts @@ -93,7 +93,7 @@ function harness(options: { drainOnClose?: boolean; refuse?: boolean } = {}) { expect(name).toBe('shared-libs-review-revalidation'); expect(timeout).toBe(CAPTURE_LONG_MS); registered = callback; }, createSharedLibsFixture: (label: string) => ({ root: `/virtual/${label}`, repo: '/virtual', env: {} }), - seedReviewSources() {}, fixtureWrite() {}, installNormalizingFilter() {}, seedSkippedAdvisory: async () => ({}), + seedReviewSources() {}, fixtureWrite() {}, installNormalizingFilter() {}, seedSkippedAdvisory: async () => ({ evidence_paths: [] }), fixtureWorkingTree: () => 'same', fixtureGit() {}, reviewLifecycleInstructions: () => '', seedPathReviewPrerequisites: () => ({ input: '' }), checkPathReviewPrerequisites: () => ({ settled: true }), reviewRevalidationPrompt: (f: any) => f.root, diff --git a/test/shared-libs-checker-interface-evidence-declared.test.ts b/test/shared-libs-checker-interface-evidence-declared.test.ts index 2682870cb..60739c33c 100644 --- a/test/shared-libs-checker-interface-evidence-declared.test.ts +++ b/test/shared-libs-checker-interface-evidence-declared.test.ts @@ -98,7 +98,7 @@ describe('native executable shared-code checker evidence', () => { const start = scenario.indexOf(marker) + marker.length; const body = scenario.slice(start, scenario.indexOf('\n });', start)); const verify = new Function('deps', 'result', new Bun.Transpiler({ loader: 'ts' }).transformSync(`const { - change, questions, expect, toolCommandTrace, reviewRecords, createHash, path, f, current, + change, questions, expect, toolCommandTrace, reviewRecords, createHash, path, f, current, sources, fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, hasTrustedSharedLibsCheck, SHARED_LIBS_ROOT, resumed, prerequisites, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt } = deps; ${body}`)); @@ -106,7 +106,7 @@ describe('native executable shared-code checker evidence', () => { .map((event: any) => ({ tool: event.message.content[0].name, input: event.message.content[0].input })) }; let checks = 0; verify({ change, questions: run.questions, expect, toolCommandTrace, reviewRecords, createHash, path, - f: run.f, current: run.current, fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, SHARED_LIBS_ROOT, + f: run.f, current: run.current, sources: run.expected.sources, fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, SHARED_LIBS_ROOT, resumed: run.resumed, prerequisites: run.prerequisites, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt, hasTrustedSharedLibsCheck: (events: unknown[], expected: any) => { checks++; diff --git a/test/shared-libs-checker-interface-evidence.test.ts b/test/shared-libs-checker-interface-evidence.test.ts index 4159746c2..3fe58e75b 100644 --- a/test/shared-libs-checker-interface-evidence.test.ts +++ b/test/shared-libs-checker-interface-evidence.test.ts @@ -144,7 +144,7 @@ describe('native executable shared-code checker evidence', () => { const start = scenario.indexOf(marker) + marker.length; const body = scenario.slice(start, scenario.indexOf('\n });', start)); const verify = new Function('deps', 'result', new Bun.Transpiler({ loader: 'ts' }).transformSync(`const { - change, questions, expect, toolCommandTrace, reviewRecords, createHash, path, f, current, + change, questions, expect, toolCommandTrace, reviewRecords, createHash, path, f, current, sources, fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, hasTrustedSharedLibsCheck, SHARED_LIBS_ROOT, resumed, prerequisites, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt } = deps; ${body}`)); @@ -153,7 +153,7 @@ describe('native executable shared-code checker evidence', () => { .map((event: any) => ({ tool: event.message.content[0].name, input: event.message.content[0].input })) }; let checks = 0; const deps = { change, questions: change === 'unchanged' ? [] : [{}], expect, toolCommandTrace, - reviewRecords, createHash, path, f: run.f, current: run.current, fixtureGit, fixtureWorkingTree, + reviewRecords, createHash, path, f: run.f, current: run.current, sources: run.expected.sources, fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, SHARED_LIBS_ROOT, resumed: run.resumed, prerequisites: run.prerequisites, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt, hasTrustedSharedLibsCheck: (events: unknown[], expected: any) => { checks++; @@ -182,6 +182,43 @@ describe('native executable shared-code checker evidence', () => { expect(() => verify({ ...deps, reviewRecords: () => [] }, native)).toThrow(); }); + test('an errored batched caller read counts only when its output holds the whole source, and the registered filtered callback passes the sources (red 40737741454)', () => { + const run = replay('filtered'); + const [file, next] = run.current.evidence_paths; + expect(call(run, 2).input.command).toBe(`cat ${shellQuote(file)}`); + const whole = result(run, 2).content; + call(run, 2).input.command = `cat ${file}; echo ===ROUTE; cat ${next}; cat missing.ts`; + Object.assign(result(run, 2), { is_error: true, content: `Exit code 1\n${whole}\n===ROUTE\ncat: missing.ts: No such file or directory` }); + expect(hasTrustedSharedLibsCheck(run.events, run.expected)).toBe(true); + expect(hasTrustedSharedLibsCheck(run.events, { ...run.expected, sources: undefined })).toBe(false); + const truncated = structuredClone(run); + result(truncated, 2).content = `Exit code 1\n${whole.slice(0, Math.floor(whole.length / 2))}`; + expect(hasTrustedSharedLibsCheck(truncated.events, truncated.expected)).toBe(false); + const single = structuredClone(run); + call(single, 2).input.command = `cat ${file}`; + expect(hasTrustedSharedLibsCheck(single.events, single.expected)).toBe(false); + const scenario = source.slice(source.indexOf("test('shared-libs-review-revalidation'")); + const marker = '}, result => {'; + const start = scenario.indexOf(marker) + marker.length; + const body = scenario.slice(start, scenario.indexOf('\n });', start)); + const verify = new Function('deps', 'result', new Bun.Transpiler({ loader: 'ts' }).transformSync(`const { + change, questions, expect, toolCommandTrace, reviewRecords, createHash, path, f, current, sources, + fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, hasTrustedSharedLibsCheck, SHARED_LIBS_ROOT, + resumed, prerequisites, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt + } = deps; ${body}`)); + const native = { events: run.events, toolCalls: run.events.filter((event: any) => event.type === 'assistant') + .map((event: any) => ({ tool: event.message.content[0].name, input: event.message.content[0].input })) }; + let passed: unknown[] = []; + verify({ change: 'filtered', questions: [{}], expect, toolCommandTrace, reviewRecords, createHash, path, f: run.f, + current: run.current, sources: run.expected.sources, fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, + SHARED_LIBS_ROOT, resumed: run.resumed, prerequisites: run.prerequisites, checkPathReviewPrerequisites, + hasPathReviewPrerequisiteReceipt, hasTrustedSharedLibsCheck: (events: unknown[], expected: any) => { + passed = Object.keys(expected.sources ?? {}); + return hasTrustedSharedLibsCheck(events, expected); + } }, native); + expect(passed.sort()).toEqual([...run.current.evidence_paths].sort()); + }); + test.each(pathKinds)('the actual %s callback consumes false proof and enforces final excluded coverage', async kind => { const run = replay(`path-${kind}`); run.kind = kind; diff --git a/test/skill-e2e-plan-design-with-ui.test.ts b/test/skill-e2e-plan-design-with-ui.test.ts index c579fac99..e0a064819 100644 --- a/test/skill-e2e-plan-design-with-ui.test.ts +++ b/test/skill-e2e-plan-design-with-ui.test.ts @@ -24,7 +24,7 @@ const ROOT = path.resolve(import.meta.dir, '..'); const FIXTURE = path.join(ROOT, 'test', 'fixtures', 'plans', 'ui-heavy-feature.md'); const designFocusBoundary = (fp: AskUserQuestionFingerprint): boolean => - fp.nativeCall?.answered === true && !fp.nativeCall.failed && fp.nativeCall.questions.some(({ question, options }) => { + fp.nativeCall?.answered === true && !fp.nativeCall.failed && fp.nativeCall.questions.some(({ header, question, options }) => { const text = question.trim().replace(/^D\d+(?:\.\d+)?\s*[—–:-]\s*/i, ''); // A target menu can mention a design system without reviewing this plan. // The source Step 0D question, or a native title (first line) that offers all @@ -46,7 +46,12 @@ const designFocusBoundary = (fp: AskUserQuestionFingerprint): boolean => // or focus on a few?") and lettered its options ("A) Review all 7 areas"). Either place // carries the offer; issue-numbered options are a finding, never Step 0D. Runs 41178131219 // and 41186376825 lettered the leading option itself ("A) All 7 passes (recommended)"). + // Every captured Step 0D (ten calls, runs 39199692993 to 41414111284) set the native header chip + // to "Focus", and no other captured question did; run 41414111284's label put the count second + // ("Full review, all 7 (recommended)"). With that header, the rating and an all-7 option suffice. const lead = text.split(/\r?\n|\s+(?=Project\/branch\/task:)/, 1)[0]!; + if (/^\s*focus\s*$/i.test(header ?? '') && rated(text) && options.length >= 2 && !issueLabeled(options) + && options.some(({ label }) => /\ball (?:7|seven)\b/i.test(label))) return true; return /^I(?:['’]ve| have) rated this plan (?:10(?:\.0+)?|[0-9](?:\.\d+)?)\/10 on design completeness\.[\s\S]*\bWant me to focus on specific areas instead(?: of all (?:7|seven))?\?/i.test(text) || (/\ball (?:7|seven) (?:design )?(?:dimensions|passes)\b/i.test(title) && /\bfocus\b/i.test(title) && (/\bdesign\b/i.test(title) || rated(text.slice(title.length))) && /\?\s*$/.test(title)) diff --git a/test/skill-e2e-shared-libs.test.ts b/test/skill-e2e-shared-libs.test.ts index ebe15eba4..d2c75ff44 100644 --- a/test/skill-e2e-shared-libs.test.ts +++ b/test/skill-e2e-shared-libs.test.ts @@ -215,6 +215,7 @@ describeE2E('Shared-code safety and review lifecycle (gate)', () => { const instructions = reviewLifecycleInstructions(f); const input = path.join(f.root, 'current-advisory.jsonl'); const { action: _priorAction, ...current } = prior; + const sources = Object.fromEntries(current.evidence_paths.map((file: string) => [file, fs.readFileSync(path.join(f.repo, file), 'utf8')])); fs.writeFileSync(input, JSON.stringify({ ...current, specialist: 'maintainability' }) + '\n'); const resumed = seedPathReviewPrerequisites(f); const prerequisites = checkPathReviewPrerequisites(f, resumed.input); @@ -248,6 +249,7 @@ describeE2E('Shared-code safety and review lifecycle (gate)', () => { coveredPaths: change === 'unchanged' ? current.evidence_paths : last.findings.find((finding: any) => finding.advisory && finding.action === 'skipped' && finding.fingerprint === current.fingerprint)?.snapshot_covered_paths, + sources, })).toBe(true); } else if (change === 'unchanged' || change === 'filtered') { // A true hash comparison cannot substitute for the trusted capture and path checks. From f1cbdbb48313957613bafb2d05346afc876fed89 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 15:15:57 -0800 Subject: [PATCH 07/10] v1.93.24.0 GSTA-253 ship judge flake: one owner for the release commit, defined skipped/kept result (#3881) * fix(ship): one owner for the release commit and a defined result for a skipped or kept version (ship judge flake, GSTA-253) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (Step 12 names the no-write kept line exactly) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): Step 15 states the no-tag rule outright (ship-workflow-clarity) 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> --- CHANGELOG.md | 7 +++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- package.json | 2 +- ship/SKILL.md | 18 +++++------------- ship/SKILL.md.tmpl | 18 +++++------------- test/fixtures/golden/agy-ship-SKILL.md | 18 +++++------------- test/fixtures/golden/claude-ship-SKILL.md | 18 +++++------------- test/fixtures/golden/codex-ship-SKILL.md | 18 +++++------------- test/fixtures/golden/factory-ship-SKILL.md | 18 +++++------------- test/ship-plan-completion-invariants.test.ts | 16 ++++++++++++++++ .../ship/capy-gsta253-ship-judge.json | 4 ++++ 12 files changed, 60 insertions(+), 81 deletions(-) create mode 100644 test/size-ledger/ship/capy-gsta253-ship-judge.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 63d0b33e8..574a8987b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.93.24.0] - 2026-11-02 + +**The /ship workflow judge loses the contradiction it cited in every recorded red sample.** + +### Fixed +- **`ship/SKILL.md` Steps 12 and 15**: Step 15 no longer tells the agent to make the VERSION/CHANGELOG commit by hand while Step 16's `gstack-ship-release finish` makes the same commit. Feature work is committed in Step 15, and `finish` commits the release files last with the co-author trailer. Step 12 now says what a `VERSION: skipped` (stamp-at-merge or changelog fragments) or `VERSION: kept` result leaves for Steps 13, 18 and 20. All six judge samples in the two recorded reds (E2E Evals 41429095451, 41445571955) named the commit conflict, and three named the undefined skipped/kept result. Three focused judge runs on the fix passed (actionability median 4 each), and none of their nine samples cited either one. + ## [1.93.23.0] - 2026-11-02 **Four tests from the 2026-11-02 flaky pass stop failing correct runs, and one more names its cause when it fails.** diff --git a/VERSION b/VERSION index f6f8c5632..8eae0b3f6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.23.0 +1.93.24.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 068816677..1e6f2ef3d 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.23.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.24.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/package.json b/package.json index 13d24a6c0..0863537e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.23", + "version": "1.93.24", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/ship/SKILL.md b/ship/SKILL.md index 43ec62eab..4970033c0 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -520,7 +520,8 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1. runs items 1 and 3–5; each decision left to you is a `STOP:` naming its rerun flag (`--bump`, `--accept-incomplete`, `--version `, `--keep-current`, `--answer`). -`ship: queue_mode stamp-at-merge` replaces items 1–4. +`VERSION: skipped` or `VERSION: kept (…; no write)` ends Step 12 with +`NEW_VERSION` unset: Step 13 follows its `NEXT:`; Steps 18 and 20 take NO_VERSION's branch. 1. **Classify state** — pure reader, never writes: ```bash @@ -641,19 +642,10 @@ Make bisectable commits; if already committed, continue to Step 16. Never create migrations may stand alone. Under 50 lines across fewer than 4 files may be one commit. 2. Order dependencies first: infrastructure → models/services → controllers/views. Each commit works alone (no broken imports or missing code). - Group VERSION + CHANGELOG + TODOS.md after the feature commits. 3. Use `: ` (feat/fix/chore/refactor/docs) and a brief body; fixing a - user-named issue adds a `Fixes: #` trailer. Only the final - VERSION/CHANGELOG commit gets the release version and co-author trailer. No Git tag: - -```bash -git commit -m "$(cat <<'EOF' -chore: bump version and changelog (vX.Y.Z.W) - -Co-Authored-By: Claude -EOF -)" -``` + user-named issue adds a `Fixes: #` trailer. Leave VERSION, CHANGELOG/fragment, TODOS.md + and the digest uncommitted: Step 16's `finish` commits them last with + `--trailer 'Co-Authored-By: Claude '`. Do not add a Git tag. --- diff --git a/ship/SKILL.md.tmpl b/ship/SKILL.md.tmpl index 68483a085..1dc0ce129 100644 --- a/ship/SKILL.md.tmpl +++ b/ship/SKILL.md.tmpl @@ -243,7 +243,8 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1. runs items 1 and 3–5; each decision left to you is a `STOP:` naming its rerun flag (`--bump`, `--accept-incomplete`, `--version `, `--keep-current`, `--answer`). -`ship: queue_mode stamp-at-merge` replaces items 1–4. +`VERSION: skipped` or `VERSION: kept (…; no write)` ends Step 12 with +`NEW_VERSION` unset: Step 13 follows its `NEXT:`; Steps 18 and 20 take NO_VERSION's branch. 1. **Classify state** — pure reader, never writes: ```bash @@ -362,19 +363,10 @@ Make bisectable commits; if already committed, continue to Step 16. Never create migrations may stand alone. Under 50 lines across fewer than 4 files may be one commit. 2. Order dependencies first: infrastructure → models/services → controllers/views. Each commit works alone (no broken imports or missing code). - Group VERSION + CHANGELOG + TODOS.md after the feature commits. 3. Use `: ` (feat/fix/chore/refactor/docs) and a brief body; fixing a - user-named issue adds a `Fixes: #` trailer. Only the final - VERSION/CHANGELOG commit gets the release version and co-author trailer. No Git tag: - -```bash -git commit -m "$(cat <<'EOF' -chore: bump version and changelog (vX.Y.Z.W) - -{{CO_AUTHOR_TRAILER}} -EOF -)" -``` + user-named issue adds a `Fixes: #` trailer. Leave VERSION, CHANGELOG/fragment, TODOS.md + and the digest uncommitted: Step 16's `finish` commits them last with + `--trailer '{{CO_AUTHOR_TRAILER}}'`. Do not add a Git tag. --- diff --git a/test/fixtures/golden/agy-ship-SKILL.md b/test/fixtures/golden/agy-ship-SKILL.md index 3b8907a44..19c18da44 100644 --- a/test/fixtures/golden/agy-ship-SKILL.md +++ b/test/fixtures/golden/agy-ship-SKILL.md @@ -521,7 +521,8 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1. runs items 1 and 3–5; each decision left to you is a `STOP:` naming its rerun flag (`--bump`, `--accept-incomplete`, `--version `, `--keep-current`, `--answer`). -`ship: queue_mode stamp-at-merge` replaces items 1–4. +`VERSION: skipped` or `VERSION: kept (…; no write)` ends Step 12 with +`NEW_VERSION` unset: Step 13 follows its `NEXT:`; Steps 18 and 20 take NO_VERSION's branch. 1. **Classify state** — pure reader, never writes: ```bash @@ -642,19 +643,10 @@ Make bisectable commits; if already committed, continue to Step 16. Never create migrations may stand alone. Under 50 lines across fewer than 4 files may be one commit. 2. Order dependencies first: infrastructure → models/services → controllers/views. Each commit works alone (no broken imports or missing code). - Group VERSION + CHANGELOG + TODOS.md after the feature commits. 3. Use `: ` (feat/fix/chore/refactor/docs) and a brief body; fixing a - user-named issue adds a `Fixes: #` trailer. Only the final - VERSION/CHANGELOG commit gets the release version and co-author trailer. No Git tag: - -```bash -git commit -m "$(cat <<'EOF' -chore: bump version and changelog (vX.Y.Z.W) - -Co-Authored-By: Claude -EOF -)" -``` + user-named issue adds a `Fixes: #` trailer. Leave VERSION, CHANGELOG/fragment, TODOS.md + and the digest uncommitted: Step 16's `finish` commits them last with + `--trailer 'Co-Authored-By: Claude '`. Do not add a Git tag. --- diff --git a/test/fixtures/golden/claude-ship-SKILL.md b/test/fixtures/golden/claude-ship-SKILL.md index 43ec62eab..4970033c0 100644 --- a/test/fixtures/golden/claude-ship-SKILL.md +++ b/test/fixtures/golden/claude-ship-SKILL.md @@ -520,7 +520,8 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1. runs items 1 and 3–5; each decision left to you is a `STOP:` naming its rerun flag (`--bump`, `--accept-incomplete`, `--version `, `--keep-current`, `--answer`). -`ship: queue_mode stamp-at-merge` replaces items 1–4. +`VERSION: skipped` or `VERSION: kept (…; no write)` ends Step 12 with +`NEW_VERSION` unset: Step 13 follows its `NEXT:`; Steps 18 and 20 take NO_VERSION's branch. 1. **Classify state** — pure reader, never writes: ```bash @@ -641,19 +642,10 @@ Make bisectable commits; if already committed, continue to Step 16. Never create migrations may stand alone. Under 50 lines across fewer than 4 files may be one commit. 2. Order dependencies first: infrastructure → models/services → controllers/views. Each commit works alone (no broken imports or missing code). - Group VERSION + CHANGELOG + TODOS.md after the feature commits. 3. Use `: ` (feat/fix/chore/refactor/docs) and a brief body; fixing a - user-named issue adds a `Fixes: #` trailer. Only the final - VERSION/CHANGELOG commit gets the release version and co-author trailer. No Git tag: - -```bash -git commit -m "$(cat <<'EOF' -chore: bump version and changelog (vX.Y.Z.W) - -Co-Authored-By: Claude -EOF -)" -``` + user-named issue adds a `Fixes: #` trailer. Leave VERSION, CHANGELOG/fragment, TODOS.md + and the digest uncommitted: Step 16's `finish` commits them last with + `--trailer 'Co-Authored-By: Claude '`. Do not add a Git tag. --- diff --git a/test/fixtures/golden/codex-ship-SKILL.md b/test/fixtures/golden/codex-ship-SKILL.md index 093381d4f..db764395a 100644 --- a/test/fixtures/golden/codex-ship-SKILL.md +++ b/test/fixtures/golden/codex-ship-SKILL.md @@ -520,7 +520,8 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1. runs items 1 and 3–5; each decision left to you is a `STOP:` naming its rerun flag (`--bump`, `--accept-incomplete`, `--version `, `--keep-current`, `--answer`). -`ship: queue_mode stamp-at-merge` replaces items 1–4. +`VERSION: skipped` or `VERSION: kept (…; no write)` ends Step 12 with +`NEW_VERSION` unset: Step 13 follows its `NEXT:`; Steps 18 and 20 take NO_VERSION's branch. 1. **Classify state** — pure reader, never writes: ```bash @@ -644,19 +645,10 @@ Make bisectable commits; if already committed, continue to Step 16. Never create migrations may stand alone. Under 50 lines across fewer than 4 files may be one commit. 2. Order dependencies first: infrastructure → models/services → controllers/views. Each commit works alone (no broken imports or missing code). - Group VERSION + CHANGELOG + TODOS.md after the feature commits. 3. Use `: ` (feat/fix/chore/refactor/docs) and a brief body; fixing a - user-named issue adds a `Fixes: #` trailer. Only the final - VERSION/CHANGELOG commit gets the release version and co-author trailer. No Git tag: - -```bash -git commit -m "$(cat <<'EOF' -chore: bump version and changelog (vX.Y.Z.W) - -Co-Authored-By: OpenAI Codex -EOF -)" -``` + user-named issue adds a `Fixes: #` trailer. Leave VERSION, CHANGELOG/fragment, TODOS.md + and the digest uncommitted: Step 16's `finish` commits them last with + `--trailer 'Co-Authored-By: OpenAI Codex '`. Do not add a Git tag. --- diff --git a/test/fixtures/golden/factory-ship-SKILL.md b/test/fixtures/golden/factory-ship-SKILL.md index 4e89c2bf5..6057b83c8 100644 --- a/test/fixtures/golden/factory-ship-SKILL.md +++ b/test/fixtures/golden/factory-ship-SKILL.md @@ -522,7 +522,8 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1. runs items 1 and 3–5; each decision left to you is a `STOP:` naming its rerun flag (`--bump`, `--accept-incomplete`, `--version `, `--keep-current`, `--answer`). -`ship: queue_mode stamp-at-merge` replaces items 1–4. +`VERSION: skipped` or `VERSION: kept (…; no write)` ends Step 12 with +`NEW_VERSION` unset: Step 13 follows its `NEXT:`; Steps 18 and 20 take NO_VERSION's branch. 1. **Classify state** — pure reader, never writes: ```bash @@ -646,19 +647,10 @@ Make bisectable commits; if already committed, continue to Step 16. Never create migrations may stand alone. Under 50 lines across fewer than 4 files may be one commit. 2. Order dependencies first: infrastructure → models/services → controllers/views. Each commit works alone (no broken imports or missing code). - Group VERSION + CHANGELOG + TODOS.md after the feature commits. 3. Use `: ` (feat/fix/chore/refactor/docs) and a brief body; fixing a - user-named issue adds a `Fixes: #` trailer. Only the final - VERSION/CHANGELOG commit gets the release version and co-author trailer. No Git tag: - -```bash -git commit -m "$(cat <<'EOF' -chore: bump version and changelog (vX.Y.Z.W) - -Co-Authored-By: Factory Droid -EOF -)" -``` + user-named issue adds a `Fixes: #` trailer. Leave VERSION, CHANGELOG/fragment, TODOS.md + and the digest uncommitted: Step 16's `finish` commits them last with + `--trailer 'Co-Authored-By: Factory Droid '`. Do not add a Git tag. --- diff --git a/test/ship-plan-completion-invariants.test.ts b/test/ship-plan-completion-invariants.test.ts index be3191b0d..4a292edd4 100644 --- a/test/ship-plan-completion-invariants.test.ts +++ b/test/ship-plan-completion-invariants.test.ts @@ -185,6 +185,22 @@ describe('ship/SKILL.md — Plan Completion gate invariants', () => { expect(entry).not.toMatch(/Step 15\.[012]/); }); + test('one owner per release commit and a defined result for a version Step 12 does not write', () => { + // Value: protects=the ship workflow judge's 6/6 cited conflict (Step 15 committed release metadata by hand while Step 16 finish also commits it) and its 3/6 undefined stamp-at-merge/kept result; fails_when=Step 15 regains a manual release commit or Step 12 drops the skipped/kept route; why_new=no pin covered commit ownership; seam=none; no_claim=judge scores + const entry = fs.readFileSync(path.join(SHIP_DIR, 'SKILL.md'), 'utf8'); + const commit = entry.slice(entry.indexOf('## Step 15:'), entry.indexOf('## Step 16:')); + expect(commit).not.toContain('git commit'); + expect(commit.replace(/\s+/g, ' ')).toMatch(/TODOS\.md and the digest uncommitted: Step 16's `finish` commits them last/); + const finish = entry.slice(entry.indexOf('## Step 16:'), entry.indexOf('### 1. Finish writers')); + expect(finish).toContain("makes Step 15's item-3 commit"); + const version = entry.slice(entry.indexOf('## Step 12:'), entry.indexOf('1. **Classify state**')).replace(/\s+/g, ' '); + expect(version).not.toContain('replaces items 1–4'); + expect(version).toContain('`VERSION: skipped`'); + expect(version).toContain('`VERSION: kept (…; no write)` ends Step 12 with `NEW_VERSION` unset'); + expect(version).toContain('ALREADY_BUMPED derives it in item 1'); + expect(version).toContain("Steps 18 and 20 take NO_VERSION's branch"); + }); + test('a rejected push stops publication and routes changed content back through verification', () => { const entry = fs.readFileSync(path.join(SHIP_DIR, 'SKILL.md'), 'utf8'); const push = entry.slice(entry.indexOf('## Step 17:'), entry.indexOf('## Step 20:')); diff --git a/test/size-ledger/ship/capy-gsta253-ship-judge.json b/test/size-ledger/ship/capy-gsta253-ship-judge.json new file mode 100644 index 000000000..9158b7122 --- /dev/null +++ b/test/size-ledger/ship/capy-gsta253-ship-judge.json @@ -0,0 +1,4 @@ +{ + "bytes": 4, + "why": "Steps 12 and 15 name who makes the release commit and what a skipped or kept version leaves for Steps 13, 18 and 20 (ship workflow judge flake, GSTA-253)" +} From ffc8c13ca0bcba8cead4ce2e02705270b14b5951 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 15:26:07 -0800 Subject: [PATCH 08/10] v1.93.25.0 docs: /setup-browser-cookies warns about the eMudhra trust gap (#3338) (#3871) * docs(setup-browser-cookies): warn that the bundled Chromium still trusts the eMudhra intermediates (#3338) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: drop the issue number from model-read skill text (archaeology lint) 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> --- CHANGELOG.md | 7 +++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- package.json | 2 +- setup-browser-cookies/SKILL.md | 2 ++ setup-browser-cookies/SKILL.md.tmpl | 2 ++ test/helpers/manual-judge-review-fixture.ts | 8 ++++++++ 7 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 574a8987b..fe1f9ae4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.93.25.0] - 2026-11-02 + +**/setup-browser-cookies now warns that the fallback browser still trusts the compromised eMudhra intermediates (#3338).** BROWSER.md carried this warning since v1.91.239.0, but the cookie import skill did not, so an agent importing signed-in cookies into the bundled Chromium never told the user. + +### Changed +- **`setup-browser-cookies/SKILL.md.tmpl`**: before the import, the agent tells the user once that gstack's bundled Chromium still accepts certificates from those intermediates, says not to use the imported sessions on a network they do not trust until the Playwright bump, and points to `BROWSER.md`, **TLS trust: the bundled Chromium's root store**. + ## [1.93.24.0] - 2026-11-02 **The /ship workflow judge loses the contradiction it cited in every recorded red sample.** diff --git a/VERSION b/VERSION index 8eae0b3f6..041b4c23a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.24.0 +1.93.25.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 1e6f2ef3d..01e83b302 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.24.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.25.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/package.json b/package.json index 0863537e1..cc3f39d04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.24", + "version": "1.93.25", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index c193d6c3f..003faa78f 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -175,6 +175,8 @@ Use the picker when any of answers 1-2 is still open; it asks for them. Use dire $B goto "" ``` +Tell the user once, before the import: gstack's headless browser trusts the root store built into gstack's bundled Chromium, which still accepts certificates from the compromised eMudhra intermediates. Until gstack moves to a Playwright release that drops them, don't use the imported sessions on a network the user does not trust. That same root's `BROWSER.md`, **TLS trust: the bundled Chromium's root store**, has the details and the check. + ## 4. Import Picker (the user confirms the browser, profile and domains there, ticks verification if they asked for it, then tells you when done; the link works once, for five minutes, so rerun the command if it expires): diff --git a/setup-browser-cookies/SKILL.md.tmpl b/setup-browser-cookies/SKILL.md.tmpl index b8c81bfa1..eddad8ae4 100644 --- a/setup-browser-cookies/SKILL.md.tmpl +++ b/setup-browser-cookies/SKILL.md.tmpl @@ -51,6 +51,8 @@ Use the picker when any of answers 1-2 is still open; it asks for them. Use dire $B goto "" ``` +Tell the user once, before the import: gstack's headless browser trusts the root store built into gstack's bundled Chromium, which still accepts certificates from the compromised eMudhra intermediates. Until gstack moves to a Playwright release that drops them, don't use the imported sessions on a network the user does not trust. That same root's `BROWSER.md`, **TLS trust: the bundled Chromium's root store**, has the details and the check. + ## 4. Import Picker (the user confirms the browser, profile and domains there, ticks verification if they asked for it, then tells you when done; the link works once, for five minutes, so rerun the command if it expires): diff --git a/test/helpers/manual-judge-review-fixture.ts b/test/helpers/manual-judge-review-fixture.ts index b5f610f52..7a7874cd5 100644 --- a/test/helpers/manual-judge-review-fixture.ts +++ b/test/helpers/manual-judge-review-fixture.ts @@ -61,10 +61,18 @@ const LATER_COOKIE_ORDER_EDITS: Array<[string, string]> = [ ["# view the page; only --verify-auth proves sign-in", "# confirm the intended account"], ]; +// The #3338 trust warning (GSTA-84) added one paragraph after step 3's goto +// (two lines longer). Removing it first lets the GSTA-49 reversal match. +const LATER_TRUST_PARAGRAPH = "Tell the user once, before the import: gstack's headless browser trusts the root store built into gstack's bundled Chromium, which still accepts certificates from the compromised eMudhra intermediates. Until gstack moves to a Playwright release that drops them, don't use the imported sessions on a network the user does not trust. That same root's `BROWSER.md`, **TLS trust: the bundled Chromium's root store**, has the details and the check.\n\n"; + export function approvedCookieWorkflowSource(source: string): string { let removedLines = 0; let removedEntrypointLines = 0; let reordered = source; + if (reordered.includes(LATER_TRUST_PARAGRAPH)) { + removedEntrypointLines += LATER_TRUST_PARAGRAPH.split('\n').length - 1; + reordered = reordered.replace(LATER_TRUST_PARAGRAPH, ''); + } for (const [later, earlier] of LATER_COOKIE_ORDER_EDITS) { if (!reordered.includes(later)) continue; removedEntrypointLines += later.split('\n').length - earlier.split('\n').length; From 2a801cf6f993d6e0ccf87ce03ad4d3f2c01eb4c2 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 15:36:06 -0800 Subject: [PATCH 09/10] fix(pr-select): USING_GBRAIN_WITH_GSTACK.md is free-only; screen-reuse lookback filed in TODOS (GST-1027) (#3888) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ TODOS.md | 4 ++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- package.json | 2 +- scripts/harness-version.json | 2 +- scripts/test-pr-profile.ts | 1 + test/paid-free-boundary.test.ts | 13 +++++++++++++ 8 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1f9ae4c..f04d7368a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.93.26.0] - 2026-11-02 + +**Editing the GBrain setup guide no longer runs every paid eval case on a PR screen.** The PR eval selector didn't recognize `USING_GBRAIN_WITH_GSTACK.md`, so it treated the file as an unknown dependency and fell back to the full suite. Two screens, #3727 and #3802, paid about $67 for it. No skill prompt or paid case reads the guide (only the README, `docs/gbrain-quick-tour.md` and free tests do), so it now selects nothing. A root doc that a paid prompt quotes, such as `ETHOS.md`, still selects its cases. The train gate's full census still runs every case, so coverage is unchanged (GST-1027). + ## [1.93.25.0] - 2026-11-02 **/setup-browser-cookies now warns that the fallback browser still trusts the compromised eMudhra intermediates (#3338).** BROWSER.md carried this warning since v1.91.239.0, but the cookie import skill did not, so an agent importing signed-in cookies into the bundled Chromium never told the user. diff --git a/TODOS.md b/TODOS.md index 9cb7eb0e1..c509fdb03 100644 --- a/TODOS.md +++ b/TODOS.md @@ -2,6 +2,10 @@ ## NEXT PRIORITY +### P3: screen verdict reuse lookback (filed 2026-11-02, GST-1027) + +- **Screen reuse sees only the newest 50 evals.yml dispatches.** `listGateRuns` (`scripts/recover-receipts.ts`) lists dispatched screens as one unfiltered page (`event=workflow_dispatch`, `per_page=50`), shared with every PR's screens and every train. At week-B traffic (220 dispatches from 2026-10-25 to 11-02, about 27 a day) that is under two days, and nothing says when a PR's earlier screen fell off the page: `gate-reuse find` prints "no earlier executed gate on this PR bound a verdict" and the full screen runs. No miss yet: the longest gap among the week-B re-screens was 44 dispatches (#3607). Page the listing until runs are older than the PR's oldest candidate head (or filter by `created>=`), and print a truncation line when the last page is full. A missed run only costs a screen, never a wrong verdict. **Effort:** S. **Priority:** P3. + ### P3: gstack-clean follow-ups (filed 2026-10-31, GSTA-226 E2, #2627) - **Codex templates delete their own scratch.** The `/codex` review, challenge and consult blocks leave `codex-err-*`, `codex-resp-*`, `codex-out-*` and `codex-prompt-*` mktemp files under `TMP_ROOT` (which falls back to `.gstack/tmp`) and the `codex-prompt.*` and `codex-focus.*` free-text files in `.gstack/tmp`; `gstack-clean` removes them after 7 days. Delete each file at the end of its block instead. Skill prose for every user, so it needs its own review. **Effort:** S. **Priority:** P3. diff --git a/VERSION b/VERSION index 041b4c23a..7886afbb2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.25.0 +1.93.26.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 01e83b302..a74951af6 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.25.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.26.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/package.json b/package.json index cc3f39d04..8d1fff930 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.25", + "version": "1.93.26", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/scripts/harness-version.json b/scripts/harness-version.json index a4bbf78c8..012cc08ed 100644 --- a/scripts/harness-version.json +++ b/scripts/harness-version.json @@ -17,7 +17,7 @@ "scripts/lib/published-text.ts": {"blob":"3f2e4d616b5dd0204035710c66b64d49055c5714","since":6}, "scripts/lib/shard-engine.ts": {"blob":"dc714d7d897aed185c0ec3d2c92ad22d954039e5","since":6,"nonBehavioral":"createShardSandbox drops the Capy launch markers (GSTACK_SESSION_KIND, GSTACK_CAPY, GSTACK_EPHEMERAL, GSTACK_INSTALL_VERDICT) from shard environments; CI never sets them, so no case's input or verdict changes there, and on a Capy machine fixtures now see the session kind they set themselves instead of the installer's"}, "scripts/test-paid-shards.ts": {"blob":"5439d0a8f6e1569fa7c924af20d67c30c81d5d30","since":6,"nonBehavioral":"credential rejections are classed infra instead of assertion: no case's pass or fail changes; only failed trials whose provider rejected the run's key move class, so no rerun spends on a rejected key"}, - "scripts/test-pr-profile.ts": {"blob":"44ae88bebf8f716f70a3bd836c8bedb03e6653e8","since":6,"nonBehavioral":"FREE_ONLY_PR_FILES gains scripts/lib/shard-jobs.ts (free-runner module) and .github/workflows/windows-smoke.yml (a workflow no paid case reads); both change only which PR paths select paid cases, never how a case runs or is judged"}, + "scripts/test-pr-profile.ts": {"blob":"dd0ca92bcf8493fd5c1e9d399cc3bc74ec70c469","since":6,"nonBehavioral":"FREE_ONLY_PR_FILES gains USING_GBRAIN_WITH_GSTACK.md (a root guide no skill prompt or paid fixture reads); it changes only which PR paths select paid cases, never how a case runs or is judged (GST-1027)"}, "scripts/test-strict-output.ts": {"blob":"01185d20dc9761b04b9b2f751ec98664fe0eaf93","since":6}, "test/helpers/agent-sdk-runner.ts": {"blob":"d66dac458b8c13250973e550edf8087d4044cd0b","since":6,"nonBehavioral":"Cost attribution only (GSTA-150 PR 2): a display-label record carries cost_case_id from runSkillTest's testName and the report binds its cost by it while the failed record and failure class still come from the named records (test/paid-report-cost.test.ts pins both); an aborted Agent SDK session logs its streamed usage as streamed_cost_usd, used only when a case has no record; a listed model-free case with no record or session is a known $0. No case's execution, outcome, failure class, rerun eligibility or panel verdict changes, so no pass-rate series resets. Also: runAgentSdkTest now validates systemPrompt before starting; every live caller already passed the claude_code preset, so the prompt the model sees is unchanged and no verdict can change"}, "test/helpers/auq-substance-panel.ts": {"blob":"4881260ef793668e4963a062db6d06a5e1f7a40f","since":6}, diff --git a/scripts/test-pr-profile.ts b/scripts/test-pr-profile.ts index 44ae88beb..dd0ca92bc 100644 --- a/scripts/test-pr-profile.ts +++ b/scripts/test-pr-profile.ts @@ -245,6 +245,7 @@ export const FREE_ONLY_PR_FILES = [ 'scripts/paid-test-durations.json', 'scripts/ubicloud/**', 'capy/skill/**', // The vendored Capy skill (Drive bootstrapper): run by Capy agents, read by no paid case; test/capy-skill-wrapper.test.ts covers it. + 'USING_GBRAIN_WITH_GSTACK.md', // The /setup-gbrain guide: README, docs/gbrain-quick-tour.md and free tests read it; no skill prompt or paid fixture does (GST-1027). 'captain/templates/**', // The /captain crew templates (CREW.md, merge-ask.md, slot-call.md, …): copied into a Captain's drive by people and Captains, read by no paid case; free test/captain-skill.test.ts covers them (#3499). 'tsconfig.test.json', // Read only by `tsc -p tsconfig.test.json` (typecheck:test); Bun's runtime reads tsconfig.json. '.gitignore', // Changes which untracked files git reports, never tracked content a case reads. diff --git a/test/paid-free-boundary.test.ts b/test/paid-free-boundary.test.ts index 284bb981f..b69cb18cb 100644 --- a/test/paid-free-boundary.test.ts +++ b/test/paid-free-boundary.test.ts @@ -125,6 +125,19 @@ describe('paid/free dependency boundary', () => { expect(ethos.selection.e2e!.length).toBeLessThan(10); }); + // Value: protects=a root guide that only README, docs and free tests read keeps a screen narrow, and a root doc a paid prompt quotes still selects beside it; fails_when=a guide-only PR restores every gate case as a "real unknown dependency" (GST-1027: screens 41246728073 and 41321112436 ran the full suite for it); + // why_new=the README regex in knownNonBehaviorFile names only five root docs; seam=FREE_ONLY_PR_FILES in scripts/test-pr-profile.ts; no_claim=the train gate's full census + test('USING_GBRAIN_WITH_GSTACK.md selects no paid case; ETHOS.md beside it keeps its selection (GST-1027)', () => { + const guide = 'USING_GBRAIN_WITH_GSTACK.md'; + expect(fs.existsSync(path.join(ROOT, guide))).toBe(true); + const alone = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: [guide] }); + expect([alone.coverage?.mode, alone.coverage?.unknownFiles, alone.selection]).toEqual(['pr', [], { e2e: [], judges: [] }]); + const ethos = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: ['ETHOS.md'] }); + expect(ethos.selection.e2e!.length).toBeGreaterThan(0); + const both = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: [guide, 'ETHOS.md'] }); + expect([both.coverage?.mode, both.selection]).toEqual(['pr', ethos.selection]); + }); + test('only audited free-only dependencies avoid unknown-dependency fallback', () => { for (const file of [...FREE_ONLY_PR_FILES, 'scripts\\test-free-shards.ts']) { const result = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: [file] }); From e85005482addbd902d2557a3deb964b29b0335bb Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 15:44:52 -0800 Subject: [PATCH 10/10] v1.93.27.0 feat(pregate): pregate-literal markers waive one deliberate old-value line (#3490) (#3891) * feat(pregate): pregate-literal markers waive one deliberate old-value line (#3490) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(changelog): fragment named after the branch Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (GST-634: a bare */ is not a reason; every marker on and above the line counts) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: a reason may not start with * (GST-634: **/ and * */ closers are not reasons) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: a pregate-literal reason cannot start with * (GST-634 docs audit) 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> --- CHANGELOG.md | 11 +++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- docs/pregate.md | 12 +++++++ docs/troubleshooting.md | 7 +++- lib/pregate/checks/literals.ts | 43 +++++++++++++++++++++--- package.json | 2 +- test/pregate-selection.test.ts | 60 +++++++++++++++++++++++++++++++++- 8 files changed, 129 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f04d7368a..ec57837bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.93.27.0] - 2026-11-02 + +**A test can keep an old value on purpose without turning off `literals` (#3490).** When a minimum version goes up, the test that checks the old version is refused has to keep the old value, and pregate failed it with `PREGATE_LITERALS`. The only way out was `warn: ["literals"]`, which turns the check off for every constant. Now that one line can carry `// pregate-literal: NAME= `. + +### Added +- **`pregate-literal:` markers.** A marker on the flagged line, or on the comment line directly above it, waives that line when it names the changed constant and its old value and gives a reason. Every other line in the file is still checked, and text inside a string is not a marker. The policy has no waiver key, so neither a base nor a pushed `pregate.json` can widen it. +- **Waivers are listed.** `literals` prints each waived line as `waived: :: ()` and adds `marker added on this branch` when the merge-base file lacks the marker, so a reviewer sees every new waiver. + +### Changed +- **docs/pregate.md** and **docs/troubleshooting.md** (`PREGATE_LITERALS`) describe the marker. + ## [1.93.26.0] - 2026-11-02 **Editing the GBrain setup guide no longer runs every paid eval case on a PR screen.** The PR eval selector didn't recognize `USING_GBRAIN_WITH_GSTACK.md`, so it treated the file as an unknown dependency and fell back to the full suite. Two screens, #3727 and #3802, paid about $67 for it. No skill prompt or paid case reads the guide (only the README, `docs/gbrain-quick-tour.md` and free tests do), so it now selects nothing. A root doc that a paid prompt quotes, such as `ETHOS.md`, still selects its cases. The train gate's full census still runs every case, so coverage is unchanged (GST-1027). diff --git a/VERSION b/VERSION index 7886afbb2..000a12e4a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.26.0 +1.93.27.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index a74951af6..e9707255b 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.26.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.27.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/docs/pregate.md b/docs/pregate.md index 9128fa093..641cd22e5 100644 --- a/docs/pregate.md +++ b/docs/pregate.md @@ -228,6 +228,18 @@ Two stages with separate clocks, both printed: elsewhere in the file is not a consumer. A call-site name such as `attempts` or `limit` is an ordinary word, so for it the file must also name the changed module. + A test that keeps the old value on purpose, such as the refused input after a + minimum version goes up, marks that one line with a comment on it or on the + comment line directly above it: + `// pregate-literal: MIN_RUNTIME='1.4.0' refused input below the minimum` + (`#` and `/*` comments work too). The marker waives only that line, and only + when it names the changed constant and its old value and gives a reason (a + reason cannot start with `*`, so a bare `*/`, `**/` or `* */` closer is not + one); text inside a string is not a marker. Every other line in the file is still + checked. The check lists each waived line as + `waived: :: ()`, adding `marker added on + this branch` when the merge-base file lacks the marker. The policy has no + waiver key, so neither a base nor a pushed `pregate.json` can widen it. - `timing`: added or changed test lines that compare an elapsed wall-clock or CPU time against a bound, or sleep and then assert, print diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d78dfb536..86bb8a1d1 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -3707,7 +3707,12 @@ directory, or under a scratch name (`zz-*`, `tmp*`, `scratch*`). appears with its old value in a test file the branch did not update — nightly- and Heavy-only files included. -**Fix.** Update every listed `file:line`, or keep the old value. +**Fix.** Update every listed `file:line`, or keep the old value. When a test +keeps the old value on purpose (the refused input after a minimum version goes +up), mark that line: `// pregate-literal: NAME= ` on the line +or on the comment line directly above it. The marker waives only that line, and +the check lists it as `waived:`. Don't use `warn: ["literals"]` for this, since +that turns the check off for every constant. ### `PREGATE_NO_LANE` diff --git a/lib/pregate/checks/literals.ts b/lib/pregate/checks/literals.ts index 2e1121a71..b34311e09 100644 --- a/lib/pregate/checks/literals.ts +++ b/lib/pregate/checks/literals.ts @@ -14,6 +14,23 @@ import { isTestFile, listRepoFiles, scansLiterals } from '../import-graph'; import type { CheckDef, CheckResult, PregateContext } from '../types'; export interface ChangedLiteral { name: string; old: string; new: string; file: string } +/** A consumer line a `pregate-literal:` marker waived; `added` when the merge-base file lacks the marker. */ +export interface LiteralWaiver { at: string; reason: string; added: boolean } + +const MARKER = /pregate-literal:[ \t]*([A-Za-z_]\w*)=(\S+)[ \t]+([^\s*].*?)[ \t]*(?:\*+\/[ \t]*)?$/; + +/** The marker in this line's trailing comment (`//`, `#` or `/*` outside a string), or null. */ +function markerIn(line: string): { marker: string; name: string; value: string; reason: string } | null { + const at = line.indexOf('pregate-literal:'); + if (at < 0) return null; + const before = line.slice(0, at); + const open = /(?:\/\/|#|\/\*)[ \t]*$/.exec(before); + if (!open) return null; + const code = before.slice(0, open.index); + if (['\'', '"', '`'].some(q => code.split(q).length % 2 === 0)) return null; + const m = MARKER.exec(line.slice(at)); + return m ? { marker: line.slice(at).trim(), name: m[1]!, value: m[2]!, reason: m[3]! } : null; +} const CONST_PATTERNS: RegExp[] = [ /^\s*export\s+(?:const|let|var)\s+([A-Z][A-Z0-9_]{1,})\s*(?::\s*[^=]+?)?\s*=\s*(.+?);?\s*$/, @@ -83,8 +100,12 @@ function unquote(v: string): string { return /^['"`]/.test(v) ? v.slice(1, -1) : * has no new-value row: the branch extended the table. A table it did not * extend, a file that already held a new-value row, and an unreadable base * still count, and the scan goes on past skipped rows to the first real consumer. + * + * A consumer line is waived by a `pregate-literal: NAME= ` comment on that line or + * on the comment line directly above it, naming this constant and its old value (#3490: the refused + * input after a minimum-version bump). Each waiver goes to `waived`; the scan goes on to the next line. */ -export function consumersOf(repoRoot: string, testFiles: readonly string[], change: ChangedLiteral, baseText?: (rel: string) => string | null | undefined): string[] { +export function consumersOf(repoRoot: string, testFiles: readonly string[], change: ChangedLiteral, baseText?: (rel: string) => string | null | undefined, waived?: LiteralWaiver[]): string[] { const value = unquote(change.old); const valueRe = /^-?\d/.test(value) ? new RegExp(`(? new RegExp(`^[ \\t]*\\[[ \\t]*${/^['"`]/.test(raw) ? `['"\`]${escape(unquote(raw))}['"\`]` : escape(raw)}[ \\t]*,`, 'm'); @@ -131,7 +152,15 @@ export function consumersOf(repoRoot: string, testFiles: readonly string[], chan if (extended === undefined) { const base = baseText?.(rel); extended = base === null || (base !== undefined && !newRowRe.test(base)); } return extended; }; - for (let i = 0; i < lines.length; i++) if (valueRe.test(lines[i]!) && (!nameRequired || oldRowRe.test(lines[i]!) || named(i)) && !skip(i)) { hits.push(`${rel}:${i + 1}`); break; } + const waiver = (i: number) => { + const m = [markerIn(lines[i]!), i > 0 && comment(lines[i - 1]!) ? markerIn(lines[i - 1]!) : null].find(c => c && c.name === change.name && unquote(c.value) === value); + if (!m) return false; + const before = baseText?.(rel); + waived?.push({ at: `${rel}:${i + 1}`, reason: m.reason, added: typeof before !== 'string' || !before.includes(m.marker) }); + return true; + }; + const carries = (i: number) => { const m = markerIn(lines[i]!); return valueRe.test(m ? lines[i]!.slice(0, lines[i]!.indexOf('pregate-literal:')) : lines[i]!); }; + for (let i = 0; i < lines.length; i++) if (carries(i) && (!nameRequired || oldRowRe.test(lines[i]!) || named(i)) && !skip(i) && !waiver(i)) { hits.push(`${rel}:${i + 1}`); break; } } return hits; } @@ -150,19 +179,23 @@ function run(ctx: PregateContext): CheckResult { if (!changes.length) return { id: 'literals', stage: 'preflight', status: 'pass', inputs, detail: `no exported constant or named timeout literal changed in ${candidates.length} modified source file(s)` }; const testFiles = listRepoFiles(ctx.repoRoot).filter(f => scansLiterals(f) && !f.startsWith('bin/')); const lines: string[] = []; + const notes: string[] = []; for (const c of changes) { + const waived: LiteralWaiver[] = []; const hits = consumersOf(ctx.repoRoot, testFiles, c, rel => { const from = ctx.touched.find(t => t.path === rel)?.renamedFrom ?? rel; const r = spawnSync('git', ['show', `${ctx.mergeBase}:${from}`], { cwd: ctx.repoRoot, encoding: 'utf8', timeout: 30_000, maxBuffer: 64 * 1024 * 1024, env: ctx.env }); if (r.status === 0) return r.stdout; const ls = spawnSync('git', ['ls-tree', ctx.mergeBase, '--', from], { cwd: ctx.repoRoot, encoding: 'utf8', timeout: 30_000, env: ctx.env }); return ls.status === 0 && !ls.stdout.trim() ? null : undefined; - }); + }, waived); + for (const w of waived) notes.push(`waived: ${w.at}: ${c.name} ${c.old} (${w.reason}${w.added ? '; marker added on this branch' : ''})`); ctx.explain.push(`literals: ${c.file} ${c.name} ${c.old} → ${c.new}: ${hits.length} test file(s) still carry ${c.old}`); for (const h of hits) lines.push(`${h}: ${c.name} still ${c.old} (now ${c.new} in ${c.file})`); } - if (lines.length) return { id: 'literals', stage: 'preflight', status: 'fail', inputs, lines, code: 'PREGATE_LITERALS', detail: `${lines.length} test location(s) still carry an old literal`, fix: 'update each listed test to the new value (nightly- and Heavy-only files included), or keep the old constant' }; - return { id: 'literals', stage: 'preflight', status: 'pass', inputs, detail: `${changes.length} changed literal(s), no test still carries an old value (${testFiles.length} test-tree files scanned)` }; + const waivers = notes.length ? `; ${notes.length} line(s) waived by pregate-literal markers` : ''; + if (lines.length) return { id: 'literals', stage: 'preflight', status: 'fail', inputs, lines: [...lines, ...notes], code: 'PREGATE_LITERALS', detail: `${lines.length} test location(s) still carry an old literal${waivers}`, fix: "update each listed test to the new value (nightly- and Heavy-only files included), keep the old constant, or, when the old value is the test's input on purpose, mark that line with `// pregate-literal: NAME= `" }; + return { id: 'literals', stage: 'preflight', status: 'pass', inputs, ...(notes.length ? { lines: notes } : {}), detail: `${changes.length} changed literal(s), no test still carries an old value (${testFiles.length} test-tree files scanned)${waivers}` }; } export const literalsCheck: CheckDef = { id: 'literals', stage: 'preflight', tier: 1, downgradable: true, needsRepoCommands: false, run }; diff --git a/package.json b/package.json index 8d1fff930..048a570d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.26", + "version": "1.93.27", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/pregate-selection.test.ts b/test/pregate-selection.test.ts index 8ed20cebf..2069493f7 100644 --- a/test/pregate-selection.test.ts +++ b/test/pregate-selection.test.ts @@ -14,7 +14,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { EXIT } from '../lib/headless-artifacts'; -import { changedLiterals, constantsIn, consumersOf } from '../lib/pregate/checks/literals'; +import { changedLiterals, constantsIn, consumersOf, type LiteralWaiver } from '../lib/pregate/checks/literals'; import { parseSummary, planRuns } from '../lib/pregate/checks/lanes'; import { DEFAULT_CONFIG, validateConfig } from '../lib/pregate/config'; import { isDocsEmptyLane } from '../lib/pregate/docs-lane'; @@ -298,6 +298,51 @@ describe('literals (pure)', () => { expect(consumersOf(root, Object.keys(files), { name: 'attempts', old: '1', new: '3', file: 'lanes/review-noloss/judge-run.ts' })).toEqual(['tests/judge-run.test.ts:3']); fs.rmSync(root, { recursive: true, force: true }); }); + // Value: protects=a test that keeps an old value on purpose (the refused input after a minimum-version bump) can waive that one line + // with a `pregate-literal: NAME=value reason` comment, while every other old-value line, a marker for another constant or value, + // a marker with no reason (a bare `*/`, `**/` or `* */` closer is not a reason) and marker text inside a string still count, and a line marked + // for one constant can still be waived for another by the comment line above; fails_when=the waiver widens past its line or a stray + // marker clears a real consumer (#3490, gbrain#7690); why_new=the only escape was warn: ["literals"] for every constant; + // seam=none; no_claim=the policy file (no key exists; see the config test) + test('consumersOf: a pregate-literal marker waives only its own line, for its own constant and old value, with a reason (#3490)', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-literals-waiver-')); + const nl = String.fromCharCode(10); + const files: Record = { + 'test/runtime.test.ts': [ + "import { MIN_RUNTIME, checkRuntime } from '../src/runtime';", + "expect(checkRuntime('1.4.0')).toBe('refused'); // pregate-literal: MIN_RUNTIME='1.4.0' refused input below the minimum", + "// pregate-literal: MIN_RUNTIME=1.4.0 the oldest runtime we still name in the error", + "expect(message('1.4.0')).toContain('upgrade');", + "expect(MIN_RUNTIME).toBe('1.4.0');", + ''].join(nl), + 'test/other-name.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); // pregate-literal: MAX_RUNTIME='1.4.0' wrong constant", ''].join(nl), + 'test/other-value.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); // pregate-literal: MIN_RUNTIME='1.3.9' wrong value", ''].join(nl), + 'test/no-reason.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); // pregate-literal: MIN_RUNTIME='1.4.0'", ''].join(nl), + 'test/in-string.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "const src = \"expect(v('1.4.0')); // pregate-literal: MIN_RUNTIME='1.4.0' looks waived\"; check('1.4.0');", ''].join(nl), + 'test/two-above.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "// pregate-literal: MIN_RUNTIME='1.4.0' too far above", '', "expect(checkRuntime('1.4.0')).toBe('refused');", ''].join(nl), + 'test/all-waived.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); # pregate-literal: MIN_RUNTIME='1.4.0' refused input", ''].join(nl), + 'test/block-no-reason.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); /* pregate-literal: MIN_RUNTIME='1.4.0' */", ''].join(nl), + 'test/block-stars.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); /* pregate-literal: MIN_RUNTIME='1.4.0' **/", ''].join(nl), + 'test/block-star-space.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); /* pregate-literal: MIN_RUNTIME='1.4.0' * */", ''].join(nl), + 'test/block-stars-reason.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "expect(checkRuntime('1.4.0')).toBe('refused'); /* pregate-literal: MIN_RUNTIME='1.4.0' refused input **/", ''].join(nl), + 'test/two-constants.test.ts': ["import { MIN_RUNTIME } from '../src/runtime';", "// pregate-literal: MIN_RUNTIME='1.4.0' lower bound of the refused range", "expect(inRange('1.4.0', '2.0.0')).toBe(false); /* pregate-literal: MAX_RUNTIME='2.0.0' upper bound */", ''].join(nl), + }; + for (const [rel, text] of Object.entries(files)) { fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); fs.writeFileSync(path.join(root, rel), text); } + const change = { name: 'MIN_RUNTIME', old: "'1.4.0'", new: "'1.4.4'", file: 'src/runtime.ts' }; + const waived: LiteralWaiver[] = []; + const base = (rel: string) => rel === 'test/runtime.test.ts' ? files[rel]!.split(nl).slice(0, 2).join(nl) : null; + expect(consumersOf(root, Object.keys(files), change, base, waived)).toEqual([ + 'test/runtime.test.ts:5', 'test/other-name.test.ts:2', 'test/other-value.test.ts:2', 'test/no-reason.test.ts:2', 'test/in-string.test.ts:2', 'test/two-above.test.ts:4', 'test/block-no-reason.test.ts:2', 'test/block-stars.test.ts:2', 'test/block-star-space.test.ts:2', + ]); + expect(waived).toEqual([ + { at: 'test/runtime.test.ts:2', reason: 'refused input below the minimum', added: false }, + { at: 'test/runtime.test.ts:4', reason: 'the oldest runtime we still name in the error', added: true }, + { at: 'test/all-waived.test.ts:2', reason: 'refused input', added: true }, + { at: 'test/block-stars-reason.test.ts:2', reason: 'refused input', added: true }, + { at: 'test/two-constants.test.ts:3', reason: 'lower bound of the refused range', added: true }, + ]); + fs.rmSync(root, { recursive: true, force: true }); + }); test('consumersOf: a history row is skipped only when this branch added the new-value row to the same table; other old-value lines still count (GST-344)', () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-literals-history-')); const nl = String.fromCharCode(10); @@ -500,6 +545,19 @@ describe('gstack-pregate (bin)', () => { expect(r.err).toContain('(PREGATE_LITERALS)'); }); + // Value: protects=end to end, a marked refused-input line passes literals and the run names the waiver, flagging one this branch + // added; a branch policy cannot add a literals waiver; fails_when=the check ignores markers, hides a waiver, or the policy grows a + // literals key (#3490); why_new=no waiver existed; seam=none; no_claim=gstack-gate-diff tagging + test('#3490: a pregate-literal marker passes literals for its line and the run lists the waiver as added on this branch', () => { + const repo = fixture(); + commit(repo, { 'test/nightly/retries.test.js': "const test = require('node:test');\nconst assert = require('node:assert');\nconst { MAX_RETRIES, retry } = require('../../lib/retry.js');\n// pregate-literal: MAX_RETRIES=3 the old cap is the input the guard must refuse\ntest('refuses 3', () => assert.throws(() => retry(() => 0, { MAX_RETRIES: 3 })));\n" }); + commit(repo, { 'lib/retry.js': "exports.MAX_RETRIES = 4;\nexports.TIMEOUT_MS = 5000;\nexports.retry = (fn) => fn();\n", 'test/retry.test.js': "const test = require('node:test');\nconst assert = require('node:assert');\nconst { MAX_RETRIES } = require('../lib/retry.js');\ntest('retries four times', () => assert.strictEqual(MAX_RETRIES, 4));\n" }); + const r = pregate(repo, ['--stage', 'preflight', '--only', 'literals']); + expect(r.status).toBe(EXIT.ok); + expect(r.out).toContain('literals: waived: test/nightly/retries.test.js:5: MAX_RETRIES 3 (the old cap is the input the guard must refuse; marker added on this branch)'); + expect(validateConfig({ ...BASE_PREGATE, literals: { allow: [{ name: 'MAX_RETRIES', value: '3' }] } }).map(e => e.path)).toEqual(['literals']); + }); + test('GST-344: a history table the branch extends passes literals through the merge-base read; one it does not extend fails', () => { const history = (...ns: number[]) => ["const { MAX_RETRIES } = require('../lib/retry.js');", 'const versions = [', ...ns.map(n => ` [${n}, 'digest-${n}'],`), '];', ''].join('\n'); const repo = fixture({ baseFiles: { 'test/history.test.js': history(2, 3) } });