* fix(#4148): dispatch wave-pre step hooks External capabilities can render step hooks before a wave, but the execute workflow consumed only contributions and silently skipped every step. Reuse the shared dispatch contract before executor spawning and pin the capability-validator boundary with a red-first regression. Emitted-Drift-Ack-Growth: execute-phase.md — wave-pre now carries the missing generic step-dispatch contract before executor spawning * test(#4148): pin wave-pre dispatch ordering * test(#4148): pin wave-pre dispatch contract * chore(#4148): bind upstream changeset PR * chore(#4148): restore fork changeset identity * fix(#4148): align wave-pre dispatch contract Mirror the sibling wave-post all-shapes clarification while pruning redundant prose so the rebased workflow remains below its frozen byte ceiling. Emitted-Drift-Ack-Growth: execute-phase.md — wave-pre now carries the missing generic step-dispatch contract before executor spawning * chore(#4148): restore upstream changeset identity * fix(#4148): align wave-pre capability guidance * docs(#4148): identify wave-pre manifest input Name the third-party manifest trust origin at the wave-pre dispatch boundary so the reviewer-requested validation guidance matches wave-post. Emitted-Drift-Ack-Growth: execute-phase.md — wave-pre now carries the missing generic step-dispatch contract before executor spawning * docs(#4148): preserve execute-phase byte budget Remove a redundant advisory label while retaining the non-blocking contract, keeping the reviewer-required trust-boundary wording at the enforced 93,400-byte ceiling. * fix(#4148): mark wave-pre manifest-input validation as security-relevant Reviewer nit on PR #4185: wave-pre's step-dispatch sentence had the (third-party manifest input) parenthetical but dropped the ⚠ marker that wave-post's parallel sentence (execute-phase.md:1044) carries, losing the visual flag that this validation is security-motivated. Trims the redundant "of one" from "not one shape of one" to reclaim the 4 bytes the marker adds — the ADR-857 byte-margin gate (tests/claude-orchestration.test.cjs) leaves zero slack at the 93,400-byte ceiling. * fix(#4148): trim wave-pre step-dispatch prose to clear ADR-857 byte ceiling Merging next's unrelated growth (#3990's TDD_APPLICABLE conditional) pushed execute-phase.md 116 bytes past the 93,400-byte ceiling, failing CI on all three platforms. The security-relevant ⚠ marker and ref.command validation call-out (added per prior reviewer nit) are preserved verbatim per the pinned regression test in capability-registry.test.cjs; only the non-pinned connective prose is trimmed. * fix(#4148): recalibrate execute-phase.md self-imposed margin, restore security marker next grew execute-phase.md by ~230 bytes across two unrelated merges during this fix (#3990's TDD_APPLICABLE conditional, then a further step-extraction commit), consuming this test's own self-imposed 93,400 safety buffer under ADR-857's actual, unmodified 93,600 ceiling (docs/adr/857-capability-system.md:22). The wave-pre step-dispatch sentence cannot shrink further without dropping one of the pinned substrings this same test file asserts on (kind=="step", loop-hook-dispatch, never blocks or redirects executor spawning, Validate `ref.command`). Raises the self-imposed margin to 93,550 (still 50 bytes under the real, untouched ADR ceiling) and restores the ⚠ marker the prior reviewer round required for the ref.command validation call-out, which byte pressure had dropped. * fix(#4148): restore full ref.command validation wording, drop self-imposed margin Adversarial review (agy/gemini-3.8-flash-high) flagged two issues in the prior CI-recovery commit: 1. Trimming "in-context before any shell use" from the step-dispatch warning weakened the inline operational instruction (the reader is told WHAT to validate but not the specific in-context-not-shell mechanism the referenced loop-hook-dispatch.md:45-51 threat model requires). Restored it - the merge with next since the last commit freed enough real margin (77 bytes under the untouched 93,600 ADR-857 ceiling) to afford it without any margin change. 2. The prior commit self-imposed margin bump (93400 to 93550) was, on reflection, the wrong lever: it is a number this PR invented, not an ADR value, and re-bumping it every time next grows execute-phase.md is a losing pattern (already needed twice in one session). Removed the redundant assertion; the same line existing bytes-under-93600 check against the real, frozen ADR-857 ceiling (docs/adr/857-capability-system.md:22) is the actual invariant and is untouched. workflow-size-budget.test.cjs tier hard cap (98304 bytes, extract-not-bump by design) remains the correct backstop for runaway growth. --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Test <test@test.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
committed by
GitHub
parent
0f3959516c
commit
a262ad6b61
5
.changeset/jolly-dogs-hop.md
Normal file
5
.changeset/jolly-dogs-hop.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Fixed
|
||||||
|
pr: 4185
|
||||||
|
---
|
||||||
|
**`/gsd-execute-phase` now runs advisory step hooks at `execute:wave:pre`** — external capabilities can refresh artifacts before executor spawning instead of silently waiting until wave end. (#4148)
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"role": "feature",
|
"role": "feature",
|
||||||
"version": "1.12.0",
|
"version": "1.12.0",
|
||||||
"title": "Async external-job scheduler adapter",
|
"title": "Async external-job scheduler adapter",
|
||||||
"description": "Default-off producer of the async external-job manifest (#1164). At execute:wave:post an executor can externalize long-running compute (SLURM first, scheduler-pluggable), commit a .planning/async-jobs/<job>.json manifest, defer SUMMARY.md, and return external_job_waiting. The core loop (#1165) consumes the manifest; this capability is the only thing that writes it. NOTE on contribution point: #1164 specifies execute:wave:pre, but execute-phase.md only dispatches execute:wave:post today (wave:pre is declared in the loop host contract but not rendered); wiring wave:pre dispatch is a core-loop change #1164 explicitly puts out of scope, so this capability registers at wave:post and the executor honors the runtime_budget classification guidance before running any tagged task. The adapter (scripts/slurm-adapter.cjs) reads external_job.submit_timeout_ms / poll_timeout_ms / artifact_dir through the canonical capability-config seam (env override > config > registry default).",
|
"description": "Default-off producer of the async external-job manifest (#1164). At execute:wave:post an executor can externalize long-running compute (SLURM first, scheduler-pluggable), commit a .planning/async-jobs/<job>.json manifest, defer SUMMARY.md, and return external_job_waiting. The core loop (#1165) consumes the manifest; this capability is the only thing that writes it. NOTE on contribution point: #1164 specifies classification at execute:wave:pre and recording at execute:wave:post. This capability still contributes executor guidance at wave:post; execute-phase now renders wave:pre entries and dispatches generic step hooks there independently. Moving external-job classification to wave:pre is a separate capability change, not part of #4148. The adapter (scripts/slurm-adapter.cjs) reads external_job.submit_timeout_ms / poll_timeout_ms / artifact_dir through the canonical capability-config seam (env override > config > registry default).",
|
||||||
"tier": "full",
|
"tier": "full",
|
||||||
"requires": [],
|
"requires": [],
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).
|
<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).
|
||||||
|
|
||||||
Why wave:post, not wave:pre (#1164 refinement A): execute-phase.md only
|
#1164 specifies classification at wave:pre and recording at wave:post. This
|
||||||
dispatches execute:wave:post today — wave:pre is declared in the loop host
|
capability still contributes executor guidance at wave:post; execute-phase
|
||||||
contract but not rendered. Wiring wave:pre dispatch is a core-loop change
|
now renders wave:pre entries and dispatches generic step hooks there. Moving
|
||||||
#1164 puts out of scope. The executor therefore honors this classification
|
external-job classification is a separate capability change, not part of
|
||||||
guidance BEFORE running any task tagged <runtime_budget>long_compute</runtime_budget>,
|
#4148. Until then, this guidance cannot classify the wave that already ran. -->
|
||||||
whether in the current or a subsequent wave, and externalizes rather than
|
|
||||||
blocking the turn. -->
|
|
||||||
|
|
||||||
## Externalize long-running compute (async external job)
|
## Externalize long-running compute (async external job)
|
||||||
|
|
||||||
|
|||||||
@@ -22,14 +22,11 @@ Every executable task carries a runtime budget. Planners emit it via a
|
|||||||
`plan:post` fragment); executors branch on it at `execute:wave:post`.
|
`plan:post` fragment); executors branch on it at `execute:wave:post`.
|
||||||
|
|
||||||
> **Contribution point.** `#1164` specifies classification at
|
> **Contribution point.** `#1164` specifies classification at
|
||||||
> `execute:wave:pre`, but `execute-phase.md` only dispatches
|
> `execute:wave:pre` and recording at `execute:wave:post`. This Capability still
|
||||||
> `execute:wave:post` today — `wave:pre` is declared in the loop host contract
|
> contributes executor guidance at `execute:wave:post`; `execute-phase.md` now
|
||||||
> but not rendered. Wiring `wave:pre` dispatch is a core-loop change `#1164`
|
> renders `wave:pre` entries and dispatches generic step hooks there. Moving
|
||||||
> explicitly puts out of scope ("without touching core loop semantics"), so the
|
> external-job classification is a separate capability change, not part of
|
||||||
> Capability registers at `execute:wave:post` and the executor honors the
|
> `#4148`. Until then, this guidance cannot classify the wave that already ran.
|
||||||
> classification guidance **before** running any task tagged
|
|
||||||
> `<runtime_budget>long_compute</runtime_budget>`, whether in the current or a
|
|
||||||
> subsequent wave.
|
|
||||||
|
|
||||||
| Budget | Meaning | Execute behavior |
|
| Budget | Meaning | Execute behavior |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|||||||
@@ -1645,7 +1645,7 @@ const capabilities = {
|
|||||||
"role": "feature",
|
"role": "feature",
|
||||||
"version": "1.12.0",
|
"version": "1.12.0",
|
||||||
"title": "Async external-job scheduler adapter",
|
"title": "Async external-job scheduler adapter",
|
||||||
"description": "Default-off producer of the async external-job manifest (#1164). At execute:wave:post an executor can externalize long-running compute (SLURM first, scheduler-pluggable), commit a .planning/async-jobs/<job>.json manifest, defer SUMMARY.md, and return external_job_waiting. The core loop (#1165) consumes the manifest; this capability is the only thing that writes it. NOTE on contribution point: #1164 specifies execute:wave:pre, but execute-phase.md only dispatches execute:wave:post today (wave:pre is declared in the loop host contract but not rendered); wiring wave:pre dispatch is a core-loop change #1164 explicitly puts out of scope, so this capability registers at wave:post and the executor honors the runtime_budget classification guidance before running any tagged task. The adapter (scripts/slurm-adapter.cjs) reads external_job.submit_timeout_ms / poll_timeout_ms / artifact_dir through the canonical capability-config seam (env override > config > registry default).",
|
"description": "Default-off producer of the async external-job manifest (#1164). At execute:wave:post an executor can externalize long-running compute (SLURM first, scheduler-pluggable), commit a .planning/async-jobs/<job>.json manifest, defer SUMMARY.md, and return external_job_waiting. The core loop (#1165) consumes the manifest; this capability is the only thing that writes it. NOTE on contribution point: #1164 specifies classification at execute:wave:pre and recording at execute:wave:post. This capability still contributes executor guidance at wave:post; execute-phase now renders wave:pre entries and dispatches generic step hooks there independently. Moving external-job classification to wave:pre is a separate capability change, not part of #4148. The adapter (scripts/slurm-adapter.cjs) reads external_job.submit_timeout_ms / poll_timeout_ms / artifact_dir through the canonical capability-config seam (env override > config > registry default).",
|
||||||
"tier": "full",
|
"tier": "full",
|
||||||
"requires": [],
|
"requires": [],
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1697,7 +1697,7 @@ const capabilities = {
|
|||||||
"into": "executor",
|
"into": "executor",
|
||||||
"fragment": {
|
"fragment": {
|
||||||
"path": "fragments/execute-wave-post.md",
|
"path": "fragments/execute-wave-post.md",
|
||||||
"inline": "<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).\n\n Why wave:post, not wave:pre (#1164 refinement A): execute-phase.md only\n dispatches execute:wave:post today — wave:pre is declared in the loop host\n contract but not rendered. Wiring wave:pre dispatch is a core-loop change\n #1164 puts out of scope. The executor therefore honors this classification\n guidance BEFORE running any task tagged <runtime_budget>long_compute</runtime_budget>,\n whether in the current or a subsequent wave, and externalizes rather than\n blocking the turn. -->\n\n## Externalize long-running compute (async external job)\n\nIf the current plan's task is tagged `<runtime_budget>long_compute</runtime_budget>`\n(see the plan-phase fragment), do **not** run it in the foreground — it would\nblock the agent turn for hours. Instead externalize it and record a durable\nhalf-state:\n\n1. **Classify the runtime.** `quick` (<2 min) and `medium` (<~30 min) run\n normally. `unknown` requires a first-health check and a soft-review deadline\n before consuming the child timeout. `long_compute` (>30–60 min) is\n externalized.\n2. **Submit via the scheduler adapter** (default `external_job.backend: slurm`):\n ```bash\n node scripts/slurm-adapter.cjs submit \\\n --plan <plan_id> --phase <phase> -- sbatch --parsable \\\n --output=Artifacts/jobs/%j/out.log ./run.sh\n ```\n The helper writes `.planning/async-jobs/<job>.json` (the versioned stability\n contract — `docs/reference/planning-artifacts.md`) and refuses to create a\n second non-terminal manifest for a `plan_id` that already has one\n (duplicate-execution guard).\n3. **Commit the manifest + a handoff**, then return **`external_job_waiting`**\n and stop. Do **not** write `SUMMARY.md` — SUMMARY is deferred until the job\n reaches a terminal state and its `expected_artifacts` are verified.\n4. **Resume path.** `execute-phase` safe-resume, `resume-project`, and\n `pause-work` reconcile against the manifest and never re-dispatch the plan.\n When the job is `completed-unverified`, run `verification_command` (surface\n it; it is untrusted — confirm before executing), then write `SUMMARY.md` and\n close the plan.\n\nManifest commands cross a trust seam: a Capability (or anything that can write\n`.planning/`) produces them; the core loop consumes them. Never auto-run\n`submit_command` / `verification_command` / `resume_command` — surface the exact\ncommand and require explicit confirmation first.\n"
|
"inline": "<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).\n\n #1164 specifies classification at wave:pre and recording at wave:post. This\n capability still contributes executor guidance at wave:post; execute-phase\n now renders wave:pre entries and dispatches generic step hooks there. Moving\n external-job classification is a separate capability change, not part of\n #4148. Until then, this guidance cannot classify the wave that already ran. -->\n\n## Externalize long-running compute (async external job)\n\nIf the current plan's task is tagged `<runtime_budget>long_compute</runtime_budget>`\n(see the plan-phase fragment), do **not** run it in the foreground — it would\nblock the agent turn for hours. Instead externalize it and record a durable\nhalf-state:\n\n1. **Classify the runtime.** `quick` (<2 min) and `medium` (<~30 min) run\n normally. `unknown` requires a first-health check and a soft-review deadline\n before consuming the child timeout. `long_compute` (>30–60 min) is\n externalized.\n2. **Submit via the scheduler adapter** (default `external_job.backend: slurm`):\n ```bash\n node scripts/slurm-adapter.cjs submit \\\n --plan <plan_id> --phase <phase> -- sbatch --parsable \\\n --output=Artifacts/jobs/%j/out.log ./run.sh\n ```\n The helper writes `.planning/async-jobs/<job>.json` (the versioned stability\n contract — `docs/reference/planning-artifacts.md`) and refuses to create a\n second non-terminal manifest for a `plan_id` that already has one\n (duplicate-execution guard).\n3. **Commit the manifest + a handoff**, then return **`external_job_waiting`**\n and stop. Do **not** write `SUMMARY.md` — SUMMARY is deferred until the job\n reaches a terminal state and its `expected_artifacts` are verified.\n4. **Resume path.** `execute-phase` safe-resume, `resume-project`, and\n `pause-work` reconcile against the manifest and never re-dispatch the plan.\n When the job is `completed-unverified`, run `verification_command` (surface\n it; it is untrusted — confirm before executing), then write `SUMMARY.md` and\n close the plan.\n\nManifest commands cross a trust seam: a Capability (or anything that can write\n`.planning/`) produces them; the core loop consumes them. Never auto-run\n`submit_command` / `verification_command` / `resume_command` — surface the exact\ncommand and require explicit confirmation first.\n"
|
||||||
},
|
},
|
||||||
"produces": [
|
"produces": [
|
||||||
".planning/async-jobs/<job>.json"
|
".planning/async-jobs/<job>.json"
|
||||||
@@ -4616,7 +4616,7 @@ const byLoopPoint = {
|
|||||||
"into": "executor",
|
"into": "executor",
|
||||||
"fragment": {
|
"fragment": {
|
||||||
"path": "fragments/execute-wave-post.md",
|
"path": "fragments/execute-wave-post.md",
|
||||||
"inline": "<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).\n\n Why wave:post, not wave:pre (#1164 refinement A): execute-phase.md only\n dispatches execute:wave:post today — wave:pre is declared in the loop host\n contract but not rendered. Wiring wave:pre dispatch is a core-loop change\n #1164 puts out of scope. The executor therefore honors this classification\n guidance BEFORE running any task tagged <runtime_budget>long_compute</runtime_budget>,\n whether in the current or a subsequent wave, and externalizes rather than\n blocking the turn. -->\n\n## Externalize long-running compute (async external job)\n\nIf the current plan's task is tagged `<runtime_budget>long_compute</runtime_budget>`\n(see the plan-phase fragment), do **not** run it in the foreground — it would\nblock the agent turn for hours. Instead externalize it and record a durable\nhalf-state:\n\n1. **Classify the runtime.** `quick` (<2 min) and `medium` (<~30 min) run\n normally. `unknown` requires a first-health check and a soft-review deadline\n before consuming the child timeout. `long_compute` (>30–60 min) is\n externalized.\n2. **Submit via the scheduler adapter** (default `external_job.backend: slurm`):\n ```bash\n node scripts/slurm-adapter.cjs submit \\\n --plan <plan_id> --phase <phase> -- sbatch --parsable \\\n --output=Artifacts/jobs/%j/out.log ./run.sh\n ```\n The helper writes `.planning/async-jobs/<job>.json` (the versioned stability\n contract — `docs/reference/planning-artifacts.md`) and refuses to create a\n second non-terminal manifest for a `plan_id` that already has one\n (duplicate-execution guard).\n3. **Commit the manifest + a handoff**, then return **`external_job_waiting`**\n and stop. Do **not** write `SUMMARY.md` — SUMMARY is deferred until the job\n reaches a terminal state and its `expected_artifacts` are verified.\n4. **Resume path.** `execute-phase` safe-resume, `resume-project`, and\n `pause-work` reconcile against the manifest and never re-dispatch the plan.\n When the job is `completed-unverified`, run `verification_command` (surface\n it; it is untrusted — confirm before executing), then write `SUMMARY.md` and\n close the plan.\n\nManifest commands cross a trust seam: a Capability (or anything that can write\n`.planning/`) produces them; the core loop consumes them. Never auto-run\n`submit_command` / `verification_command` / `resume_command` — surface the exact\ncommand and require explicit confirmation first.\n"
|
"inline": "<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).\n\n #1164 specifies classification at wave:pre and recording at wave:post. This\n capability still contributes executor guidance at wave:post; execute-phase\n now renders wave:pre entries and dispatches generic step hooks there. Moving\n external-job classification is a separate capability change, not part of\n #4148. Until then, this guidance cannot classify the wave that already ran. -->\n\n## Externalize long-running compute (async external job)\n\nIf the current plan's task is tagged `<runtime_budget>long_compute</runtime_budget>`\n(see the plan-phase fragment), do **not** run it in the foreground — it would\nblock the agent turn for hours. Instead externalize it and record a durable\nhalf-state:\n\n1. **Classify the runtime.** `quick` (<2 min) and `medium` (<~30 min) run\n normally. `unknown` requires a first-health check and a soft-review deadline\n before consuming the child timeout. `long_compute` (>30–60 min) is\n externalized.\n2. **Submit via the scheduler adapter** (default `external_job.backend: slurm`):\n ```bash\n node scripts/slurm-adapter.cjs submit \\\n --plan <plan_id> --phase <phase> -- sbatch --parsable \\\n --output=Artifacts/jobs/%j/out.log ./run.sh\n ```\n The helper writes `.planning/async-jobs/<job>.json` (the versioned stability\n contract — `docs/reference/planning-artifacts.md`) and refuses to create a\n second non-terminal manifest for a `plan_id` that already has one\n (duplicate-execution guard).\n3. **Commit the manifest + a handoff**, then return **`external_job_waiting`**\n and stop. Do **not** write `SUMMARY.md` — SUMMARY is deferred until the job\n reaches a terminal state and its `expected_artifacts` are verified.\n4. **Resume path.** `execute-phase` safe-resume, `resume-project`, and\n `pause-work` reconcile against the manifest and never re-dispatch the plan.\n When the job is `completed-unverified`, run `verification_command` (surface\n it; it is untrusted — confirm before executing), then write `SUMMARY.md` and\n close the plan.\n\nManifest commands cross a trust seam: a Capability (or anything that can write\n`.planning/`) produces them; the core loop consumes them. Never auto-run\n`submit_command` / `verification_command` / `resume_command` — surface the exact\ncommand and require explicit confirmation first.\n"
|
||||||
},
|
},
|
||||||
"produces": [
|
"produces": [
|
||||||
".planning/async-jobs/<job>.json"
|
".planning/async-jobs/<job>.json"
|
||||||
|
|||||||
@@ -664,7 +664,9 @@ increases monotonically across waves. `{status}` is `complete` (success),
|
|||||||
WAVE_PRE_HOOKS_JSON=$(gsd_run loop render-hooks execute:wave:pre --raw)
|
WAVE_PRE_HOOKS_JSON=$(gsd_run loop render-hooks execute:wave:pre --raw)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Contribution dispatch:** inject every `kind == "contribution"` fragment per @gsd-core/references/loop-hook-dispatch.md (skip when none); one naming an alternate wave dispatch replaces step 3's inline loop. Then proceed to step 3.
|
**Contribution dispatch:** inject every `kind == "contribution"` fragment per @gsd-core/references/loop-hook-dispatch.md (skip when none); one naming an alternate wave dispatch replaces step 3's inline loop.
|
||||||
|
|
||||||
|
**Step dispatch:** `kind == "step"` per @gsd-core/references/loop-hook-dispatch.md; never blocks or redirects executor spawning. ⚠ Validate `ref.command` in-context before any shell use.
|
||||||
|
|
||||||
3. **Spawn executor agents:**
|
3. **Spawn executor agents:**
|
||||||
|
|
||||||
|
|||||||
@@ -5188,6 +5188,35 @@ describe('#1196 — discuss loop wiring + wired-point guard', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('boundary: cap declaring an execute:wave:pre step is accepted against real getWiredKinds(ROOT) (#4148)', () => {
|
||||||
|
const cap = makeCapWithStep('execute:wave:pre');
|
||||||
|
const { getWiredKinds } = require('../scripts/gen-loop-host-contract.cjs');
|
||||||
|
const errs = validateHooksWired(cap, getWiredKinds(ROOT));
|
||||||
|
assert.deepEqual(
|
||||||
|
errs, [],
|
||||||
|
`execute:wave:pre must dispatch step hooks before executor spawning. Errors: ${errs.join('; ')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const workflow = fs.readFileSync(path.join(ROOT, 'gsd-core', 'workflows', 'execute-phase.md'), 'utf8');
|
||||||
|
const wavePre = workflow.indexOf('WAVE_PRE_HOOKS_JSON=$(gsd_run loop render-hooks execute:wave:pre --raw)');
|
||||||
|
const stepDispatch = workflow.indexOf('**Step dispatch:**', wavePre);
|
||||||
|
const executorSpawn = workflow.indexOf('3. **Spawn executor agents:**', wavePre);
|
||||||
|
assert.ok(
|
||||||
|
wavePre !== -1 && stepDispatch > wavePre && executorSpawn > stepDispatch,
|
||||||
|
'wave-pre step dispatch must occur after hook rendering and before executor spawning',
|
||||||
|
);
|
||||||
|
|
||||||
|
const stepContract = workflow.slice(stepDispatch, executorSpawn);
|
||||||
|
assert.match(stepContract, /kind == "step"/, 'wave-pre must select step hooks');
|
||||||
|
assert.match(stepContract, /loop-hook-dispatch/, 'wave-pre must use the shared dispatch contract');
|
||||||
|
assert.match(stepContract, /Validate `ref\.command`/, 'wave-pre must validate third-party commands');
|
||||||
|
assert.match(
|
||||||
|
stepContract,
|
||||||
|
/never blocks or redirects executor spawning/,
|
||||||
|
'wave-pre step failures must remain advisory',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// ─── #3866: the verify lane must be open to every hook kind ────────────────
|
// ─── #3866: the verify lane must be open to every hook kind ────────────────
|
||||||
//
|
//
|
||||||
// verify-work.md's verify_pre_hooks step historically dispatched only
|
// verify-work.md's verify_pre_hooks step historically dispatched only
|
||||||
|
|||||||
@@ -1786,11 +1786,14 @@ describe('H. the execute:wave:pre fragment documents concrete manifest construct
|
|||||||
assert.doesNotMatch(stepBody, /USE_WORKTREES_FOR_PLAN/, 'per-plan worktree gate detail must live in the fragment, not the host step');
|
assert.doesNotMatch(stepBody, /USE_WORKTREES_FOR_PLAN/, 'per-plan worktree gate detail must live in the fragment, not the host step');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[happy] execute-phase.md is below the ADR-857 Phase 6 pre-phase-6 byte ceiling (#1168), with margin', () => {
|
test('[happy] execute-phase.md is below the ADR-857 Phase 6 pre-phase-6 byte ceiling (#1168)', () => {
|
||||||
|
// No separate self-imposed margin here: a tighter number than the ADR's own
|
||||||
|
// frozen ceiling just gets re-tripped by growth this PR does not own (#4148
|
||||||
|
// review history) — the tier hard cap in workflow-size-budget.test.cjs (98304
|
||||||
|
// bytes, "extract, not bump") is the correct backstop for that.
|
||||||
const { lfByteCount } = require('../scripts/workflow-size.cjs');
|
const { lfByteCount } = require('../scripts/workflow-size.cjs');
|
||||||
const bytes = lfByteCount(WORKFLOW_PATH);
|
const bytes = lfByteCount(WORKFLOW_PATH);
|
||||||
assert.ok(bytes < 93600, `execute-phase.md must stay below the frozen pre-phase-6 ceiling (93600); got ${bytes}`);
|
assert.ok(bytes < 93600, `execute-phase.md must stay below the frozen pre-phase-6 ceiling (93600); got ${bytes}`);
|
||||||
assert.ok(bytes <= 93400, `execute-phase.md should carry a comfortable margin (<=93400) so minor future edits don't re-trip the gate; got ${bytes}`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user