harness-everything 0.30.10 → 0.31.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +24 -4
- package/README.md +8 -9
- package/VERIFICATION.md +1 -1
- package/benchmarks/results/live-host/claude-2026-10-08-issue-297/README.md +27 -0
- package/benchmarks/results/pr-303-review-2026-10-13/README.md +81 -0
- package/benchmarks/results/pr-303-review-2026-10-13/ci/run.json +1330 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-clean-env.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-inherited-flag.log +34 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-captured.json +46 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-diagnosis.md +13 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate.json +46 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/summary.json +57 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-collision.log +8 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-consistency.log +624 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-docs-capabilities.log +131 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-platform-compatibility.log +9 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-openai.log +6 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-submission.log +5 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-references.log +6 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-release.log +73 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-repo-contract.log +5 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-invariants.log +785 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-skills.log +44 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/summary.json +12 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-consistency.log +624 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-release.log +73 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/summary.json +79 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mechanism.log +1737 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mutations.log +16 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test.log +3297 -0
- package/benchmarks/results/pr-303-review-2026-10-13/manifest.json +319 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/binding-resolution-green.log +36 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/determinism.json +174 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-evidence.json +314 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-report.json +638 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/red.log +240 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/untyped-fable-probe.json +22 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/workflow-obligations-green.log +254 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-clean-env-fixed.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-inherited-flag-fixed.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.meta.json +8 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stderr.log +90 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stdout.log +3186 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/verify-gate-final.json +46 -0
- package/bin/cli.js +1 -1
- package/ci/invariant-routing.test.js +7 -4
- package/ci/mechanism-2ag-router-worktree-contract.test.js +2 -2
- package/ci/mechanism-2ai-workspace-fingerprint-batch.test.js +2 -1
- package/ci/mechanism-2an-workflow-obligations.test.js +352 -3
- package/ci/mechanism-2aq-fable-stage-bindings.test.js +168 -0
- package/ci/mechanism-2ar-binding-resolution.test.js +211 -0
- package/ci/mechanism-35-verification-contract.test.js +1 -1
- package/ci/mutation-check.js +2 -0
- package/ci/mutation-loader.js +4 -0
- package/ci/openai-plugin-package.test.js +7 -5
- package/ci/prompt-contract-regressions.test.js +7 -6
- package/ci/router-workflow-plan.test.js +109 -14
- package/ci/skill-routing-check.js +44 -15
- package/ci/workflow-plan-consumers.test.js +41 -1
- package/docs/architecture.md +10 -10
- package/docs/enforcement-locks.md +1 -1
- package/docs/mechanism-first-skill-mesh.md +12 -11
- package/docs/openai-plugin.md +1 -1
- package/docs/philosophy.md +7 -6
- package/docs/platform-capabilities.md +7 -5
- package/docs/routing.md +16 -17
- package/docs/system-one-observations.md +1 -1
- package/docs/system-one-routing.md +10 -7
- package/docs/system-one-suggestion-gates.md +5 -5
- package/docs/workflow-runtime.md +6 -2
- package/docs/workflows/harness-everything.md +14 -14
- package/evals/harness-everything/tasks/positive-tier1.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier2.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier3.yaml +1 -1
- package/fable-mode/CONTRACT-FORMAT.md +4 -1
- package/fable-mode/SKILL.md +6 -8
- package/fable-mode/agents/fable-orchestrator.md +10 -3
- package/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/fable-mode/fable-haiku/SKILL.md +1 -1
- package/fable-mode/fable-opus/SKILL.md +1 -1
- package/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/fable-mode/scripts/workflow-plan-consumer.js +101 -2
- package/harness-everything/SKILL.md +8 -7
- package/harness-everything/references/router-workflow-plan.md +11 -6
- package/harness-everything/references/triage-and-tiers.md +21 -23
- package/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
- package/harness-everything/scripts/kernel-router-core.js +52 -55
- package/harness-everything/scripts/kernel-router.js +48 -5
- package/harness-everything/scripts/router-contract-core.js +7 -7
- package/harness-everything/scripts/router-contract.js +1 -3
- package/harness-everything/scripts/routing-keywords.json +417 -121
- package/harness-everything/scripts/tier-router.js +64 -113
- package/hooks/scripts/contract-test.js +29 -3
- package/hooks/scripts/lib/binding-resolver.js +76 -0
- package/hooks/scripts/lib/fable-contracts.js +1 -1
- package/hooks/scripts/lib/workflow-obligations.js +292 -7
- package/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/hooks/scripts/workflow-disposition.js +116 -2
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/contract-test.js +29 -3
- package/plugins/harness-everything/hooks/scripts/lib/binding-resolver.js +76 -0
- package/plugins/harness-everything/hooks/scripts/lib/fable-contracts.js +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +292 -7
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +116 -2
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/scripts/lib/advisory-text.js +2 -2
- package/plugins/harness-everything/scripts/lib/generated-skills.js +78 -0
- package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +4 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +6 -8
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +10 -3
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +101 -2
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +8 -7
- package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +11 -6
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +21 -23
- package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +52 -55
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +48 -5
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +7 -7
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +1 -3
- package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +417 -121
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +64 -113
- package/sbom.cdx.json +7 -7
- package/scripts/lib/advisory-text.js +2 -2
- package/scripts/lib/generated-skills.js +78 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "UNCHECKED_DISCOVERED",
|
|
3
|
+
"reason": "1 verification candidate(s) detected but not executed",
|
|
4
|
+
"repoRoot": "<workspace>",
|
|
5
|
+
"roots": [
|
|
6
|
+
{
|
|
7
|
+
"path": "<workspace>",
|
|
8
|
+
"status": "UNCHECKED_DISCOVERED",
|
|
9
|
+
"reason": "1 verification candidate(s) detected but not executed",
|
|
10
|
+
"checks": [
|
|
11
|
+
{
|
|
12
|
+
"id": "package-test",
|
|
13
|
+
"run": "npm run test",
|
|
14
|
+
"cwd": "<workspace>",
|
|
15
|
+
"source": "package-auto",
|
|
16
|
+
"exitCode": 0,
|
|
17
|
+
"durationMs": 245096
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"candidates": [
|
|
21
|
+
{
|
|
22
|
+
"source": "git-hook",
|
|
23
|
+
"run": "git commit (pre-commit hook configured)",
|
|
24
|
+
"cwd": "<workspace>"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"checks": [
|
|
30
|
+
{
|
|
31
|
+
"id": "package-test",
|
|
32
|
+
"run": "npm run test",
|
|
33
|
+
"cwd": "<workspace>",
|
|
34
|
+
"source": "package-auto",
|
|
35
|
+
"exitCode": 0,
|
|
36
|
+
"durationMs": 245096
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"candidates": [
|
|
40
|
+
{
|
|
41
|
+
"source": "git-hook",
|
|
42
|
+
"run": "git commit (pre-commit hook configured)",
|
|
43
|
+
"cwd": "<workspace>"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -103,7 +103,7 @@ Commands:
|
|
|
103
103
|
error instead of silently doing nothing.
|
|
104
104
|
self-regression Run syntax and routing checks before committing changes (alias: test)
|
|
105
105
|
next "<prompt>" Print the Harness Kernel routing recommendation for a prompt:
|
|
106
|
-
tier/rationale + mandatory invariants +
|
|
106
|
+
tier/rationale + mandatory invariants + normalized knowledge signals.
|
|
107
107
|
Hookless platforms can call this explicitly at the start of software
|
|
108
108
|
work; hosts with UserPromptSubmit hooks receive it automatically.
|
|
109
109
|
verify-install Compare installed Harness manifests and skill trees with
|
|
@@ -11,13 +11,15 @@ check(tier2.status===0,'kernel router exits successfully');
|
|
|
11
11
|
check(/RECOMMENDED TIER:\s*Tier 2/i.test(tier2.stdout),'classifier result is preserved');
|
|
12
12
|
check(tier2.stdout.includes('"strategy":"iterative-single"'),'Tier 2 selects iterative-single workflow');
|
|
13
13
|
check(tier2.stdout.includes('loop-awareness:'),'iterative work exposes loop-awareness instead of a hard budget');
|
|
14
|
-
check(tier2.stdout.includes('
|
|
14
|
+
check(tier2.stdout.includes('ACTIVE-STEP KNOWLEDGE BINDINGS (MUST)'),'active-step binding resolution is explicitly mandatory');
|
|
15
15
|
check(tier2.stdout.includes('ORCHESTRATION POLICY: Selected-topology required obligations and applicable skill core contracts are semantic MUSTs'),'orchestration policy preserves semantic MUST obligations');
|
|
16
16
|
check(tier2.stdout.includes('WORKFLOW SEMANTIC CONTRACT (NON-BLOCKING OBSERVATION)'),'kernel separates semantic contract from non-blocking observation');
|
|
17
17
|
check(tier2.stdout.includes('Tier 3 / Fable broad mutation MUST resolve isolation'),'worktree isolation disposition is a semantic MUST');
|
|
18
18
|
check(!tier2.stdout.includes('SELECTED WORKFLOW IS GUIDANCE'),'old planning-guidance label is absent');
|
|
19
19
|
check(!tier2.stdout.includes('unavailable isolation means BLOCKED'),'old worktree hard-block wording is absent');
|
|
20
|
-
check(tier2.stdout.includes('
|
|
20
|
+
check(tier2.stdout.includes('future-step bindings remain undisclosed'),'future step knowledge stays undisclosed');
|
|
21
|
+
check(tier2.stdout.includes('unknown/unavailable binding stays visible and unresolved'),'required binding gaps remain visible without adding a lock');
|
|
22
|
+
check(!tier2.stdout.includes('evaluate-suggestions-before-skip'),'global read-every-suggestion invariant is removed');
|
|
21
23
|
check(tier2.stdout.includes('visible-status-updates'),'workflow plan carries mandatory user-visible status invariant');
|
|
22
24
|
check(tier2.stdout.includes('USER-VISIBLE HARNESS STATUS CONTRACT (MUST)'),'kernel emits the mandatory unified status contract');
|
|
23
25
|
for(const field of ['### 🚦 Harness Status','- **Current:**','- **Read / Evidence:**','- **Next:**','- **Risk / Blocked:**']){
|
|
@@ -31,7 +33,8 @@ check(tier2.stdout.includes('semantic communication MUST, not a hard execution l
|
|
|
31
33
|
const tier3=spawnSync(process.execPath,[kernel,'audit the entire repository architecture and coordinate multiple modules'],{cwd:ROOT,encoding:'utf8'});
|
|
32
34
|
check(tier3.status===0,'Tier 3 routing exits successfully');
|
|
33
35
|
check(/RECOMMENDED TIER:\s*Tier 3/i.test(tier3.stdout),'macro task remains Tier 3');
|
|
34
|
-
check(tier3.stdout.includes('
|
|
36
|
+
check(tier3.stdout.includes('isolated-worktree-before-mutation'),'Tier 3 keeps the worktree isolation invariant');
|
|
37
|
+
check(!tier3.stdout.includes('using-git-worktrees/SKILL.md'),'Tier 3 does not push a global worktree skill path');
|
|
35
38
|
check(tier3.stdout.includes('Tier 3 / Fable broad mutation MUST resolve isolation'),'Tier 3 requires an isolation disposition without a hard lock');
|
|
36
39
|
|
|
37
40
|
const hs=read('harness-everything/SKILL.md');
|
|
@@ -60,7 +63,7 @@ const philosophy=read('docs/philosophy.md');
|
|
|
60
63
|
check(/Contract Strength: MUST \/ SHOULD \/ MAY/i.test(philosophy),'philosophy defines canonical MUST/SHOULD/MAY strength');
|
|
61
64
|
for(const phrase of [
|
|
62
65
|
'Route before software/project execution',
|
|
63
|
-
'
|
|
66
|
+
'Resolve active required skill/reference bindings before their step',
|
|
64
67
|
'Resolve selected-topology invariants/stages/checks/synthesis/verification',
|
|
65
68
|
'Discover relevant environment/host facts',
|
|
66
69
|
'Resolve Tier-3/Fable isolation before broad mutation',
|
|
@@ -16,7 +16,7 @@ const tier3 = router.buildRouterContract({
|
|
|
16
16
|
|
|
17
17
|
assert.strictEqual(tier3.strategy, 'fable-staged');
|
|
18
18
|
assert.ok(tier3.requiredInvariants.includes('isolated-worktree-before-mutation'), 'Tier 3 plan must carry mutation isolation invariant');
|
|
19
|
-
assert.
|
|
19
|
+
assert.strictEqual(tier3.suggestedSkills.length, 0, 'Tier 3 must not push a global worktree skill list');
|
|
20
20
|
assert.strictEqual(tier3.workspace.required, false, 'Git worktree isolation must remain distinct from durable multi-agent workspace state');
|
|
21
21
|
assert.strictEqual(tier3.mutationIsolation.onUnavailable, 'degraded', 'unavailable isolation records an explicit degraded fallback instead of a cognitive hard block');
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ const tier2 = router.buildRouterContract({
|
|
|
29
29
|
|
|
30
30
|
assert.strictEqual(tier2.strategy, 'iterative-single');
|
|
31
31
|
assert.ok(!tier2.requiredInvariants.includes('isolated-worktree-before-mutation'), 'Tier 2 must not be globally forced into major-workflow isolation');
|
|
32
|
-
assert.
|
|
32
|
+
assert.strictEqual(tier2.suggestedSkills.length, 0, 'Tier 2 also has no topology-wide skill suggestions');
|
|
33
33
|
|
|
34
34
|
const explicitFable = router.buildWorkflowPlan({
|
|
35
35
|
routingStatus: 'ok',
|
|
@@ -189,7 +189,8 @@ try {
|
|
|
189
189
|
});
|
|
190
190
|
check(routed.status === 0, '#169 large-workspace Tier-2 fixture routes normally');
|
|
191
191
|
const requirements = JSON.stringify([
|
|
192
|
-
{ id: 'req-fingerprint', summary: 'Exercise large-workspace shell admission', acceptance: 'the shell call remains available above the fingerprint observation cap' },
|
|
192
|
+
{ id: 'req-fingerprint', stepType: 'behavior-change', summary: 'Exercise large-workspace shell admission', acceptance: 'the shell call remains available above the fingerprint observation cap' },
|
|
193
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify shell admission', acceptance: 'objective test evidence is recorded' },
|
|
193
194
|
]);
|
|
194
195
|
const planned = node('hooks/scripts/workflow-disposition.js', undefined, [
|
|
195
196
|
'plan', '--session-id', sessionId,
|
|
@@ -32,6 +32,9 @@ function run(script, args = [], input = null) {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
function readJson(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
35
|
+
function responseOf(result) {
|
|
36
|
+
try { return JSON.parse(result.stdout || ''); } catch (_) { return null; }
|
|
37
|
+
}
|
|
35
38
|
|
|
36
39
|
console.log('=== Issue #85 requirements-first workflow contract ===');
|
|
37
40
|
|
|
@@ -72,9 +75,16 @@ check((workflow.unresolved || []).includes('decompose:pending'), 'decomposition
|
|
|
72
75
|
check((workflow.unresolved || []).includes('compose:pending'), 'workflow-composition gap is visible');
|
|
73
76
|
|
|
74
77
|
const requirements = JSON.stringify([
|
|
75
|
-
{ id: 'req-fix', summary: 'Correct the checkout failure', acceptance: 'the failing scenario succeeds'
|
|
76
|
-
{ id: 'req-
|
|
78
|
+
{ id: 'req-fix', stepType: 'behavior-change', summary: 'Correct the checkout failure', acceptance: 'the failing scenario succeeds' },
|
|
79
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the checkout change', acceptance: 'objective check evidence is recorded' },
|
|
80
|
+
]);
|
|
81
|
+
const untypedRequirements = JSON.stringify([
|
|
82
|
+
{ id: 'req-untyped', summary: 'Correct the checkout failure', acceptance: 'the failing scenario succeeds' },
|
|
77
83
|
]);
|
|
84
|
+
check(run(controller, [
|
|
85
|
+
'plan', '--session-id', sessionId, '--requirements-json', untypedRequirements,
|
|
86
|
+
'--strategy', 'iterative-single', '--evidence', 'legacy untyped planning fixture',
|
|
87
|
+
]).status !== 0, 'new single-agent plans cannot bypass active step typing');
|
|
78
88
|
const planned = run(controller, [
|
|
79
89
|
'plan', '--session-id', sessionId,
|
|
80
90
|
'--requirements-json', requirements,
|
|
@@ -83,7 +93,7 @@ const planned = run(controller, [
|
|
|
83
93
|
]);
|
|
84
94
|
check(planned.status === 0, 'requirements and workflow selection are recorded before execution');
|
|
85
95
|
obligations = readJson(obligationFile);
|
|
86
|
-
check(obligations.requirements.map(item => item.id).join(',') === 'req-fix,req-
|
|
96
|
+
check(obligations.requirements.map(item => item.id).join(',') === 'req-fix,req-verify', 'requirement fragments are machine-readable');
|
|
87
97
|
check(obligations.workflowSelection.disposition === 'confirmed', 'workflow choice is explicitly confirmed after decomposition');
|
|
88
98
|
check(obligations.obligations.find(item => item.id === 'decompose').status === 'pass', 'decompose obligation resolves from requirement fragments');
|
|
89
99
|
check(obligations.obligations.find(item => item.id === 'compose').status === 'pass', 'compose obligation resolves from workflow-selection evidence');
|
|
@@ -111,6 +121,14 @@ check((workflow.unresolved || []).includes('verify:blocked'), 'blocked verificat
|
|
|
111
121
|
|
|
112
122
|
check(run(controller, ['obligation', '--session-id', sessionId, '--obligation-id', 'verify', '--disposition', 'pass', '--evidence', 'npm test and focused regression passed']).status === 0,
|
|
113
123
|
'verification can later resolve to pass with evidence');
|
|
124
|
+
check(run(controller, ['binding', '--session-id', sessionId, '--step-id', 'req-fix', '--binding-id', 'tdd', '--disposition', 'loaded', '--evidence', 'read and followed the TDD core contract before behavior changes']).status === 0,
|
|
125
|
+
'behavior-change binding resolves before its active step passes');
|
|
126
|
+
check(run(controller, ['step', '--session-id', sessionId, '--step-id', 'req-fix', '--disposition', 'pass', '--evidence', 'behavior fix and regression case are complete']).status === 0,
|
|
127
|
+
'behavior-change step advances after TDD binding resolution');
|
|
128
|
+
check(run(controller, ['binding', '--session-id', sessionId, '--step-id', 'req-verify', '--binding-id', 'verification-loop', '--disposition', 'loaded', '--evidence', 'read the verification contract for this step']).status === 0,
|
|
129
|
+
'verification skill resolves only when the verification step becomes active');
|
|
130
|
+
check(run(controller, ['step', '--session-id', sessionId, '--step-id', 'req-verify', '--disposition', 'pass', '--evidence', 'objective checks completed and recorded']).status === 0,
|
|
131
|
+
'verification step passes with objective evidence');
|
|
114
132
|
check(run(statePersist, [], {
|
|
115
133
|
session_id: sessionId, cwd: ROOT, hook_event_name: 'PostToolUse',
|
|
116
134
|
tool_name: 'Bash', tool_input: { command: 'npm test' },
|
|
@@ -122,10 +140,339 @@ check(resolvedStop.status === 0, 'resolved workflow Stop remains fail-open');
|
|
|
122
140
|
check(workflow.state === 'satisfied', 'planning + execution + verification allow satisfied');
|
|
123
141
|
check((workflow.unresolved || []).length === 0, 'satisfied workflow has no unresolved obligations');
|
|
124
142
|
|
|
143
|
+
const stepSession = 'issue297-active-step-bindings';
|
|
144
|
+
const stepPayload = { session_id: stepSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
|
|
145
|
+
check(run(kernel, [], stepPayload).status === 0, 'Tier 2 route persists for active-step binding coverage');
|
|
146
|
+
const stepDir = getSessionDir(ROOT, stepSession, stepPayload);
|
|
147
|
+
const stepWorkflowFile = path.join(stepDir, 'workflow-run.json');
|
|
148
|
+
const stepObligationFile = path.join(stepDir, 'workflow-obligations.json');
|
|
149
|
+
|
|
150
|
+
console.log('\n=== Binding declaration validation ===');
|
|
151
|
+
let invalidBindingCase = 0;
|
|
152
|
+
for (const field of ['requiredBindings', 'optionalBindings']) {
|
|
153
|
+
for (const [valueName, value] of [
|
|
154
|
+
['null', null],
|
|
155
|
+
['object', { id: 'silently-dropped' }],
|
|
156
|
+
['string', 'silently-dropped'],
|
|
157
|
+
['boolean', false],
|
|
158
|
+
['number', 0],
|
|
159
|
+
]) {
|
|
160
|
+
for (const typed of [true, false]) {
|
|
161
|
+
invalidBindingCase++;
|
|
162
|
+
const sessionId = `issue303-invalid-binding-${invalidBindingCase}`;
|
|
163
|
+
const payload = { session_id: sessionId, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
|
|
164
|
+
const routed = run(kernel, [], payload);
|
|
165
|
+
check(routed.status === 0, `invalid ${field} ${valueName} fixture route persists (${typed ? 'typed' : 'untyped'})`);
|
|
166
|
+
const dir = getSessionDir(ROOT, sessionId, payload);
|
|
167
|
+
const workflowFile = path.join(dir, 'workflow-run.json');
|
|
168
|
+
const obligationFile = path.join(dir, 'workflow-obligations.json');
|
|
169
|
+
const workflowBefore = readJson(workflowFile);
|
|
170
|
+
const obligationsBefore = readJson(obligationFile);
|
|
171
|
+
const malformed = { id: 'req-review', summary: 'Review a change', acceptance: 'the change is reviewed', [field]: value };
|
|
172
|
+
const requirements = typed
|
|
173
|
+
? [
|
|
174
|
+
{ ...malformed, stepType: 'review' },
|
|
175
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the change', acceptance: 'objective evidence is recorded' },
|
|
176
|
+
]
|
|
177
|
+
: [malformed];
|
|
178
|
+
const result = run(controller, [
|
|
179
|
+
'plan', '--session-id', sessionId, '--requirements-json', JSON.stringify(requirements),
|
|
180
|
+
'--strategy', 'iterative-single', '--evidence', 'validate binding declaration types',
|
|
181
|
+
]);
|
|
182
|
+
check(result.status === 2, `plan exits 2 for ${field} ${valueName} (${typed ? 'typed' : 'untyped'} requirement)`);
|
|
183
|
+
check(result.stdout === '', `rejected ${field} ${valueName} plan writes no stdout (${typed ? 'typed' : 'untyped'})`);
|
|
184
|
+
check(new RegExp(field).test(result.stderr || ''), `rejection identifies ${field} for ${valueName} (${typed ? 'typed' : 'untyped'} requirement)`);
|
|
185
|
+
check(JSON.stringify(readJson(workflowFile)) === JSON.stringify(workflowBefore),
|
|
186
|
+
`rejected ${field} ${valueName} plan leaves workflow state unchanged (${typed ? 'typed' : 'untyped'})`);
|
|
187
|
+
check(JSON.stringify(readJson(obligationFile)) === JSON.stringify(obligationsBefore),
|
|
188
|
+
`rejected ${field} ${valueName} plan leaves obligation state unchanged (${typed ? 'typed' : 'untyped'})`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const emptyBindingsSession = 'issue303-empty-binding-arrays';
|
|
194
|
+
const emptyBindingsPayload = { session_id: emptyBindingsSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
|
|
195
|
+
check(run(kernel, [], emptyBindingsPayload).status === 0, 'empty-array binding fixture route persists');
|
|
196
|
+
const emptyBindingsDir = getSessionDir(ROOT, emptyBindingsSession, emptyBindingsPayload);
|
|
197
|
+
const emptyBindingsRequirements = JSON.stringify([
|
|
198
|
+
{ id: 'req-review-omitted', stepType: 'review', summary: 'Review a change', acceptance: 'the change is reviewed' },
|
|
199
|
+
{ id: 'req-review-empty', stepType: 'review', summary: 'Review another change', acceptance: 'the second change is reviewed', requiredBindings: [], optionalBindings: [] },
|
|
200
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify both reviews', acceptance: 'objective evidence is recorded' },
|
|
201
|
+
]);
|
|
202
|
+
const emptyBindingsPlan = run(controller, [
|
|
203
|
+
'plan', '--session-id', emptyBindingsSession, '--requirements-json', emptyBindingsRequirements,
|
|
204
|
+
'--strategy', 'iterative-single', '--evidence', 'omitted and empty binding arrays are supported',
|
|
205
|
+
]);
|
|
206
|
+
check(emptyBindingsPlan.status === 0, 'plan accepts omitted and explicit empty binding arrays');
|
|
207
|
+
const emptyBindingsState = readJson(path.join(emptyBindingsDir, 'workflow-obligations.json'));
|
|
208
|
+
check(emptyBindingsState.requirements[0].requiredBindings.length === 0 && emptyBindingsState.requirements[0].optionalBindings.length === 0,
|
|
209
|
+
'omitted binding arrays remain empty after normalization');
|
|
210
|
+
check(emptyBindingsState.requirements[1].requiredBindings.length === 0 && emptyBindingsState.requirements[1].optionalBindings.length === 0,
|
|
211
|
+
'explicit empty binding arrays remain empty after normalization');
|
|
212
|
+
|
|
213
|
+
const stepRequirements = JSON.stringify([
|
|
214
|
+
{ id: 'req-review', stepType: 'review', summary: 'Inspect the existing checkout change', acceptance: 'the relevant diff has been reviewed', optionalBindings: ['security-review', 'review-guidance'] },
|
|
215
|
+
{ id: 'req-change', stepType: 'behavior-change', summary: 'Correct the checkout behavior', acceptance: 'the reported behavior is corrected' },
|
|
216
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the checkout change', acceptance: 'objective verification evidence is recorded' },
|
|
217
|
+
{ id: 'req-commit', stepType: 'commit', summary: 'Commit the requested change', acceptance: 'the authorized commit is recorded' },
|
|
218
|
+
]);
|
|
219
|
+
check(run(controller, [
|
|
220
|
+
'plan', '--session-id', stepSession,
|
|
221
|
+
'--requirements-json', stepRequirements,
|
|
222
|
+
'--strategy', 'iterative-single',
|
|
223
|
+
'--evidence', 'the request contains review, behavior-change, verification, and commit steps',
|
|
224
|
+
]).status === 0, 'normalized ordered steps are recorded in the existing requirements contract');
|
|
225
|
+
let stepState = readJson(stepObligationFile);
|
|
226
|
+
check(stepState.requirements.map(item => item.stepType || 'legacy').join(',') === 'review,behavior-change,verification,commit',
|
|
227
|
+
'typed steps preserve request order');
|
|
228
|
+
check((stepState.requirements[1].requiredBindings || []).map(item => item.id).join(',') === 'tdd',
|
|
229
|
+
'behavior-change step materializes required TDD binding');
|
|
230
|
+
check((stepState.requirements[2].requiredBindings || []).map(item => item.id).join(',') === 'verification-loop',
|
|
231
|
+
'verification step owns its verification-loop binding');
|
|
232
|
+
check((stepState.requirements[3].requiredBindings || []).map(item => item.id).join(',') === 'git-commit',
|
|
233
|
+
'commit step materializes required git-commit binding');
|
|
234
|
+
const stepsWithoutVerification = JSON.stringify([
|
|
235
|
+
{ id: 'req-review-only', stepType: 'review', summary: 'Review the diff', acceptance: 'the diff is reviewed' },
|
|
236
|
+
]);
|
|
237
|
+
check(run(controller, [
|
|
238
|
+
'plan', '--session-id', stepSession, '--requirements-json', stepsWithoutVerification,
|
|
239
|
+
'--strategy', 'iterative-single', '--evidence', 'invalid fixture omits mandatory verification',
|
|
240
|
+
]).status !== 0, 'typed-step plan cannot omit the mandatory verification step');
|
|
241
|
+
|
|
242
|
+
const stepStart = run(controller, ['start', '--session-id', stepSession]);
|
|
243
|
+
check(stepStart.status === 0, 'start accepts a confirmed typed-step workflow');
|
|
244
|
+
let stepResponse = responseOf(stepStart) || {};
|
|
245
|
+
check(stepResponse.activeStep?.id === 'req-review' && stepResponse.activeStep?.stepType === 'review',
|
|
246
|
+
'start returns the first ordered step as active');
|
|
247
|
+
check(stepResponse.activeStepCommands?.resolveBinding.includes('--step-id "req-review"') &&
|
|
248
|
+
stepResponse.activeStepCommands?.passStep.includes('--step-id "req-review"'),
|
|
249
|
+
'start returns concrete commands for resolving the active binding and passing the step');
|
|
250
|
+
check(stepResponse.activeStep?.requiredBindings?.length === 0 &&
|
|
251
|
+
stepResponse.activeStep?.optionalBindings?.map(item => item.id).join(',') === 'security-review,review-guidance',
|
|
252
|
+
'review starts without TDD and surfaces only its optional binding');
|
|
253
|
+
check(stepResponse.activeStep?.optionalBindings?.[0]?.path === 'security-review/SKILL.md',
|
|
254
|
+
'active optional binding includes its load path');
|
|
255
|
+
check(!JSON.stringify(stepResponse).includes('tdd') && !JSON.stringify(stepResponse).includes('git-commit'),
|
|
256
|
+
'start does not disclose future-step bindings');
|
|
257
|
+
check(run(controller, [
|
|
258
|
+
'binding', '--session-id', stepSession, '--step-id', 'req-commit', '--binding-id', 'git-commit',
|
|
259
|
+
'--disposition', 'loaded', '--evidence', 'premature future-step load attempt',
|
|
260
|
+
]).status !== 0, 'future-step binding cannot be resolved before its step becomes active');
|
|
261
|
+
stepState = readJson(stepObligationFile);
|
|
262
|
+
check(stepState.activeStepId === 'req-review', 'active step is persisted in workflow-obligations state');
|
|
263
|
+
check(stepState.obligations.find(item => item.id === 'execute').suggestedSkills.length === 0 &&
|
|
264
|
+
stepState.obligations.find(item => item.id === 'verify').suggestedSkills.length === 0,
|
|
265
|
+
'typed-step workflows do not reintroduce global suggested-skill pushes');
|
|
266
|
+
check(run(controller, [
|
|
267
|
+
'step', '--session-id', stepSession, '--step-id', 'req-review', '--disposition', 'pass',
|
|
268
|
+
'--evidence', 'review finished without resolving optional bindings',
|
|
269
|
+
]).status !== 0, 'active step cannot pass before surfaced optional bindings are resolved');
|
|
270
|
+
|
|
271
|
+
check(run(controller, [
|
|
272
|
+
'binding', '--session-id', stepSession, '--step-id', 'req-review', '--binding-id', 'security-review',
|
|
273
|
+
'--disposition', 'loaded', '--evidence', 'loaded the security review checklist for this step',
|
|
274
|
+
]).status === 0, 'active optional binding can be loaded with evidence');
|
|
275
|
+
check(run(controller, [
|
|
276
|
+
'binding', '--session-id', stepSession, '--step-id', 'req-review', '--binding-id', 'review-guidance',
|
|
277
|
+
'--disposition', 'not-needed',
|
|
278
|
+
]).status !== 0, 'optional binding cannot be marked not-needed without evidence');
|
|
279
|
+
check(run(controller, [
|
|
280
|
+
'binding', '--session-id', stepSession, '--step-id', 'req-review', '--binding-id', 'review-guidance',
|
|
281
|
+
'--disposition', 'not-needed', '--evidence', 'the bounded diff review does not need the broader triage guide',
|
|
282
|
+
]).status === 0, 'active optional binding can be marked not-needed with evidence');
|
|
283
|
+
const reviewAdvance = run(controller, [
|
|
284
|
+
'step', '--session-id', stepSession, '--step-id', 'req-review', '--disposition', 'pass',
|
|
285
|
+
'--evidence', 'the relevant diff and acceptance criteria were reviewed',
|
|
286
|
+
]);
|
|
287
|
+
const reviewResponse = responseOf(reviewAdvance) || {};
|
|
288
|
+
check(reviewAdvance.status === 0, 'review step can pass after its surfaced optional binding is resolved');
|
|
289
|
+
stepResponse = reviewResponse;
|
|
290
|
+
check(stepResponse.activeStep?.id === 'req-change' &&
|
|
291
|
+
stepResponse.activeStep?.requiredBindings?.map(item => item.id).join(',') === 'tdd',
|
|
292
|
+
'review advance returns only the behavior-change step and its required TDD binding');
|
|
293
|
+
check(stepResponse.activeStep?.requiredBindings?.[0]?.path === 'tdd/SKILL.md' &&
|
|
294
|
+
!JSON.stringify(stepResponse).includes('verification-loop') && !JSON.stringify(stepResponse).includes('git-commit'),
|
|
295
|
+
'behavior-change output includes its TDD path but keeps later bindings hidden');
|
|
296
|
+
check(run(controller, [
|
|
297
|
+
'step', '--session-id', stepSession, '--step-id', 'req-change', '--disposition', 'pass',
|
|
298
|
+
'--evidence', 'behavior changed',
|
|
299
|
+
]).status !== 0, 'behavior-change cannot pass before required TDD binding is resolved');
|
|
300
|
+
check(run(controller, [
|
|
301
|
+
'binding', '--session-id', stepSession, '--step-id', 'req-change', '--binding-id', 'tdd',
|
|
302
|
+
'--disposition', 'loaded', '--evidence', 'read and followed the TDD core contract before changing behavior',
|
|
303
|
+
]).status === 0, 'required TDD binding resolves with load evidence');
|
|
304
|
+
const changeAdvance = run(controller, [
|
|
305
|
+
'step', '--session-id', stepSession, '--step-id', 'req-change', '--disposition', 'pass',
|
|
306
|
+
'--evidence', 'the checkout behavior and regression case are implemented',
|
|
307
|
+
]);
|
|
308
|
+
const changeResponse = responseOf(changeAdvance) || {};
|
|
309
|
+
check(changeAdvance.status === 0 && changeResponse.activeStep?.id === 'req-verify',
|
|
310
|
+
'resolved behavior-change advances to the mandatory verification step');
|
|
311
|
+
stepResponse = changeResponse;
|
|
312
|
+
check(stepResponse.activeStep?.requiredBindings?.map(item => item.id).join(',') === 'verification-loop' &&
|
|
313
|
+
!JSON.stringify(stepResponse).includes('git-commit'),
|
|
314
|
+
'verification binding is disclosed only when verification becomes active');
|
|
315
|
+
check(run(controller, [
|
|
316
|
+
'binding', '--session-id', stepSession, '--step-id', 'req-verify', '--binding-id', 'verification-loop',
|
|
317
|
+
'--disposition', 'loaded', '--evidence', 'read the verification-loop contract for this verification step',
|
|
318
|
+
]).status === 0, 'verification binding resolves at its own step');
|
|
319
|
+
const verifyAdvance = run(controller, [
|
|
320
|
+
'step', '--session-id', stepSession, '--step-id', 'req-verify', '--disposition', 'pass',
|
|
321
|
+
'--evidence', 'objective checks completed and their result is recorded',
|
|
322
|
+
]);
|
|
323
|
+
const verifyResponse = responseOf(verifyAdvance) || {};
|
|
324
|
+
check(verifyAdvance.status === 0 && verifyResponse.activeStep?.id === 'req-commit',
|
|
325
|
+
'verification step advances to commit only after its binding resolves');
|
|
326
|
+
stepResponse = verifyResponse;
|
|
327
|
+
check(stepResponse.activeStep?.requiredBindings?.map(item => item.id).join(',') === 'git-commit' &&
|
|
328
|
+
!JSON.stringify(stepResponse).includes('tdd') && !JSON.stringify(stepResponse).includes('verification-loop'),
|
|
329
|
+
'commit binding is disclosed only when the commit step becomes active');
|
|
330
|
+
check(run(controller, [
|
|
331
|
+
'binding', '--session-id', stepSession, '--step-id', 'req-commit', '--binding-id', 'git-commit',
|
|
332
|
+
'--disposition', 'loaded', '--evidence', 'read the commit authorization and staged-diff contract',
|
|
333
|
+
]).status === 0, 'required git-commit binding resolves at the commit step');
|
|
334
|
+
const commitAdvance = run(controller, [
|
|
335
|
+
'step', '--session-id', stepSession, '--step-id', 'req-commit', '--disposition', 'pass',
|
|
336
|
+
'--evidence', 'the explicitly requested commit was created and checked',
|
|
337
|
+
]);
|
|
338
|
+
const commitResponse = responseOf(commitAdvance) || {};
|
|
339
|
+
check(commitAdvance.status === 0 && commitResponse.activeStep === null,
|
|
340
|
+
'advancing the last step leaves no active-step bindings');
|
|
341
|
+
stepState = readJson(stepObligationFile);
|
|
342
|
+
check(stepState.obligations.find(item => item.id === 'execute').status === 'pass' &&
|
|
343
|
+
stepState.obligations.find(item => item.id === 'verify').status === 'pass',
|
|
344
|
+
'completed typed steps resolve their aggregate execute and verify obligations with linked evidence');
|
|
345
|
+
const typedResolvedStop = run(stopGate, [], { session_id: stepSession, cwd: ROOT, hook_event_name: 'Stop' });
|
|
346
|
+
const typedWorkflow = readJson(stepWorkflowFile);
|
|
347
|
+
check(typedResolvedStop.status === 0 && typedWorkflow.state === 'satisfied',
|
|
348
|
+
'all typed steps can satisfy the workflow without separate aggregate dispositions');
|
|
349
|
+
check((typedWorkflow.unresolved || []).length === 0, 'satisfied typed-step workflow has no unresolved obligations');
|
|
350
|
+
|
|
351
|
+
const verifyOnlySession = 'issue297-verification-only';
|
|
352
|
+
const verifyOnlyPayload = { session_id: verifyOnlySession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
|
|
353
|
+
check(run(kernel, [], verifyOnlyPayload).status === 0, 'verification-only workflow route persists');
|
|
354
|
+
const verifyOnlyObligationFile = path.join(getSessionDir(ROOT, verifyOnlySession, verifyOnlyPayload), 'workflow-obligations.json');
|
|
355
|
+
const verifyOnlyRequirements = JSON.stringify([
|
|
356
|
+
{ id: 'req-verify-only', stepType: 'verification', summary: 'Verify existing behavior', acceptance: 'objective checks pass' },
|
|
357
|
+
]);
|
|
358
|
+
check(run(controller, [
|
|
359
|
+
'plan', '--session-id', verifyOnlySession, '--requirements-json', verifyOnlyRequirements,
|
|
360
|
+
'--strategy', 'iterative-single', '--evidence', 'the request verifies existing behavior without implementation work',
|
|
361
|
+
]).status === 0, 'verification-only typed plan is accepted');
|
|
362
|
+
const verifyOnlyStart = run(controller, ['start', '--session-id', verifyOnlySession]);
|
|
363
|
+
check(verifyOnlyStart.status === 0 && responseOf(verifyOnlyStart)?.activeStep?.id === 'req-verify-only',
|
|
364
|
+
'verification-only typed plan starts at its verification step');
|
|
365
|
+
let verifyOnlyState = readJson(verifyOnlyObligationFile);
|
|
366
|
+
check(verifyOnlyState.obligations.find(item => item.id === 'execute')?.status === 'pass' &&
|
|
367
|
+
verifyOnlyState.obligations.find(item => item.id === 'execute')?.evidence.includes('verification-only workflow'),
|
|
368
|
+
'empty typed execution group resolves as explicit verification-only evidence');
|
|
369
|
+
check(run(controller, [
|
|
370
|
+
'binding', '--session-id', verifyOnlySession, '--step-id', 'req-verify-only', '--binding-id', 'verification-loop',
|
|
371
|
+
'--disposition', 'loaded', '--evidence', 'loaded and followed the verification binding for the active step',
|
|
372
|
+
]).status === 0, 'verification-only step resolves its required binding');
|
|
373
|
+
check(run(controller, [
|
|
374
|
+
'step', '--session-id', verifyOnlySession, '--step-id', 'req-verify-only', '--disposition', 'pass',
|
|
375
|
+
'--evidence', 'the existing behavior passed its objective verification checks',
|
|
376
|
+
]).status === 0, 'verification-only step passes with objective evidence');
|
|
377
|
+
const verifyOnlyStop = run(stopGate, [], { session_id: verifyOnlySession, cwd: ROOT, hook_event_name: 'Stop' });
|
|
378
|
+
const verifyOnlyWorkflow = readJson(path.join(getSessionDir(ROOT, verifyOnlySession, verifyOnlyPayload), 'workflow-run.json'));
|
|
379
|
+
verifyOnlyState = readJson(verifyOnlyObligationFile);
|
|
380
|
+
check(verifyOnlyStop.status === 0 && verifyOnlyWorkflow.state === 'satisfied',
|
|
381
|
+
'verification-only workflow can satisfy without an execution step');
|
|
382
|
+
check(verifyOnlyState.obligations.find(item => item.id === 'verify')?.status === 'pass',
|
|
383
|
+
'verification-only completion also resolves the aggregate verify obligation');
|
|
384
|
+
|
|
385
|
+
const unknownSession = 'issue297-unknown-binding';
|
|
386
|
+
const unknownPayload = { session_id: unknownSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
|
|
387
|
+
check(run(kernel, [], unknownPayload).status === 0, 'route persists for unknown-required-binding coverage');
|
|
388
|
+
const unknownDir = getSessionDir(ROOT, unknownSession, unknownPayload);
|
|
389
|
+
const unknownRequirements = JSON.stringify([
|
|
390
|
+
{ id: 'req-unknown-review', stepType: 'review', summary: 'Review the unfamiliar domain change', acceptance: 'the unfamiliar behavior is assessed', requiredBindings: ['unregistered-review-skill'] },
|
|
391
|
+
{ id: 'req-unknown-verify', stepType: 'verification', summary: 'Verify the reviewed change', acceptance: 'verification evidence exists' },
|
|
392
|
+
]);
|
|
393
|
+
check(run(controller, [
|
|
394
|
+
'plan', '--session-id', unknownSession, '--requirements-json', unknownRequirements,
|
|
395
|
+
'--strategy', 'iterative-single', '--evidence', 'a review-only capability was requested explicitly',
|
|
396
|
+
]).status === 0, 'unknown required binding is accepted into the auditable contract');
|
|
397
|
+
const unknownStart = run(controller, ['start', '--session-id', unknownSession]);
|
|
398
|
+
check(unknownStart.status === 0, 'start surfaces rather than silently discarding an unknown required binding');
|
|
399
|
+
stepResponse = responseOf(unknownStart) || {};
|
|
400
|
+
check(stepResponse.activeStep?.requiredBindings?.[0]?.id === 'unregistered-review-skill' &&
|
|
401
|
+
stepResponse.activeStep?.requiredBindings?.[0]?.path === null &&
|
|
402
|
+
stepResponse.activeStep?.requiredBindings?.[0]?.status === 'pending',
|
|
403
|
+
'unknown required binding remains visible as pending/unavailable');
|
|
404
|
+
check(run(controller, [
|
|
405
|
+
'binding', '--session-id', unknownSession, '--step-id', 'req-unknown-review', '--binding-id', 'unregistered-review-skill',
|
|
406
|
+
'--disposition', 'loaded', '--evidence', 'attempted to load unknown skill',
|
|
407
|
+
]).status !== 0, 'unknown required binding cannot be falsely marked loaded');
|
|
408
|
+
check(run(controller, [
|
|
409
|
+
'step', '--session-id', unknownSession, '--step-id', 'req-unknown-review', '--disposition', 'pass',
|
|
410
|
+
'--evidence', 'review finished',
|
|
411
|
+
]).status !== 0, 'step with unknown required binding cannot pass');
|
|
412
|
+
const unknownMutationReminder = run(workflowGate, [], {
|
|
413
|
+
session_id: unknownSession, cwd: ROOT, hook_event_name: 'PreToolUse',
|
|
414
|
+
tool_name: 'Write', tool_input: { file_path: path.join(ROOT, 'tmp-unknown-binding.txt'), content: 'x' },
|
|
415
|
+
});
|
|
416
|
+
check(unknownMutationReminder.status === 0, 'unknown required binding does not hard-block ordinary tools');
|
|
417
|
+
const unknownStop = run(stopGate, [], { session_id: unknownSession, cwd: ROOT, hook_event_name: 'Stop' });
|
|
418
|
+
const unknownWorkflow = readJson(path.join(unknownDir, 'workflow-run.json'));
|
|
419
|
+
check(unknownStop.status === 0 && unknownWorkflow.state === 'running',
|
|
420
|
+
'missing binding keeps Stop fail-open while workflow remains unresolved');
|
|
421
|
+
check((unknownWorkflow.unresolved || []).some(item => item.includes('unregistered-review-skill')),
|
|
422
|
+
'Stop reports the unresolved unknown binding');
|
|
423
|
+
|
|
424
|
+
const referenceSession = 'issue297-custom-reference-binding';
|
|
425
|
+
const referencePayload = { session_id: referenceSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
|
|
426
|
+
check(run(kernel, [], referencePayload).status === 0, 'custom-reference workflow route persists');
|
|
427
|
+
const referenceRequirements = JSON.stringify([
|
|
428
|
+
{ id: 'req-reference-review', stepType: 'review', summary: 'Review the domain-specific behavior', acceptance: 'the domain rule is checked', requiredBindings: [{ id: 'domain-reference', path: 'docs\\philosophy.md' }] },
|
|
429
|
+
{ id: 'req-reference-verify', stepType: 'verification', summary: 'Verify the domain change', acceptance: 'objective check evidence exists' },
|
|
430
|
+
]);
|
|
431
|
+
check(run(controller, [
|
|
432
|
+
'plan', '--session-id', referenceSession, '--requirements-json', referenceRequirements,
|
|
433
|
+
'--strategy', 'iterative-single', '--evidence', 'the review step requires one explicit domain reference',
|
|
434
|
+
]).status === 0, 'step contracts accept an explicit skill/reference path');
|
|
435
|
+
const referenceStart = run(controller, ['start', '--session-id', referenceSession]);
|
|
436
|
+
stepResponse = responseOf(referenceStart) || {};
|
|
437
|
+
check(referenceStart.status === 0 && stepResponse.activeStep?.requiredBindings?.[0]?.path === 'docs/philosophy.md',
|
|
438
|
+
'custom reference path is normalized and disclosed only at its active step');
|
|
439
|
+
check(run(controller, [
|
|
440
|
+
'binding', '--session-id', referenceSession, '--step-id', 'req-reference-review', '--binding-id', 'domain-reference',
|
|
441
|
+
'--disposition', 'loaded', '--evidence', 'read docs/philosophy.md before completing the review',
|
|
442
|
+
]).status === 0, 'explicit reference binding can resolve with evidence');
|
|
443
|
+
check(run(controller, [
|
|
444
|
+
'step', '--session-id', referenceSession, '--step-id', 'req-reference-review', '--disposition', 'pass',
|
|
445
|
+
'--evidence', 'the relevant domain rule was applied in the review',
|
|
446
|
+
]).status === 0, 'active step can pass after its explicit reference binding is resolved');
|
|
447
|
+
const unsafeReferenceRequirements = JSON.stringify([
|
|
448
|
+
{ id: 'req-unsafe-reference', stepType: 'review', summary: 'Review a rule', acceptance: 'the rule is checked', requiredBindings: [{ id: 'unsafe-reference', path: '../private.md' }] },
|
|
449
|
+
{ id: 'req-unsafe-verify', stepType: 'verification', summary: 'Verify the review', acceptance: 'objective check evidence exists' },
|
|
450
|
+
]);
|
|
451
|
+
const unsafeReferenceSession = 'issue297-unsafe-reference-binding';
|
|
452
|
+
check(run(kernel, [], { session_id: unsafeReferenceSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' }).status === 0,
|
|
453
|
+
'unsafe-reference workflow route persists');
|
|
454
|
+
check(run(controller, [
|
|
455
|
+
'plan', '--session-id', unsafeReferenceSession, '--requirements-json', unsafeReferenceRequirements,
|
|
456
|
+
'--strategy', 'iterative-single', '--evidence', 'unsafe traversal path should be rejected',
|
|
457
|
+
]).status !== 0, 'step bindings reject paths that traverse outside repository scope');
|
|
458
|
+
const driveReferenceSession = 'issue297-drive-reference-binding';
|
|
459
|
+
check(run(kernel, [], { session_id: driveReferenceSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' }).status === 0,
|
|
460
|
+
'drive-reference workflow route persists');
|
|
461
|
+
const driveReferenceRequirements = JSON.stringify([
|
|
462
|
+
{ id: 'req-drive-reference', stepType: 'review', summary: 'Review a rule', acceptance: 'the rule is checked', requiredBindings: [{ id: 'drive-reference', path: 'C:outside.md' }] },
|
|
463
|
+
{ id: 'req-drive-verify', stepType: 'verification', summary: 'Verify the review', acceptance: 'objective check evidence exists' },
|
|
464
|
+
]);
|
|
465
|
+
check(run(controller, [
|
|
466
|
+
'plan', '--session-id', driveReferenceSession, '--requirements-json', driveReferenceRequirements,
|
|
467
|
+
'--strategy', 'iterative-single', '--evidence', 'drive-relative path should be rejected',
|
|
468
|
+
]).status !== 0, 'step bindings reject Windows drive-relative paths');
|
|
469
|
+
|
|
125
470
|
const mismatchSession = 'issue85-replan';
|
|
126
471
|
const mismatchPayload = { session_id: mismatchSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
|
|
127
472
|
check(run(kernel, [], mismatchPayload).status === 0, 'second Tier 2 route persists for replan discriminator');
|
|
128
473
|
const mismatchDir = getSessionDir(ROOT, mismatchSession, mismatchPayload);
|
|
474
|
+
const initialSignals = readJson(path.join(mismatchDir, 'workflow-run.json')).workflowPlan.knowledgeSignals || [];
|
|
475
|
+
check(initialSignals.includes('tdd-test'), 'initial route records normalized knowledge signals');
|
|
129
476
|
const mismatchPlan = run(controller, [
|
|
130
477
|
'plan', '--session-id', mismatchSession,
|
|
131
478
|
'--requirements-json', requirements,
|
|
@@ -140,6 +487,8 @@ check(mismatchObligations.workflowSelection.requestedStrategy === 'fable-staged'
|
|
|
140
487
|
mismatchObligations.workflowSelection.confirmedStrategy === 'fable-staged',
|
|
141
488
|
'requested and router-confirmed post-decomposition strategy are preserved');
|
|
142
489
|
check(mismatchWorkflow.pendingPlan?.strategy === 'fable-staged', 'recomposed plan becomes the pending execution contract');
|
|
490
|
+
check(JSON.stringify(mismatchWorkflow.pendingPlan?.knowledgeSignals) === JSON.stringify(initialSignals),
|
|
491
|
+
'strategy recomposition preserves the original knowledge signals');
|
|
143
492
|
const mismatchStop = run(stopGate, [], { session_id: mismatchSession, cwd: ROOT, hook_event_name: 'Stop' });
|
|
144
493
|
const mismatchAfterStop = readJson(path.join(mismatchDir, 'workflow-run.json'));
|
|
145
494
|
check(mismatchStop.status === 0 && mismatchAfterStop.state === 'pending' &&
|