* test(#4254): sequential executor root pin — failing-first regression + matrix The new suite executes the shipped supplied-root-pin guard against real git fixtures (drifted primary-checkout cwd halts before the write and the FATAL names both roots; matching cwd permits it; unexpanded/empty pins halt; normalization forms; submodule and sibling boundaries; metacharacter quoting; drive-letter form gate) and locks the dispatch contract across execute-phase.md, its sequential-root-pin step fragment, and worktree-path-safety.md. The #2772 per-plan serialization assertion retargets to the fragment that now carries those rules (ADR-857 Phase 6 ceiling), plus the host-step wiring. * fix(#4254): pin sequential executor to the orchestrator's validated root Sequential-mode dispatch told the executor to self-derive PROJECT_ROOT from its own cwd; every existing guard is worktree-mode-only or self-referential, so an executor spawned with a drifted cwd committed onto the wrong checkout silently. - worktree-path-safety.md step 0p: mode-agnostic supplied-root pin guard, composed by the orchestrator at build time with the literal $ORCHESTRATOR_WT (git-vs-git comparison on both sides — representation-safe on Windows, the #4296 lesson), fail-closed on empty/unexpanded pins, registered-submodule allowance, warn-and-proceed only when the dispatch carries no pin block. - execute-phase.md sequential branch: build-time embed of the bound <project_root_pin> via the new execute-phase/steps/sequential-root-pin.md fragment (ADR-857 Phase 6 frozen ceiling — the host step cannot grow; the wave serialization rules move with the fragment, verbatim in substance) plus the per-write/commit pin instruction in <sequential_execution>. Worktree-mode dispatch untouched (its self-derived toplevel IS correct there). - INVENTORY rows (5 locales) + INVENTORY-MANIFEST + install-tree goldens regenerated for the new fragment; changeset added. * chore(#4254): backfill changeset PR number * fix(#4254): accept backslash-separated Windows drive pins CI on windows-latest showed every permit-path test failing with "Actual root: <none>": pins composed from Node's path.join arrive in the backslash drive form (C:\Users\RUNNER~1\...), which the guard's absolute-form gate rejected before the cwd-side root was ever computed — a legitimate matching pin could never pass. The gate now accepts either separator ([A-Za-z]:[\\/]); git -C resolves both forms (and 8.3 short names) to the same canonical toplevel, so the git-vs-git comparison is unaffected. Form-gate tests cover the emitted (C:/…) and produced (C:\…) spellings plus short names. * fix(#4254): portable drive-form gate for MSYS bash The bracket class [\\/] that accepted backslash drive pins parses inconsistently on MSYS bash (the Windows CI leg still rejected C:\ pins — every permit-path test red with "Actual root: <none>"). Replace it with standard pattern escaping outside brackets: [A-Za-z]:/*|[A-Za-z]:\\* — the escape form is version- and build-portable. Verified across all forms: both drive spellings accepted; bare "C:", relative, empty, and unexpanded rejected. * fix(#4254): runtime-generated backslash comparator + self-describing FATAL The Windows CI legs failed every #4254 permit-path row with 'Actual root: <none>' across two prior pattern spellings ([\\/] and \\*). Stage misattribution: <none> appears whenever the FATAL fires BEFORE the cwd-side capture assigns ACTUAL_ROOT — the absolute-form gate was what fired. Mechanism: the test harness spawns bash -c <script> through the Windows command-line boundary; that round-trip applies one extra shell-quoting pass with double-quote semantics — a backslash written twice in the script text arrives halved, while a lone backslash survives (the pin displays intact; row 9's pure-bash gate independently showed the halved pattern rejecting C:\ while C:/ still passed its surviving arm). On windows-latest every pin carries backslashes (os.tmpdir() is the 8.3 short form C:\Users\RUNNER~1\...), so the gate ate every pin before the actual root was ever computed. Fix, robust by construction: - the drive-form gate generates its backslash comparator at RUNTIME (BS=$(printf '\134'); match [A-Za-z]:"$BS"*) — the shipped guard now contains no doubled backslash anywhere, enforced by a regression assertion on the extracted guard text; - the FATAL self-describes: Guard stage (pin-unbound / form-gate / actual-capture / pinned-capture / root-mismatch) plus a Diagnostic line carrying git's own stderr for capture failures and both compared values for mismatches — future platform failures name their stage in the log; - row 9's hand-rolled duplicate case gate (transit-fragile copy, #4296 Minor 1 duplication smell) is replaced by driving the SHIPPED guard and asserting the stage; rows 2/4 pin the new stage machinery. Validated on darwin across drift/match/relative/unbound/empty/bare-drive/ forward-and-backslash drive forms, each also re-run under a simulated Windows transit (every doubled backslash halved) with identical outcomes. * fix(#4254): close the empty-comparator fail-open seam in the drive-form gate Self-review of the runtime-generated backslash comparator: if printf's octal escape ever returned empty, the drive arm [A-Za-z]:"$BS"* would widen to drive-RELATIVE pins (C:foo) — the construction's one theoretical fail-open path. Fail closed with a self-describing diagnostic instead of trusting the shell's printf. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# Sequential root pin (#4254)
|
||||
|
||||
Apply response_language to all user-facing prose — narration between tool calls, status
|
||||
updates, progress notes, and findings included; preserve code, paths, and identifiers.
|
||||
|
||||
Read and execute this fragment from `execute-phase.md`'s **Sequential mode** branch before
|
||||
composing any sequential dispatch prompt. It owns the sequential root-pin build-time embed,
|
||||
the `<required_reading>` root substitution, and the wave serialization rules.
|
||||
|
||||
## Root pin — ORCHESTRATOR build-time embed (#4254; NOT a sub-agent runtime step)
|
||||
|
||||
Before this dispatch, read `gsd-core/references/worktree-path-safety.md` step 0p
|
||||
("Supplied-root pin") and copy its guard template into this prompt inside a
|
||||
`<project_root_pin>` block, substituting `{PINNED_ROOT}` with the literal value of
|
||||
`$ORCHESTRATOR_WT` resolved at execute_waves entry, shell-single-quoted per that section's
|
||||
composition contract — the dispatched prompt must carry the bound, runnable guard verbatim;
|
||||
do not pass this instruction through in its place.
|
||||
|
||||
In this dispatch's `<required_reading>`, also replace the self-derivation line
|
||||
`PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)` with
|
||||
`PROJECT_ROOT='<the same literal $ORCHESTRATOR_WT>'` — a sequential executor must never
|
||||
re-derive its root from its own (possibly drifted) cwd. This substitution is sequential-mode
|
||||
ONLY: worktree-mode dispatches keep the self-derived line — an isolated executor's own
|
||||
toplevel IS its correct (and intentionally different) worktree, and substituting the
|
||||
orchestrator's root there would break every worktree-mode dispatch.
|
||||
|
||||
## Wave serialization (moved verbatim from the host step — ADR-857 Phase 6 ceiling, #1168)
|
||||
|
||||
When worktrees are disabled for a plan (per-plan or project-level), that plan's executor runs
|
||||
on the main working tree. If **any** plan in the current wave dropped to sequential mode,
|
||||
execute the affected plan(s) **one at a time** to avoid concurrent writes to the main working
|
||||
tree — plans in the same wave that retained worktree isolation can still run in parallel
|
||||
alongside the sequential ones, but two non-worktree plans in the same wave must serialize.
|
||||
When the project-level `USE_WORKTREES=false`, all plans in the wave serialize regardless of
|
||||
the `PARALLELIZATION` setting.
|
||||
Reference in New Issue
Block a user