harness-everything 0.8.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.
- 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 +17 -0
- package/README.md +2 -2
- package/benchmarks/results/reviews/pr142-verification.json +95 -0
- package/ci/action-gate.test.js +1 -1
- 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/workflow-execution-contract.test.js +60 -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/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 +3 -1
- package/harness-everything/SKILL.md +11 -10
- package/harness-everything/references/triage-and-tiers.md +76 -107
- package/harness-everything/scripts/kernel-router-core.js +263 -0
- package/harness-everything/scripts/kernel-router.js +97 -225
- package/harness-everything/scripts/router-contract-core.js +612 -0
- package/harness-everything/scripts/router-contract.js +21 -581
- package/hooks/hooks.json +22 -1
- package/hooks/scripts/contract-test.js +24 -2
- package/hooks/scripts/lib/workflow-isolation.js +102 -0
- package/hooks/scripts/lib/workflow-runtime.js +102 -0
- package/hooks/scripts/state-persist.js +1 -0
- package/hooks/scripts/workflow-disposition.js +97 -0
- package/hooks/scripts/workflow-gate.js +53 -0
- package/hooks/scripts/workflow-stop-gate.js +44 -0
- 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 +20 -1
- package/plugins/harness-everything/hooks/scripts/contract-test.js +24 -2
- package/plugins/harness-everything/hooks/scripts/lib/workflow-isolation.js +102 -0
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +102 -0
- package/plugins/harness-everything/hooks/scripts/state-persist.js +1 -0
- package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +97 -0
- package/plugins/harness-everything/hooks/scripts/workflow-gate.js +53 -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/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 +3 -1
- 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/scripts/kernel-router-core.js +263 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +97 -225
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +612 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +21 -581
- 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/using-git-worktrees/SKILL.md +14 -16
- 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,11 +163,15 @@ 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, /
|
|
166
|
+
assert.match(first.stdout, /WORKFLOW SKILLS \(EVALUATE APPLICABILITY .*SELECTED WORKFLOW IS MANDATORY\)/);
|
|
168
167
|
assert.match(first.stdout, /evaluate-suggestions-before-skip/);
|
|
169
168
|
assert.match(first.stdout, /read its complete SKILL\.md entry/);
|
|
170
169
|
assert.doesNotMatch(first.stdout, /BASE EXECUTION LOOP/);
|
|
171
|
-
assert.match(first.stdout, /
|
|
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/);
|
|
172
175
|
|
|
173
176
|
const trivial = route('Update one README typo');
|
|
174
177
|
assert.strictEqual(trivial.status, 0, trivial.stderr);
|
|
@@ -183,4 +186,4 @@ assert.match(unmatched.stdout, /RECOMMENDED TIER:\s*Unclassified/i);
|
|
|
183
186
|
assert.match(unmatched.stdout, /"tier":"unclassified"/i);
|
|
184
187
|
assert.doesNotMatch(unmatched.stdout, /RECOMMENDED TIER:\s*Tier 1/i);
|
|
185
188
|
|
|
186
|
-
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);
|