@haposoft/cafekit 0.16.7 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +169 -272
- package/bin/__tests__/codex-native.test.js +100 -202
- package/bin/__tests__/develop-contract.test.js +122 -1413
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +154 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +90 -925
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +30 -161
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +18 -2949
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +27 -0
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +25 -0
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +44 -56
- package/src/claude/agents/debugger.md +8 -88
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +6 -14
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +12 -16
- package/src/claude/agents/inspector.md +4 -3
- package/src/claude/agents/project-manager.md +9 -17
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +15 -15
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -83
- package/src/claude/hooks/spec-state.cjs +142 -39
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +42 -22
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +2 -3
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +11 -19
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -121
- package/src/claude/scripts/spec-resolver.cjs +41 -458
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +29 -81
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +26 -83
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +17 -46
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +16 -45
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +39 -104
- package/src/claude/skills/test/references/execution-strategy.md +93 -21
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
- package/src/codex/hooks/lib/spec-utils.cjs +6 -52
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +13 -81
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +6 -12
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/lib/context.cjs +0 -616
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3941
- package/src/claude/skills/code-review/references/verification-gate.md +0 -46
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
|
@@ -1,1132 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const assert = require('node:assert/strict');
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
const { spawnSync } = require('node:child_process');
|
|
8
|
-
const test = require('node:test');
|
|
9
|
-
|
|
10
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '../..');
|
|
11
|
-
const SCAFFOLD = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-scaffold.cjs');
|
|
12
|
-
const VALIDATOR = path.join(PACKAGE_ROOT, 'src/claude/scripts/validate-spec-output.cjs');
|
|
13
|
-
const GROUNDER = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-ground.cjs');
|
|
14
|
-
const AUTHORING_VALIDATOR = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-authoring-validation.cjs');
|
|
15
|
-
|
|
16
|
-
// C2 SemanticReviewReceipt (R1-01, frozen): a minimal but exact-shape completed
|
|
17
|
-
// receipt for fixtures that need `ready_for_implementation` true.
|
|
18
|
-
function completedSemanticReview({ reviewedCriteria, counterexamples }) {
|
|
19
|
-
return {
|
|
20
|
-
status: 'completed',
|
|
21
|
-
semantic_digest: null,
|
|
22
|
-
verdict: 'PASS',
|
|
23
|
-
lifecycle_disposition: 'CONTINUE',
|
|
24
|
-
findings: [],
|
|
25
|
-
unresolved_decisions: [],
|
|
26
|
-
graph_coverage: [
|
|
27
|
-
'criterion_local', 'cross_criterion', 'runtime_path', 'assumption_provenance', 'compatibility_migration',
|
|
28
|
-
].map((surface) => ({ surface, covered: true, notes: 'Fixture coverage for structural contract test.' })),
|
|
29
|
-
repair_round: 0,
|
|
30
|
-
reviewed_criteria: reviewedCriteria,
|
|
31
|
-
counterexamples,
|
|
32
|
-
reviewer_evidence: { assurance: 'Routine', summary: 'Fixture reviewer evidence for structural contract test.' },
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function exec(root, script, args) {
|
|
37
|
-
return spawnSync(process.execPath, [script, ...args], { cwd: root, encoding: 'utf8' });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function output(result) { return `${result.stdout}\n${result.stderr}`; }
|
|
41
|
-
|
|
42
|
-
function task(id, title, ownedPath, action = 'modify', dependencies = 'none', criterion = 'R1.1', taskRole = 'subject') {
|
|
43
|
-
return `# Task ${id}: ${title}
|
|
44
|
-
**Status:** pending
|
|
45
|
-
|
|
46
|
-
## Outcome
|
|
47
|
-
Deliver observable ${title.toLowerCase()} behavior through the declared entrypoint.
|
|
48
|
-
|
|
49
|
-
## Scope
|
|
50
|
-
- **In scope:** Exact behavior for ${ownedPath}.
|
|
51
|
-
- **Out of scope:** Unrelated runtime behavior.
|
|
52
|
-
|
|
53
|
-
## Anchors and Ownership
|
|
54
|
-
| ID | Type | Target | Role | Access | Action |
|
|
55
|
-
|---|---|---|---|---|---|
|
|
56
|
-
| A-${id}-01 | file | \`${ownedPath}\` | owner | write | ${action} |
|
|
57
|
-
| A-${id}-02 | artifact | \`artifacts/${id}.json\` | verifier | write | create |
|
|
58
|
-
|
|
59
|
-
## Changes
|
|
60
|
-
- [ ] ${criterion ? `Implement the owned behavior. _Requirements: ${criterion.slice(1)}_` : 'Verify the subject implementation through the canonical proof boundary.'}
|
|
61
|
-
|
|
62
|
-
## Acceptance
|
|
63
|
-
${criterion ? `- **${criterion}:** The command returns the concrete expected state.` : '- The verifier produces an observable proof artifact without claiming the subject acceptance criterion.'}
|
|
64
|
-
|
|
65
|
-
## Dependencies
|
|
66
|
-
- ${dependencies}
|
|
67
|
-
|
|
68
|
-
## Verification Plan
|
|
69
|
-
- **Verification ref:** V1
|
|
70
|
-
- **Task role:** ${taskRole}
|
|
71
|
-
- **Command:** \`node --test test/service.test.js\`
|
|
72
|
-
- **Expected:** Exit code 0 and exact state enabled.
|
|
73
|
-
- **Negative path:** Invalid input returns the specified failure state.
|
|
74
|
-
- **Reachability:** \`src/entry.js\`
|
|
75
|
-
`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function makeFixture({ ready = false } = {}) {
|
|
79
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-sk21-validator-'));
|
|
80
|
-
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
|
|
81
|
-
fs.mkdirSync(path.join(root, 'test'), { recursive: true });
|
|
82
|
-
fs.mkdirSync(path.join(root, 'artifacts'), { recursive: true });
|
|
83
|
-
fs.writeFileSync(path.join(root, 'src/entry.js'), 'module.exports = { enabled: true };\n');
|
|
84
|
-
fs.writeFileSync(path.join(root, 'src/service.js'), 'module.exports = { enabled: true };\n');
|
|
85
|
-
fs.writeFileSync(path.join(root, 'src/design-boundary.js'), 'module.exports = {};\n');
|
|
86
|
-
fs.writeFileSync(path.join(root, 'test/service.test.js'), 'require("node:test")("service",()=>{});\n');
|
|
87
|
-
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ scripts: { test: 'node --test' } }));
|
|
88
|
-
const boundaries = [{
|
|
89
|
-
id: 'B-OWN', type: 'ownership', tasks: ['R1-01', 'R1-02'],
|
|
90
|
-
write_sets: {
|
|
91
|
-
'R1-01': ['src/entry.js', 'artifacts/R1-01.json'],
|
|
92
|
-
'R1-02': ['src/service.js', 'artifacts/R1-02.json'],
|
|
93
|
-
},
|
|
94
|
-
}, {
|
|
95
|
-
id: 'B-V', type: 'proof', subject: 'R1-01', verifier: 'R1-02',
|
|
96
|
-
verification_ref: 'V1', artifact_anchor: 'A-R1-02-02',
|
|
97
|
-
}];
|
|
98
|
-
const scaffold = exec(root, SCAFFOLD, [
|
|
99
|
-
'feature', '--tasks', 'R1-01-entry,R1-02-verify', '--boundaries', JSON.stringify(boundaries),
|
|
100
|
-
]);
|
|
101
|
-
assert.equal(scaffold.status, 0, scaffold.stderr);
|
|
102
|
-
const specDir = path.join(root, 'specs/feature');
|
|
103
|
-
fs.writeFileSync(path.join(specDir, 'requirements.md'), `# Requirements
|
|
104
|
-
|
|
105
|
-
## Requirements
|
|
106
|
-
### Requirement 1: Service behavior
|
|
107
|
-
- **R1.1**: When valid input arrives, the system shall return enabled state; invalid input shall return rejected state.
|
|
108
|
-
- **R1.2**: The verifier shall produce a separate observable proof artifact for the enabled and rejected states.
|
|
109
|
-
`);
|
|
110
|
-
fs.writeFileSync(path.join(specDir, 'design.md'), `# Design
|
|
111
|
-
|
|
112
|
-
## Architecture
|
|
113
|
-
The entry delegates to the service and emits a verifier-owned artifact.
|
|
114
|
-
|
|
115
|
-
## Typed Anchors
|
|
116
|
-
| ID | Type | Target | Role | Access | Action |
|
|
117
|
-
|---|---|---|---|---|---|
|
|
118
|
-
| A-D-01 | file | \`src/design-boundary.js\` | design boundary | read | read |
|
|
119
|
-
| A-D-02 | artifact | \`artifacts/design-proof.json\` | taskless proof evidence | write | create |
|
|
120
|
-
|
|
121
|
-
## Canonical Contracts & Invariants
|
|
122
|
-
### D1 — Delegation decision
|
|
123
|
-
The entry delegates exactly once.
|
|
124
|
-
### I1 — Invalid-state invariant
|
|
125
|
-
Invalid input never returns enabled state.
|
|
126
|
-
### C1 — Result contract
|
|
127
|
-
The result contains an enabled boolean.
|
|
128
|
-
### T1 — Service transition
|
|
129
|
-
The transition moves rejected input to failure and valid input to enabled; recovery retries from rejected.
|
|
130
|
-
|
|
131
|
-
## Verification Definitions
|
|
132
|
-
- **V1**: Criteria R1.1; Owner R1-01; Proof criteria R1.2; Proof owner R1-02; Evidence anchor A-R1-02-02; Decision refs D1, I1, C1; Method command \`node --test test/service.test.js\`; Expected exit 0 with enabled state and verifier artifact; Negative/failure invalid input returns rejected state without an enabled result; Reachability/grounding entrypoint \`src/entry.js\` via A-D-01, A-R1-02-02.
|
|
133
|
-
`);
|
|
134
|
-
fs.writeFileSync(path.join(specDir, 'tasks/task-R1-01-entry.md'), task('R1-01', 'Entry', 'src/entry.js'));
|
|
135
|
-
fs.writeFileSync(path.join(specDir, 'tasks/task-R1-02-verify.md'), task('R1-02', 'Verify', 'src/service.js', 'modify', 'none', 'R1.2', 'verifier'));
|
|
136
|
-
const promoted = exec(root, SCAFFOLD, ['feature', '--sync-semantic-model']);
|
|
137
|
-
assert.equal(promoted.status, 0, output(promoted));
|
|
138
|
-
const specPath = path.join(specDir, 'spec.json');
|
|
139
|
-
if (!ready) return { root, specDir, specPath };
|
|
140
|
-
// C16/D13: only spec-authoring-validation.cjs may flip authoring.* to
|
|
141
|
-
// validated and write the matching receipt (I21/R3.9) — run the real
|
|
142
|
-
// coordinator instead of hand-writing those fields.
|
|
143
|
-
const coordinated = exec(root, AUTHORING_VALIDATOR, [specDir]);
|
|
144
|
-
assert.equal(coordinated.status, 0, output(coordinated));
|
|
145
|
-
const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
|
|
146
|
-
spec.validation.status = 'completed';
|
|
147
|
-
spec.validation.semantic_review = completedSemanticReview({
|
|
148
|
-
reviewedCriteria: ['R1.1', 'R1.2'],
|
|
149
|
-
counterexamples: [{
|
|
150
|
-
criterion: 'R1.1', case_kind: 'failure',
|
|
151
|
-
scenario: 'The service receives an invalid input object without the required discriminator.',
|
|
152
|
-
expected: 'The service returns rejected state and never emits enabled true.',
|
|
153
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
154
|
-
}, {
|
|
155
|
-
criterion: 'R1.2', case_kind: 'adversarial',
|
|
156
|
-
scenario: 'The implementation reports enabled state without producing the verifier-owned artifact.',
|
|
157
|
-
expected: 'Verification fails because the separate proof artifact is absent.',
|
|
158
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
159
|
-
}],
|
|
160
|
-
});
|
|
161
|
-
fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
|
|
162
|
-
const digest = exec(root, VALIDATOR, [specDir, '--semantic-digest']);
|
|
163
|
-
assert.equal(digest.status, 0, output(digest));
|
|
164
|
-
spec.validation.semantic_review.semantic_digest = digest.stdout.trim();
|
|
165
|
-
spec.ready_for_implementation = true;
|
|
166
|
-
fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
|
|
167
|
-
return { root, specDir, specPath };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function mutateFixture(fixture, mutate) {
|
|
171
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
172
|
-
mutate(state);
|
|
173
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
174
|
-
return exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function refreshSemanticDigest(fixture) {
|
|
178
|
-
const previous = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
179
|
-
const promoted = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
|
|
180
|
-
assert.equal(promoted.status, 0, output(promoted));
|
|
181
|
-
// C16/D13: authoring.* and its receipt are never hand-restored — only
|
|
182
|
-
// spec-authoring-validation.cjs may write them (I21). Re-run it best-effort
|
|
183
|
-
// over the current (possibly just-edited) bytes; a fixture that
|
|
184
|
-
// deliberately broke grounding/validation after the initial ready fixture
|
|
185
|
-
// leaves authoring at scaffold's own draft reset instead, which is fine —
|
|
186
|
-
// the caller is exercising a different failure, not readiness itself.
|
|
187
|
-
const wasValidated = ['requirements', 'design', 'research', 'tasks']
|
|
188
|
-
.some((field) => previous.authoring[field] === 'validated');
|
|
189
|
-
if (wasValidated) exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
|
|
190
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
191
|
-
state.validation.status = previous.validation.status;
|
|
192
|
-
state.validation.semantic_review = previous.validation.semantic_review;
|
|
193
|
-
state.validation.semantic_review.semantic_digest = null;
|
|
194
|
-
state.ready_for_implementation = false;
|
|
195
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
196
|
-
const digest = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
|
|
197
|
-
assert.equal(digest.status, 0, output(digest));
|
|
198
|
-
state.validation.semantic_review.semantic_digest = digest.stdout.trim();
|
|
199
|
-
state.ready_for_implementation = previous.ready_for_implementation;
|
|
200
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function promoteSemanticModel(fixture) {
|
|
204
|
-
const promoted = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
|
|
205
|
-
assert.equal(promoted.status, 0, output(promoted));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function appendTaskAnchor(fixture, row) {
|
|
209
|
-
const taskPath = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
210
|
-
const content = fs.readFileSync(taskPath, 'utf8').replace(
|
|
211
|
-
'| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |',
|
|
212
|
-
`| A-R1-01-02 | artifact | \`artifacts/R1-01.json\` | verifier | write | create |\n${row}`,
|
|
213
|
-
);
|
|
214
|
-
fs.writeFileSync(taskPath, content);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function canonicalResearch21() {
|
|
218
|
-
return `# Research
|
|
219
|
-
|
|
220
|
-
## Uncertainty
|
|
221
|
-
- Which exact service boundary owns the enabled state and its rejected-input behavior?
|
|
222
|
-
|
|
223
|
-
## Evidence Summary
|
|
224
|
-
- Inspection of \`src/service.js\` shows the module exports the enabled-state boundary used by this fixture.
|
|
225
|
-
|
|
226
|
-
## Decision
|
|
227
|
-
- Keep the service module as canonical owner because the inspected implementation exposes the required observable state.
|
|
228
|
-
|
|
229
|
-
## Remaining Gaps
|
|
230
|
-
- None — the inspected service evidence resolves the ownership uncertainty for this bounded fixture.
|
|
231
|
-
`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function makeTasklessReadyFixture() {
|
|
235
|
-
const fixture = makeFixture();
|
|
236
|
-
fs.rmSync(path.join(fixture.specDir, 'tasks'), { recursive: true, force: true });
|
|
237
|
-
const designPath = path.join(fixture.specDir, 'design.md');
|
|
238
|
-
fs.writeFileSync(designPath, fs.readFileSync(designPath, 'utf8')
|
|
239
|
-
.replace('Owner R1-01', 'Owner A-D-01')
|
|
240
|
-
.replace('Proof owner R1-02', 'Proof owner A-D-02')
|
|
241
|
-
.replace('Evidence anchor A-R1-02-02', 'Evidence anchor A-D-02')
|
|
242
|
-
.replace('via A-D-01, A-R1-02-02', 'via A-D-01, A-D-02'));
|
|
243
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
244
|
-
delete state.task_files;
|
|
245
|
-
delete state.task_registry;
|
|
246
|
-
state.coordination = { boundaries: [] };
|
|
247
|
-
// Dropping task topology requires the taskless authoring state exactly:
|
|
248
|
-
// scaffold's closed-world check refuses task_files/task_registry omission
|
|
249
|
-
// paired with anything but authoring.tasks = 'absent'.
|
|
250
|
-
state.authoring.tasks = 'absent';
|
|
251
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
252
|
-
const promoted = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
|
|
253
|
-
assert.equal(promoted.status, 0, output(promoted));
|
|
254
|
-
// C16/D13: only spec-authoring-validation.cjs may flip authoring.* to
|
|
255
|
-
// validated and write the matching receipt (I21/R3.9) — run the real
|
|
256
|
-
// coordinator instead of hand-writing those fields.
|
|
257
|
-
const coordinated = exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
|
|
258
|
-
assert.equal(coordinated.status, 0, output(coordinated));
|
|
259
|
-
const validatedState = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
260
|
-
validatedState.validation.status = 'completed';
|
|
261
|
-
validatedState.validation.semantic_review = completedSemanticReview({
|
|
262
|
-
reviewedCriteria: ['R1.1', 'R1.2'],
|
|
263
|
-
counterexamples: [{
|
|
264
|
-
criterion: 'R1.1', case_kind: 'failure',
|
|
265
|
-
scenario: 'The service receives invalid input without the required discriminator.',
|
|
266
|
-
expected: 'The service returns rejected state and never emits enabled true.',
|
|
267
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
268
|
-
}, {
|
|
269
|
-
criterion: 'R1.2', case_kind: 'adversarial',
|
|
270
|
-
scenario: 'The subject reports success without producing the design-owned proof artifact.',
|
|
271
|
-
expected: 'Verification fails because the separate taskless proof evidence is absent.',
|
|
272
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
273
|
-
}],
|
|
274
|
-
});
|
|
275
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(validatedState, null, 2)}\n`);
|
|
276
|
-
const digest = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
|
|
277
|
-
assert.equal(digest.status, 0, output(digest));
|
|
278
|
-
const readyState = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
279
|
-
readyState.validation.semantic_review.semantic_digest = digest.stdout.trim();
|
|
280
|
-
readyState.ready_for_implementation = true;
|
|
281
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(readyState, null, 2)}\n`);
|
|
282
|
-
return fixture;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
test('generated schema 2.1 artifact validates after concrete authoring projection', () => {
|
|
286
|
-
const fixture = makeFixture();
|
|
287
|
-
try {
|
|
288
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
289
|
-
assert.equal(result.status, 0, output(result));
|
|
290
|
-
assert.match(output(result), /structural checks do not replace semantic judgment/);
|
|
291
|
-
const ground = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
292
|
-
assert.equal(ground.status, 0, output(ground));
|
|
293
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test('validator module API is side-effect free and matches CLI validation and digest results', () => {
|
|
297
|
-
const imported = spawnSync(process.execPath, ['-e', `
|
|
298
|
-
const api = require(${JSON.stringify(VALIDATOR)});
|
|
299
|
-
process.stdout.write(Object.keys(api).sort().join(','));
|
|
300
|
-
`], { encoding: 'utf8' });
|
|
301
|
-
assert.equal(imported.status, 0, output(imported));
|
|
302
|
-
assert.equal(imported.stderr, '');
|
|
303
|
-
assert.equal(imported.stdout, 'computeSemanticDigest21,validateSpec');
|
|
304
|
-
|
|
305
|
-
const fixture = makeFixture();
|
|
306
|
-
try {
|
|
307
|
-
const api = require(VALIDATOR);
|
|
308
|
-
const directValidation = api.validateSpec(fixture.specDir);
|
|
309
|
-
const cliValidation = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
310
|
-
assert.equal(cliValidation.status, directValidation.errors.length === 0 ? 0 : 1, output(cliValidation));
|
|
311
|
-
assert.deepEqual(directValidation.errors, []);
|
|
312
|
-
for (const warning of directValidation.warnings) assert.match(output(cliValidation), new RegExp(`\\[WARN\\] ${warning.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
|
|
313
|
-
|
|
314
|
-
const explicitSpec = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
315
|
-
const implicitDigest = api.computeSemanticDigest21(fixture.specDir);
|
|
316
|
-
const explicitDigest = api.computeSemanticDigest21(fixture.specDir, explicitSpec);
|
|
317
|
-
const cliDigest = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
|
|
318
|
-
assert.equal(cliDigest.status, 0, output(cliDigest));
|
|
319
|
-
assert.deepEqual(implicitDigest, explicitDigest);
|
|
320
|
-
assert.deepEqual(implicitDigest.errors, []);
|
|
321
|
-
assert.equal(implicitDigest.digest, cliDigest.stdout.trim());
|
|
322
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
test('ready schema 2.1 requires exact receipt coverage and traceable V definition', () => {
|
|
326
|
-
const fixture = makeFixture({ ready: true });
|
|
327
|
-
try {
|
|
328
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
329
|
-
assert.equal(result.status, 0, output(result));
|
|
330
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
test('digest is stable on lifecycle fields and stale on authoring, policy, or topology changes', () => {
|
|
334
|
-
const fixture = makeFixture({ ready: true });
|
|
335
|
-
try {
|
|
336
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
337
|
-
const digest = state.validation.semantic_review.semantic_digest;
|
|
338
|
-
state.status = 'blocked';
|
|
339
|
-
state.updated_at = '2030-01-01T00:00:00Z';
|
|
340
|
-
state.task_registry['tasks/task-R1-01-entry.md'].status = 'in_progress';
|
|
341
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
342
|
-
assert.equal(exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']).stdout.trim(), digest);
|
|
343
|
-
state.scope_lock.in_scope.push('new semantic scope');
|
|
344
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
345
|
-
const stale = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
346
|
-
assert.equal(stale.status, 1);
|
|
347
|
-
assert.match(output(stale), /semantic_digest: stale/);
|
|
348
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
test('receipt rejects proof self-owner and duplicate generic counterexamples', () => {
|
|
352
|
-
const fixture = makeFixture({ ready: true });
|
|
353
|
-
try {
|
|
354
|
-
let result = mutateFixture(fixture, (state) => {
|
|
355
|
-
const proof = state.coordination.boundaries.find((boundary) => boundary.type === 'proof');
|
|
356
|
-
proof.verifier = proof.subject;
|
|
357
|
-
});
|
|
358
|
-
assert.equal(result.status, 1);
|
|
359
|
-
assert.match(output(result), /proof subject must differ from verifier/);
|
|
360
|
-
|
|
361
|
-
const fresh = makeFixture({ ready: true });
|
|
362
|
-
try {
|
|
363
|
-
result = mutateFixture(fresh, (state) => {
|
|
364
|
-
state.validation.semantic_review.counterexamples.push({
|
|
365
|
-
criterion: 'R1.1', case_kind: 'failure', scenario: 'works', expected: 'correct behavior',
|
|
366
|
-
decision_refs: ['D1'], verification_ref: 'V1',
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
assert.equal(result.status, 1);
|
|
370
|
-
assert.match(output(result), /generic boilerplate|duplicate counterexample fingerprint/);
|
|
371
|
-
} finally { fs.rmSync(fresh.root, { recursive: true, force: true }); }
|
|
372
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
test('validator rejects missing deliverable, recovery, and verifier-owned artifact anchor', () => {
|
|
376
|
-
const cases = [
|
|
377
|
-
[(state) => { state.coordination.boundaries.push({ id: 'B-D', type: 'dependency', producer: 'R1-01', consumer: 'R1-02' }); }, /malformed typed boundary/],
|
|
378
|
-
[(state) => { state.coordination.boundaries.push({ id: 'B-T', type: 'transition', design_ref: 'T1', owner: 'R1-01', consumers: ['R1-02'], precondition: 'input is accepted', postcondition: 'state is enabled', failure: 'state is rejected' }); }, /malformed typed boundary/],
|
|
379
|
-
[(state) => { state.coordination.boundaries.find((boundary) => boundary.type === 'proof').artifact_anchor = 'A-R1-01-02'; }, /must be owned by verifier/],
|
|
380
|
-
];
|
|
381
|
-
for (const [mutate, expected] of cases) {
|
|
382
|
-
const fixture = makeFixture();
|
|
383
|
-
try {
|
|
384
|
-
const result = mutateFixture(fixture, mutate);
|
|
385
|
-
assert.equal(result.status, 1);
|
|
386
|
-
assert.match(output(result), expected);
|
|
387
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test('dependency boundary binds exact producer and consumer anchors to the registry DAG edge', () => {
|
|
392
|
-
const fixture = makeFixture();
|
|
393
|
-
try {
|
|
394
|
-
const producerPath = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
395
|
-
const consumerPath = path.join(fixture.specDir, 'tasks/task-R1-02-verify.md');
|
|
396
|
-
fs.writeFileSync(producerPath, fs.readFileSync(producerPath, 'utf8').replace(
|
|
397
|
-
'| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |',
|
|
398
|
-
'| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |\n| A-R1-01-03 | artifact | `artifacts/deliverable.json` | producer | write | create |',
|
|
399
|
-
));
|
|
400
|
-
fs.writeFileSync(consumerPath, fs.readFileSync(consumerPath, 'utf8').replace(
|
|
401
|
-
'| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |',
|
|
402
|
-
'| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |\n| A-R1-02-03 | artifact | `artifacts/deliverable.json` | consumer | read | read |',
|
|
403
|
-
).replace('## Dependencies\n- none', '## Dependencies\n- tasks/task-R1-01-entry.md'));
|
|
404
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
405
|
-
state.coordination.boundaries.push({
|
|
406
|
-
id: 'B-D', type: 'dependency', producer: 'R1-01', consumer: 'R1-02',
|
|
407
|
-
deliverable: 'artifacts/deliverable.json',
|
|
408
|
-
});
|
|
409
|
-
state.coordination.boundaries.find((boundary) => boundary.type === 'ownership')
|
|
410
|
-
.write_sets['R1-01'].push('artifacts/deliverable.json');
|
|
411
|
-
state.task_registry['tasks/task-R1-02-verify.md'].dependencies = ['tasks/task-R1-01-entry.md'];
|
|
412
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
413
|
-
promoteSemanticModel(fixture);
|
|
414
|
-
const valid = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
415
|
-
assert.equal(valid.status, 0, output(valid));
|
|
416
|
-
|
|
417
|
-
state.task_registry['tasks/task-R1-02-verify.md'].dependencies = [];
|
|
418
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
419
|
-
const drift = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
420
|
-
assert.equal(drift.status, 1);
|
|
421
|
-
assert.match(output(drift), /must bind the registry DAG edge|exact projection/);
|
|
422
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
test('schema 2.1 reuses core machine lifecycle invariants without mutating artifacts', () => {
|
|
426
|
-
const cases = [
|
|
427
|
-
['scope-lock-shape', (state) => { state.scope_lock = true; }, /scope_lock: must be an object/],
|
|
428
|
-
['scope-lock-source', (state) => { state.scope_lock.source = ''; }, /scope_lock\.source: must be a non-empty string/],
|
|
429
|
-
['scope-lock-duplicates', (state) => { state.scope_lock.in_scope = ['same', 'same']; }, /in_scope: must be an array of unique/],
|
|
430
|
-
['scope-lock-policy', (state) => { state.scope_lock.expansion_policy = 'silent'; }, /expansion_policy: must be requires-user-approval/],
|
|
431
|
-
['validation-shape', (state) => { state.validation = 'completed'; }, /validation: must be an object|requires status and semantic_review/],
|
|
432
|
-
['registry-required-field', (state) => { delete state.task_registry['tasks/task-R1-01-entry.md'].blocker; }, /missing blocker/],
|
|
433
|
-
['registry-status', (state) => { state.task_registry['tasks/task-R1-01-entry.md'].status = 'ready'; }, /status: must be one of/],
|
|
434
|
-
['blocked-without-blocker', (state) => {
|
|
435
|
-
state.task_registry['tasks/task-R1-01-entry.md'].status = 'blocked';
|
|
436
|
-
state.task_registry['tasks/task-R1-01-entry.md'].last_updated_at = '2026-08-13T01:00:00Z';
|
|
437
|
-
}, /blocked status requires a non-empty blocker/],
|
|
438
|
-
['reversed-task-time', (state) => {
|
|
439
|
-
const entry = state.task_registry['tasks/task-R1-01-entry.md'];
|
|
440
|
-
entry.status = 'in_progress';
|
|
441
|
-
entry.started_at = '2026-08-13T02:00:00Z';
|
|
442
|
-
entry.last_updated_at = '2026-08-13T01:00:00Z';
|
|
443
|
-
}, /started_at <= last_updated_at/],
|
|
444
|
-
['reversed-spec-time', (state) => { state.updated_at = '2000-01-01T00:00:00Z'; }, /created_at <= updated_at/],
|
|
445
|
-
['ready-validation-fail-closed', (state) => { state.ready_for_implementation = true; }, /requires validation.status completed|requires completed semantic review/],
|
|
446
|
-
];
|
|
447
|
-
for (const [name, mutate, expected] of cases) {
|
|
448
|
-
const fixture = makeFixture();
|
|
449
|
-
try {
|
|
450
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
451
|
-
mutate(state);
|
|
452
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
453
|
-
const before = fs.readFileSync(fixture.specPath, 'utf8');
|
|
454
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
455
|
-
assert.equal(result.status, 1, `${name} unexpectedly passed\n${output(result)}`);
|
|
456
|
-
assert.match(output(result), expected, name);
|
|
457
|
-
assert.equal(fs.readFileSync(fixture.specPath, 'utf8'), before, `${name} mutated spec.json`);
|
|
458
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test('canonical task graph enforces substantive sections and exact criterion authority', () => {
|
|
463
|
-
const cases = [
|
|
464
|
-
['generic-outcome', (fixture) => {
|
|
465
|
-
const file = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
466
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(
|
|
467
|
-
'Deliver observable entry behavior through the declared entrypoint.', 'Done.',
|
|
468
|
-
));
|
|
469
|
-
}, /Outcome must be concrete and non-generic/],
|
|
470
|
-
['changes-acceptance-drift', (fixture) => {
|
|
471
|
-
const file = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
472
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace('_Requirements: 1.1_', '_Requirements: 1.2_'));
|
|
473
|
-
}, /Changes and Acceptance must trace the same exact RN.M criteria/],
|
|
474
|
-
['orphan-criterion', (fixture) => {
|
|
475
|
-
fs.appendFileSync(path.join(fixture.specDir, 'requirements.md'), '- **R1.3**: The system shall return a distinct audit state.\n');
|
|
476
|
-
}, /R1\.3: (?:acceptance criterion not covered by any task|requires exactly one task implementation owner)/],
|
|
477
|
-
['duplicate-local-authority', (fixture) => {
|
|
478
|
-
const file = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
479
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(
|
|
480
|
-
'- **R1.1:** The command returns the concrete expected state.',
|
|
481
|
-
'- **R1.1:** The command returns the concrete expected state.\n- **R1.1:** The same criterion is claimed twice.',
|
|
482
|
-
));
|
|
483
|
-
}, /Acceptance contains duplicate RN.M authority/],
|
|
484
|
-
['proof-verifier-cannot-duplicate-acceptance', (fixture) => {
|
|
485
|
-
const file = path.join(fixture.specDir, 'tasks/task-R1-02-verify.md');
|
|
486
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8')
|
|
487
|
-
.replace('_Requirements: 1.2_', '_Requirements: 1.1_')
|
|
488
|
-
.replace(/R1\.2/g, 'R1.1'));
|
|
489
|
-
}, /requires exactly one implementation owner/],
|
|
490
|
-
];
|
|
491
|
-
for (const [name, mutate, expected] of cases) {
|
|
492
|
-
const fixture = makeFixture();
|
|
493
|
-
try {
|
|
494
|
-
mutate(fixture);
|
|
495
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
496
|
-
assert.equal(result.status, 1, `${name} unexpectedly passed\n${output(result)}`);
|
|
497
|
-
assert.match(output(result), expected, name);
|
|
498
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
test('research authoring is an exact pointer-file state machine and participates in digest drift', () => {
|
|
503
|
-
const invalidCases = [
|
|
504
|
-
['absent-pointer', (fixture, state) => { state.research = 'research.md'; }, /absent requires no pointer/],
|
|
505
|
-
['absent-file', (fixture) => { fs.writeFileSync(path.join(fixture.specDir, 'research.md'), '# Research\n'); }, /absent requires no physical artifact/],
|
|
506
|
-
['draft-missing', (_fixture, state) => { state.authoring.research = 'draft'; }, /requires canonical pointer|requires a physical regular file/],
|
|
507
|
-
];
|
|
508
|
-
for (const [name, mutate, expected] of invalidCases) {
|
|
509
|
-
const fixture = makeFixture();
|
|
510
|
-
try {
|
|
511
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
512
|
-
mutate(fixture, state);
|
|
513
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
514
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
515
|
-
assert.equal(result.status, 1, `${name} unexpectedly passed`);
|
|
516
|
-
assert.match(output(result), expected, name);
|
|
517
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const structuralMutations = [
|
|
521
|
-
...['Uncertainty', 'Evidence Summary', 'Decision', 'Remaining Gaps'].map((heading) => [
|
|
522
|
-
`missing-${heading}`, (content) => content.replace(`## ${heading}`, `## Missing ${heading}`),
|
|
523
|
-
new RegExp(`requires exactly one ## ${heading} section`),
|
|
524
|
-
]),
|
|
525
|
-
...['Uncertainty', 'Evidence Summary', 'Decision', 'Remaining Gaps'].map((heading) => [
|
|
526
|
-
`duplicate-${heading}`, (content) => `${content}\n## ${heading}\n- A second machine section must be rejected.\n`,
|
|
527
|
-
new RegExp(`requires exactly one ## ${heading} section`),
|
|
528
|
-
]),
|
|
529
|
-
['placeholder-uncertainty', (content) => content.replace(/## Uncertainty[\s\S]*?## Evidence Summary/, '## Uncertainty\n- TBD\n\n## Evidence Summary'), /Uncertainty must contain concrete non-placeholder content/],
|
|
530
|
-
['placeholder-evidence', (content) => content.replace(/## Evidence Summary[\s\S]*?## Decision/, '## Evidence Summary\n- TODO\n\n## Decision'), /Evidence Summary must contain concrete non-placeholder content/],
|
|
531
|
-
['placeholder-decision', (content) => content.replace(/## Decision[\s\S]*?## Remaining Gaps/, '## Decision\n- Pending\n\n## Remaining Gaps'), /Decision must contain concrete non-placeholder content/],
|
|
532
|
-
['ungrounded-evidence', (content) => content.replace('`src/service.js`', '`src/phantom-service.js`'), /Evidence Summary must ground/],
|
|
533
|
-
['bare-none', (content) => content.replace(/- None[^\n]+/, '- None'), /Remaining Gaps None requires concrete rationale or evidence/],
|
|
534
|
-
['placeholder-gap', (content) => content.replace(/- None[^\n]+/, '- TODO'), /Remaining Gaps must contain a concrete gap/],
|
|
535
|
-
];
|
|
536
|
-
for (const [name, mutate, expected] of structuralMutations) {
|
|
537
|
-
const fixture = makeFixture();
|
|
538
|
-
try {
|
|
539
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
540
|
-
state.authoring.research = 'draft';
|
|
541
|
-
state.research = 'research.md';
|
|
542
|
-
fs.writeFileSync(path.join(fixture.specDir, 'research.md'), mutate(canonicalResearch21()));
|
|
543
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
544
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
545
|
-
assert.equal(result.status, 1, `${name} unexpectedly passed\n${output(result)}`);
|
|
546
|
-
assert.match(output(result), expected, name);
|
|
547
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
for (const remainingGaps of [
|
|
551
|
-
'- None — the inspected repository evidence resolves the bounded ownership question.',
|
|
552
|
-
'- The external retry limit remains unresolved and can change the recovery design.',
|
|
553
|
-
]) {
|
|
554
|
-
const fixture = makeFixture();
|
|
555
|
-
try {
|
|
556
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
557
|
-
state.authoring.research = 'draft';
|
|
558
|
-
state.research = 'research.md';
|
|
559
|
-
fs.writeFileSync(path.join(fixture.specDir, 'research.md'), canonicalResearch21().replace(/- None[^\n]+/, remainingGaps));
|
|
560
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
561
|
-
assert.equal(exec(fixture.root, VALIDATOR, [fixture.specDir]).status, 0);
|
|
562
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Production lifecycle order (I15/R3.7): research is added and left draft
|
|
566
|
-
// BEFORE readiness exists, the coordinator proves it clean, and only then
|
|
567
|
-
// does the semantic reviewer compute a digest and the finalizer promote —
|
|
568
|
-
// never the reverse. Start from the plain (not yet ready) fixture so the
|
|
569
|
-
// coordinator never has to run against an already-completed, now-stale
|
|
570
|
-
// review (that ordering is out of the coordinator's own contract).
|
|
571
|
-
const fixture = makeFixture();
|
|
572
|
-
try {
|
|
573
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
574
|
-
state.research = 'research.md';
|
|
575
|
-
state.authoring.research = 'draft';
|
|
576
|
-
fs.writeFileSync(path.join(fixture.specDir, 'research.md'), canonicalResearch21());
|
|
577
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
578
|
-
// C16/D13: only spec-authoring-validation.cjs may flip authoring.research
|
|
579
|
-
// (and every other non-absent stage) to validated and write the matching
|
|
580
|
-
// receipt (I21/R3.9).
|
|
581
|
-
const coordinated = exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
|
|
582
|
-
assert.equal(coordinated.status, 0, output(coordinated));
|
|
583
|
-
const validated = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
584
|
-
validated.validation.status = 'completed';
|
|
585
|
-
validated.validation.semantic_review = completedSemanticReview({
|
|
586
|
-
reviewedCriteria: ['R1.1', 'R1.2'],
|
|
587
|
-
counterexamples: [{
|
|
588
|
-
criterion: 'R1.1', case_kind: 'failure',
|
|
589
|
-
scenario: 'The service receives an invalid input object without the required discriminator.',
|
|
590
|
-
expected: 'The service returns rejected state and never emits enabled true.',
|
|
591
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
592
|
-
}, {
|
|
593
|
-
criterion: 'R1.2', case_kind: 'adversarial',
|
|
594
|
-
scenario: 'The implementation reports enabled state without producing the verifier-owned artifact.',
|
|
595
|
-
expected: 'Verification fails because the separate proof artifact is absent.',
|
|
596
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
597
|
-
}],
|
|
598
|
-
});
|
|
599
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(validated, null, 2)}\n`);
|
|
600
|
-
const digest = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
|
|
601
|
-
assert.equal(digest.status, 0, output(digest));
|
|
602
|
-
validated.validation.semantic_review.semantic_digest = digest.stdout.trim();
|
|
603
|
-
validated.ready_for_implementation = true;
|
|
604
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(validated, null, 2)}\n`);
|
|
605
|
-
assert.equal(exec(fixture.root, VALIDATOR, [fixture.specDir]).status, 0);
|
|
606
|
-
fs.appendFileSync(path.join(fixture.specDir, 'research.md'), '\nA second grounded conclusion changes the authored research artifact.\n');
|
|
607
|
-
const stale = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
608
|
-
assert.equal(stale.status, 1);
|
|
609
|
-
assert.match(output(stale), /stale.*research/);
|
|
610
|
-
fs.unlinkSync(path.join(fixture.specDir, 'research.md'));
|
|
611
|
-
const refused = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
|
|
612
|
-
assert.equal(refused.status, 1);
|
|
613
|
-
assert.doesNotMatch(refused.stdout, /^sha256:/m);
|
|
614
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
test('canonical T/V definitions, proof anchors, phases, and access normalization share one model', () => {
|
|
618
|
-
const invalidCases = [
|
|
619
|
-
['incidental-verification', (fixture) => {
|
|
620
|
-
const file = path.join(fixture.specDir, 'design.md');
|
|
621
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace('- **V1**:', '- Verification V1:'));
|
|
622
|
-
}, /Verification Definitions requires at least one canonical V definition|missing canonical V definition/],
|
|
623
|
-
['source-write-as-proof', (fixture, state) => {
|
|
624
|
-
state.coordination.boundaries.find((boundary) => boundary.type === 'proof').artifact_anchor = 'A-R1-02-01';
|
|
625
|
-
}, /source write is not proof/],
|
|
626
|
-
['ownership-owned-phase', (_fixture, state) => {
|
|
627
|
-
state.coordination.phases = [{
|
|
628
|
-
id: 'P1', task_ids: ['R1-01', 'R1-02'], entry_condition: 'Both tasks are authored.',
|
|
629
|
-
exit_condition: 'Both task outcomes are verified.', owner_boundary: 'B-OWN',
|
|
630
|
-
}];
|
|
631
|
-
}, /ownership cannot own phase progression/],
|
|
632
|
-
['phase-participant-and-coverage-drift', (_fixture, state) => {
|
|
633
|
-
state.coordination.phases = [{
|
|
634
|
-
id: 'P1', task_ids: ['R1-01'], entry_condition: 'The subject task is authored.',
|
|
635
|
-
exit_condition: 'The subject task is verified.', owner_boundary: 'B-V',
|
|
636
|
-
}];
|
|
637
|
-
}, /participants must match phase task_ids exactly|R1-02 must appear in exactly one phase/],
|
|
638
|
-
['incidental-transition-reference', (fixture, state) => {
|
|
639
|
-
const design = path.join(fixture.specDir, 'design.md');
|
|
640
|
-
fs.writeFileSync(design, fs.readFileSync(design, 'utf8').replace('### T1 — Service transition', 'The prose mentions T1 service transition'));
|
|
641
|
-
state.coordination.boundaries.push({
|
|
642
|
-
id: 'B-T', type: 'transition', design_ref: 'T1', owner: 'R1-01', consumers: ['R1-02'],
|
|
643
|
-
precondition: 'Valid input is accepted by the entry boundary.',
|
|
644
|
-
postcondition: 'Enabled state is persisted for the consumer.',
|
|
645
|
-
failure: 'Invalid input leaves the service rejected.',
|
|
646
|
-
recovery: 'Retry begins from the rejected state without duplicate output.',
|
|
647
|
-
});
|
|
648
|
-
}, /missing canonical design T definition/],
|
|
649
|
-
...[
|
|
650
|
-
['Criteria', 'Coverage'],
|
|
651
|
-
['Owner', 'Ownership'],
|
|
652
|
-
['Proof criteria', 'Proof coverage'],
|
|
653
|
-
['Proof owner', 'Verifier'],
|
|
654
|
-
['Evidence anchor', 'Evidence'],
|
|
655
|
-
['Decision refs', 'References'],
|
|
656
|
-
['Method', 'Procedure'],
|
|
657
|
-
['Expected', 'Outcome'],
|
|
658
|
-
['Negative/failure', 'Failure'],
|
|
659
|
-
['Reachability/grounding', 'Grounding'],
|
|
660
|
-
].map(([label, replacement]) => [`v-definition-missing-${label}`, (fixture) => {
|
|
661
|
-
const design = path.join(fixture.specDir, 'design.md');
|
|
662
|
-
fs.writeFileSync(design, fs.readFileSync(design, 'utf8').replace(label, replacement));
|
|
663
|
-
}, /V definition must use Criteria; Owner; optional Proof criteria; Proof owner; Evidence anchor; Decision refs; Method; Expected; Negative\/failure; Reachability\/grounding grammar/]),
|
|
664
|
-
];
|
|
665
|
-
for (const [name, mutate, expected] of invalidCases) {
|
|
666
|
-
const fixture = makeFixture();
|
|
667
|
-
try {
|
|
668
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
669
|
-
mutate(fixture, state);
|
|
670
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
671
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
672
|
-
assert.equal(result.status, 1, `${name} unexpectedly passed\n${output(result)}`);
|
|
673
|
-
assert.match(output(result), expected, name);
|
|
674
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const fixture = makeFixture();
|
|
678
|
-
try {
|
|
679
|
-
for (const name of ['tasks/task-R1-01-entry.md', 'tasks/task-R1-02-verify.md']) {
|
|
680
|
-
const file = path.join(fixture.specDir, name);
|
|
681
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(/\| write \| modify \|/i, '| WRITE | MODIFY |'));
|
|
682
|
-
}
|
|
683
|
-
promoteSemanticModel(fixture);
|
|
684
|
-
const validate = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
685
|
-
const ground = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
686
|
-
assert.equal(validate.status, 0, output(validate));
|
|
687
|
-
assert.equal(ground.status, 0, output(ground));
|
|
688
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
test('explicit Access and Action remain authoritative when Role prose claims create', () => {
|
|
692
|
-
const fixture = makeFixture({ ready: true });
|
|
693
|
-
try {
|
|
694
|
-
const design = path.join(fixture.specDir, 'design.md');
|
|
695
|
-
fs.writeFileSync(design, fs.readFileSync(design, 'utf8').replace(
|
|
696
|
-
'| A-D-01 | file | `src/design-boundary.js` | design boundary | read | read |',
|
|
697
|
-
'| A-D-01 | file | `src/role-cannot-create.js` | create this planned file | read | read |',
|
|
698
|
-
));
|
|
699
|
-
refreshSemanticDigest(fixture);
|
|
700
|
-
const validator = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
701
|
-
const grounder = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
702
|
-
assert.equal(validator.status, 1, output(validator));
|
|
703
|
-
assert.equal(grounder.status, 1, output(grounder));
|
|
704
|
-
assert.match(output(validator), /A-D-01: read target not found.*src\/role-cannot-create\.js/);
|
|
705
|
-
assert.match(output(grounder), /A-D-01: read target not found.*src\/role-cannot-create\.js/);
|
|
706
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
test('typed boundaries require exact non-empty resource maps and participation by every task', () => {
|
|
710
|
-
const mapMutations = [
|
|
711
|
-
['empty', (boundary) => { boundary.write_sets['R1-01'] = []; }, /must be a non-empty array of unique exact targets/],
|
|
712
|
-
['extra', (boundary) => { boundary.write_sets['R1-01'].push('src/unclaimed.js'); }, /lacks exact task write anchor/],
|
|
713
|
-
];
|
|
714
|
-
for (const [name, mutate, expected] of mapMutations) {
|
|
715
|
-
const fixture = makeFixture();
|
|
716
|
-
try {
|
|
717
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
718
|
-
mutate(state.coordination.boundaries.find((boundary) => boundary.type === 'ownership'));
|
|
719
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
720
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
721
|
-
assert.equal(result.status, 1, `${name}: ${output(result)}`);
|
|
722
|
-
assert.match(output(result), expected);
|
|
723
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const fixture = makeFixture();
|
|
727
|
-
try {
|
|
728
|
-
const thirdPath = 'tasks/task-R1-03-third.md';
|
|
729
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
730
|
-
fs.writeFileSync(path.join(fixture.root, 'src/third.js'), 'module.exports = { enabled: true };\n');
|
|
731
|
-
fs.writeFileSync(path.join(fixture.specDir, thirdPath), task('R1-03', 'Third', 'src/third.js', 'modify', 'none', '', 'subject'));
|
|
732
|
-
state.task_files.push(thirdPath);
|
|
733
|
-
state.task_registry[thirdPath] = {
|
|
734
|
-
...state.task_registry['tasks/task-R1-01-entry.md'], id: 'R1-03', title: 'Third', dependencies: [],
|
|
735
|
-
};
|
|
736
|
-
const ownership = state.coordination.boundaries.find((boundary) => boundary.type === 'ownership');
|
|
737
|
-
ownership.tasks.push('R1-03');
|
|
738
|
-
ownership.write_sets['R1-03'] = ['src/third.js', 'artifacts/R1-03.json'];
|
|
739
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
740
|
-
promoteSemanticModel(fixture);
|
|
741
|
-
assert.equal(exec(fixture.root, VALIDATOR, [fixture.specDir]).status, 0);
|
|
742
|
-
|
|
743
|
-
ownership.tasks = ownership.tasks.filter((id) => id !== 'R1-03');
|
|
744
|
-
delete ownership.write_sets['R1-03'];
|
|
745
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
746
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
747
|
-
assert.equal(result.status, 1, output(result));
|
|
748
|
-
assert.match(output(result), /task R1-03 must participate in at least one justified typed boundary/);
|
|
749
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
test('same-target writers cannot hide outside declared coordination pairs', () => {
|
|
753
|
-
const fixture = makeFixture();
|
|
754
|
-
try {
|
|
755
|
-
const thirdPath = 'tasks/task-R1-03-shadow.md';
|
|
756
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
757
|
-
fs.writeFileSync(
|
|
758
|
-
path.join(fixture.specDir, thirdPath),
|
|
759
|
-
task('R1-03', 'Shadow writer', 'src/entry.js', 'modify', 'none', '', 'subject'),
|
|
760
|
-
);
|
|
761
|
-
state.task_files.push(thirdPath);
|
|
762
|
-
state.task_registry[thirdPath] = {
|
|
763
|
-
...state.task_registry['tasks/task-R1-01-entry.md'],
|
|
764
|
-
id: 'R1-03',
|
|
765
|
-
title: 'Shadow writer',
|
|
766
|
-
dependencies: [],
|
|
767
|
-
};
|
|
768
|
-
state.coordination.boundaries.push({
|
|
769
|
-
id: 'B-OWN-SHADOW',
|
|
770
|
-
type: 'ownership',
|
|
771
|
-
tasks: ['R1-02', 'R1-03'],
|
|
772
|
-
write_sets: {
|
|
773
|
-
'R1-02': ['src/service.js', 'artifacts/R1-02.json'],
|
|
774
|
-
'R1-03': ['src/entry.js', 'artifacts/R1-03.json'],
|
|
775
|
-
},
|
|
776
|
-
});
|
|
777
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
778
|
-
|
|
779
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
780
|
-
assert.equal(result.status, 1, output(result));
|
|
781
|
-
assert.match(
|
|
782
|
-
output(result),
|
|
783
|
-
/R1-01 and R1-03 both hold a write anchor for src\/entry\.js with no dependency boundary connecting them/,
|
|
784
|
-
);
|
|
785
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
786
|
-
});
|
|
787
|
-
|
|
788
|
-
test('schema 2.1 grounding follows actual readiness and task execution phase', () => {
|
|
789
|
-
const fixture = makeFixture();
|
|
790
|
-
try {
|
|
791
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
792
|
-
const taskPath = 'tasks/task-R1-01-entry.md';
|
|
793
|
-
state.task_registry[taskPath] = {
|
|
794
|
-
...state.task_registry[taskPath],
|
|
795
|
-
status: 'blocked',
|
|
796
|
-
blocker: 'Waiting for a concrete external prerequisite.',
|
|
797
|
-
last_updated_at: '2026-08-17T00:00:00Z',
|
|
798
|
-
};
|
|
799
|
-
fs.writeFileSync(
|
|
800
|
-
path.join(fixture.specDir, taskPath),
|
|
801
|
-
fs.readFileSync(path.join(fixture.specDir, taskPath), 'utf8').replace('**Status:** pending', '**Status:** blocked'),
|
|
802
|
-
);
|
|
803
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
804
|
-
const blocked = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
805
|
-
assert.equal(blocked.status, 0, output(blocked));
|
|
806
|
-
|
|
807
|
-
state.task_registry[taskPath] = {
|
|
808
|
-
...state.task_registry[taskPath],
|
|
809
|
-
status: 'in_progress',
|
|
810
|
-
blocker: null,
|
|
811
|
-
started_at: '2026-08-17T00:00:00Z',
|
|
812
|
-
last_updated_at: '2026-08-17T00:00:00Z',
|
|
813
|
-
};
|
|
814
|
-
fs.writeFileSync(
|
|
815
|
-
path.join(fixture.specDir, taskPath),
|
|
816
|
-
fs.readFileSync(path.join(fixture.specDir, taskPath), 'utf8').replace('**Status:** blocked', '**Status:** in_progress'),
|
|
817
|
-
);
|
|
818
|
-
fs.writeFileSync(path.join(fixture.root, 'artifacts/R1-01.json'), '{}\n');
|
|
819
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
820
|
-
const materialized = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
821
|
-
assert.equal(materialized.status, 0, output(materialized));
|
|
822
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
test('schema 2.1 grounding permits a missing planned read only through its direct create dependency', () => {
|
|
826
|
-
const fixture = makeFixture();
|
|
827
|
-
try {
|
|
828
|
-
const producerPath = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
829
|
-
const consumerPath = path.join(fixture.specDir, 'tasks/task-R1-02-verify.md');
|
|
830
|
-
fs.writeFileSync(producerPath, fs.readFileSync(producerPath, 'utf8').replace(
|
|
831
|
-
'| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |',
|
|
832
|
-
'| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |\n| A-R1-01-03 | file | `artifacts/planned-output.json` | owner | write | create |',
|
|
833
|
-
));
|
|
834
|
-
fs.writeFileSync(consumerPath, fs.readFileSync(consumerPath, 'utf8').replace(
|
|
835
|
-
'| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |',
|
|
836
|
-
'| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |\n| A-R1-02-03 | file | `artifacts/planned-output.json` | consumer | read | read |',
|
|
837
|
-
).replace('- none\n\n## Verification Plan', '- tasks/task-R1-01-entry.md\n\n## Verification Plan'));
|
|
838
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
839
|
-
state.coordination.boundaries[0].write_sets['R1-01'].push('artifacts/planned-output.json');
|
|
840
|
-
state.coordination.boundaries.push({
|
|
841
|
-
id: 'B-PLANNED-READ', type: 'dependency', producer: 'R1-01', consumer: 'R1-02',
|
|
842
|
-
deliverable: 'artifacts/planned-output.json',
|
|
843
|
-
});
|
|
844
|
-
state.task_registry['tasks/task-R1-02-verify.md'].dependencies = ['tasks/task-R1-01-entry.md'];
|
|
845
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
846
|
-
const synced = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
|
|
847
|
-
assert.equal(synced.status, 0, output(synced));
|
|
848
|
-
const grounded = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
849
|
-
assert.equal(grounded.status, 0, output(grounded));
|
|
850
|
-
|
|
851
|
-
const withoutDependency = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
852
|
-
withoutDependency.task_registry['tasks/task-R1-02-verify.md'].dependencies = [];
|
|
853
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(withoutDependency, null, 2)}\n`);
|
|
854
|
-
const refused = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
855
|
-
assert.equal(refused.status, 1, output(refused));
|
|
856
|
-
assert.match(output(refused), /read target not found.*artifacts\/planned-output\.json/);
|
|
857
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
858
|
-
});
|
|
859
|
-
|
|
860
|
-
test('schema 2.1 rejects unknown and dead machine fields outside the explicit adapter', () => {
|
|
861
|
-
const cases = [
|
|
862
|
-
['top-level', (state) => { state.dead_machine_field = true; }, /dead_machine_field: unknown or legacy field is not canonical in closed schema 2\.1/],
|
|
863
|
-
['validation', (state) => { state.validation.legacy_digest = 'inert'; }, /validation: fields must be exactly status, semantic_review, semantic_review_history, authoring_validation/],
|
|
864
|
-
['scope-lock', (state) => { state.scope_lock.legacy_mode = true; }, /scope_lock: fields must be exactly/],
|
|
865
|
-
];
|
|
866
|
-
for (const [name, mutate, expected] of cases) {
|
|
867
|
-
const fixture = makeFixture();
|
|
868
|
-
try {
|
|
869
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
870
|
-
mutate(state);
|
|
871
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
872
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
873
|
-
assert.equal(result.status, 1, `${name}: ${output(result)}`);
|
|
874
|
-
assert.match(output(result), expected);
|
|
875
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
876
|
-
}
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
test('digest mode refuses malformed physical topology and tracks substantive mutation classes', () => {
|
|
880
|
-
const mutationCases = [
|
|
881
|
-
['authoring', (_fixture, state) => { state.authoring.design = 'draft'; }],
|
|
882
|
-
['policy', (_fixture, state) => {
|
|
883
|
-
state.workflow_policy.assurance_level = 'Elevated';
|
|
884
|
-
state.workflow_policy.classified_minimum.assurance_level = 'Elevated';
|
|
885
|
-
}],
|
|
886
|
-
['coordination', (_fixture, state) => { state.coordination.boundaries[0].id = 'B-OWN-RENAMED'; }],
|
|
887
|
-
['task-content', (fixture) => {
|
|
888
|
-
const file = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
889
|
-
fs.appendFileSync(file, '\nConcrete authoring note changes the owned behavior.\n');
|
|
890
|
-
}],
|
|
891
|
-
];
|
|
892
|
-
for (const [name, mutate] of mutationCases) {
|
|
893
|
-
const fixture = makeFixture({ ready: true });
|
|
894
|
-
try {
|
|
895
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
896
|
-
const original = state.validation.semantic_review.semantic_digest;
|
|
897
|
-
mutate(fixture, state);
|
|
898
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
899
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
|
|
900
|
-
assert.equal(result.status, 0, `${name}: ${output(result)}`);
|
|
901
|
-
assert.notEqual(result.stdout.trim(), original, `${name} did not invalidate digest`);
|
|
902
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const malformed = makeFixture();
|
|
906
|
-
try {
|
|
907
|
-
const taskPath = path.join(malformed.specDir, 'tasks/task-R1-02-verify.md');
|
|
908
|
-
fs.writeFileSync(taskPath, fs.readFileSync(taskPath, 'utf8').replace('## Anchors and Ownership', '## Broken Ownership'));
|
|
909
|
-
const result = exec(malformed.root, VALIDATOR, [malformed.specDir, '--semantic-digest']);
|
|
910
|
-
assert.equal(result.status, 1);
|
|
911
|
-
assert.doesNotMatch(result.stdout, /^sha256:/m);
|
|
912
|
-
assert.match(output(result), /digest requires exactly one ## Anchors and Ownership|requires one table/);
|
|
913
|
-
} finally { fs.rmSync(malformed.root, { recursive: true, force: true }); }
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
test('taskless ready spec rejects a phantom design anchor through shared grounding', () => {
|
|
917
|
-
const fixture = makeTasklessReadyFixture();
|
|
918
|
-
try {
|
|
919
|
-
const designPath = path.join(fixture.specDir, 'design.md');
|
|
920
|
-
fs.writeFileSync(designPath, fs.readFileSync(designPath, 'utf8')
|
|
921
|
-
.replace('src/design-boundary.js', 'src/phantom-boundary.js'));
|
|
922
|
-
refreshSemanticDigest(fixture);
|
|
923
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
924
|
-
assert.equal(result.status, 1, output(result));
|
|
925
|
-
assert.match(output(result), /grounding: design\.md: A-D-01: read target not found/);
|
|
926
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
test('ready task rejects a dangling canonical A-D reference', () => {
|
|
930
|
-
const fixture = makeFixture({ ready: true });
|
|
931
|
-
try {
|
|
932
|
-
const taskPath = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
933
|
-
fs.writeFileSync(taskPath, fs.readFileSync(taskPath, 'utf8')
|
|
934
|
-
.replace('- **In scope:**', '- **Canonical design anchor:** A-D-99\n- **In scope:**'));
|
|
935
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
936
|
-
assert.equal(result.status, 1, output(result));
|
|
937
|
-
assert.match(output(result), /dangling canonical design anchor reference A-D-99/);
|
|
938
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
939
|
-
});
|
|
940
|
-
|
|
941
|
-
test('dependency topology cannot legalize duplicate criterion implementation owners', () => {
|
|
942
|
-
const fixture = makeFixture();
|
|
943
|
-
try {
|
|
944
|
-
const producerPath = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
945
|
-
const verifierPath = path.join(fixture.specDir, 'tasks/task-R1-02-verify.md');
|
|
946
|
-
fs.writeFileSync(producerPath, fs.readFileSync(producerPath, 'utf8').replace(
|
|
947
|
-
'| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |',
|
|
948
|
-
'| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |\n| A-R1-01-03 | artifact | `artifacts/dependency.json` | producer | write | create |',
|
|
949
|
-
));
|
|
950
|
-
fs.writeFileSync(verifierPath, fs.readFileSync(verifierPath, 'utf8')
|
|
951
|
-
.replace('_Requirements: 1.2_', '_Requirements: 1.1_')
|
|
952
|
-
.replace(/R1\.2/g, 'R1.1')
|
|
953
|
-
.replace('| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |', '| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |\n| A-R1-02-03 | artifact | `artifacts/dependency.json` | consumer | read | read |')
|
|
954
|
-
.replace('## Dependencies\n- none', '## Dependencies\n- tasks/task-R1-01-entry.md'));
|
|
955
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
956
|
-
state.coordination.boundaries = [{
|
|
957
|
-
id: 'B-OWN', type: 'ownership', tasks: ['R1-01', 'R1-02'],
|
|
958
|
-
write_sets: {
|
|
959
|
-
'R1-01': ['src/entry.js', 'artifacts/R1-01.json', 'artifacts/dependency.json'],
|
|
960
|
-
'R1-02': ['src/service.js', 'artifacts/R1-02.json'],
|
|
961
|
-
},
|
|
962
|
-
}, {
|
|
963
|
-
id: 'B-D', type: 'dependency', producer: 'R1-01', consumer: 'R1-02',
|
|
964
|
-
deliverable: 'artifacts/dependency.json',
|
|
965
|
-
}];
|
|
966
|
-
state.task_registry['tasks/task-R1-02-verify.md'].dependencies = ['tasks/task-R1-01-entry.md'];
|
|
967
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
968
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
969
|
-
assert.equal(result.status, 1, output(result));
|
|
970
|
-
assert.match(output(result), /R1\.1: requires exactly one implementation owner; claimed by R1-01, R1-02/);
|
|
971
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
test('separate proof verifier passes without duplicating subject acceptance ownership', () => {
|
|
975
|
-
const fixture = makeFixture({ ready: true });
|
|
976
|
-
try {
|
|
977
|
-
const verifier = fs.readFileSync(path.join(fixture.specDir, 'tasks/task-R1-02-verify.md'), 'utf8');
|
|
978
|
-
assert.doesNotMatch(verifier, /^\s*[-*+]\s+(?:\*\*)?R1\.1/m);
|
|
979
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
980
|
-
assert.equal(result.status, 0, output(result));
|
|
981
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
982
|
-
});
|
|
983
|
-
|
|
984
|
-
test('readiness action matrix permits absent create but rejects absent read modify and delete', () => {
|
|
985
|
-
const cases = [
|
|
986
|
-
['create', 'write', 0],
|
|
987
|
-
['read', 'read', 1],
|
|
988
|
-
['modify', 'write', 1],
|
|
989
|
-
['delete', 'write', 1],
|
|
990
|
-
];
|
|
991
|
-
for (const [action, access, expectedStatus] of cases) {
|
|
992
|
-
const fixture = makeFixture({ ready: true });
|
|
993
|
-
try {
|
|
994
|
-
appendTaskAnchor(fixture, `| A-R1-01-03 | file | \`src/missing-${action}.js\` | owner | ${access} | ${action} |`);
|
|
995
|
-
if (access === 'write') {
|
|
996
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
997
|
-
state.coordination.boundaries.find((boundary) => boundary.type === 'ownership')
|
|
998
|
-
.write_sets['R1-01'].push(`src/missing-${action}.js`);
|
|
999
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
1000
|
-
}
|
|
1001
|
-
refreshSemanticDigest(fixture);
|
|
1002
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
1003
|
-
assert.equal(result.status, expectedStatus, `${action}: ${output(result)}`);
|
|
1004
|
-
if (expectedStatus === 1) assert.match(output(result), new RegExp(`grounding: .* ${action} target not found`));
|
|
1005
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
test('ready semantic digest is stale after anchor topology or policy edits', () => {
|
|
1010
|
-
const cases = [
|
|
1011
|
-
(fixture) => {
|
|
1012
|
-
const file = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
|
|
1013
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace('src/entry.js', 'src/renamed-entry.js'));
|
|
1014
|
-
},
|
|
1015
|
-
(fixture, state) => { state.coordination.boundaries[0].id = 'B-OWN-EDITED'; },
|
|
1016
|
-
(_fixture, state) => {
|
|
1017
|
-
state.workflow_policy.assurance_level = 'Elevated';
|
|
1018
|
-
state.workflow_policy.classified_minimum.assurance_level = 'Elevated';
|
|
1019
|
-
},
|
|
1020
|
-
];
|
|
1021
|
-
for (const mutate of cases) {
|
|
1022
|
-
const fixture = makeFixture({ ready: true });
|
|
1023
|
-
try {
|
|
1024
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
1025
|
-
mutate(fixture, state);
|
|
1026
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
1027
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
1028
|
-
assert.equal(result.status, 1, output(result));
|
|
1029
|
-
assert.match(output(result), /semantic_digest: stale/);
|
|
1030
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
test('validator and CLI grounder reject the same missing modify counterexample', () => {
|
|
1035
|
-
const fixture = makeFixture({ ready: true });
|
|
1036
|
-
try {
|
|
1037
|
-
appendTaskAnchor(fixture, '| A-R1-01-03 | file | `src/shared-missing.js` | owner | write | modify |');
|
|
1038
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
1039
|
-
state.coordination.boundaries.find((boundary) => boundary.type === 'ownership')
|
|
1040
|
-
.write_sets['R1-01'].push('src/shared-missing.js');
|
|
1041
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
1042
|
-
refreshSemanticDigest(fixture);
|
|
1043
|
-
const validator = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
1044
|
-
const grounder = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
1045
|
-
assert.equal(validator.status, 1, output(validator));
|
|
1046
|
-
assert.equal(grounder.status, 1, output(grounder));
|
|
1047
|
-
const counterexample = /tasks\/task-R1-01-entry\.md: A-R1-01-03: modify target not found in work tree: src\/shared-missing\.js/;
|
|
1048
|
-
assert.match(output(validator), counterexample);
|
|
1049
|
-
assert.match(output(grounder), counterexample);
|
|
1050
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
test('schema 2.1 policy is exactly five fields and rejects inert override receipt locations', () => {
|
|
1054
|
-
const cases = [
|
|
1055
|
-
(state) => { state.override_receipt = null; },
|
|
1056
|
-
(state) => { state.workflow_policy.override_receipt = null; },
|
|
1057
|
-
];
|
|
1058
|
-
for (const mutate of cases) {
|
|
1059
|
-
const fixture = makeFixture();
|
|
1060
|
-
try {
|
|
1061
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
1062
|
-
assert.deepEqual(Object.keys(state.workflow_policy).sort(), [
|
|
1063
|
-
'assurance_level', 'classified_minimum', 'planning_depth', 'risks', 'version',
|
|
1064
|
-
]);
|
|
1065
|
-
mutate(state);
|
|
1066
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
1067
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
1068
|
-
assert.equal(result.status, 1, output(result));
|
|
1069
|
-
assert.match(output(result), /override receipt is inert|v2\.1 fields must be exactly/);
|
|
1070
|
-
assert.doesNotMatch(output(result), /persist only workflow_policy\.override_receipt/);
|
|
1071
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1075
|
-
test('semantic authority mutation families reject empty inventory, ambiguous owners, vague proof, and projection drift', () => {
|
|
1076
|
-
const cases = [
|
|
1077
|
-
['empty-criteria', (fixture) => {
|
|
1078
|
-
const file = path.join(fixture.specDir, 'requirements.md');
|
|
1079
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(/^- \*\*R1\.[12]\*\*:[^\n]+\n/gm, ''));
|
|
1080
|
-
}, /non-empty RN\.M inventory|criteria: must be a non-empty array/],
|
|
1081
|
-
['taskless-owner-ambiguity', (fixture, state) => {
|
|
1082
|
-
state.semantic_model.verification_definitions[0].proof_owner = 'A-D-01';
|
|
1083
|
-
state.semantic_model.verification_definitions[0].evidence_anchor = 'A-D-01';
|
|
1084
|
-
state.semantic_model.criteria.find((criterion) => criterion.kind === 'proof').owner = 'A-D-01';
|
|
1085
|
-
}, /subject\/proof owners must be distinct/],
|
|
1086
|
-
['vague-method', (fixture) => {
|
|
1087
|
-
const file = path.join(fixture.specDir, 'design.md');
|
|
1088
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(
|
|
1089
|
-
'Method command `node --test test/service.test.js`', 'Method inspect it',
|
|
1090
|
-
));
|
|
1091
|
-
}, /Method must be exact command|structured method/],
|
|
1092
|
-
['vague-reachability', (fixture) => {
|
|
1093
|
-
const file = path.join(fixture.specDir, 'design.md');
|
|
1094
|
-
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(
|
|
1095
|
-
'Reachability/grounding entrypoint `src/entry.js` via A-D-01, A-R1-02-02',
|
|
1096
|
-
'Reachability/grounding reachable through the application',
|
|
1097
|
-
));
|
|
1098
|
-
}, /Reachability\/grounding must be entrypoint|projection drift/],
|
|
1099
|
-
['machine-markdown-drift', (_fixture, state) => {
|
|
1100
|
-
state.semantic_model.anchors[0].target = 'src/authority-only-edit.js';
|
|
1101
|
-
}, /semantic_model: differs from Markdown projection/],
|
|
1102
|
-
];
|
|
1103
|
-
for (const [name, mutate, expected] of cases) {
|
|
1104
|
-
const fixture = name === 'taskless-owner-ambiguity' ? makeTasklessReadyFixture() : makeFixture();
|
|
1105
|
-
try {
|
|
1106
|
-
const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
|
|
1107
|
-
mutate(fixture, state);
|
|
1108
|
-
fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
1109
|
-
const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
1110
|
-
assert.equal(result.status, 1, `${name}: ${output(result)}`);
|
|
1111
|
-
assert.match(output(result), expected, name);
|
|
1112
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
1113
|
-
}
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
test('route and schema grounding never accepts design self-reference as repository evidence', () => {
|
|
1117
|
-
for (const type of ['route', 'schema']) for (const action of ['create', 'modify']) {
|
|
1118
|
-
const fixture = makeFixture();
|
|
1119
|
-
try {
|
|
1120
|
-
const designPath = path.join(fixture.specDir, 'design.md');
|
|
1121
|
-
const id = type === 'route' ? 'A-D-03' : 'A-D-04';
|
|
1122
|
-
const target = type === 'route' ? 'POST /self-grounded' : 'SelfGroundedRecord';
|
|
1123
|
-
fs.writeFileSync(designPath, fs.readFileSync(designPath, 'utf8')
|
|
1124
|
-
.replace('| A-D-02 | artifact', `| ${id} | ${type} | \`${target}\` | registration boundary | write | ${action} |\n| A-D-02 | artifact`)
|
|
1125
|
-
.replace('via A-D-01, A-R1-02-02', `via A-D-01, ${id}, A-R1-02-02`));
|
|
1126
|
-
promoteSemanticModel(fixture);
|
|
1127
|
-
const result = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
|
|
1128
|
-
assert.equal(result.status, action === 'create' ? 0 : 1, `${type}/${action}: ${output(result)}`);
|
|
1129
|
-
if (action === 'modify') assert.match(output(result), new RegExp(`${type} target is not semantically reachable`));
|
|
1130
|
-
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
|
1131
|
-
}
|
|
1132
|
-
});
|