38 KiB
Promotion Security Review Fixes Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make the captured candidate archive and a strict exact-five bundle validator the only promotion authority while closing evidence, lifecycle, staging, output, freshness, and workflow gaps found by security review.
Architecture: Candidate identity and local verification are derived exclusively from the inode-captured tar stream. The finalizer revalidates archived subordinate evidence, provider signatures, freshness, role-separated trust, and then creates and validates an exact-five bundle before returning descriptor- and inode-bound cleanup metadata. Provider execution, CLI output publication, and workflow gating expose small injectable boundaries so failure and cleanup behavior can be tested directly.
Tech Stack: Node.js 24, TypeScript, Zod, Vitest, GNU tar, bubblewrap, Gitea Actions workflow generation.
Global Constraints
- Work sequentially on the current clean
developHEAD and produce one review-fix commit. - Every production change follows focused RED, observed expected failure, minimal GREEN, and regression verification.
- Candidate verification performs no checkout reads; test subprocesses from outside the checkout with contradictory canaries.
- Preserve real GNU tar and bubblewrap coverage; do not claim native uploader or atomic
renameat2/unlinkatsemantics. - Gitea 1.26.4 and act_runner 1.0.0 exact-five upload/download/cancel behavior remains an explicitly documented external smoke boundary.
Task 1: Canonical Captured Archive and Archived Local Authority
Files:
- Modify:
scripts/lib/release-candidate.ts - Modify:
scripts/lib/local-release-evidence.ts - Modify:
scripts/lib/ci-candidate-archive.ts - Delete:
scripts/lib/promotion-verifier.ts - Delete:
scripts/verify-provider-evidence.ts - Delete:
scripts/verify-supply-chain-promotion.ts - Modify:
tests/unit/security-followup.test.ts - Modify:
tests/unit/supply-chain.test.ts - Modify:
tests/integration/security-followup-archive.test.ts
Interfaces:
-
Consumes: captured tar bytes plus expected SHA-256.
-
Produces:
withVerifiedCapturedCandidate()callback data derived only from the extracted, exact-member, digest-verified tar;verifyArchivedLocalEvidence()independently recomputes all feasible archived checks. -
Add failing tests for invalid tar, archive/tree mismatch, contradictory archived subordinate FAIL, exact archived policy bytes, and checkout-independent execution.
-
Run focused tests and record the expected RED diagnostics in the durable task report.
-
Archive the exact policy/verifier inputs required for independent release, supply-chain, dependency, license, vulnerability, and secret-scan checks.
-
Re-run producer checks against the extracted archive and require their result to agree with the assessment and member identities.
-
Remove the obsolete standalone PASS issuers and route all fixture checking through real captured tar/finalizer validation.
-
Run focused archive, supply-chain, and integration tests to GREEN.
Task 2: Exact-Five Validator and Role-Separated Trust
Files:
- Create:
scripts/lib/exact-promotion-bundle.ts - Create:
scripts/verify-exact-promotion-bundle.ts - Modify:
scripts/lib/provider-evidence.ts - Modify:
scripts/lib/promotion-stager.ts - Modify:
scripts/contracts/promotion-artifacts.ts - Modify:
tests/unit/security-followup.test.ts - Modify:
tests/unit/ci-artifact-contract.test.ts
Interfaces:
-
Consumes: exactly five captured byte buffers and two trusted Ed25519 identities.
-
Produces:
verifyExactPromotionBundle()that requires literal verifier identity/version, provider and subordinate PASS states, exact hashes, and equal run/source/candidate/provider/trust fields. -
Add failing tests for provider FAIL/absence, arbitrary provider hash, swapped roles, shared-field mismatch, archive/report digest mismatch, and identical role keys.
-
Run focused tests and record RED.
-
Implement strict exact-five parsing/cross-record validation and expose a downstream CLI command.
-
Reject equal DER-SPKI fingerprints and equal role key identity before evaluation/finalization.
-
Invoke exact-five validation inside the finalizer before publication; the full real-build fixture rerun remains sandbox-blocked below.
Task 3: Provider Lifecycle and Freshness
Files:
- Modify:
scripts/lib/provider-supervisor.ts - Create:
scripts/lib/provider-process-runner.ts - Modify:
scripts/run-and-validate-provider.ts - Modify:
tests/unit/security-followup.test.ts - Modify:
tests/unit/ci-artifact-contract.test.ts
Interfaces:
-
Consumes: a spawned bubblewrap child, injected timeout/clock, captured report.
-
Produces: a runner that SIGKILLs on timeout but rejects only after
close, and supervision that samples freshness after provider/report capture. -
Add failing stubborn-descendant/short-timeout and sequence-clock expiry tests.
-
Run focused tests and record RED.
-
Extract the process runner, wait for close after timeout, and preserve the timeout diagnostic.
-
Issue provider timestamps immediately before execution, validate with a fresh clock after capture, and reject crossing expiry.
-
Run focused lifecycle tests, including real stubborn descendants, to GREEN; the shared real-build/bubblewrap fixture remains sandbox-blocked below.
Task 4: Inode-Pinned Staging and Output-Failure Cleanup
Files:
- Modify:
scripts/lib/promotion-stager.ts - Create:
scripts/lib/stage-verified-promotion-cli.ts - Modify:
scripts/stage-verified-promotion.ts - Modify:
scripts/cleanup-verified-promotion.ts - Modify:
tests/unit/ci-artifact-contract.test.ts
Interfaces:
-
Produces:
FinalizedPromotion.stagingIdentityand a testable CLI function whose append failure invokes cleanup from the in-memory result. -
Add failing tests for leaf replacement during writes, final visibility mismatch, partial-failure cleanup, GITHUB_OUTPUT open/write failure, and expiry during staging.
-
Run focused tests and record RED.
-
Open the created leaf with
O_DIRECTORY|O_NOFOLLOW, write through/proc/self/fd/<leafFd>, pin dev/ino, require visible identity equality, and propagate identity through cleanup. -
Force directory/file modes with
fchmod(0700/0400)independent of a restrictive owner-preserving umask. -
Extract CLI dependencies; on any post-finalization output failure call direct cleanup before rethrowing.
-
Revalidate evidence freshness before sealing and immediately before publication; isolated lifecycle/mode/output tests are GREEN and the shared real-build fixture remains sandbox-blocked below.
Task 5: Workflow and Install Policy
Files:
- Modify:
package.json - Modify:
scripts/check-ci-contract.ts - Modify:
scripts/contracts/ci-gates.ts - Modify:
scripts/generate-ci-workflow.ts - Modify:
config/ci/gates.json - Modify:
.gitea/workflows/quality-gates.yml - Modify:
tests/unit/ci-workflow-generation.test.ts - Modify:
tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap
Interfaces:
-
Produces: promotion job
if: ${{ always() && needs.immutable_build.result == 'success' && needs.vulnerability_provider.result == 'success' && needs.provenance_provider.result == 'success' }}and install-bearing script graph enforcement. -
Add failing contract/generator tests for the job condition, upload without
always(), missing cleanup outputs, and nested install scripts lacking--ignore-scripts. -
Run focused tests and record RED.
-
Add
--ignore-scriptstoverify:lockfileand recursively reject each reachable install invocation without it. -
Extend the typed job condition and render the explicit cancellation-resistant exact-needs predicate.
-
Regenerate workflow/snapshot and run workflow contract/byte tests to GREEN.
Task 6: Fixtures, Documentation, Full Verification, and Commit
Files:
-
Modify:
scripts/check-supply-chain-provider-fixtures.ts -
Modify:
docs/security/supply-chain.md -
Modify:
docs/operations/ci-quality-gates.md -
Modify:
.superpowers/sdd/2026-08-01-release-evidence-remediation/task-4-report.md(ignored durable report) -
Replace plaintext candidate fixtures with a real tar and canonical captured-archive/exact-five validation.
-
Rewrite operator docs around the sole captured-archive/exact-five authority and retain the Gitea/runner external-smoke residual.
-
Run focused fixtures, archive integration, workflow snapshot/bytes, full unit, types, lint,
check:ci, and diff checks; escalate only a sandbox-caused EPERM. -
Append all RED/GREEN and verification evidence/constraints to the durable report.
-
Invoke verification-before-completion, review the complete diff, commit once, and report commit/range/status.
Review-Fix Wave D: Sealed Bytes, Replay Context, Scan Trust, and Cancellation
Constraint: Work only in the existing uncommitted tree. Do not write .git, stage, or commit. Each task follows a focused RED→GREEN cycle and records sandbox EPERM separately from product failures.
Task D1: Seal the actual staged inode bytes
Files: scripts/lib/promotion-stager.ts, tests/unit/security-followup.test.ts, tests/unit/ci-artifact-contract.test.ts
Interface: The staging writer captures each canonical file through the already-open leaf FD using O_NOFOLLOW; it requires a regular single-link inode, mode 0400, stable dev/ino/size, and the declared SHA-256. verifyExactPromotionBundle receives only these captured staged buffers immediately before return.
- Add RED tests for unlink/recreate and chmod/mutation after a file write.
- Implement bounded descriptor-relative capture and exact-five seal validation.
- Run focused staging tests to GREEN.
Task D2: Bind downstream verification to external expected identity
Files: scripts/lib/exact-promotion-bundle.ts, scripts/verify-exact-promotion-bundle.ts, tests/unit/ci-artifact-contract.test.ts
Interface: verifyExactPromotionBundle requires expected.run.id, expected.run.attempt, expected.sourceRevision, and expected.archiveSha256; optional bundle/dist/lock/source-set digests are compared when supplied. The CLI obtains these values from dedicated environment variables and never derives them from the bundle.
- Add a RED signed other-run replay test.
- Implement external expected-context comparison in library and CLI.
- Run exact-bundle tests to GREEN where the sandbox permits.
Task D3: Pin mkdir-to-open identity
Files: scripts/lib/promotion-stager.ts, tests/unit/security-followup.test.ts, docs/security/supply-chain.md, docs/operations/ci-quality-gates.md
Interface: A post-mkdir/pre-open test hook can replace the leaf. The implementation compares mkdir-returned pathname metadata with the O_DIRECTORY|O_NOFOLLOW handle fstat before any write; it never uses pathname chmod.
- Add a RED pre-open replacement test.
- Compare created and opened metadata and reject replacement.
- Document the residual portable Node same-UID pre-lstat/native-privilege boundary.
Task D4: Conservatively parse install invocations
Files: scripts/lib/package-script-graph.ts, tests/unit/ci-workflow-generation.test.ts
Interface: A bounded shell/token parser recognizes pnpm install|i, npm install|ci|i, and yarn install after supported manager-global options with split or = values. Any reachable package-manager invocation that cannot be classified is rejected.
- Add the eight required global-option/alias RED cases plus malformed fail-closed cases.
- Implement tokenization and manager-specific invocation classification.
- Run install-policy tests to GREEN.
Task D5: Signed secret-scan attestation
Files: scripts/lib/provider-evidence.ts, scripts/lib/provider-supervisor.ts, scripts/lib/provider-upload-validator.ts, scripts/lib/promotion-stager.ts, scripts/lib/exact-promotion-bundle.ts, relevant unit/integration tests and docs.
Interface: Vulnerability evidence v2 contains a strict secretScanAttestation with status: PASS, local-assessment, source-set, policy, SARIF, and scan-input digests. The supervisor derives the expected tuple from captured archive members, exports it to the provider, and upload/final verification requires exact equality under the Ed25519 signature.
- Add RED forged-empty-SARIF and attestation-mismatch tests.
- Derive one captured-archive scan context and bind it through supervisor, signed schema, finalizer records, and exact validation.
- Run provider/security tests to GREEN where the sandbox permits.
Task D6: Cancellation-safe workflow and exact upload paths
Files: scripts/contracts/ci-gates.ts, scripts/generate-ci-workflow.ts, config/ci/gates.json, generated workflow/snapshot, workflow tests, and operations/security docs.
Interface: Promotion uses a typed dependency-success/no-job-if variant, so cancellation cannot be overridden by job-level always(). Step cleanup retains bare always() for ordinary failures. Upload documentation names the five canonical paths under staging_root and states cancellation cleanup remains a runner/native smoke boundary.
- Add RED generator/contract assertions for no promotion job
ifand retained cleanupalways(). - Regenerate workflow and snapshot after the typed condition change.
- Correct operator/security wording and run workflow/CI checks to GREEN.
Task D7: Verification
- Run focused suites after each GREEN, then affected/full unit tests, all TypeScript targets, ESLint,
check:ci, generated-byte check, and both diff checks. - Append exact PASS totals and sandbox-blocked commands to the ignored durable report.
- Report modified files and remaining native/Gitea/unsandboxed verification boundaries; do not attempt git staging or commit.
Wave E: Unified parser and downstream boundary review
Task E1: One tokenized manager parser
Files: scripts/lib/package-script-graph.ts, tests/unit/ci-workflow-generation.test.ts
Interface: One parse result reports manager invocations, package-script dependencies, unsupported controls, and effective lifecycle suppression. Both graph traversal and install policy consume it. Single &, unknown manager grammar, and malformed options fail closed. The last valid --ignore-scripts assignment controls the effective value; false, contradictory, valueless, and malformed assignments are unsafe. Lifecycle-capable mutation builtins are never implicit repository scripts and require effective suppression.
- Add RED tables for single-ampersand segmentation, false/override/malformed suppression, global-option run/implicit dependencies, yarn/corepack reachability, and builtin/script-name collisions.
- Replace the regex traversal and separate install scan with one parser result.
- Run the parser-focused and full workflow-generation suites.
Task E2: Evaluator-owned secret-scan equality
Files: scripts/lib/provider-evidence.ts, tests/unit/security-followup.test.ts, finalizer tests.
Interface: evaluatePromotionEvidence itself compares the parsed vulnerability report's signed secretScanAttestation with expected.secretScanAttestation. A mismatch makes vulnerability and overall promotion status FAIL_UNVERIFIED, including the production finalizer path.
- Add a RED evaluator mismatch test.
- Implement exact equality before vulnerability PASS assignment.
- Run security/provider-focused tests.
Task E3: Downstream CLI exact-five contract
Files: tests/unit/ci-artifact-contract.test.ts, tests/unit/security-followup.test.ts, scripts/verify-exact-promotion-bundle.ts if required.
Interface: A real finalizer-produced canonical exact-five directory passes the downstream CLI when all required external expected values and trust keys are supplied. Every required expected variable missing or mismatched exits non-zero. Optional digests remain exact when present.
- Add RED happy-path and required-env negative coverage using real finalizer output where sandbox execution permits.
- Make only the minimal CLI/library changes needed for GREEN.
- Separate child-process sandbox blockers from library assertions.
Task E4: Verification
- Run focused parser/security/CLI suites, TypeScript, ESLint,
check:ci, andgit diff --check. - Run full unit if feasible and report nested-process
EPERMseparately. - Do not stage or commit.
Wave F: Manager parser boundary hardening
Constraint: Continue in the existing uncommitted tree. Do not write .git, stage, or commit. Add behavior tests before production changes and keep unsupported manager grammar fail-closed.
Task F1: Workspace dispatch and authoritative script lookup
Files: scripts/lib/package-script-graph.ts, tests/unit/ci-workflow-generation.test.ts
Interface: The parser receives the authoritative root scripts record. Explicit run resolves a root script; pnpm/yarn implicit dispatch resolves only a known root script. Yarn workspace and workspaces dispatch are unsupported because the root graph does not load workspace package scripts. Builtin aliases are canonicalized before root-script lookup.
- Add RED policy and graph tables for the three Yarn workspace dispatchers, pnpm
ln, and unknown manager subcommands. - Remove workspace dispatchers from safe builtins, pass known root scripts into the parser, and canonicalize
pnpm lnto lifecyclelinkbefore implicit lookup. - Run the focused dependency/lifecycle cases to GREEN.
Task F2: Shell comments and lifecycle option state
Files: scripts/lib/package-script-graph.ts, tests/unit/ci-workflow-generation.test.ts
Interface: Unquoted # in a manager-bearing command is unsupported control syntax; quoted # remains ordinary token content. Lifecycle options are parsed in order into a canonical suppression state covering --ignore-scripts, --no-ignore-scripts, and --config.ignore-scripts; conflicting, malformed, unknown, or ineffective states fail closed. Other option-like lifecycle arguments require an explicit manager allowlist.
- Add RED comment, negative suppression, supported positive, and unknown lifecycle-option tables.
- Implement comment-aware tokenization and one ordered lifecycle argument parser.
- Preserve the checked-in
--frozen-lockfile --ignore-scriptspath and run focused tests to GREEN.
Task F3: Verification
- Validate every checked-in package script through graph/install consumers without false positives.
- Run the full workflow-generation file and related security tests.
- Run all TypeScript targets, ESLint,
check:ci, andgit diff --check; report nested-processEPERMseparately. - Update the durable report; do not stage or commit.
Wave G: Verified cleanup and complete gate/parser preflight
Constraint: Continue in the existing uncommitted tree. Do not write .git, stage, or commit. Every production change follows a focused failing behavior test.
Task G1: Verified-FD-only failure cleanup
Files: scripts/lib/promotion-stager.ts, tests/unit/security-followup.test.ts, security/operations documentation.
Interface: openedIdentityVerified becomes true only after the opened directory descriptor matches the post-mkdir device/inode. Failure cleanup may unlink canonical files or rmdir only through that verified descriptor and a still-matching visible identity. A mismatched opened descriptor and any visible replacement are close-only; a moved original directory remains for fixture/operator cleanup because portable Node cannot safely recover it.
- Change the pre-open replacement regression to require both the replacement canary and displaced original directory to survive the failure.
- Run the focused test to RED against parent-directory identity scanning.
- Remove unverified inode discovery/recovery and gate descriptor cleanup on explicit identity verification.
- Run staging race and cleanup tests to GREEN and document the native residual.
Task G2: Contract-wide lifecycle preflight
Files: scripts/contracts/ci-gates.ts, scripts/run-ci-gate.ts, optional focused runner helper, tests/unit/ci-workflow-generation.test.ts.
Interface: loadCiGateContract runs validateInstallScriptPolicy over every unique contract command script after script existence and graph checks. The runner enters its execution callback only after this loader succeeds, enabling a no-execute regression without relying on a nested child process.
- Add RED loader tables for contradictory npm suppression, pnpm config false, and
pnpm ln, plus a production runner-boundary no-execute spy. - Enforce contract-command install policy and route runner execution through the preflight boundary.
- Run loader/runner preflight tests to GREEN.
Task G3: Foreign manifest scope by command class
Files: scripts/lib/package-script-graph.ts, tests/unit/ci-workflow-generation.test.ts.
Interface: Manager-global options that change cwd, manifest or workspace scope are recorded during parsing. Explicit and implicit package-script dispatch with any such option is unsupported under the root-only graph. Lifecycle commands remain classifiable and are accepted only when their own ordered suppression/option grammar is safe.
- Add RED policy+graph tables for pnpm filter/dir/
-C, npm workspace/prefix, and Yarn cwd dispatch. - Add positive externally scoped lifecycle cases with verified suppression.
- Track scope options and reject only package-script dispatch; run focused tests to GREEN.
Task G4: Argument-sensitive builtin grammar and verification
Files: scripts/lib/package-script-graph.ts, tests/unit/ci-workflow-generation.test.ts, durable report.
Interface: Broad command-name-only safe builtins are replaced by exact per-manager read-only invocations. Init/explore/Yarn npm namespaces are unsupported. Audit is accepted only as an exact bare read-only command; fix and all unknown arguments are rejected.
- Add RED policy+graph coverage for npm init/explore/audit-fix and Yarn npm publish, plus a bare-audit positive.
- Replace permissive builtin lookup with exact argument grammar.
- Audit every current package script for graph/policy false positives.
- Run staging/parser/no-execute/workflow/security suites, all TypeScript targets, ESLint,
check:ci, andgit diff --check; record sandboxEPERMseparately and do not stage or commit.
Wave H: npm post-script scope-option boundary
Constraint: Continue in the existing uncommitted tree. Do not write .git, stage, or commit. Reproduce every reviewer command in a failing test before changing the parser.
Task H1: Explicit and implicit npm dispatch arguments
Files: scripts/lib/package-script-graph.ts, tests/unit/ci-workflow-generation.test.ts.
Interface: After an explicit npm run/run-script dependency or an implicit npm lifecycle script, manager options before the first literal -- are parsed using an exact harmless allowlist. Workspace/prefix selectors (--workspace, -w, --workspaces, --prefix, including supported attached/equal forms) and unknown manager options fail closed. Tokens after the first literal -- are script arguments and cannot change the authoritative manifest scope.
- Add RED policy-and-graph coverage for all seven reviewer inputs, short/equal forms, unknown pre-delimiter options, the literal
--boundary, and ordinary current-tree dispatch. - Implement one npm post-script argument parser shared by explicit and implicit dispatch.
- Run focused parser tests to GREEN.
Task H2: Contract loader and runner boundary
Files: tests/unit/ci-workflow-generation.test.ts, contract preflight only if the RED test exposes a separate integration defect.
Interface: Every reviewer input is rejected by contract loading while the referenced root scripts exist and are otherwise safe. withCiGatePreflight must not enter its callback for any rejected command.
- Add a table-driven loader/no-callback regression for the same seven reviewer inputs.
- Run focused preflight tests to GREEN.
Task H3: Verification
- Re-audit current package scripts through graph and policy consumers.
- Run workflow/security suites, all TypeScript targets, ESLint,
check:ci, andgit diff --check. - Record results in the durable report and do not stage or commit.
Verification evidence: The focused npm parser/preflight selection passed
31/31. The workflow file passed 208/210; its two remaining tests reached the
known nested-spawn sandbox boundary and reported EPERM. Security, supply-chain,
and local-promotion tests passed 64/64. All six TypeScript targets, ESLint,
check:ci, and git diff --check passed. Auditing the checked-in package found
zero policy failures across 109 scripts and zero graph failures across 108
entries (excluding the intentionally direct runner entry ci:gate). A broader
artifact-contract run passed 102 assertions and blocked 23 fixture cases at the
same nested git ls-files EPERM boundary. No .git write was performed.
Local pnpm 11.17 execution showed post-script --filter/--dir tokens arriving
in the root script's argv, and official Yarn run documentation defines all
parameters after the script name as script arguments. Those pre-existing
negative expectations were therefore corrected to positive regressions; only
npm receives the new post-script manager-option grammar.
Wave I: npm hook closure and environment scope
Constraint: Continue in the existing uncommitted tree. Do not write .git,
stage, or commit. Add focused behavior tests and observe RED before each
production change.
Task I1: npm pre/main/post dependency closure
Files: scripts/lib/package-script-graph.ts,
tests/unit/ci-workflow-generation.test.ts.
Interface: Explicit npm run/run-script and implicit
start/stop/restart/test return existing root-manifest lifecycle hooks in
pre, main, post order. Hooks are omitted only when ordered manager/tail
suppression is unambiguously effective before the first literal --; bare,
false, negative, contradictory, malformed, or post-delimiter suppression keeps
hook traversal active or fails closed.
- Add RED policy/graph tables for nested, test, and restart pre/post hooks.
- Add RED suppression positives and false/negative/contradictory/delimiter negatives.
- Make npm tail parsing update the invocation suppression state and expand dependencies.
- Run hook/parser tests to GREEN.
Task I2: Tokenized npm scope environment
Files: scripts/lib/package-script-graph.ts, scripts/contracts/ci-gates.ts,
tests/unit/ci-workflow-generation.test.ts.
Interface: Case-insensitive assignments to npm_config_workspace,
npm_config_workspaces, or npm_config_prefix fail closed when their shell
segment executes npm. Direct assignment, env, /usr/bin/env, and an exported
assignment inherited by a later npm segment are covered without raw-substring
false positives for quoted text. withCiGatePreflight also rejects the same
sensitive keys inherited through process.env before entering its callback.
- Add RED policy/graph coverage for all reviewer assignment forms and quoted/current-tree positives.
- Add RED loader/no-callback coverage for command assignments and inherited process environment.
- Implement token/segment assignment state and the preflight environment boundary.
- Run environment/parser/preflight tests to GREEN.
Task I3: Verification
- Audit every current script through graph and policy consumers.
- Run focused parser/preflight, workflow/security, all TypeScript targets,
ESLint,
check:ci, andgit diff --check. - Update durable operations/security documentation and record sandbox-only nested spawn failures separately; do not stage or commit.
Verification evidence: Hook closure began RED 9/9 and GREEN 9/9;
ordered suppression began with 6 expected failures and finished GREEN 22/22;
wrapper/export/inherited environment coverage began with 7 expected failures
and finished GREEN 28/28. A final all-command-class environment RED 3/3
closed scoped lifecycle and builtin invocations. The combined Wave I focused
selection passed 59/59. The complete workflow file passed 269/271; its only
two failures were the existing nested child-spawn EPERM fixtures. Security,
supply-chain, and local-promotion tests passed 64/64. All six TypeScript
targets, ESLint, check:ci, and git diff --check passed. The checked-in tree
had zero policy failures across 109 scripts, zero graph failures across 108
entries after excluding the intentional direct runner entry ci:gate, and no
sensitive inherited npm scope environment. No .git write was performed.
Wave J: coherent npm environment state and hook semantics
Constraint: Continue in the existing uncommitted tree. Do not write .git,
stage, or commit. Add each reviewer form as a failing regression before changing
the parser.
Task J1: Stateful shell npm-scope environment analysis
Files: scripts/lib/package-script-graph.ts,
tests/unit/ci-workflow-generation.test.ts.
Interface: Shell segments carry a conservative npm-scope environment state
to later reachable npm invocations. The parser recognizes any static path whose
basename is env, optionally behind command, and rejects case-insensitive
scope assignments in direct or env-wrapper contexts. Static assignment/export
and set -a transitions are modeled across segments. Dynamic assignment names
and environment mutations that cannot be modeled accurately (set +a,
unset, export -n, eval, dot/source) make later npm dispatch unsupported.
Quoted harmless text and non-scope static assignments remain accepted; analysis
uses token and segment structure rather than raw substring matching.
- Add RED policy/graph tables for every reviewer state transition, env path, command wrapper, dynamic assignment name, and unsupported mutation.
- Implement a shared tokenized shell-environment state machine and immediate npm invocation environment inspection.
- Add harmless quoted/static positive regressions and run focused tests GREEN.
Task J2: Actual npm lifecycle-hook suppression semantics
Files: scripts/lib/package-script-graph.ts,
tests/unit/ci-workflow-generation.test.ts.
Interface: Existing pre/main/post hooks are traversed for run,
run-script, start, stop, restart, and test. Bare --ignore-scripts
means true and omits hooks just like explicit true. False, negative,
contradictory, malformed, and post-delimiter forms retain hook traversal or fail
closed according to the existing ordered grammar.
- Add hook safety/order coverage for run-script, start, and stop.
- Move bare suppression forms to positive regressions and retain all false, negative, contradictory, and delimiter negatives.
- Remove the explicitly-valued distinction and run focused tests GREEN.
Task J3: Contract boundary and verification
- Run every environment reviewer command through policy, graph, contract
loading, and
withCiGatePreflight, asserting the callback is never entered. - Retain the inherited process-environment regression and audit the current package tree for policy/graph false positives.
- Run workflow/security suites, all TypeScript targets, ESLint,
check:ci, andgit diff --check; record sandbox-only failures and do not stage or commit.
Verification evidence: The initial Wave J selection produced 38 expected
failures across loader/policy/graph environment cases and bare hook suppression,
then passed 84/84 after implementation. A separate unsupported dynamic env-wrapper
expansion regression went RED 2/2 and GREEN 2/2; the final combined selection
passed 86/86. The full workflow file passed 327/329, with only the existing two
nested child-spawn EPERM fixtures failing at the sandbox boundary. Security,
supply-chain, local-promotion, and promotion-readiness tests passed 71/71. All
six TypeScript targets, ESLint, check:ci, and git diff --check passed. The
checked-in tree had zero policy failures across 109 scripts, zero graph failures
across 108 entries after excluding the intentional direct runner entry ci:gate,
and no sensitive inherited npm scope environment. No .git write was performed.
Wave K: common shell-prefix grammar
Constraint: Continue in the existing uncommitted tree. Do not write .git,
stage, or commit. Every wrapper/prefix reviewer command must be RED in all four
public enforcement paths before production changes.
Task K1: Shared prefix parser and state-builtin targeting
Files: scripts/lib/package-script-graph.ts,
tests/unit/ci-workflow-generation.test.ts.
Interface: A single token-based prefix helper consumes leading static
assignments, then exact command/exec wrapper chains and their supported
separator syntax. It reports the effective command token/index, whether parsing
is uncertain, and the leading assignments. Both immediate npm env inspection
and persistent export/set state updates use this result. command -- is
accepted; unknown command options and unmodeled exec options before npm/env
fail closed. Static paths retain basename-env behavior.
- Add common policy/graph RED cases for
exec env,exec /bin/env,command exec env,command -- env, assignment-prefixedexport, and assignment-prefixedset -a. - Reuse the same reviewer table through
loadCiGateContractandwithCiGatePreflight, asserting rejection and no callback entry. - Implement the shared prefix parser, route immediate env inspection and state-builtin updates through it, and run the reviewer selection GREEN.
- Preserve split assignment/export ordering, dynamic LHS, quoted text,
harmless
MESSAGE=..., and supported command-wrapper positives.
Task K2: Hook selection and final verification
Files: tests/unit/ci-workflow-generation.test.ts, durable report.
- Ensure the final focused selection explicitly includes the bare npm hook suppression table as well as prefix/environment policy and runner tests.
- Run the full workflow file and security/supply/local-promotion suites; classify only the known nested-spawn sandbox failures separately.
- Audit all current scripts through policy and graph, then run all TypeScript
targets, ESLint,
check:ci, andgit diff --check; do not stage or commit.
Verification evidence: The nine shared shell-prefix reviewer commands began
RED in both enforcement tables, producing 18 expected failures across
loader/runner and policy/graph, then passed 18/18 after the common parser was
connected. The prefix negatives plus harmless positives passed 39/39. The final
focused selection explicitly combined prefix cases, dynamic environment cases,
effective/bare npm hook suppression, and harmless positives and passed 106/106.
The complete workflow file passed 353/355; its only two failures were the known
nested child-spawn EPERM fixtures. Security, supply-chain, local-promotion, and
promotion-readiness tests passed 71/71. The current package tree had zero policy
failures across 109 scripts, zero graph failures across 108 entries after
excluding ci:gate, and no sensitive inherited npm scope environment. All six
TypeScript targets, ESLint, check:ci, and git diff --check passed. The shared
workspace was preserved and no .git write was performed.
Wave L: structural manager-prefix gap rejection
Constraint: Continue in the shared uncommitted tree. Do not write .git,
stage, or commit. Generalize the existing parser; do not add wrapper names to an
allowlist.
Task L1: Reject unmodeled tokens before package managers
Files: scripts/lib/package-script-graph.ts,
tests/unit/ci-workflow-generation.test.ts.
Interface: The common shell-prefix result identifies the first effective
command after modeled assignments and command/exec wrappers. When manager
scanning later finds a package manager, every token between that effective
command position and the manager position must belong to a grammar explicitly
consumed by immediate env or corepack parsing. Otherwise the invocation is
unsupported. This structural rule covers nice, absolute-path nice, nohup,
and future unknown wrappers without naming them.
- Add policy/graph RED coverage for the four env-wrapper reviewer commands
and direct unknown-wrapper manager commands (
nice npm,time pnpm). - Reuse the env-wrapper reviewer commands through contract loading and
withCiGatePreflight, asserting the callback remains false. - Implement one structural gap check in manager parsing and run RED cases GREEN without adding wrapper names.
- Retain modeled assignment,
command/exec/env/corepack, current-tree, quoted echo, and harmless assignment positives.
Task L2: Verification
- Run a focused selection containing structural negatives and all modeled prefix/environment positives.
- Run the workflow and security/supply/local-promotion suites, current-tree
policy/graph/environment audit, all TypeScript targets, ESLint,
check:ci, andgit diff --check; record sandbox-only failures and do not stage or commit.
Verification evidence: The six unmodeled-prefix reviewer commands began RED
in both enforcement tables, producing 12 expected loader/runner and policy/graph
failures, then passed 12/12 after one structural prefix-gap check was added. The
unmodeled negatives plus harmless/modeled positives passed 36/36. The final
focused Wave H–L environment/prefix and effective/bare hook selection passed
121/121. The full workflow file passed 368/370, with only the two known nested
child-spawn EPERM fixtures failing at the sandbox boundary. Security,
supply-chain, local-promotion, and promotion-readiness tests passed 71/71. The
current package tree had zero policy failures across 109 scripts, zero graph
failures across 108 entries after excluding ci:gate, and no sensitive inherited
npm scope environment. All six TypeScript targets, ESLint, check:ci, and
git diff --check passed. No wrapper-name allowlist was added, the workspace was
preserved, and no .git write was performed.