harness-everything 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.github/workflows/ci.yml +12 -2
  4. package/AGENTS.md +36 -33
  5. package/CHANGELOG.md +23 -0
  6. package/README.md +9 -8
  7. package/benchmarks/results/reviews/pr142-verification.json +95 -0
  8. package/ci/action-gate.test.js +1 -1
  9. package/ci/doc-capability-consistency.test.js +3 -0
  10. package/ci/invariant-routing.test.js +105 -34
  11. package/ci/mechanism-2af-worktree-isolation.test.js +152 -0
  12. package/ci/mechanism-2ag-router-worktree-contract.test.js +44 -0
  13. package/ci/mechanism-2ah-workflow-lifecycle.test.js +140 -0
  14. package/ci/openai-plugin-package.test.js +14 -7
  15. package/ci/workflow-execution-contract.test.js +60 -0
  16. package/docs/architecture.md +112 -106
  17. package/docs/mechanism-first-skill-mesh.md +93 -99
  18. package/docs/openai-plugin.md +1 -1
  19. package/docs/philosophy.md +40 -30
  20. package/docs/platform-capabilities.md +10 -2
  21. package/docs/reviews/pr-142-issue-131.md +89 -0
  22. package/docs/routing.md +68 -103
  23. package/docs/workflow-runtime.md +109 -0
  24. package/fable-mode/CONTRACT-FORMAT.md +3 -1
  25. package/fable-mode/SKILL.md +1 -1
  26. package/fable-mode/execution-guardrails/SKILL.md +1 -1
  27. package/fable-mode/fable-haiku/SKILL.md +1 -1
  28. package/fable-mode/fable-opus/SKILL.md +1 -1
  29. package/fable-mode/fable-sonnet/SKILL.md +1 -1
  30. package/fable-mode/scripts/workflow-plan-consumer.js +3 -1
  31. package/harness-everything/SKILL.md +17 -23
  32. package/harness-everything/references/triage-and-tiers.md +81 -98
  33. package/harness-everything/scripts/kernel-router-core.js +263 -0
  34. package/harness-everything/scripts/kernel-router.js +97 -212
  35. package/harness-everything/scripts/router-contract-core.js +612 -0
  36. package/harness-everything/scripts/router-contract.js +21 -581
  37. package/hooks/hooks.json +22 -1
  38. package/hooks/scripts/contract-test.js +24 -2
  39. package/hooks/scripts/lib/workflow-isolation.js +102 -0
  40. package/hooks/scripts/lib/workflow-runtime.js +102 -0
  41. package/hooks/scripts/state-persist.js +1 -0
  42. package/hooks/scripts/workflow-disposition.js +97 -0
  43. package/hooks/scripts/workflow-gate.js +53 -0
  44. package/hooks/scripts/workflow-stop-gate.js +44 -0
  45. package/opencode-plugin/plugin.json +1 -1
  46. package/package.json +2 -2
  47. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  48. package/plugins/harness-everything/hooks/hooks.json +20 -1
  49. package/plugins/harness-everything/hooks/scripts/contract-test.js +24 -2
  50. package/plugins/harness-everything/hooks/scripts/lib/workflow-isolation.js +102 -0
  51. package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +102 -0
  52. package/plugins/harness-everything/hooks/scripts/state-persist.js +1 -0
  53. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +97 -0
  54. package/plugins/harness-everything/hooks/scripts/workflow-gate.js +53 -0
  55. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +44 -0
  56. package/plugins/harness-everything/plugin.json +1 -1
  57. package/plugins/harness-everything/scripts/lib/advisory-text.js +53 -54
  58. package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +3 -1
  59. package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
  60. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
  61. package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
  62. package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
  63. package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
  64. package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +3 -1
  65. package/plugins/harness-everything/skills/harness-everything/SKILL.md +17 -23
  66. package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +81 -98
  67. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +263 -0
  68. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +97 -212
  69. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +612 -0
  70. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +21 -581
  71. package/plugins/harness-everything/skills/using-git-worktrees/SKILL.md +14 -16
  72. package/plugins/harness-everything/skills/using-git-worktrees/references/workflow-details.md +44 -85
  73. package/scripts/lib/advisory-text.js +53 -54
  74. package/using-git-worktrees/SKILL.md +14 -16
  75. package/using-git-worktrees/references/workflow-details.md +44 -85
@@ -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
+ }
@@ -14,9 +14,6 @@ const PORTABLE_MANIFEST = JSON.parse(fs.readFileSync(path.join(PLUGIN, 'plugin.j
14
14
 
15
15
  function sha256(file) {
16
16
  const bytes = fs.readFileSync(file);
17
- // Git stores text blobs with LF, while a Windows checkout may materialize
18
- // canonical files with CRLF. Package parity compares canonical content, not
19
- // platform checkout EOL bytes.
20
17
  const normalized = bytes.includes(0)
21
18
  ? bytes
22
19
  : Buffer.from(bytes.toString('utf8').replace(/\r\n/g, '\n'), 'utf8');
@@ -138,6 +135,8 @@ assert.ok(hooks.hooks.Stop?.length, 'Stop hook missing');
138
135
  const hookText = JSON.stringify(hooks);
139
136
  assert.match(hookText, /PLUGIN_ROOT/, 'plugin hooks must resolve from PLUGIN_ROOT');
140
137
  assert.match(hookText, /commandWindows/, 'plugin hooks need a Windows command override');
138
+ assert.match(hookText, /workflow-gate\.js/, 'plugin hooks must package workflow bypass enforcement');
139
+ assert.match(hookText, /workflow-stop-gate\.js/, 'plugin hooks must package workflow completion enforcement');
141
140
 
142
141
  const session = spawnSync(process.execPath, [path.join(PLUGIN, 'hooks', 'session-start.js')], { encoding: 'utf8' });
143
142
  assert.strictEqual(session.status, 0, session.stderr);
@@ -164,14 +163,22 @@ assert.strictEqual(second.status, 0, second.stderr);
164
163
  assert.strictEqual(first.stdout, second.stdout, 'identical routing input must produce identical output');
165
164
  assert.match(first.stdout, /RECOMMENDED TIER:\s*Tier 2/i);
166
165
  assert.match(first.stdout, /REQUIRED HARNESS INVARIANTS/);
167
- assert.match(first.stdout, /SUGGESTED SKILLS \(ADVISORY/);
166
+ assert.match(first.stdout, /WORKFLOW SKILLS \(EVALUATE APPLICABILITY .*SELECTED WORKFLOW IS MANDATORY\)/);
167
+ assert.match(first.stdout, /evaluate-suggestions-before-skip/);
168
+ assert.match(first.stdout, /read its complete SKILL\.md entry/);
168
169
  assert.doesNotMatch(first.stdout, /BASE EXECUTION LOOP/);
169
- assert.match(first.stdout, /Do not enforce workflow order/);
170
+ assert.match(first.stdout, /ORCHESTRATION POLICY: Mandatory applicable workflow/);
171
+ assert.match(first.stdout, /WORKFLOW EXECUTION CONTRACT \(MANDATORY WHEN SELECTED\)/);
172
+ assert.match(first.stdout, /Selected workflow: iterative-single/);
173
+ assert.match(first.stdout, /Completion requires the workflow obligations and objective verification/);
174
+ assert.doesNotMatch(first.stdout, /execution remains advisory after evaluation/);
170
175
 
171
176
  const trivial = route('Update one README typo');
172
177
  assert.strictEqual(trivial.status, 0, trivial.stderr);
173
178
  assert.match(trivial.stdout, /RECOMMENDED TIER:\s*Tier 1/i);
174
- assert.match(trivial.stdout, /No mandatory domain skill/);
179
+ assert.match(trivial.stdout, /Suggested skills: none/);
180
+ assert.match(trivial.stdout, /No domain skill was suggested/);
181
+ assert.doesNotMatch(trivial.stdout, /evaluate-suggestions-before-skip/);
175
182
 
176
183
  const unmatched = route('frobnicate the quux');
177
184
  assert.strictEqual(unmatched.status, 0, unmatched.stderr);
@@ -179,4 +186,4 @@ assert.match(unmatched.stdout, /RECOMMENDED TIER:\s*Unclassified/i);
179
186
  assert.match(unmatched.stdout, /"tier":"unclassified"/i);
180
187
  assert.doesNotMatch(unmatched.stdout, /RECOMMENDED TIER:\s*Tier 1/i);
181
188
 
182
- console.log(`OpenAI plugin package verified: ${skillNames.length} skills, publication metadata, hooks, marketplace, deterministic routing.`);
189
+ console.log(`OpenAI plugin package verified: ${skillNames.length} skills, publication metadata, hooks, marketplace, deterministic mandatory-workflow routing.`);
@@ -0,0 +1,60 @@
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
+ let failed = 0;
11
+
12
+ function check(condition, message) {
13
+ if (condition) console.log(` PASS ${message}`);
14
+ else { console.error(` FAIL ${message}`); failed++; }
15
+ }
16
+
17
+ function runNode(args, options = {}) {
18
+ return spawnSync(process.execPath, args, { cwd: ROOT, encoding: 'utf8', ...options });
19
+ }
20
+
21
+ console.log('=== Workflow Execution Enforcement ===');
22
+
23
+ for (const rel of [
24
+ 'harness-everything/scripts/kernel-router.js',
25
+ 'harness-everything/scripts/kernel-router-core.js',
26
+ 'hooks/scripts/workflow-gate.js',
27
+ 'hooks/scripts/workflow-stop-gate.js',
28
+ 'hooks/scripts/workflow-disposition.js',
29
+ ]) {
30
+ const result = runNode(['--check', path.join(ROOT, rel)]);
31
+ check(result.status === 0, `${rel} parses as valid JavaScript`);
32
+ }
33
+
34
+ const routed = runNode([
35
+ path.join(ROOT, 'harness-everything/scripts/kernel-router.js'),
36
+ 'audit the entire repository architecture and coordinate multiple modules',
37
+ ]);
38
+ check(routed.status === 0, 'enforced kernel route exits successfully');
39
+ check(routed.stdout.includes('"strategy":"fable-staged"'), 'macro prompt selects fable-staged');
40
+ check(routed.stdout.includes('WORKFLOW EXECUTION CONTRACT (MANDATORY WHEN SELECTED)'), 'wrapper emits mandatory execution contract');
41
+ check(routed.stdout.includes('Execute the selected workflow to resolution'), 'wrapper requires workflow resolution');
42
+ check(routed.stdout.includes('workflow-uncovered-scope'), 'wrapper exposes only explicit evidence-backed escape path');
43
+ check(!routed.stdout.includes('execution remains advisory after evaluation'), 'wrapper removes advisory-execution runtime wording');
44
+
45
+ const disposition = fs.readFileSync(path.join(ROOT, 'hooks/scripts/workflow-disposition.js'), 'utf8');
46
+ check(disposition.includes("'workflow-uncovered-scope'"), 'escape supports workflow-uncovered-scope');
47
+ check(disposition.includes("'host-capability-unavailable'"), 'escape supports host-capability-unavailable');
48
+ check(disposition.includes('generic simple/routine/already-clear reasons are intentionally rejected'), 'generic confidence-based escape reasons are rejected');
49
+ check(disposition.includes('--scope is required') && disposition.includes('--evidence is required') && disposition.includes('--stage-id must name'), 'escape requires a declared stage, scope and evidence');
50
+
51
+ const hooks = JSON.parse(fs.readFileSync(path.join(ROOT, 'hooks/hooks.json'), 'utf8'));
52
+ check((hooks.hooks.PreToolUse || []).some(entry => entry.id === 'harness:pre:workflow-gate'), 'Claude hook wiring includes pre-mutation workflow gate');
53
+ check((hooks.hooks.Stop || []).some(entry => entry.id === 'harness:stop:workflow-completion-gate'), 'Claude hook wiring includes workflow completion gate');
54
+
55
+ const pluginHooks = JSON.parse(fs.readFileSync(path.join(ROOT, 'plugins/harness-everything/hooks/hooks.json'), 'utf8'));
56
+ check((pluginHooks.hooks.PreToolUse || []).some(entry => (entry.hooks || []).some(hook => /workflow-gate\.js/.test(hook.command || ''))), 'OpenAI plugin hook package carries workflow gate adapter');
57
+ check((pluginHooks.hooks.Stop || []).some(entry => (entry.hooks || []).some(hook => /workflow-stop-gate\.js/.test(hook.command || ''))), 'OpenAI plugin hook package carries workflow completion adapter');
58
+
59
+ console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'}: workflow execution enforcement (${failed} failure${failed === 1 ? '' : 's'})`);
60
+ process.exit(failed === 0 ? 0 : 1);