harness-everything 0.8.0 → 0.9.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/.github/workflows/ci.yml +12 -2
- package/AGENTS.md +36 -35
- package/CHANGELOG.md +51 -0
- package/README.md +2 -2
- package/benchmarks/results/reviews/pr142-verification.json +95 -0
- package/ci/action-gate.test.js +83 -3
- package/ci/doc-capability-consistency.test.js +3 -0
- package/ci/invariant-routing.test.js +87 -74
- package/ci/mechanism-2af-worktree-isolation.test.js +152 -0
- package/ci/mechanism-2ag-router-worktree-contract.test.js +44 -0
- package/ci/mechanism-2ah-workflow-lifecycle.test.js +140 -0
- package/ci/openai-plugin-package.test.js +9 -6
- package/ci/phase4-ensemble-memory.test.js +128 -10
- package/ci/router-workflow-plan.test.js +23 -0
- package/ci/workflow-execution-contract.test.js +133 -0
- package/ci/workflow-plan-consumers.test.js +18 -0
- package/docs/architecture.md +111 -114
- package/docs/mechanism-first-skill-mesh.md +92 -103
- package/docs/openai-plugin.md +1 -1
- package/docs/philosophy.md +40 -33
- package/docs/platform-capabilities.md +2 -2
- package/docs/reviews/pr-142-issue-131.md +89 -0
- package/docs/routing.md +61 -124
- package/docs/workflow-runtime.md +109 -0
- package/fable-mode/CONTRACT-FORMAT.md +3 -1
- package/fable-mode/SKILL.md +1 -1
- package/fable-mode/agents/fable-orchestrator.md +9 -7
- 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 +13 -8
- package/harness-everything/SKILL.md +11 -10
- package/harness-everything/references/triage-and-tiers.md +76 -107
- package/harness-everything/schemas/router-task-shape.schema.json +4 -2
- package/harness-everything/schemas/router-workflow-plan.schema.json +3 -2
- package/harness-everything/scripts/kernel-router-core.js +265 -0
- package/harness-everything/scripts/kernel-router.js +118 -223
- package/harness-everything/scripts/router-contract-core.js +633 -0
- package/harness-everything/scripts/router-contract.js +21 -581
- package/harness-everything/scripts/tier-router.js +19 -1
- package/hooks/hooks.json +25 -4
- package/hooks/scripts/action-gate.js +111 -2
- package/hooks/scripts/contract-test.js +27 -2
- package/hooks/scripts/lib/workflow-isolation.js +102 -0
- package/hooks/scripts/lib/workflow-runtime.js +254 -0
- package/hooks/scripts/state-persist.js +1 -0
- package/hooks/scripts/subagent-scope-guard.js +20 -3
- package/hooks/scripts/workflow-disposition.js +104 -0
- package/hooks/scripts/workflow-gate.js +56 -0
- package/hooks/scripts/workflow-stop-gate.js +44 -0
- package/multi-agent-workspace/SKILL.md +2 -2
- package/multi-agent-workspace/scripts/index_memory.js +107 -1
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +2 -2
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/hooks.json +44 -1
- package/plugins/harness-everything/hooks/scripts/action-gate.js +111 -2
- package/plugins/harness-everything/hooks/scripts/contract-test.js +27 -2
- package/plugins/harness-everything/hooks/scripts/lib/workflow-isolation.js +102 -0
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +254 -0
- package/plugins/harness-everything/hooks/scripts/state-persist.js +1 -0
- package/plugins/harness-everything/hooks/scripts/subagent-scope-guard.js +20 -3
- package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +104 -0
- package/plugins/harness-everything/hooks/scripts/workflow-gate.js +56 -0
- package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +44 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/scripts/lib/advisory-text.js +51 -54
- package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +3 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +9 -7
- 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 +13 -8
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +11 -10
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +76 -107
- package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json +4 -2
- package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +3 -2
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +265 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +118 -223
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +633 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +21 -581
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +19 -1
- package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +2 -2
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/index_memory.js +107 -1
- package/plugins/harness-everything/skills/self-evolve/SKILL.md +6 -6
- package/plugins/harness-everything/skills/self-evolve/references/memory-resolution.md +22 -33
- package/plugins/harness-everything/skills/self-evolve/scripts/persist-memory.js +336 -57
- package/plugins/harness-everything/skills/self-evolve/scripts/self-regression.js +1 -0
- package/plugins/harness-everything/skills/using-git-worktrees/SKILL.md +14 -16
- package/plugins/harness-everything/skills/using-git-worktrees/references/workflow-details.md +44 -85
- package/scripts/lib/advisory-text.js +51 -54
- package/self-evolve/SKILL.md +6 -6
- package/self-evolve/references/memory-resolution.md +22 -33
- package/self-evolve/scripts/persist-memory.js +336 -57
- package/self-evolve/scripts/self-regression.js +1 -0
- package/using-git-worktrees/SKILL.md +14 -16
- package/using-git-worktrees/references/workflow-details.md +44 -85
|
@@ -7,19 +7,15 @@ const ROOT = path.resolve(__dirname, '..');
|
|
|
7
7
|
let failed = 0;
|
|
8
8
|
|
|
9
9
|
function check(condition, message) {
|
|
10
|
-
if (condition) {
|
|
11
|
-
|
|
12
|
-
} else {
|
|
13
|
-
console.error(` FAIL ${message}`);
|
|
14
|
-
failed++;
|
|
15
|
-
}
|
|
10
|
+
if (condition) console.log(` PASS ${message}`);
|
|
11
|
+
else { console.error(` FAIL ${message}`); failed++; }
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
function read(rel) {
|
|
19
15
|
return fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
console.log('===
|
|
18
|
+
console.log('=== Mandatory Workflow Routing Contract ===');
|
|
23
19
|
|
|
24
20
|
const kernel = path.join(ROOT, 'harness-everything', 'scripts', 'kernel-router.js');
|
|
25
21
|
const tier2 = spawnSync(process.execPath, [kernel, 'add a login endpoint with tests and update the implementation'], {
|
|
@@ -36,25 +32,30 @@ check(tier2.stdout.includes('Verify before claim'), 'verify-before-claim invaria
|
|
|
36
32
|
check(tier2.stdout.includes('after 3 same-signature failures'), 'failure escalation invariant is present');
|
|
37
33
|
check(tier2.stdout.includes('loop-budget:'), 'iterative-single loop budget invariant is present');
|
|
38
34
|
check(tier2.stdout.includes('evaluate-suggestions-before-skip'), 'non-empty suggestions inject evaluate-before-skip invariant');
|
|
39
|
-
check(tier2.stdout.includes('
|
|
40
|
-
check(tier2.stdout.includes('
|
|
35
|
+
check(tier2.stdout.includes('WORKFLOW SKILLS (EVALUATE APPLICABILITY'), 'domain suggestions remain applicability inputs');
|
|
36
|
+
check(tier2.stdout.includes('SELECTED WORKFLOW IS MANDATORY'), 'selected workflow execution is mandatory');
|
|
41
37
|
check(tier2.stdout.includes('tdd:'), 'Tier 2 can still recommend TDD');
|
|
42
38
|
check(!tier2.stdout.includes('BASE EXECUTION LOOP'), 'legacy fixed base-execution-loop output is suppressed');
|
|
43
|
-
check(tier2.stdout.includes('
|
|
39
|
+
check(tier2.stdout.includes('Mandatory applicable workflow'), 'orchestration policy requires selected applicable workflow');
|
|
40
|
+
check(tier2.stdout.includes('model controls HOW to satisfy it'), 'reasoning/implementation freedom remains inside workflow');
|
|
41
|
+
check(tier2.stdout.includes('WORKFLOW EXECUTION CONTRACT (MANDATORY WHEN SELECTED)'), 'kernel emits workflow execution contract');
|
|
42
|
+
check(tier2.stdout.includes('- State: unpersisted'), 'manual route without host session does not claim persisted lifecycle');
|
|
43
|
+
check(tier2.stdout.includes('Generic simple/routine/already-clear reasons cannot waive'), 'model confidence cannot bypass workflow');
|
|
44
|
+
check(tier2.stdout.includes('Escape is limited to workflow-uncovered-scope or host-capability-unavailable'), 'workflow escape is exception-only');
|
|
45
|
+
check(!tier2.stdout.includes('execution remains advisory after evaluation'), 'old advisory execution wording is absent');
|
|
46
|
+
check(!tier2.stdout.includes('isolated-worktree-before-mutation'), 'Tier 2 does not inherit the major-workflow worktree invariant');
|
|
44
47
|
|
|
45
48
|
const tier2Checkpoints = tier2.stdout.match(/HARNESS ROUTING CHECKPOINT \(REQUIRED VISIBLE STATE\)/g) || [];
|
|
46
|
-
check(tier2Checkpoints.length === 1, 'Tier 2 emits exactly one
|
|
47
|
-
check(tier2.stdout.includes('- Tier: Tier 2'), 'checkpoint exposes
|
|
48
|
-
check(tier2.stdout.includes('- Strategy: iterative-single'), 'checkpoint exposes
|
|
49
|
+
check(tier2Checkpoints.length === 1, 'Tier 2 emits exactly one routing checkpoint');
|
|
50
|
+
check(tier2.stdout.includes('- Tier: Tier 2'), 'checkpoint exposes Tier 2');
|
|
51
|
+
check(tier2.stdout.includes('- Strategy: iterative-single'), 'checkpoint exposes selected strategy');
|
|
49
52
|
check(/- Required invariants: .*verify-before-claim/.test(tier2.stdout), 'checkpoint exposes required invariants');
|
|
50
|
-
check(/- Suggested skills: .*tdd/.test(tier2.stdout), 'checkpoint exposes
|
|
51
|
-
check(tier2.stdout.includes('Suggestion evaluation: MANDATORY'), 'checkpoint
|
|
52
|
-
check(tier2.stdout.includes('read its complete SKILL.md entry'), 'checkpoint requires reading
|
|
53
|
-
check(tier2.stdout.includes('name, description, router summary'), 'checkpoint rejects metadata-only
|
|
54
|
-
check(tier2.stdout.includes('routine/common task'), 'checkpoint rejects
|
|
55
|
-
check(tier2.stdout.includes('
|
|
56
|
-
check(tier2.stdout.includes('unresolved/unavailable rather than skipped'), 'checkpoint preserves unresolved suggestions instead of silent skip');
|
|
57
|
-
check(tier2.stdout.includes('If all are skipped after evaluation'), 'all-skipped rationale is grounded after evaluation');
|
|
53
|
+
check(/- Suggested skills: .*tdd/.test(tier2.stdout), 'checkpoint exposes suggestions');
|
|
54
|
+
check(tier2.stdout.includes('Suggestion evaluation: MANDATORY'), 'checkpoint requires suggestion evaluation');
|
|
55
|
+
check(tier2.stdout.includes('read its complete SKILL.md entry'), 'checkpoint requires reading skill entry');
|
|
56
|
+
check(tier2.stdout.includes('name, description, router summary'), 'checkpoint rejects metadata-only omission');
|
|
57
|
+
check(tier2.stdout.includes('routine/common task'), 'checkpoint rejects routine-task omission shortcut');
|
|
58
|
+
check(tier2.stdout.includes('selected workflow is mandatory'), 'checkpoint distinguishes skill applicability from topology obligation');
|
|
58
59
|
|
|
59
60
|
const tier3 = spawnSync(process.execPath, [kernel, 'audit the entire repository architecture and coordinate multiple modules'], {
|
|
60
61
|
cwd: ROOT,
|
|
@@ -62,86 +63,98 @@ const tier3 = spawnSync(process.execPath, [kernel, 'audit the entire repository
|
|
|
62
63
|
});
|
|
63
64
|
check(tier3.status === 0, 'Tier 3 kernel routing exits successfully');
|
|
64
65
|
check(/RECOMMENDED TIER:\s*Tier 3/i.test(tier3.stdout), 'macro task remains Tier 3');
|
|
65
|
-
check(tier3.stdout.includes('"strategy":"fable-staged"'), 'Tier 3
|
|
66
|
-
check(tier3.stdout.includes('fable-mode / fable-discipline'), 'Tier 3
|
|
67
|
-
check(tier3.stdout.includes('
|
|
68
|
-
check(tier3.stdout.includes('
|
|
69
|
-
check(tier3.stdout.includes('
|
|
66
|
+
check(tier3.stdout.includes('"strategy":"fable-staged"'), 'Tier 3 task selects fable-staged');
|
|
67
|
+
check(tier3.stdout.includes('fable-mode / fable-discipline'), 'Tier 3 plan exposes Fable capabilities');
|
|
68
|
+
check(tier3.stdout.includes('selected Fable topology must be entered and resolved before completion'), 'selected Fable topology is mandatory');
|
|
69
|
+
check(tier3.stdout.includes('WORKFLOW EXECUTION CONTRACT (MANDATORY WHEN SELECTED)'), 'Tier 3 emits execution contract');
|
|
70
|
+
check(tier3.stdout.includes('isolated-worktree-before-mutation'), 'Tier 3 structured plan carries the worktree isolation invariant');
|
|
71
|
+
check(tier3.stdout.includes('using-git-worktrees'), 'Tier 3 structured plan surfaces the worktree workflow skill');
|
|
72
|
+
check(tier3.stdout.includes('mutations require Git worktree isolation'), 'Tier 3 execution contract requires worktree isolation before mutation');
|
|
73
|
+
check(tier3.stdout.includes('unavailable isolation means BLOCKED'), 'Tier 3 worktree failure blocks instead of falling back in place');
|
|
70
74
|
|
|
71
75
|
const harnessSkill = read('harness-everything/SKILL.md');
|
|
72
|
-
check(!/Requests that already name a skill/i.test(harnessSkill), 'harness entrypoint no longer opts out when another skill is named');
|
|
73
76
|
check(/Work that names or matches another skill/i.test(harnessSkill), 'named domain skills still pass through Harness routing');
|
|
74
|
-
check(/
|
|
75
|
-
check(/
|
|
76
|
-
check(/
|
|
77
|
-
check(
|
|
78
|
-
check(/
|
|
79
|
-
check(/Using one suggestion does not waive evaluation of others/i.test(harnessSkill), 'skill contract applies evaluation per omitted suggestion');
|
|
80
|
-
check(/Unreadable suggestions are `unresolved\/unavailable`/i.test(harnessSkill), 'skill contract defines unreadable suggestion disposition');
|
|
81
|
-
check(/If all are skipped, give one brief flow-grounded reason/i.test(harnessSkill), 'skill contract preserves evaluated all-skipped rationale');
|
|
82
|
-
check(/mandatory evaluation, advisory execution/i.test(harnessSkill), 'skill entry states the core disposition succinctly');
|
|
77
|
+
check(/selected topology is not advisory/i.test(harnessSkill), 'skill contract makes selected topology mandatory');
|
|
78
|
+
check(/"Simple", "routine".*cannot justify omission/i.test(harnessSkill), 'skill contract rejects confidence/routine omission shortcuts');
|
|
79
|
+
check(/explicit escape with uncovered scope \+ evidence/i.test(harnessSkill), 'skill contract defines evidence-backed escape');
|
|
80
|
+
check(/Complete only after applicable workflow obligations and objective verification resolve/i.test(harnessSkill), 'skill contract gates completion on workflow resolution');
|
|
81
|
+
check(/mandatory applicable workflow; flexible reasoning\/implementation/i.test(harnessSkill), 'skill entry states core policy');
|
|
83
82
|
|
|
84
83
|
const pluginHarnessSkill = read('plugins/harness-everything/skills/harness-everything/SKILL.md');
|
|
85
84
|
check(pluginHarnessSkill === harnessSkill, 'canonical/plugin Harness SKILL.md copies remain identical');
|
|
86
|
-
|
|
87
|
-
check(
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
check(read('plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js') === read('harness-everything/scripts/kernel-router.js'), 'canonical/plugin kernel-router copies remain identical');
|
|
86
|
+
check(read('plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js') === read('harness-everything/scripts/kernel-router-core.js'), 'canonical/plugin kernel-router core copies remain identical');
|
|
87
|
+
check(read('plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md') === read('harness-everything/references/triage-and-tiers.md'), 'canonical/plugin triage references remain identical');
|
|
88
|
+
|
|
89
|
+
const worktreeSkill = read('using-git-worktrees/SKILL.md');
|
|
90
|
+
check(/Major workflow mode/i.test(worktreeSkill), 'worktree skill defines mandatory major-workflow mode');
|
|
91
|
+
check(/never fall back to the primary working tree/i.test(worktreeSkill), 'worktree skill blocks unsafe in-place fallback for major work');
|
|
92
|
+
check(/Creation\/entry failure => `BLOCKED`/i.test(worktreeSkill), 'worktree skill makes isolation failure a blocked state');
|
|
93
|
+
check(read('plugins/harness-everything/skills/using-git-worktrees/SKILL.md') === worktreeSkill, 'canonical/plugin worktree skill copies remain identical');
|
|
90
94
|
|
|
91
95
|
const cognitiveSkill = read('install-cognitive-os/SKILL.md');
|
|
92
|
-
check(/policy, not a required peer-skill selection/i.test(cognitiveSkill), 'Cognitive OS
|
|
93
|
-
check(!/Tasks already governed by a more specific domain skill/i.test(cognitiveSkill), 'domain skill selection no longer disables Cognitive OS policy');
|
|
96
|
+
check(/policy, not a required peer-skill selection/i.test(cognitiveSkill), 'Cognitive OS remains reasoning policy rather than peer dependency');
|
|
94
97
|
|
|
95
98
|
const hooks = JSON.parse(read('hooks/hooks.json'));
|
|
96
99
|
const promptHooks = hooks.hooks && hooks.hooks.UserPromptSubmit;
|
|
97
100
|
const command = promptHooks && promptHooks[0] && promptHooks[0].hooks && promptHooks[0].hooks[0] && promptHooks[0].hooks[0].command;
|
|
98
|
-
check(command === 'node "${CLAUDE_PLUGIN_ROOT}/harness-everything/scripts/kernel-router.js"', 'UserPromptSubmit anchors
|
|
101
|
+
check(command === 'node "${CLAUDE_PLUGIN_ROOT}/harness-everything/scripts/kernel-router.js"', 'UserPromptSubmit anchors kernel to Claude plugin root');
|
|
102
|
+
const preHooks = hooks.hooks && hooks.hooks.PreToolUse || [];
|
|
103
|
+
const workflowHook = preHooks.find(entry => entry.id === 'harness:pre:workflow-gate');
|
|
104
|
+
check(Boolean(workflowHook), 'Claude PreToolUse includes workflow gate');
|
|
105
|
+
check(workflowHook && /Bash/.test(workflowHook.matcher) && /PowerShell/.test(workflowHook.matcher) && /Edit/.test(workflowHook.matcher) && /Write/.test(workflowHook.matcher), 'Claude workflow gate covers shell and artifact mutation');
|
|
106
|
+
const stopHooks = hooks.hooks && hooks.hooks.Stop || [];
|
|
107
|
+
check(stopHooks.some(entry => entry.id === 'harness:stop:workflow-completion-gate'), 'Claude Stop includes Fable workflow completion gate');
|
|
108
|
+
|
|
109
|
+
const openaiHooks = JSON.parse(read('plugins/harness-everything/hooks/hooks.json'));
|
|
110
|
+
const openaiWorkflowHook = (openaiHooks.hooks && openaiHooks.hooks.PreToolUse || []).find(entry =>
|
|
111
|
+
(entry.hooks || []).some(hook => String(hook.command || '').includes('workflow-gate.js'))
|
|
112
|
+
);
|
|
113
|
+
check(openaiWorkflowHook && /Bash/.test(openaiWorkflowHook.matcher) && /apply_patch/.test(openaiWorkflowHook.matcher), 'Codex/OpenAI packaged workflow gate covers Bash and apply_patch');
|
|
99
114
|
|
|
100
115
|
const triageDoc = read('harness-everything/references/triage-and-tiers.md');
|
|
101
|
-
check(
|
|
102
|
-
check(
|
|
103
|
-
check(
|
|
104
|
-
check(
|
|
105
|
-
check(triageDoc
|
|
106
|
-
check(
|
|
107
|
-
check(triageDoc.includes('Using one suggested skill does not waive evaluation'), 'reference preserves per-suggestion evaluation');
|
|
108
|
-
check(triageDoc.includes('cannot be resolved/read is not a valid skip'), 'reference preserves unresolved/unavailable state');
|
|
109
|
-
check(triageDoc.includes('Kernel emission alone is not proof'), 'reference preserves the #82 host-evidence boundary');
|
|
116
|
+
check(/Mandatory applicable workflow/i.test(triageDoc), 'triage reference documents mandatory applicable workflow');
|
|
117
|
+
check(/topology becomes the run's execution contract/i.test(triageDoc), 'triage reference makes topology a contract');
|
|
118
|
+
check(/workflow-uncovered-scope/i.test(triageDoc), 'triage reference documents narrow escape policy');
|
|
119
|
+
check(/A tier is \*\*not\*\* a fixed TODO\/TDD\/Fable pipeline/i.test(triageDoc), 'triage reference rejects universal tier pipeline');
|
|
120
|
+
check(/```mermaid/.test(triageDoc), 'triage reference retains executable flowchart documentation');
|
|
121
|
+
check(/cross-host live-enforcement claim/i.test(triageDoc), 'triage reference preserves host evidence boundary');
|
|
110
122
|
|
|
111
123
|
const routingDoc = read('docs/routing.md');
|
|
112
|
-
check(
|
|
113
|
-
check(routingDoc
|
|
114
|
-
check(routingDoc
|
|
124
|
+
check(/Mandatory applicable workflow/i.test(routingDoc), 'routing docs use mandatory workflow policy');
|
|
125
|
+
check(/workflow-gate\.js/.test(routingDoc), 'routing docs describe PreToolUse workflow gate');
|
|
126
|
+
check(/workflow-stop-gate\.js/.test(routingDoc), 'routing docs describe completion gate');
|
|
127
|
+
check(/workflow-disposition\.js/.test(routingDoc), 'routing docs describe explicit escape mechanism');
|
|
128
|
+
check(!/mandatory to evaluate and advisory to execute/i.test(routingDoc), 'routing docs remove old advisory execution contract');
|
|
115
129
|
|
|
116
130
|
const architectureDoc = read('docs/architecture.md');
|
|
117
|
-
check(
|
|
118
|
-
check(architectureDoc
|
|
131
|
+
check(/topology becomes a lifecycle contract/i.test(architectureDoc), 'architecture makes selected topology lifecycle contract');
|
|
132
|
+
check(/workflow-gate\.js/.test(architectureDoc), 'architecture includes workflow bypass gate');
|
|
133
|
+
check(!/Mandatory evaluation, advisory execution/i.test(architectureDoc), 'architecture removes old advisory execution policy');
|
|
119
134
|
|
|
120
135
|
const philosophyDoc = read('docs/philosophy.md');
|
|
121
|
-
check(
|
|
136
|
+
check(/Constrain Lifecycle, Not Reasoning/i.test(philosophyDoc), 'philosophy separates lifecycle constraint from reasoning freedom');
|
|
137
|
+
check(/Applicable workflow is mandatory/i.test(philosophyDoc), 'philosophy states applicable workflow requirement');
|
|
138
|
+
check(!/Mandatory Evaluation, Advisory Execution/i.test(philosophyDoc), 'philosophy removes old advisory execution policy');
|
|
122
139
|
|
|
123
140
|
const meshDoc = read('docs/mechanism-first-skill-mesh.md');
|
|
124
|
-
check(
|
|
141
|
+
check(/mandatory applicable workflow \+ local skill autonomy/i.test(meshDoc), 'skill mesh documents mandatory outer workflow and local skill autonomy');
|
|
142
|
+
check(/selected topology is mandatory for the run/i.test(meshDoc), 'skill mesh distinguishes selected topology from skill suggestions');
|
|
125
143
|
|
|
126
144
|
const capabilitiesDoc = read('docs/platform-capabilities.md');
|
|
127
|
-
check(capabilitiesDoc.includes('repository/agent contract'), 'platform docs distinguish
|
|
128
|
-
check(capabilitiesDoc.includes('does not
|
|
145
|
+
check(capabilitiesDoc.includes('repository/agent contract'), 'platform docs distinguish contract from host enforcement evidence');
|
|
146
|
+
check(capabilitiesDoc.includes('does not upgrade `platform-compatibility.json` live-host status values'), 'routing change does not inflate compatibility status');
|
|
129
147
|
|
|
130
|
-
const
|
|
131
|
-
check(
|
|
132
|
-
check(
|
|
148
|
+
const advisory = read('scripts/lib/advisory-text.js');
|
|
149
|
+
check(/For EVERY suggested skill/i.test(advisory), 'generated advisory instructions require per-suggestion applicability evaluation');
|
|
150
|
+
check(/selected workflow topology is mandatory once applicable/i.test(advisory), 'advisory surfaces carry mandatory workflow semantics');
|
|
151
|
+
check(/No universal skill pipeline/i.test(advisory), 'advisory surfaces reject universal skill pipeline');
|
|
152
|
+
check(/workflow genuinely cannot cover part of the task/i.test(advisory), 'advisory surfaces constrain workflow escape');
|
|
153
|
+
check(read('plugins/harness-everything/scripts/lib/advisory-text.js') === advisory, 'OpenAI plugin advisory mirror matches canonical source');
|
|
133
154
|
|
|
134
155
|
const agents = read('AGENTS.md');
|
|
135
|
-
check(
|
|
136
|
-
|
|
137
|
-
const advisory = read('scripts/lib/advisory-text.js');
|
|
138
|
-
check(advisory.includes('For EVERY suggested skill'), 'generated advisory instructions require per-suggestion evaluation');
|
|
139
|
-
check(advisory.includes('entry/basic flow before skipping it'), 'Codex AGENTS generator includes read-before-skip');
|
|
140
|
-
check(advisory.includes('There is NO universal TODO/TDD/Fable sequence'), 'advisory surfaces reject the old fixed execution pipeline');
|
|
141
|
-
check(!advisory.includes('Initialize the `todo-driven-workflow` checklist first'), 'old forced Tier 2/3 checklist pipeline is removed');
|
|
142
|
-
check(!advisory.includes('load `fable-mode` and `fable-discipline`, run sub-agents'), 'old forced Tier 3 Fable pipeline is removed');
|
|
143
|
-
const pluginAdvisory = read('plugins/harness-everything/scripts/lib/advisory-text.js');
|
|
144
|
-
check(pluginAdvisory === advisory, 'OpenAI plugin advisory mirror matches canonical source');
|
|
156
|
+
check(/Selected workflow is mandatory; reasoning inside it stays flexible/i.test(agents), 'repository agent contract matches mandatory workflow policy');
|
|
157
|
+
check(!/Router suggestions are mandatory to evaluate, advisory to execute/i.test(agents), 'repository agent contract removes old advisory execution rule');
|
|
145
158
|
|
|
146
159
|
console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'}: invariant routing contract (${failed} failure${failed === 1 ? '' : 's'})`);
|
|
147
160
|
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { spawnSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
10
|
+
const GATE = path.join(ROOT, 'hooks', 'scripts', 'workflow-gate.js');
|
|
11
|
+
let failed = 0;
|
|
12
|
+
|
|
13
|
+
function check(condition, message) {
|
|
14
|
+
if (condition) console.log(`✅ worktree-isolation: ${message}`);
|
|
15
|
+
else { console.error(`❌ worktree-isolation: ${message}`); failed++; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function git(cwd, args) {
|
|
19
|
+
return spawnSync('git', args, { cwd, encoding: 'utf8', windowsHide: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-worktree-isolation-'));
|
|
23
|
+
const home = path.join(temp, 'home');
|
|
24
|
+
const repo = path.join(temp, 'repo');
|
|
25
|
+
const linked = path.join(temp, 'repo-linked');
|
|
26
|
+
fs.mkdirSync(home, { recursive: true });
|
|
27
|
+
fs.mkdirSync(repo, { recursive: true });
|
|
28
|
+
|
|
29
|
+
const env = {
|
|
30
|
+
...process.env,
|
|
31
|
+
HOME: home,
|
|
32
|
+
USERPROFILE: home,
|
|
33
|
+
HARNESS_STATE_HOME: path.join(home, '.agents', 'harness-everything'),
|
|
34
|
+
CLAUDE: '1',
|
|
35
|
+
};
|
|
36
|
+
process.env.HARNESS_STATE_HOME = env.HARNESS_STATE_HOME;
|
|
37
|
+
process.env.HOME = home;
|
|
38
|
+
process.env.USERPROFILE = home;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (process.platform === 'win32') {
|
|
42
|
+
const { key } = require('../hooks/scripts/lib/workflow-isolation');
|
|
43
|
+
const shortProgramFiles = path.join(process.env.SystemDrive || 'C:', 'PROGRA~1');
|
|
44
|
+
if (fs.existsSync(shortProgramFiles)) {
|
|
45
|
+
check(key(shortProgramFiles) === key(fs.realpathSync.native(shortProgramFiles)), 'Windows short and long path spellings identify the same physical directory');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
check(git(repo, ['init']).status === 0, 'fixture git repository initializes');
|
|
49
|
+
fs.writeFileSync(path.join(repo, 'README.md'), 'fixture\n', 'utf8');
|
|
50
|
+
check(git(repo, ['add', 'README.md']).status === 0, 'fixture file stages');
|
|
51
|
+
check(git(repo, ['-c', 'user.name=Harness Test', '-c', 'user.email=harness@example.invalid', 'commit', '-m', 'fixture']).status === 0, 'fixture baseline commits');
|
|
52
|
+
|
|
53
|
+
const workspace = require(path.join(ROOT, 'scripts', 'lib', 'workspace'));
|
|
54
|
+
const state = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'harness-state'));
|
|
55
|
+
const sessionId = 'worktree-isolation-test';
|
|
56
|
+
const context = { session_id: sessionId, host_id: 'mechanism-test', cwd: repo };
|
|
57
|
+
workspace.bindWorkspaceSession(sessionId, repo, context, true);
|
|
58
|
+
const sessionDir = state.getSessionDir(repo, sessionId, context);
|
|
59
|
+
const workflowFile = path.join(sessionDir, 'workflow-run.json');
|
|
60
|
+
|
|
61
|
+
function writeWorkflow(tier, strategy) {
|
|
62
|
+
fs.writeFileSync(workflowFile, `${JSON.stringify({
|
|
63
|
+
schemaVersion: 1,
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
createdAtMs: Date.now(),
|
|
66
|
+
sessionId,
|
|
67
|
+
tier,
|
|
68
|
+
strategy,
|
|
69
|
+
state: 'active',
|
|
70
|
+
}, null, 2)}\n`, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function runGate(tool, cwd, input = {}) {
|
|
74
|
+
const payload = {
|
|
75
|
+
session_id: sessionId,
|
|
76
|
+
host_id: 'mechanism-test',
|
|
77
|
+
cwd,
|
|
78
|
+
tool_name: tool,
|
|
79
|
+
tool_input: input,
|
|
80
|
+
};
|
|
81
|
+
return spawnSync(process.execPath, [GATE], {
|
|
82
|
+
cwd: ROOT,
|
|
83
|
+
env,
|
|
84
|
+
input: JSON.stringify(payload),
|
|
85
|
+
encoding: 'utf8',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Tier 3 isolation is independent of the selected execution topology.
|
|
90
|
+
writeWorkflow('tier3', 'iterative-single');
|
|
91
|
+
|
|
92
|
+
const primaryWrite = runGate('Write', repo, { file_path: path.join(repo, 'src.js'), content: 'x' });
|
|
93
|
+
check(primaryWrite.status === 2, 'Tier 3 direct write is blocked in the primary working tree');
|
|
94
|
+
check(/worktree isolation/i.test(primaryWrite.stderr), 'blocked write explains the isolation requirement');
|
|
95
|
+
|
|
96
|
+
const primaryDelete = runGate('Bash', repo, { command: 'rm -rf src' });
|
|
97
|
+
check(primaryDelete.status === 2, 'Tier 3 destructive shell mutation is blocked in the primary working tree');
|
|
98
|
+
|
|
99
|
+
for (const command of ['git status > tracked.txt', 'git log --output=tracked.txt', 'find . -delete', 'fd -x rm', 'rg --pre touch x', 'git show $(touch tracked.txt)', 'Get-Content (Remove-Item src)']) {
|
|
100
|
+
check(runGate('Bash', repo, { command }).status === 2, `shell side effect is not read-only: ${command}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const chainedBypass = runGate('Bash', repo, { command: 'git status --short && rm -rf src' });
|
|
104
|
+
check(chainedBypass.status === 2, 'read-only prefix cannot hide a chained mutation');
|
|
105
|
+
|
|
106
|
+
const readOnly = runGate('Bash', repo, { command: 'git status --short' });
|
|
107
|
+
check(readOnly.status === 0, 'read-only discovery remains allowed before isolation');
|
|
108
|
+
|
|
109
|
+
const setup = runGate('Bash', repo, { command: `git worktree add "${linked}" -b harness-isolation-test` });
|
|
110
|
+
check(setup.status === 0, 'git worktree creation is allowed as the isolation transition');
|
|
111
|
+
|
|
112
|
+
const createLinked = git(repo, ['worktree', 'add', linked, '-b', 'harness-isolation-test']);
|
|
113
|
+
check(createLinked.status === 0, 'fixture linked worktree is created');
|
|
114
|
+
|
|
115
|
+
const isolatedWrite = runGate('Write', linked, { file_path: path.join(linked, 'src.js'), content: 'x' });
|
|
116
|
+
check(isolatedWrite.status === 0, `Tier 3 mutation is allowed after entering a linked worktree (${isolatedWrite.stderr.trim() || 'allowed'})`);
|
|
117
|
+
|
|
118
|
+
check(runGate('Write', linked, { file_path: path.join(repo, 'src.js') }).status === 2, 'linked cwd cannot authorize a primary-tree target');
|
|
119
|
+
check(runGate('Write', linked, { file_path: '../repo/src.js' }).status === 2, 'relative traversal cannot escape the worktree');
|
|
120
|
+
check(runGate('apply_patch', linked, `*** Begin Patch\n*** Add File: ${path.join(repo, 'src.js')}\n+x\n*** End Patch`).status === 2, 'raw patch targets are checked');
|
|
121
|
+
check(runGate('apply_patch', linked, { patch: `*** Begin Patch\n*** Update File: README.md\n*** Move to: ${path.join(repo, 'moved.md')}\n@@\n-fixture\n+x\n*** End Patch` }).status === 2, 'patch move destination cannot escape isolation');
|
|
122
|
+
check(runGate('exec_command', repo, { cmd: 'rm -rf src', workdir: repo }).status === 2, 'native shell alias cannot skip mutation gating');
|
|
123
|
+
check(runGate('Bash', linked, { command: `rm "${path.join(repo, 'README.md')}"` }).status === 2, 'explicit primary-tree shell target is blocked');
|
|
124
|
+
check(runGate('Bash', linked, { command: 'cd ../repo && node build.js' }).status === 2, 'shell directory change cannot hide the effective working directory');
|
|
125
|
+
check(runGate('Write', linked, {}).status === 2, 'unknown direct target is blocked');
|
|
126
|
+
const junction = path.join(linked, 'outside');
|
|
127
|
+
fs.symlinkSync(repo, junction, process.platform === 'win32' ? 'junction' : 'dir');
|
|
128
|
+
check(runGate('Write', linked, { file_path: path.join(junction, 'src.js') }).status === 2, 'symlink or junction escape is blocked');
|
|
129
|
+
fs.unlinkSync(junction);
|
|
130
|
+
|
|
131
|
+
const escaped = JSON.parse(fs.readFileSync(workflowFile, 'utf8'));
|
|
132
|
+
escaped.state = 'escaped';
|
|
133
|
+
fs.writeFileSync(workflowFile, JSON.stringify(escaped));
|
|
134
|
+
check(runGate('Write', repo, { file_path: path.join(repo, 'src.js') }).status === 2, 'legacy escaped state never waives isolation');
|
|
135
|
+
escaped.state = 'blocked';
|
|
136
|
+
fs.writeFileSync(workflowFile, JSON.stringify(escaped));
|
|
137
|
+
check(runGate('Write', linked, { file_path: path.join(linked, 'src.js') }).status === 2, 'blocked workflow cannot mutate even inside isolation');
|
|
138
|
+
fs.writeFileSync(workflowFile, '{broken');
|
|
139
|
+
check(runGate('Write', linked, { file_path: path.join(linked, 'src.js') }).status === 2, 'corrupt recorded workflow cannot silently authorize mutation');
|
|
140
|
+
|
|
141
|
+
writeWorkflow('tier2', 'iterative-single');
|
|
142
|
+
const tier2Write = runGate('Write', repo, { file_path: path.join(repo, 'small.js'), content: 'x' });
|
|
143
|
+
check(tier2Write.status === 0, 'Tier 2 is not globally forced into worktree isolation');
|
|
144
|
+
} finally {
|
|
145
|
+
try { fs.rmSync(temp, { recursive: true, force: true }); } catch (_) { /* best effort */ }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (failed > 0) {
|
|
149
|
+
console.error(`FAIL: worktree isolation gate (${failed} failure${failed === 1 ? '' : 's'})`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
console.log('PASS: major-workflow worktree isolation gate');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
8
|
+
const router = require(path.join(ROOT, 'harness-everything', 'scripts', 'router-contract.js'));
|
|
9
|
+
|
|
10
|
+
const tier3 = router.buildRouterContract({
|
|
11
|
+
routingStatus: 'ok',
|
|
12
|
+
recommendedTier: 'Tier 3',
|
|
13
|
+
rationale: 'major architecture change',
|
|
14
|
+
signals: { macroScope: true, dependentStages: true },
|
|
15
|
+
}).workflowPlan;
|
|
16
|
+
|
|
17
|
+
assert.strictEqual(tier3.strategy, 'fable-staged');
|
|
18
|
+
assert.ok(tier3.requiredInvariants.includes('isolated-worktree-before-mutation'), 'Tier 3 plan must carry mutation isolation invariant');
|
|
19
|
+
assert.ok(tier3.suggestedSkills.includes('using-git-worktrees'), 'Tier 3 plan must surface the worktree workflow');
|
|
20
|
+
assert.strictEqual(tier3.workspace.required, false, 'Git worktree isolation must remain distinct from durable multi-agent workspace state');
|
|
21
|
+
|
|
22
|
+
const tier2 = router.buildRouterContract({
|
|
23
|
+
routingStatus: 'ok',
|
|
24
|
+
recommendedTier: 'Tier 2',
|
|
25
|
+
rationale: 'bounded bug fix',
|
|
26
|
+
signals: { tier2Keyword: true },
|
|
27
|
+
}).workflowPlan;
|
|
28
|
+
|
|
29
|
+
assert.strictEqual(tier2.strategy, 'iterative-single');
|
|
30
|
+
assert.ok(!tier2.requiredInvariants.includes('isolated-worktree-before-mutation'), 'Tier 2 must not be globally forced into major-workflow isolation');
|
|
31
|
+
assert.ok(!tier2.suggestedSkills.includes('using-git-worktrees'), 'Tier 2 keeps worktree use task-dependent');
|
|
32
|
+
|
|
33
|
+
const explicitFable = router.buildWorkflowPlan({
|
|
34
|
+
routingStatus: 'ok',
|
|
35
|
+
tier: 'tier3',
|
|
36
|
+
taskShape: router.buildTaskShape({
|
|
37
|
+
tier: 'tier3',
|
|
38
|
+
signals: { macroScope: true, dependentStages: true },
|
|
39
|
+
explicitRequest: { strategy: 'fable-staged' },
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
assert.ok(explicitFable.requiredInvariants.includes('isolated-worktree-before-mutation'), 'direct buildWorkflowPlan callers receive the same isolation contract');
|
|
43
|
+
|
|
44
|
+
console.log('PASS: router major-workflow plan carries worktree isolation independently of multi-agent workspace state.');
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { spawnSync } = require('child_process');
|
|
9
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
10
|
+
const plugin = process.argv.includes('--plugin');
|
|
11
|
+
const runtimeRoot = plugin ? path.join(ROOT, 'plugins/harness-everything') : ROOT;
|
|
12
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-workflow-lifecycle-'));
|
|
13
|
+
const repo = path.join(temp, 'repo');
|
|
14
|
+
const linked = path.join(temp, 'linked');
|
|
15
|
+
fs.mkdirSync(repo);
|
|
16
|
+
const env = { ...process.env, HARNESS_STATE_HOME: path.join(temp, 'state'), HARNESS_WORKSPACE_ROOT: repo };
|
|
17
|
+
process.env.HARNESS_STATE_HOME = env.HARNESS_STATE_HOME;
|
|
18
|
+
const state = require(path.join(runtimeRoot, 'hooks/scripts/lib/harness-state'));
|
|
19
|
+
const sessionId = 'workflow-lifecycle';
|
|
20
|
+
const payload = { session_id: sessionId, cwd: repo };
|
|
21
|
+
const sessionDir = state.getSessionDir(repo, sessionId);
|
|
22
|
+
const file = path.join(sessionDir, 'workflow-run.json');
|
|
23
|
+
const read = target => JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
24
|
+
const write = (target, value) => fs.writeFileSync(target, JSON.stringify(value));
|
|
25
|
+
let passed = 0;
|
|
26
|
+
function check(value, message) { assert.ok(value, message); passed++; console.log('PASS ' + message); }
|
|
27
|
+
function node(script, input, args = []) {
|
|
28
|
+
const relative = plugin && script.startsWith('harness-everything/') ? 'skills/' + script : script;
|
|
29
|
+
return spawnSync(process.execPath, [path.join(runtimeRoot, relative), ...args], { cwd: repo, env, input: input && JSON.stringify(input), encoding: 'utf8' });
|
|
30
|
+
}
|
|
31
|
+
function git(args) {
|
|
32
|
+
const result = spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
|
|
33
|
+
assert.strictEqual(result.status, 0, result.stderr);
|
|
34
|
+
}
|
|
35
|
+
const route = prompt => node('harness-everything/scripts/kernel-router.js', { ...payload, prompt });
|
|
36
|
+
const gate = (tool, input, cwd = linked) => node('hooks/scripts/workflow-gate.js', { ...payload, cwd, tool_name: tool, tool_input: input });
|
|
37
|
+
const stop = extra => node('hooks/scripts/workflow-stop-gate.js', { ...payload, ...extra });
|
|
38
|
+
const control = (...args) => node('hooks/scripts/workflow-disposition.js', null, [args[0], '--session-id', sessionId, ...args.slice(1)]);
|
|
39
|
+
const stages = [
|
|
40
|
+
{ stageId: 'build', goal: 'fix', agent: 'fable-worker', task: 'fix bounded module', dependsOn: [], writeSet: ['src'], checkCommand: 'node test-build.js', passCondition: 'exit 0' },
|
|
41
|
+
{ stageId: 'optional', goal: 'external scope', agent: 'fable-worker', task: 'external task', dependsOn: [], writeSet: [], checkCommand: 'node external.js', passCondition: 'exit 0' },
|
|
42
|
+
{ stageId: 'verify', goal: 'verify', agent: 'fable-verifier', task: 'independent review', dependsOn: ['build', 'optional'], writeSet: [], checkCommand: 'node verify.js', passCondition: 'exit 0' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
git(['init']); fs.writeFileSync(path.join(repo, 'README.md'), 'fixture\n'); git(['add', '.']);
|
|
47
|
+
git(['-c', 'user.name=Harness Test', '-c', 'user.email=harness@example.invalid', 'commit', '-m', 'fixture']);
|
|
48
|
+
git(['worktree', 'add', linked, '-b', 'isolated']);
|
|
49
|
+
const routed = route('Refactor the entire repository architecture in dependent stages.');
|
|
50
|
+
check(routed.status === 0, 'prompt hook creates workflow');
|
|
51
|
+
const initial = read(file);
|
|
52
|
+
check(initial.state === 'pending' && initial.workflowId && initial.strategy === 'fable-staged', 'selected route is pending with identity');
|
|
53
|
+
check(initial.requiredInvariants.includes('stage-contracts') && initial.requiredInvariants.includes('isolated-worktree-before-mutation'), 'persisted contract retains full invariant set');
|
|
54
|
+
check(!fs.readFileSync(file, 'utf8').includes('Refactor the entire'), 'state stores prompt hash without prompt text');
|
|
55
|
+
check(gate('Write', { file_path: path.join(linked, 'src.js') }).status === 2, 'Fable entry is required even inside worktree');
|
|
56
|
+
check(stop().status === 2, 'cannot stop before entry');
|
|
57
|
+
check(route('continue').status === 0 && read(file).workflowId === initial.workflowId, 'continuation does not erase active obligation');
|
|
58
|
+
check(route('Update one README typo').status === 0 && read(file).strategy === 'fable-staged', 'simple prompt cannot downgrade unresolved workflow');
|
|
59
|
+
const spec = path.join(sessionDir, 'workflow-stages.json');
|
|
60
|
+
check(gate('Write', { file_path: spec }, repo).status === 0, 'bounded stage specification can be bootstrapped without source mutation');
|
|
61
|
+
fs.linkSync(path.join(repo, 'README.md'), spec);
|
|
62
|
+
check(gate('Write', { file_path: spec }, repo).status === 2, 'bootstrap file cannot alias a primary-tree artifact');
|
|
63
|
+
fs.unlinkSync(spec);
|
|
64
|
+
check(gate('Write', { file_path: file }, repo).status === 2, 'bootstrap exception excludes active workflow state');
|
|
65
|
+
write(spec, stages);
|
|
66
|
+
const command = `node "${path.join(runtimeRoot, 'hooks/scripts/workflow-disposition.js')}" start --session-id ${sessionId}`;
|
|
67
|
+
check(gate('Bash', { command }, repo).status === 0, 'trusted controller can start from bound primary root');
|
|
68
|
+
check(gate('Bash', { command: command + ' && rm src.js' }, repo).status === 2, 'controller prefix cannot hide shell execution');
|
|
69
|
+
const started = control('start');
|
|
70
|
+
check(started.status === 0, 'controller starts a real Fable run: ' + started.stderr);
|
|
71
|
+
const active = read(file);
|
|
72
|
+
const runRoot = path.join(state.getStateRoot(repo), 'fable-runs', active.runId);
|
|
73
|
+
const runFile = path.join(runRoot, 'run.json');
|
|
74
|
+
const manifest = read(runFile);
|
|
75
|
+
check(manifest.workflowId === active.workflowId && manifest.sessionId === sessionId, 'run is explicitly bound to workflow and session');
|
|
76
|
+
const allowedMutation = gate('Write', { file_path: path.join(linked, 'src.js') });
|
|
77
|
+
check(allowedMutation.status === 0, 'entered workflow allows isolated artifact mutation: ' + allowedMutation.stderr);
|
|
78
|
+
for (const invalid of [{ sessionId: null }, { workflowId: 'unrelated' }, { createdAt: 'invalid' }]) {
|
|
79
|
+
write(runFile, { ...manifest, ...invalid });
|
|
80
|
+
check(stop().status === 2, 'uncorrelated run cannot satisfy completion: ' + JSON.stringify(invalid));
|
|
81
|
+
}
|
|
82
|
+
write(runFile, manifest);
|
|
83
|
+
check(control('escape', '--reason-code', 'routine', '--scope', 'task', '--evidence', 'easy').status === 2, 'confidence escape rejected');
|
|
84
|
+
check(control('escape', '--reason-code', 'workflow-uncovered-scope', '--scope', 'task', '--evidence', 'uncovered').status === 2, 'unscoped whole-workflow escape rejected');
|
|
85
|
+
check(control('escape', '--stage-id', 'optional', '--reason-code', 'workflow-uncovered-scope', '--scope', 'external observation', '--evidence', 'fixture external host unavailable').status === 0, 'scoped evidence-backed exception is recorded');
|
|
86
|
+
check(read(file).state === 'running' && stop().status === 2, 'escape leaves covered obligations unresolved');
|
|
87
|
+
check(gate('Write', { file_path: path.join(repo, 'src.js') }, repo).status === 2, 'actual escape command cannot waive isolation');
|
|
88
|
+
check(control('escape', '--stage-id', 'verify', '--reason-code', 'host-capability-unavailable', '--scope', 'verifier', '--evidence', 'missing host').status === 2, 'required verifier cannot be escaped');
|
|
89
|
+
function observe(stage, worker, exitCode) {
|
|
90
|
+
return node('hooks/scripts/contract-test.js', { ...payload, worker_id: worker, tool_name: 'Bash', tool_input: { command: stages.find(item => item.stageId === stage).checkCommand }, tool_response: { exitCode, stdout: 'fixture evidence' } });
|
|
91
|
+
}
|
|
92
|
+
check(observe('verify', 'reviewer', 0).status === 2, 'verifier cannot pass ahead of its dependency');
|
|
93
|
+
check(observe('build', 'builder', 1).status === 2 && stop().status === 2, 'failed objective check prevents completion');
|
|
94
|
+
check(observe('build', 'builder', 0).status === 0, 'failed stage may recover with observed passing evidence');
|
|
95
|
+
check(observe('verify', 'reviewer', 0).status === 0, 'independent verifier evidence is observed');
|
|
96
|
+
const buildFile = path.join(runRoot, 'contracts/build.json');
|
|
97
|
+
const savedBuild = read(buildFile);
|
|
98
|
+
fs.unlinkSync(buildFile);
|
|
99
|
+
check(stop().status === 2, 'deleted contract cannot make remaining directory entries look complete');
|
|
100
|
+
write(buildFile, savedBuild);
|
|
101
|
+
const evidenceFile = path.join(runRoot, 'evidence/build.json');
|
|
102
|
+
const savedEvidence = read(evidenceFile);
|
|
103
|
+
write(evidenceFile, { ...savedEvidence, exitCode: null });
|
|
104
|
+
check(stop().status === 2, 'status pass without observed exit zero is insufficient');
|
|
105
|
+
write(evidenceFile, savedEvidence);
|
|
106
|
+
const mutationState = read(file);
|
|
107
|
+
write(file, { ...mutationState, lastMutationAt: Date.now() + 10000 });
|
|
108
|
+
check(stop().status === 2, 'verification predating a new mutation is stale');
|
|
109
|
+
write(file, mutationState);
|
|
110
|
+
check(stop().status === 0 && read(file).state === 'satisfied', 'all covered stages and verifier resolve workflow to satisfied');
|
|
111
|
+
check(route('Refactor the entire repository architecture in dependent stages.').status === 0 && read(file).workflowId !== initial.workflowId, 'next completed-task boundary creates a fresh workflow');
|
|
112
|
+
check(stop({ stop_hook_active: true }).status === 0 && read(file).state === 'blocked', 'Stop retry reports blocked instead of faking completion');
|
|
113
|
+
check(gate('Write', { file_path: path.join(linked, 'src.js') }).status === 2, 'blocked state still prohibits mutation');
|
|
114
|
+
for (let i = 0; i < 3; i++) { check(control('start').status === 0, 'bounded replan attempt ' + i); control('block', '--evidence', 'fixture blocker'); }
|
|
115
|
+
check(control('start').status === 2 && read(file).state === 'blocked', 'exhausted replan budget remains blocked');
|
|
116
|
+
fs.writeFileSync(file, '{broken');
|
|
117
|
+
check(route('continue').status === 2 && fs.readFileSync(file, 'utf8') === '{broken', 'router cannot overwrite unreadable unresolved state');
|
|
118
|
+
check(stop().status === 2, 'malformed state cannot silently pass completion');
|
|
119
|
+
write(file, { state: 'active' });
|
|
120
|
+
check(gate('Write', { file_path: path.join(linked, 'src.js') }).status === 2 && stop().status === 2, 'partial JSON state cannot bypass workflow gates');
|
|
121
|
+
fs.unlinkSync(file);
|
|
122
|
+
check(route('Fix this checkout bug with a regression test').status === 0 && read(file).strategy === 'iterative-single', 'bounded fix selects iterative lifecycle');
|
|
123
|
+
check(gate('Write', { file_path: path.join(repo, 'small.js') }, repo).status === 0, 'ordinary iterative mutation does not require isolation');
|
|
124
|
+
check(stop().status === 2, 'direct/iterative route cannot complete unverified mutation');
|
|
125
|
+
check(stop({ stop_hook_active: true }).status === 0 && read(file).state === 'blocked', 'iterative retry reports incomplete state');
|
|
126
|
+
check(control('start').status === 0, 'blocked iterative route can resume without a Fable stage map');
|
|
127
|
+
node('hooks/scripts/state-persist.js', { ...payload, tool_name: 'Bash', tool_input: { command: 'npm test' }, tool_response: { stdout: 'no code' } });
|
|
128
|
+
check(stop().status === 2, 'verification without numeric exit status is unknown');
|
|
129
|
+
node('hooks/scripts/state-persist.js', { ...payload, tool_name: 'Bash', tool_input: { command: 'npm test' }, tool_response: { exitCode: 0, stdout: 'passed' } });
|
|
130
|
+
check(stop().status === 0 && read(file).state === 'satisfied', 'observed iterative verification resolves completion');
|
|
131
|
+
console.log(`PASS: workflow lifecycle (${passed} assertions)`);
|
|
132
|
+
} finally {
|
|
133
|
+
if (!temp.startsWith(path.join(os.tmpdir(), 'harness-workflow-lifecycle-'))) throw new Error('unsafe fixture cleanup');
|
|
134
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
if (!plugin) {
|
|
137
|
+
const packaged = spawnSync(process.execPath, [__filename, '--plugin'], { cwd: ROOT, env: process.env, encoding: 'utf8' });
|
|
138
|
+
assert.strictEqual(packaged.status, 0, packaged.stdout + packaged.stderr);
|
|
139
|
+
console.log('PASS: packaged OpenAI workflow lifecycle mirrors source (' + passed + ' assertions)');
|
|
140
|
+
}
|