Commit Graph

4 Commits

Author SHA1 Message Date
Tom Boucher
ed819aa4d6 fix(#4379): make the TDD RED-commit pathspec language-agnostic (#4715)
* fix(#4379): make the TDD RED pathspec language-agnostic

The pathspec IS this gate's definition of "a test file", and it listed
only JS/TS conventions. Go's *_test.go matches none of them, so a commit
adding a failing Go test was invisible, RED_COMMIT came back empty, and
every behaviour-adding task halted with TDD GATE TRIPPED. references/tdd.md
already advertises `go test ./...` and `cargo test` as supported, so the
gate was refusing to see tests the docs promised to support.

Two corrections, both measured against a seeded repo rather than reasoned:

- cover the conventions tdd.md advertises: *_test.go, test_*.py,
  *_test.py, *_test.exs, *_spec.rb, *_test.rb.
- drop the `**/` prefix. It does NOT match a path with no directory
  component, so a root-level foo.test.js was invisible even in the
  language the gate did support -- a second defect the report did not
  mention. A bare glob matches at every depth.

Deliberately not widened to ordinary source: a pathspec matching
implementation files would make the gate pass on any in-scope commit,
which is worse than tripping wrongly. Rust is a known gap for that exact
reason and is now documented rather than silently broken.

Driving the shipped pathspec against a seeded repo: before, 0 of 7
language/root conventions matched; after, 7 of 7, with src/impl.go and
src/lib.rs correctly unmatched in both.

Emitted-Drift-Ack-Growth: execute-phase.md — the widened pathspec plus the comment recording why it must not cover ordinary source
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(#4379): drive the shipped RED pathspec against a real repo

The existing row pinned the pathspec as a literal string, which the fix
makes stale. Re-point it, and add behavioural coverage that EXTRACTS the
pathspec from the shipped workflow and runs git log with it against a
seeded repo -- re-typing the pattern into the test would only assert that
two copies of a string agree.

Rows: every advertised convention is visible; a root-level test file is
not invisible (the half the report missed); existing JS/TS still matches;
implementation files never match, so the gate can still trip; and Rust
inline #[test] stays out of reach, asserted rather than left silent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#4379): add changeset fragment

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#4379): be honest about the widened pathspec's cost

Adversarial review: the rationale comment claimed the change was safe
without naming what it gives up. `*.spec.*` can match a non-test file
carrying the word (api.spec.json, openapi.spec.yaml), which lets the gate
pass on a commit touching only that. Not new -- `**/*.spec.*` already
matched those at any nested path, so dropping `**/` extends the same
class to the root -- but the comment should say so rather than imply
the widening is free.

Also: the tdd.md list named Ruby and Elixir as recognised while the
detection step above it enumerates only Node/Python/Go/Rust. Say
explicitly that the gate's pathspec is wider than the detected project
types, and why that is deliberate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(#4379): drop a vacuous row, fold its point into a real one

Adversarial review: the "rust inline #[test] remains out of reach" row
asserted src/lib.rs never matches -- the identical assertion to the
"implementation files never match" row directly below it. It exercised
nothing about #[test] semantics and would have passed against almost any
fix, so it was coverage theatre.

Delete it and move its rationale into the row that already carries the
assertion, where it explains WHY the Rust gap follows from that row
holding: the two cannot both be satisfied by a path-based gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#4379): move the pathspec rationale out of a size-capped file

execute-phase.md sits under a FROZEN byte ceiling (ADR-857 Phase 6,
#1168: < 93600). The 20-line rationale comment I added pushed it to
93933 and tripped seven tests, all the same ceiling. Base was 92371, so
the budget was 1229 bytes and the comment spent 1481.

Keep six lines at the call site -- what the pathspec is, why it is not
wider, where to read more -- and move the trade-off detail to
references/tdd.md, which has no ceiling. That is the right home anyway:
the workflow is loaded into context on every run, the reference is read
on demand.

92882 bytes, 718 under. Pathspec line byte-identical; re-proved
behaviour after the trim: 0/7 conventions before, 7/7 after, no
implementation files matched either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#4379): refresh the compact-content benchmark baseline

execute-phase.md changed size, so the committed baseline drifted. The
script's own contract makes it a report that exits 0, but the test
asserts the committed baseline is up to date -- refresh via --write,
which is what the drift message instructs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#4379): backfill the changeset PR number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 00:57:08 -04:00
Tom Boucher
db4d8a9bae fix(#4619): execute-phase computes decimal/N-segment phase numbers without breaking shell arithmetic (#4644)
* fix(#4619): execute-phase computes decimal/N-segment phase numbers without breaking shell arithmetic

$((10#${PHASE_NUMBER})) is a hard bash/zsh syntax error when PHASE_NUMBER is
decimal (01.1, from an inserted phase) or N-segment (23.1.2) — neither is
valid shell-arithmetic syntax at all, and the failed expansion aborts the
rest of the snippet in a non-interactive shell. safe_resume_gate runs
unconditionally before trusting STATE.md or dispatching any executor, so
execute-phase failed at its own gate before the first executor on any
decimal phase, regardless of workflow.tdd_mode. Regression from #4194.

Fixes all 4 sites: safe_resume_gate and the TDD gate in
workflows/execute-phase.md, the completion-signal spot-check fallback in
workflows/execute-phase/steps/completion-reconciliation.md, and the
executor gate validation example in references/tdd.md. Each now zero-strips
only the leading integer segment into a *_INT variable (via %%.* / #
parameter expansion — always valid shell syntax regardless of what follows)
and keeps the remainder as an escaped-dot string for the anchored commit-
scope regex, exactly as issue #4619 verified in both bash and zsh. A plain
integer phase (12, 01) computes byte-identically to before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(#4619): pin the decimal/N-segment fix and characterize the pre-fix bug

Behavioral coverage via real bash execution: the old $((10#01.1)) form
throws (characterizes the bug, matching the issue's own reproduction); the
new form resolves 01.1 -> 1\.1 and 23.1.2 -> 23\.1\.2, unchanged for plain
integers (12 -> 12, 01 -> 1); the resulting anchored ERE matches
feat(01.1-03):/test(1.1-3): and correctly rejects feat(01-03):,
feat(01.2-03):, feat(011-03):, feat(12-03): for a decimal phase — mirroring
issue #4619's own verified table exactly. Updates
safe-resume-gate-anchoring.test.cjs's 4 existing source-text assertions
(one per site) to the new fixed text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#4634): refine the shell-arith drift detector to distinguish safe from unsafe arithmetic

With #4619's fix in place, the guard's original "ban $((10#... outright,
match any occurrence" was too blunt: it flagged a comment merely mentioning
the pattern in prose, the now-safe $((10#$PHASE_INT)) arithmetic on an
already-%%.*-stripped integer, and the always-safe plan-id arithmetic
(plan ids are plain integers, never decimal). Refines the detector to skip
full-line comments and to only flag a captured variable/placeholder name
that contains "phase" and does NOT end in _INT/_int — the naming convention
the #4619 fix establishes at all four sites for "already reduced to a safe
integer." A plan-id variable was never phase-number arithmetic in the first
place and is excluded on the same basis.

This closes epic #4634's D6 ("lint-phase-id-drift... passes with no new
exemptions") and D7 ("a decimal and N-segment phase id survive an
end-to-end execute-phase selection without error") for real — the guard now
reports zero violations across all five .cts/.md rules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: regenerate conformance-tier manifests for the new test file

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(#4619): cover the plain-padded-integer near-miss matrix too

Review found the anchored-ERE near-miss coverage only exercised the
decimal case (PHASE_NUMBER=01.1); issue #4619's own worked table also
verifies the plain padded-integer case (01 -> PHASE_N=1) against its own
near-miss set (matches 01-03, rejects 01.1-03/011-03/12-03). Adds the
missing assertion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(#4619): add Fixed changeset

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4619): correct JS backslash-escaping in safe-resume-gate anchoring test

The test's string-literal assertions for the PHASE_FRAC//./\\.} pattern wrote
only 2 backslash characters in JS source, which single-quoted-string parsing
collapses to 1 real backslash at runtime -- but the workflow/reference files
actually contain 2 raw backslash bytes at that position (needed so bash's
${var//pattern/replacement} produces the correct single-backslash output).
Write 4 backslash characters in the JS source at all 4 occurrences so the
runtime string matches the files' real bytes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#4619): refresh the committed compact-content benchmark baseline

The new PHASE_INT/PHASE_FRAC arithmetic lines added to
gsd-core/workflows/execute-phase.md shifted its committed compaction-ratio
baseline. Regenerate via `node scripts/benchmark-compact-content.cjs --write`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(#4619): note the safe_resume_gate arithmetic growth in the test header

The emitted-attribution gate flags execute-phase.md growing 91253 -> 91846
bytes (593 bytes). The growth is the fix: the safe_resume_gate and TDD RED
block now derive PHASE_INT/PHASE_FRAC before computing PHASE_N, so a
decimal/N-segment phase number (e.g. 01.1, 2.3.1) zero-strips its leading
integer segment via base-10 arithmetic instead of forcing the whole value
through $((10#...)) and hitting a hard shell syntax error on the first dot.

A blank line previously separated the Emitted-Drift-Ack-Growth trailer from
the Co-Authored-By trailer below it, which splits git's trailer-block
detection: only the last contiguous non-blank run of Key: Value lines at the
end of a commit message is recognized as trailers, so the growth ack was
silently read as ordinary body text and the differential-attribution gate
failed with the growth unacknowledged. Joining the two trailers into one
contiguous block fixes it.

Emitted-Drift-Ack-Growth: execute-phase.md — adds PHASE_INT/PHASE_FRAC derivation to the safe_resume_gate and TDD RED commit-scope grep so a decimal/N-segment phase number zero-strips its leading integer segment via base-10 arithmetic instead of failing on a non-numeric value (#4619)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(#4208): replace chmod-based restore-failure injection with a root-proof git shim

`tests/commit-files-deletion.test.cjs`'s two restore-failure tests simulated
an unwritable index via a `post-index-change` hook running `chmod a-w` on
the git dir. That relies on the OS enforcing the *owner's own* permission
bits against itself, which uid 0 (a routine identity inside this repo's
Docker-based gsd-test benches) does not: every DAC check short-circuits true
for root, so the write the chmod meant to block silently succeeds, the
restore comes back clean, and the disclosure/rollback behavior under test
never actually gets exercised.

This is CLAUDE.md's own named anti-pattern for I/O-failure injection
("Cross-platform test IO-failure injection" — chmod tricks fail under root
Docker/CI). It is confirmed as the actual root cause here, not a production
defect: `src/commands.cts`'s `restoreRemovedEntries`/rollback-disclosure
logic (added by #4253, merged just before this run) was hand-traced and
manually reproduced end to end on an unprivileged workstation against a
freshly built `gsd-core/bin/lib/commands.cjs`, and it already produces
exactly the `staging_failed` + "could not be restored" / "could NOT be
restored during rollback" results both tests assert. The other
`post-index-change`-based tests in this file (a `sleep` to force a timeout;
a real `update-index` to flip a restored entry's mode) are unaffected
because neither depends on a permission check — consistent with only the
two chmod-based tests failing on the real remote run.

Replaces the chmod fixture with a fake `git` placed ahead of the real one on
PATH that fails only `update-index --add --cacheinfo` — the one call the
restore makes — unconditionally, regardless of privilege level. Every other
git invocation execs straight through to the real binary, so the rest of
each scenario (`rm --cached`, the restore's own `ls-files` verification,
etc.) is exercised exactly as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#4619): backfill changeset pr number to 4644

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4619): feed the bash fixture script via stdin, not argv, to fix Windows CI

Passing the script as a `-c "<script>"` argv element made it subject to
Windows' CreateProcess command-line argument encoding, which silently
dropped the escaped-dot backslashes before bash ever saw them (observed on
PR #4644's windows-latest CI shard: `1\.1` came back as `1.1`). Feeding the
same script via stdin instead removes argv entirely from the transport, so
there is nothing for Windows to re-encode. POSIX behavior is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 15:47:14 -04:00
Tom Boucher
2cf119f57e fix(#4217): reconcile artifacts before classifying an abnormally-ended executor (#4442)
* fix(#4217): reconcile artifacts before classifying abnormal ends

* test(#4217): pin the completion-reconciliation contract

* chore(#4217): regen derived inventory and install-tree fixtures

* test(#4217): follow the #4003 anchoring pins into the reconciliation fragment

Emitted-Drift-Ack-Growth: execute-phase.md — the runtime-neutral completion-reconciliation pointer, the two Codex wait-rule bindings, and the step-7 reconcile-first gate net +33 bytes over the extracted fallback block (#4217)

* chore(#4217): add changeset fragment

* chore(#4217): backfill PR number in changeset fragment

---------

Co-authored-by: sim <sim@local>
2026-09-06 18:57:38 -04:00
Tom Boucher
7c52344284 fix(#4003): anchor the safe-resume gate's plan-scope greps to the milestone (#4194)
* test(#4003): safe_resume_gate must grep an anchored padding-tolerant scope

* fix(#4003): anchor the resume-gate scope greps and bound them to the milestone tag

Emitted-Drift-Ack-Growth: execute-phase.md — #4003 rewrites three commit-scope greps (safe_resume_gate, TDD RED, completion spot-check) to anchored zero-pad-tolerant regexes with a milestone tag bound; growth is the fix itself

* test(#4003): align shape assertions with the implemented gate text

* fix: bump fast-uri past GHSA-jqff-g426-hqxp (transitive, advisory reddened next)

* fix(#4003): bound the TDD RED grep to the milestone and fix tdd.md's example greps

* test(#4003): the gate pin tracks the anchored scope grep

* fix(#4003): trim the gate rationale to hold the 93400 margin ceiling

* test(#4003): the RED-grep pin tracks the milestone-bounded invocation

* chore(#4003): changeset for the anchored resume-gate scope

* chore(#4003): backfill changeset pr number

---------

Co-authored-by: sim <sim@local>
2026-09-02 13:45:14 -04:00