From 8a0c1bce2e67eed70453178141fe635e92a10f79 Mon Sep 17 00:00:00 2001 From: sim Date: Fri, 7 Aug 2026 10:09:18 -0400 Subject: [PATCH] test(#3149): correct stale tdd_mode assertion and drop a marker-token collision Two failures from the remote runner on 654b2cc10, both introduced here. 1. tests/mcp-catalog-parity.install.test.cjs greps emitted workflow files for the bare substring 'gsd:section' and treats its presence in a composed file as an un-stripped marker. debug.md's new Step 0 prose documented the field by writing that token literally, so the emitted file tripped the gate even though the parser correctly ignored it as prose. Reworded to 'applicability-section markers'. Same class as DEFECT.PROMPT-INJECTION-SCAN-COLLISION. 2. tests/debug-session-management.test.cjs asserted debug.md contains the literal 'config-get workflow.tdd_mode'. That call is gone. The invariant it protected -- tdd_mode comes from the workflow.tdd_mode key, never a bare top-level one -- is unchanged, and now has a stronger behavioral home: init-debug.test.cjs row A9 asserts a bare key is ignored and the canonical key is honored, whatever the read mechanism. Co-Authored-By: Claude Opus 5 --- gsd-core/workflows/debug.md | 2 +- tests/debug-session-management.test.cjs | 12 +++++++++-- .../3149-init-debug-entry-point.json | 2 +- tests/init-debug.test.cjs | 20 +++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/gsd-core/workflows/debug.md b/gsd-core/workflows/debug.md index 2fb1effaf..497c24653 100644 --- a/gsd-core/workflows/debug.md +++ b/gsd-core/workflows/debug.md @@ -28,7 +28,7 @@ One round-trip carries everything this workflow needs (#3149 — this call repla - `debug_dir` — an absolute path anchored on `project_root` (#2376: `debug_file_path` values handed to the spawned `gsd-debug-session-manager` must resolve regardless of that subagent's own cwd, which may differ from the orchestrator's — build them as `{debug_dir}/{slug}.md`, never a bare `.planning/debug/...` literal). - `debugger_model` — the resolved model for `gsd-debugger` spawns; used as `{debugger_model}` below and governed by the model-omission rule in step 2. - `tdd_mode` — used as `{TDD_MODE}` in the session parameter blocks below. -- `section_manifest` — `null` today, because this workflow carries no `` markers. **When it is `null`, read this workflow in full.** When it is present, read only the files named in its `read` array. `null` and an empty `included` array are NOT the same: `null` means "no manifest for this workflow", an empty `included` means "nothing applies". +- `section_manifest` — `null` today, because this workflow declares no applicability-section markers of its own. **When it is `null`, read this workflow in full.** When it is present, read only the files named in its `read` array. `null` and an empty `included` array are NOT the same: `null` means "no manifest for this workflow", an empty `included` means "nothing applies". **If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated. diff --git a/tests/debug-session-management.test.cjs b/tests/debug-session-management.test.cjs index 8936cf72d..29e450b1f 100644 --- a/tests/debug-session-management.test.cjs +++ b/tests/debug-session-management.test.cjs @@ -76,13 +76,21 @@ describe('debug session management implementation', () => { path.join(process.cwd(), 'gsd-core/workflows/debug.md'), 'utf8' ); + // #3149: tdd_mode now arrives on the `init.debug` bundle rather than a + // `config-get` call in this workflow, so the old literal-call assertion no + // longer describes reality. The invariant it protected is unchanged and is + // asserted at its new home: `cmdInitDebug` resolves `config.workflow`'s + // `tdd_mode`, never a bare top-level key — proven behaviorally in + // tests/init-debug.test.cjs ('honors workflow.tdd_mode, ignores a bare + // top-level tdd_mode'). What must remain true HERE is only that this + // workflow never reintroduces a bare-key read of its own. assert.ok( !content.includes('config-get tdd_mode'), 'debug.md must not use bare "tdd_mode" key — use "workflow.tdd_mode" to match every other consumer' ); assert.ok( - content.includes('config-get workflow.tdd_mode'), - 'debug.md must read tdd_mode via the "workflow.tdd_mode" key' + content.includes('tdd_mode'), + 'debug.md must still consume tdd_mode (now from the init.debug bundle)' ); }); diff --git a/tests/emitted-drift-acks/3149-init-debug-entry-point.json b/tests/emitted-drift-acks/3149-init-debug-entry-point.json index 46a939cab..3612e2f6f 100644 --- a/tests/emitted-drift-acks/3149-init-debug-entry-point.json +++ b/tests/emitted-drift-acks/3149-init-debug-entry-point.json @@ -1,6 +1,6 @@ { "version": 1, "paths": { - "debug.md": "#3149 (prerequisite for #3128, ADR-1671 admission gate 2): debug.md gains a dedicated `init.debug` entry point (cmdInitDebug) and its Step 0 collapses THREE separate `gsd_run` round-trips into one. Removed: `gsd_run query state.load` (line 20, replaced in place), the `resolve-model gsd-debugger --pick model` block, and the `config-get workflow.tdd_mode --raw` block — 2 prose lead-ins and 2 fenced code blocks in total. Added: a 6-bullet extraction list documenting the bundle's fields (`commit_docs`, the now TOP-LEVEL `response_language`, `debug_dir`, `debugger_model`, `tdd_mode`, `section_manifest`) plus the `section_manifest: null` -> read-everything rule and the null-vs-empty-included distinction. Net SOURCE growth is +607 bytes (20,555 -> 21,162): the bullets that document one bundle cost more bytes than the two shell round-trips they replace, which is the intended trade — the round-trips cost three subprocess spawns at RUN time on every /gsd:debug invocation. No `` marker is added and WHEN_VOCABULARY is unchanged at 29, so the composeWorkflow emission path is byte-identical in shape to before; only this file's own content moved. The `{TDD_MODE}` and `{debugger_model}` placeholders in the session-parameter blocks (lines ~137-145, ~226-234) are deliberately left byte-identical — they now resolve from the init bundle instead of shell variables, and rewording them would ripple into tests/fix-2257-debug-nonterminal-resume.test.cjs and tests/debug-session-manager-commit.test.cjs for no behavioral gain." + "debug.md": "#3149 (prerequisite for #3128, ADR-1671 admission gate 2): debug.md gains a dedicated `init.debug` entry point (cmdInitDebug) and its Step 0 collapses THREE separate `gsd_run` round-trips into one. Removed: `gsd_run query state.load` (line 20, replaced in place), the `resolve-model gsd-debugger --pick model` block, and the `config-get workflow.tdd_mode --raw` block — 2 prose lead-ins and 2 fenced code blocks in total. Added: a 6-bullet extraction list documenting the bundle's fields (`commit_docs`, the now TOP-LEVEL `response_language`, `debug_dir`, `debugger_model`, `tdd_mode`, `section_manifest`) plus the `section_manifest: null` -> read-everything rule and the null-vs-empty-included distinction. Net SOURCE growth is +618 bytes (20,555 -> 21,173): the bullets that document one bundle cost more bytes than the two shell round-trips they replace, which is the intended trade — the round-trips cost three subprocess spawns at RUN time on every /gsd:debug invocation. No applicability-section marker is added and WHEN_VOCABULARY is unchanged at 29, so the composeWorkflow emission path is byte-identical in shape to before; only this file's own content moved. The `{TDD_MODE}` and `{debugger_model}` placeholders in the session-parameter blocks (lines ~137-145, ~226-234) are deliberately left byte-identical — they now resolve from the init bundle instead of shell variables, and rewording them would ripple into tests/fix-2257-debug-nonterminal-resume.test.cjs and tests/debug-session-manager-commit.test.cjs for no behavioral gain." } } diff --git a/tests/init-debug.test.cjs b/tests/init-debug.test.cjs index 0f28dca57..2c6d5e654 100644 --- a/tests/init-debug.test.cjs +++ b/tests/init-debug.test.cjs @@ -157,6 +157,26 @@ describe('init.debug resolves identically to the three calls it replaces (matrix assert.equal(viaInit.tdd_mode, true, 'the root value must be inherited, not lost'); assert.equal(String(viaInit.tdd_mode), viaConfigGet.output.trim()); }); + + test('honors workflow.tdd_mode, ignores a bare top-level tdd_mode (row A9)', () => { + // The invariant tests/debug-session-management.test.cjs used to guard by + // grepping debug.md for `config-get workflow.tdd_mode`. Asserted here + // behaviorally instead, which is strictly stronger: a bare top-level key + // must NOT be honored, whatever the read mechanism. + writeConfig(tmpDir, { tdd_mode: true }); + assert.equal( + runJson(['init', 'debug'], tmpDir).tdd_mode, + false, + 'a bare top-level tdd_mode key must be ignored — the canonical key is workflow.tdd_mode' + ); + + writeConfig(tmpDir, { workflow: { tdd_mode: true } }); + assert.equal( + runJson(['init', 'debug'], tmpDir).tdd_mode, + true, + 'the canonical workflow.tdd_mode key must be honored' + ); + }); }); // ─── Group B: bundle shape ─────────────────────────────────────────────────