* test(#4505): cover runtime-aware overrides and routing precedence Failing-first for both halves of the issue, plus the precedence layers a naive fix silently defeats. Every row drives the REAL CLI in a subprocess. That is load-bearing: the defect is WHICH function the shipped call sites reach, so a row calling the resolver in-process would pass while every real spawn stayed broken. It also makes GSD_RUNTIME hermetic -- it is ambient, and an in-process row would leak it into its neighbours. Two fixture mechanics are documented in the helper because each silently invalidates a row when got wrong, and both were found by measuring rather than by reading the loader: - the loader reads `process.env.GSD_HOME || os.homedir()`, so redirecting only HOME leaves a developer's real ~/.gsd/defaults.json in play; - the mere EXISTENCE of a .planning/ directory disables the shared-defaults layer, so a fixture that creates one stops exercising the "poisoned global" path #2297 acceptance #4 is about. Measured: .planning/ with config -> gpt-5.6-terra; .planning/ present but empty -> gpt-5.6-terra; no .planning/ at all -> "". Rows cover: both reported repros; the init payload a real spawn reads; the omit gate, the runtime tier map and model_overrides each outranking the tier table; model/tier coherence under dynamic routing; resolve-execution with the attempt absent; max_escalations at limit-1/limit/limit+1 plus a cap of 0; and four fail-safe rows pinning that only a value canonicalizing to a recognised non-Claude runtime may outrank an omit. Registers the docs-guard exemption path: the rows quote the documented first-spawn contract in comments. The file still never READS a docs/ path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#4505): resolve models through the active runtime and the tier table Consolidates #4495 and #4493. One gap: the function every agent spawn goes through consulted neither mechanism that was supposed to make resolution runtime- and tier-aware. Half A -- the runtime was READ from config, not resolved. resolveActiveRuntime (GSD_RUNTIME -> config.runtime -> per-install marker -> claude) existed and worked, but was called exactly once in the file. Every other site read config['runtime'] raw, and that key is normally absent, so model_profile_overrides.<runtime>.<tier> was inert for any install that identifies its runtime through the environment or the marker. Same override both times, differing only in WHERE the runtime is declared: GSD_RUNTIME=opencode, no runtime key -> sonnet (ignored) runtime:"opencode" in the config -> TEST-OPENCODE (works) Half B -- nothing consulted dynamic_routing on the first spawn, though docs/features/dynamic-routing-with-failure-tier-escalation.md documents "the resolver picks tier_models[default_tier] for the FIRST spawn". The tier-table lookup is extracted into ONE helper both entry points call, so the first-spawn value and the escalated value cannot drift; resolveModelInternal calls it at attempt 0 and resolveModelForTier at the real attempt. Placement is the documented composition, not a convenience. The same doc says "model_overrides always wins; dynamic_routing.tier_models[<tier>] resolves above models.<phase_type> and model_profile" -- so the step sits BELOW model_overrides, the model_policy preset, the runtime tier map, the resolve_model_ids:"omit" gate and the claude tier override, and ABOVE the profile lookup. An earlier cut routed every call site through resolveModelForTier instead, which returns the tier model directly and therefore skipped three of those layers: with an omit and a non-Claude runtime it handed out a model id where the gate had returned "". Criterion 1 is applied in full, including the two value-policy reads #4192 had recorded as "NOT via resolveActiveRuntime". The tests decided it: switching them breaks nothing, so that reading was never enforced -- and the old behaviour defeated #4192's own principle that an explicit pin must not be silently unpinned (claude-opus-4-8 under GSD_RUNTIME=opencode collapsed to the Claude-only alias opus). #4192's comment is updated in place rather than left stale. The step-3 opt-in signal is CANONICALIZED. Comparing the raw config field against the literal 'claude' made runtime:"Claude", "claude-code" and even 5 count as non-Claude opt-ins and outrank an explicit omit -- failing OPEN in exactly the #2297 case the guard exists to protect. null now covers both "not a string" and "not a runtime we recognise", and both read as NOT an opt-in. Verified cell by cell against a pristine origin/next worktree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#4505): backfill changeset PR number (#4726) --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
222 lines
12 KiB
JavaScript
222 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Pinned baseline for scripts/lint-docs-guard-registration.cjs's
|
|
* `docs-guard-exempt` identity ratchet — mirrors
|
|
* scripts/lint-allow-test-rule-refs.cjs's `allow-test-rule` ratchet
|
|
* (scripts/lib/allowlist-ratchet.cjs, ADR-456's pattern).
|
|
*
|
|
* Every test file basename here is a KNOWN, already-reviewed
|
|
* `// docs-guard-exempt: <reason>` marker. Nothing caps how many files may
|
|
* carry the marker in the abstract, but ADDING a new one anywhere in
|
|
* tests/*.test.cjs fails `lint-docs-guard-registration` until this baseline
|
|
* is deliberately updated — so a new exemption always shows up as a
|
|
* reviewable diff here, instead of silently opting a file out of the
|
|
* docs-guard registration requirement with zero signal.
|
|
*
|
|
* A file listed here that no longer exists, or no longer carries a real
|
|
* `docs-guard-exempt:` marker in its first 20 lines, is reported STALE and
|
|
* must be pruned (ratchet-down, same as the sibling gate).
|
|
*
|
|
* Seeded 2026-08-23 (#3753 security follow-up FIX 2) from every real marker
|
|
* found via scripts/lint-docs-guard-registration.cjs's own `findExemption`
|
|
* scan of tests/*.test.cjs — derived, not retyped from memory.
|
|
*
|
|
* Extended 2026-08-23 (#3753 correctness follow-up): the reader-detection
|
|
* fixes (Defects A/B/C) surfaced 86 previously-undetected docs/ readers. 40
|
|
* were genuine docs-content guards (added to DOCS_GUARD_TESTS in
|
|
* scripts/docs-guard-registry.cjs); the 46 added here touch a docs/ path
|
|
* only incidentally (fixture data, external URL citations, comment-only
|
|
* mentions, metadata labels, or scan-exclusion lists) — see each file's own
|
|
* `docs-guard-exempt:` marker for its specific reason.
|
|
*/
|
|
const DOCS_GUARD_EXEMPT_BASELINE = [
|
|
'adr-612-bracket-phase-counting.test.cjs',
|
|
'adr-parser.test.cjs',
|
|
'adr-parser.unit.test.cjs',
|
|
'agent-marker-documentation-guard.test.cjs',
|
|
'antigravity-upgrades.test.cjs',
|
|
'capability-cli.test.cjs',
|
|
'capability-validator-task-content-resolver.test.cjs',
|
|
'ci-docs-guard-registry.test.cjs',
|
|
'ci-test-scope.test.cjs',
|
|
'cline-install.test.cjs',
|
|
'code-review-depth.test.cjs',
|
|
'code-review-pipeline-regression.test.cjs',
|
|
'codebuddy-upgrades.test.cjs',
|
|
'commands.test.cjs',
|
|
'commit-docs-bypass.test.cjs',
|
|
'compact-content-partition-guard.test.cjs',
|
|
'complexity-trigger.test.cjs',
|
|
'concurrency-safety.test.cjs',
|
|
'cursor-imperative-reference.test.cjs',
|
|
'declarative-reference-antigravity.test.cjs',
|
|
'declarative-reference-zcode.test.cjs',
|
|
'emitted-attribution.test.cjs',
|
|
'eslint-rules.test.cjs',
|
|
'estimate-calibrate.test.cjs',
|
|
'gen-context-index.test.cjs',
|
|
'gen-registry.test.cjs',
|
|
'gsd-agent-isolation-guard.test.cjs',
|
|
'hermes-dispatch-upgrade.test.cjs',
|
|
'install-minimal-hooks.test.cjs',
|
|
'install-runtime-artifacts.test.cjs',
|
|
'installer-migration-config-root-marker.test.cjs',
|
|
'installer-migration-pi-extension-ext.test.cjs',
|
|
'installer-migrations.test.cjs',
|
|
'kimi-upgrades.test.cjs',
|
|
'lint-allow-test-rule-refs.test.cjs',
|
|
'lint-docs-command-form.test.cjs',
|
|
'lint-docs-required.test.cjs',
|
|
'manifest-version-sync.test.cjs',
|
|
'milestone-archive.test.cjs',
|
|
'model-resolver.test.cjs',
|
|
'new-project-mvp-prompt.test.cjs',
|
|
'onboard-command.test.cjs',
|
|
'opencode-command-dir-plural.test.cjs',
|
|
'phase.test.cjs',
|
|
'pr-branch-planning-filter.test.cjs',
|
|
'precommit-alias-drift-hook.test.cjs',
|
|
'removed-but-needed-lint.test.cjs',
|
|
'repo-invariants.test.cjs',
|
|
'require-issue-link-policy.test.cjs',
|
|
'reviewer-manifest-body.test.cjs',
|
|
'reviewer-step-dispatch.test.cjs',
|
|
'run-tests-harness.test.cjs',
|
|
'runtime-name-policy.test.cjs',
|
|
'security-prompt-injection.security.test.cjs',
|
|
'shipped-reference-cites.test.cjs',
|
|
'state.test.cjs',
|
|
'worktree-safety.test.cjs',
|
|
];
|
|
|
|
/**
|
|
* Security follow-up FIX 3: a per-file fingerprint of every distinct
|
|
* `docs/...` path TOKEN referenced by each baselined file (derived via
|
|
* scripts/lint-docs-guard-registration.cjs's `extractDocsPathReferences`,
|
|
* never retyped from memory). A baselined file whose live fingerprint
|
|
* DIFFERS from the recorded one here fails the lint — the exemption's
|
|
* premise ("this file doesn't really guard shipped docs content") may no
|
|
* longer hold and a human must re-confirm it before the entry is updated.
|
|
* Keeping this a plain, sorted, diffable path list (not a hash) is
|
|
* deliberate: a reviewer can see exactly WHAT changed from the PR diff
|
|
* alone.
|
|
*
|
|
* Seeded 2026-08-23 alongside DOCS_GUARD_EXEMPT_BASELINE above, from the
|
|
* exact same scan.
|
|
*/
|
|
const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
|
|
// #2761: cites docs/adr/612-bracket-phase-id-convention.md in an explanatory
|
|
// comment describing ADR-612's Decision 1; the file never reads that (or any)
|
|
// docs/ file — every read call it makes targets a tmpdir .planning fixture.
|
|
'adr-612-bracket-phase-counting.test.cjs': ['docs/adr/612-bracket-phase-id-convention.md'],
|
|
'adr-parser.test.cjs': ['docs/adr/0001.md', 'docs/adr/0002.md', 'docs/adr/0010.md', 'docs/adr/NNNN.md'],
|
|
'adr-parser.unit.test.cjs': ['docs/adr/0001.md', 'docs/adr/0099.md', 'docs/adr/NNNN.md'],
|
|
'agent-marker-documentation-guard.test.cjs': ['docs/reference', 'docs/reference/workflow-fragments.md'],
|
|
'antigravity-upgrades.test.cjs': ['docs/cli', 'docs/cli/gcli-migration', 'docs/cli/permissions'],
|
|
'capability-cli.test.cjs': ['docs/reference/gsd-capability-command.md'],
|
|
// #3970: cites docs/adr/3646-per-task-content-resolution-seam.md in an
|
|
// explanatory comment describing ADR-3646's Decision 3; the file never
|
|
// reads that (or any) docs/ file.
|
|
'capability-validator-task-content-resolver.test.cjs': ['docs/adr/3646-per-task-content-resolution-seam.md'],
|
|
'ci-docs-guard-registry.test.cjs': [
|
|
'docs/AGENTS.md', 'docs/COMMANDS.md', 'docs/INVENTORY.md', 'docs/a.md', 'docs/adr',
|
|
'docs/adr/0001-example.md', 'docs/adrenaline.md', 'docs/bar.md', 'docs/foo.md', 'docs/how-to/foo.md',
|
|
'docs/how-to/some-unrelated-guide.md', 'docs/how-to/x.md', 'docs/some-unrelated-file.md',
|
|
'docs/totally-unrelated.md',
|
|
],
|
|
// #4641: cites docs/adr/4641-windows-selector-consolidation.md in an
|
|
// explanatory comment describing why the retired `windows` scope must not
|
|
// be restored; the file never reads that (or any) docs/ file.
|
|
'ci-test-scope.test.cjs': [
|
|
'docs/a.md', 'docs/adr', 'docs/adr/22-plan-drift-guard.md',
|
|
'docs/adr/4641-windows-selector-consolidation.md', 'docs/how-to/configure-model-profiles.md',
|
|
'docs/installer-migrations.md', 'docs/ja-JP', 'docs/ja-JP/USAGE.md', 'docs/usage.md', 'docs/x.md',
|
|
],
|
|
'cline-install.test.cjs': ['docs/guide.md'],
|
|
'code-review-depth.test.cjs': ['docs/src/auth/x.ts'],
|
|
'code-review-pipeline-regression.test.cjs': ['docs/DEVELOPMENT.md'],
|
|
'codebuddy-upgrades.test.cjs': ['docs/cli/sub-agents'],
|
|
'commands.test.cjs': ['docs/x.md'],
|
|
'commit-docs-bypass.test.cjs': [
|
|
'docs/40-design.md', 'docs/CONFIGURATION.md', 'docs/readme.md', 'docs/tracked-var-mentioning',
|
|
],
|
|
// #4403: cites docs/PARTITION-RULES.md as a "see" pointer in the module docstring
|
|
// describing the operational spec this guard implements; the file never reads that
|
|
// (or any) docs/ file.
|
|
'compact-content-partition-guard.test.cjs': ['docs/PARTITION-RULES.md'],
|
|
'complexity-trigger.test.cjs': ['docs/readme.md'],
|
|
// #3884: cites docs/CLI-TOOLS.md:736 in an explanatory comment describing
|
|
// the real `frontmatter get <file> [--field key]` CLI shape; the file
|
|
// never reads that (or any) docs/ file.
|
|
'concurrency-safety.test.cjs': ['docs/CLI-TOOLS.md'],
|
|
'cursor-imperative-reference.test.cjs': ['docs/sdk/typescript'],
|
|
'declarative-reference-antigravity.test.cjs': ['docs/cli/features'],
|
|
'declarative-reference-zcode.test.cjs': ['docs/reference/host-integration-capability-matrix.md'],
|
|
'emitted-attribution.test.cjs': ['docs/README.md', 'docs/tests', 'docs/tests/helpers/install-shared.cjs'],
|
|
// #3914: cites docs/adr/3889-process-exit-contract.md ~:350-362 in an
|
|
// explanatory comment describing the intentional computed-property
|
|
// boundary move; the file never reads that (or any) docs/ file.
|
|
'eslint-rules.test.cjs': ['docs/adr/3889-process-exit-contract.md', 'docs/readme.md'],
|
|
'estimate-calibrate.test.cjs': [
|
|
'docs/adr', 'docs/adr/2629-phase-effort-estimation-calibration.md', 'docs/reference',
|
|
'docs/reference/planning-artifacts.md',
|
|
],
|
|
'gen-context-index.test.cjs': ['docs/CONTEXT-INDEX.json', 'docs/INVENTORY-MANIFEST.json'],
|
|
'gen-registry.test.cjs': ['docs/registries', 'docs/registries/reviewers.json'],
|
|
'gsd-agent-isolation-guard.test.cjs': ['docs/adr/1239-...md', 'docs/adr/1239-gsd-embeddable-orchestration-engine.md'],
|
|
'hermes-dispatch-upgrade.test.cjs': ['docs/guides/delegation-patterns.md'],
|
|
'install-minimal-hooks.test.cjs': ['docs/en/hooks', 'docs/en/users/features/hooks'],
|
|
'install-runtime-artifacts.test.cjs': ['docs/CONFIGURATION.md', 'docs/adr/58-...md', 'docs/adr/58-runtime-install-policy-module.md', 'docs/cli/slash-commands'],
|
|
'installer-migration-config-root-marker.test.cjs': ['docs/installer-migrations.md'],
|
|
'installer-migration-pi-extension-ext.test.cjs': ['docs/installer-migrations.md'],
|
|
'installer-migrations.test.cjs': ['docs/installer-migrations.md'],
|
|
'kimi-upgrades.test.cjs': ['docs/reference/host-integration-capability-matrix.md'],
|
|
'lint-allow-test-rule-refs.test.cjs': ['docs/readme.md'],
|
|
'lint-docs-command-form.test.cjs': ['docs/adr', 'docs/adr/999-example.md', 'docs/how-to/example.md'],
|
|
'lint-docs-required.test.cjs': [
|
|
'docs/COMMANDS.md', 'docs/USER-GUIDE.md', 'docs/adr', 'docs/adr/0001-foo.md', 'docs/adr/0099-new.md',
|
|
'docs/agents', 'docs/agents/triage-labels.md',
|
|
],
|
|
'manifest-version-sync.test.cjs': [],
|
|
// #3884: re-confirmed — the added docs/CLI-TOOLS.md:458 reference is the
|
|
// same class as the existing docs/TESTING-SUITES.md one (a placement-note
|
|
// / explanatory comment citing documented CLI behavior for context, never
|
|
// a read target); the exemption's premise still holds for both.
|
|
'milestone-archive.test.cjs': ['docs/CLI-TOOLS.md', 'docs/TESTING-SUITES.md'],
|
|
// #4505: additionally cites docs/features/dynamic-routing-with-failure-tier-escalation.md
|
|
// in explanatory comments, quoting the documented first-spawn contract the new rows
|
|
// assert against; the file never reads that (or any) docs/ file — every read it makes
|
|
// targets a tmpdir .planning fixture.
|
|
'model-resolver.test.cjs': [
|
|
'docs/TESTING-SUITES.md', 'docs/features/dynamic-routing-with-failure-tier-escalation.md',
|
|
],
|
|
'new-project-mvp-prompt.test.cjs': ['docs/CONFIGURATION.md'],
|
|
'onboard-command.test.cjs': ['docs/adr/0001-runtime.md'],
|
|
'opencode-command-dir-plural.test.cjs': ['docs/commands'],
|
|
'phase.test.cjs': ['docs/adr/3524-...md', 'docs/adr/3524-cjs-sdk-hard-seam.md'],
|
|
'pr-branch-planning-filter.test.cjs': ['docs/readme.md'],
|
|
'precommit-alias-drift-hook.test.cjs': ['docs/adr/0174-...md', 'docs/adr/0174-retire-gsd-sdk-package-boundary.md'],
|
|
'removed-but-needed-lint.test.cjs': [
|
|
'docs/README.md', 'docs/getting-started.md', 'docs/gsd-new-workspace.md', 'docs/setup.md', 'docs/some-doc.md',
|
|
],
|
|
'repo-invariants.test.cjs': ['docs/FEATURES.md', 'docs/workflows/README'],
|
|
'require-issue-link-policy.test.cjs': ['docs/-prefixed', 'docs/CONFIGURATION.md', 'docs/a.md', 'docs/b.md', 'docs/guide.md'],
|
|
'reviewer-manifest-body.test.cjs': ['docs/how-to/ship-a-reviewer-lane.md'],
|
|
// #4209: 'docs/spec.md' is a synthetic, never-read fake path proving dispatchReviewerLanes
|
|
// has no code-review-specific special-casing; 'docs/adr/456-test-rigor-architecture.md' is a
|
|
// prose citation in a code comment justifying the fast-check property tests below (#4209
|
|
// review). Neither is a real filesystem read — the file never reads any docs/ file.
|
|
'reviewer-step-dispatch.test.cjs': ['docs/adr/456-test-rigor-architecture.md', 'docs/spec.md'],
|
|
'run-tests-harness.test.cjs': ['docs/TESTING-SUITES.md'],
|
|
'runtime-name-policy.test.cjs': ['docs/customize/skills'],
|
|
'security-prompt-injection.security.test.cjs': ['docs/notes.md'],
|
|
'shipped-reference-cites.test.cjs': [],
|
|
// #4186: the record-session usage-contract test cites the documented
|
|
// signature in docs/CLI-TOOLS.md in its explanatory comment.
|
|
'state.test.cjs': ['docs/CLI-TOOLS.md', 'docs/CONFIGURATION.md', 'docs/reference/state-md.md'],
|
|
'worktree-safety.test.cjs': ['docs/SUMMARY.md'],
|
|
};
|
|
|
|
module.exports = { DOCS_GUARD_EXEMPT_BASELINE, DOCS_GUARD_EXEMPT_DOCS_PATHS };
|