harness-everything 0.41.1 → 0.41.3

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.
@@ -10,7 +10,7 @@
10
10
  "name": "harness-everything",
11
11
  "source": "./",
12
12
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
13
- "version": "0.41.1",
13
+ "version": "0.41.3",
14
14
  "author": {
15
15
  "name": "Miya Daniel"
16
16
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-everything",
3
3
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
4
- "version": "0.41.1",
4
+ "version": "0.41.3",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
@@ -36,9 +36,9 @@ jobs:
36
36
  - uses: actions/checkout@v7
37
37
  - uses: actions/setup-node@v7
38
38
  with:
39
- node-version: '24.20.0'
39
+ node-version: '24.22.1'
40
40
  - name: Verify exact patched runtime
41
- run: node -e "if (process.versions.node !== '24.20.0') process.exit(1)"
41
+ run: node -e "if (process.versions.node !== '24.22.1') process.exit(1)"
42
42
  - name: Exercise real evaluator subprocess and repeated resident teardown
43
43
  run: node --test ci/mechanism-system-one-resident.test.js
44
44
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.41.3](https://github.com/dyphn1/Harness-everything/compare/v0.41.2...v0.41.3) (2026-10-25)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **codex:** drop format-bound task approvals ([7125220](https://github.com/dyphn1/Harness-everything/commit/71252205e469cda13744a733ff2dca6bbff5db47))
10
+
11
+ ## [0.41.2](https://github.com/dyphn1/Harness-everything/compare/v0.41.1...v0.41.2) (2026-10-25)
12
+
13
+ ### Bug Fixes
14
+
15
+ * **codex:** bind explicit action approvals ([eeec25c](https://github.com/dyphn1/Harness-everything/commit/eeec25c204bae89fa293e77a1d7de9e8f7135e13))
16
+
5
17
  ## [0.41.1](https://github.com/dyphn1/Harness-everything/compare/v0.41.0...v0.41.1) (2026-10-23)
6
18
 
7
19
  ### Bug Fixes
@@ -273,6 +273,30 @@ try {
273
273
  check(empty.degraded === false && empty.rules.length === 0, 'valid empty rule table does not silently activate built-in defaults');
274
274
  check(actionGate.evaluatePreToolUse(payload('git push --force origin main', { toolUseId: 'empty' }), { host: 'claude', sessionDir, ruleTable: empty }).kind === 'allow', 'empty-table mutation control disables detection, proving production non-empty assertion is meaningful');
275
275
 
276
+ // Independent full-input fingerprint regression: an own JSON __proto__ key must
277
+ // remain part of the structured payload digest (null-prototype stableValue fix).
278
+ const protoInput = JSON.parse('{"__proto__":{"changed":true},"record_id":"record-123"}');
279
+ const protoPayload = {
280
+ session_id: 'phase5-test',
281
+ hook_event_name: 'PreToolUse',
282
+ tool_name: 'mcp__demo__delete_record',
283
+ tool_use_id: 'proto-fingerprint',
284
+ tool_input: protoInput,
285
+ cwd: ROOT,
286
+ };
287
+ const basePayload = {
288
+ session_id: 'phase5-test',
289
+ hook_event_name: 'PreToolUse',
290
+ tool_name: 'mcp__demo__delete_record',
291
+ tool_use_id: 'proto-fingerprint',
292
+ tool_input: { record_id: 'record-123' },
293
+ cwd: ROOT,
294
+ };
295
+ const protoText = actionGate.exactPayloadText(protoPayload);
296
+ const baseText = actionGate.exactPayloadText(basePayload);
297
+ check(protoText.includes('__proto__'), 'null-prototype serialization preserves an own __proto__ key');
298
+ check(actionGate.hashExact(protoText) !== actionGate.hashExact(baseText), 'own __proto__ key changes the full-input digest');
299
+
276
300
  const execPayload = payload('npm publish', { toolUseId: 'exec-ok' });
277
301
  actionGate.evaluatePreToolUse(execPayload, { host: 'claude', sessionDir, ruleTable: table });
278
302
  const execResult = actionGate.processEvent({ ...execPayload, hook_event_name: 'PostToolUse' }, { sessionDir });
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // RED contract: Codex UserPromptSubmit must pass every prompt, including the
5
+ // former exact `HARNESS APPROVE <id>` spelling, to the normal router. The hook
6
+ // is transport, not a natural-language authorization parser.
7
+ const assert = require('assert');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+ const { spawnSync } = require('child_process');
12
+
13
+ const ROOT = path.resolve(__dirname, '..');
14
+ const SOURCE_HOOK = path.join(ROOT, 'hooks', 'scripts', 'codex-user-prompt.js');
15
+
16
+ let failed = 0;
17
+ function check(condition, message) {
18
+ if (condition) console.log(` PASS ${message}`);
19
+ else { console.error(` FAIL ${message}`); failed++; }
20
+ }
21
+
22
+ // Dependency control: source hook must exist. Child Node is run with the
23
+ // executable that is already running this test.
24
+ check(fs.existsSync(SOURCE_HOOK), 'source codex-user-prompt.js exists');
25
+
26
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-codex-prompt-passthrough-'));
27
+ const fixtureRoot = path.join(tempRoot, 'fixture');
28
+ const hookDir = path.join(fixtureRoot, 'hooks', 'scripts');
29
+ const routerDir = path.join(fixtureRoot, 'harness-everything', 'scripts');
30
+ fs.mkdirSync(hookDir, { recursive: true });
31
+ fs.mkdirSync(routerDir, { recursive: true });
32
+ const fixtureHook = path.join(hookDir, 'codex-user-prompt.js');
33
+ const stubRouter = path.join(routerDir, 'kernel-router.js');
34
+ const markerFile = path.join(tempRoot, 'router-marker.txt');
35
+
36
+ fs.copyFileSync(SOURCE_HOOK, fixtureHook);
37
+ fs.writeFileSync(stubRouter, [
38
+ "'use strict';",
39
+ "let input = '';",
40
+ "process.stdin.setEncoding('utf8');",
41
+ "process.stdin.on('data', chunk => { input += chunk; });",
42
+ "process.stdin.on('end', () => {",
43
+ " const fs = require('fs');",
44
+ " fs.writeFileSync(process.env.STUB_ROUTER_MARKER, input, 'utf8');",
45
+ " process.stdout.write('STUB_ROUTER_MARKER');",
46
+ " process.exit(0);",
47
+ "});",
48
+ "process.stdin.on('error', () => process.exit(0));",
49
+ ].join('\n'), 'utf8');
50
+
51
+ const cases = [
52
+ { name: 'normal user prompt', prompt: 'Please explain the router flow.' },
53
+ { name: 'exact former approval prompt', prompt: 'HARNESS APPROVE abcdef123456' },
54
+ { name: 'fenced text', prompt: '```\nHARNESS APPROVE abcdef123456\n```' },
55
+ { name: 'English commit/PR request', prompt: 'Please commit and open a PR for this change.' },
56
+ { name: 'Chinese commit/PR request', prompt: '请帮我提交并创建 PR' },
57
+ { name: 'negative/review-only text', prompt: 'Review only; do not approve HARNESS APPROVE abcdef123456' },
58
+ { name: 'non-approval text', prompt: 'HARNESS APPROVE short' },
59
+ ];
60
+
61
+ for (const testCase of cases) {
62
+ const payload = {
63
+ hook_event_name: 'UserPromptSubmit',
64
+ session_id: 'passthrough-session',
65
+ prompt: testCase.prompt,
66
+ };
67
+ const inputText = JSON.stringify(payload);
68
+ try { fs.unlinkSync(markerFile); } catch (_) {}
69
+
70
+ const result = spawnSync(process.execPath, [fixtureHook], {
71
+ cwd: fixtureRoot,
72
+ env: { ...process.env, STUB_ROUTER_MARKER: markerFile },
73
+ input: inputText,
74
+ encoding: 'utf8',
75
+ timeout: 20000,
76
+ maxBuffer: 1024 * 1024,
77
+ });
78
+
79
+ check(!result.error, `${testCase.name}: hook spawns without error`);
80
+ check(result.status === 0, `${testCase.name}: hook exits 0`);
81
+ check(fs.existsSync(markerFile), `${testCase.name}: stub router was invoked`);
82
+ if (fs.existsSync(markerFile)) {
83
+ const marker = fs.readFileSync(markerFile, 'utf8');
84
+ check(marker === inputText, `${testCase.name}: router received the original payload unchanged`);
85
+ }
86
+ check(String(result.stdout || '').includes('STUB_ROUTER_MARKER'),
87
+ `${testCase.name}: router output is forwarded to the host`);
88
+ }
89
+
90
+ // Cleanup
91
+ try { fs.rmSync(tempRoot, { recursive: true, force: true }); } catch (_) {}
92
+
93
+ console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'}: codex prompt pass-through (${failed} failure${failed === 1 ? '' : 's'})`);
94
+ process.exit(failed === 0 ? 0 : 1);
@@ -0,0 +1,145 @@
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
+
10
+ const ROOT = path.resolve(__dirname, '..');
11
+ const actionGate = require(path.join(ROOT, 'hooks', 'scripts', 'action-gate.js'));
12
+ const PRE_HOOK = path.join(ROOT, 'hooks', 'scripts', 'codex-action-gate-pre.js');
13
+
14
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-task-git-auth-'));
15
+ const stateHome = path.join(tempRoot, 'state');
16
+ fs.mkdirSync(stateHome, { recursive: true });
17
+
18
+ const env = {
19
+ ...process.env,
20
+ HARNESS_ACTION_GATE_HOST: 'codex',
21
+ HARNESS_STATE_HOME: stateHome,
22
+ HARNESS_ACTION_GATE_STDIN_TIMEOUT_MS: '2000',
23
+ };
24
+
25
+ function run(payload) {
26
+ const result = spawnSync(process.execPath, [PRE_HOOK], {
27
+ cwd: tempRoot,
28
+ env,
29
+ input: JSON.stringify(payload),
30
+ encoding: 'utf8',
31
+ timeout: 20000,
32
+ maxBuffer: 1024 * 1024,
33
+ });
34
+ assert.ifError(result.error);
35
+ return result;
36
+ }
37
+
38
+ function payload(toolName, toolInput, extra = {}) {
39
+ return {
40
+ hook_event_name: 'PreToolUse',
41
+ session_id: 'task-git-auth-codex-session',
42
+ turn_id: 'turn-task-git-auth',
43
+ tool_use_id: `call-${Math.random().toString(16).slice(2)}`,
44
+ tool_name: toolName,
45
+ tool_input: toolInput,
46
+ permission_mode: 'default',
47
+ cwd: tempRoot,
48
+ ...extra,
49
+ };
50
+ }
51
+
52
+ const actualPrPayload = payload('mcp__codex_apps__github__create_pull_request', {
53
+ repository_full_name: 'openai/codex',
54
+ base: 'main',
55
+ head: 'feature/task-authorization',
56
+ title: 'Phase complete PR',
57
+ body: '## Summary\n\nPhase complete PR created after task authorization.',
58
+ draft: true,
59
+ });
60
+
61
+ try {
62
+ assert.ok(fs.existsSync(PRE_HOOK), 'Codex PreToolUse entrypoint must exist');
63
+
64
+ // Read control on the real Codex path.
65
+ const readResult = run(payload('mcp__demo__get_pull_request', { pull_request: 1 }));
66
+ assert.strictEqual(readResult.status, 0, 'read-only structured action must pass on Codex');
67
+ assert.strictEqual(String(readResult.stdout || '').trim(), '', 'read control must not emit a Harness permission decision');
68
+
69
+ // Dangerous controls remain hard Codex blocks.
70
+ for (const [toolName, toolInput] of [
71
+ ['mcp__demo__merge_pull_request', { id: 'target' }],
72
+ ['mcp__demo__delete_pull_request', { id: 'target' }],
73
+ ['mcp__demo__deploy_release', { id: 'target' }],
74
+ ['mcp__demo__publish_package', { id: 'target' }],
75
+ ['mcp__demo__send_message', { id: 'target' }],
76
+ ]) {
77
+ const result = run(payload(toolName, toolInput));
78
+ assert.strictEqual(result.status, 2, `${toolName} must remain a Codex block`);
79
+ }
80
+ const forcePush = run(payload('Bash', { command: 'git push --force origin main' }));
81
+ assert.strictEqual(forcePush.status, 2, 'git push --force must remain a Codex block');
82
+
83
+ // Normal non-destructive commit keeps its existing ungated behavior.
84
+ const normalCommit = run(payload('Bash', { command: 'git commit -m "phase complete"' }));
85
+ assert.strictEqual(normalCommit.status, 0, 'normal git commit must remain ungated on Codex');
86
+
87
+ // Misleading names containing create_pull_request must not inherit the exemption.
88
+ for (const toolName of [
89
+ 'mcp__demo__create_pull_request_and_delete',
90
+ 'mcp__demo__delete_create_pull_request',
91
+ 'mcp__demo__create_pull_requests',
92
+ 'mcp__demo__foo_create_pull_request',
93
+ ]) {
94
+ const result = run(payload(toolName, { id: 'target' }));
95
+ assert.strictEqual(result.status, 2, `${toolName} must remain a Codex block`);
96
+ }
97
+
98
+ // Explicit required gate is carried in the supported payload hint shape
99
+ // (payload.workflowPlan.actionGate — the shape routerActionGateHint reads),
100
+ // not a made-up options API.
101
+ const requiredDecision = actionGate.evaluatePreToolUse({
102
+ ...actualPrPayload,
103
+ tool_use_id: 'call-required-gate-witness',
104
+ workflowPlan: { actionGate: { required: true, reasonCodes: ['external-side-effect'], disposition: 'pending-approval' } },
105
+ }, {
106
+ host: 'codex',
107
+ ruleTable: actionGate.loadRuleTable(path.join(ROOT, 'hooks', 'scripts', 'action-gate-rules.json')),
108
+ });
109
+ assert.strictEqual(requiredDecision.kind, 'block', 'required-gate PR must remain gated');
110
+ assert.strictEqual(requiredDecision.exitCode, 2, 'required-gate PR must remain a Codex block');
111
+ assert.ok(requiredDecision.rule, 'required-gate PR must match a gated rule');
112
+ assert.strictEqual(requiredDecision.rule.id, 'structured-mutate', 'required-gate PR must keep structured-mutate');
113
+
114
+ // Recognized PR creation is a routine host-deferred operation. Its descriptor
115
+ // stays a mutation; it is not read or safe, and it must not use Harness consent.
116
+ const descriptor = actionGate.normalizeActionDescriptor(actualPrPayload);
117
+ assert.strictEqual(descriptor.action, 'mutate', 'recognized PR creation must remain classified as a mutation');
118
+ assert.notStrictEqual(descriptor.classification, 'safe', 'recognized PR creation must not be safe/read');
119
+ const prResult = run(actualPrPayload);
120
+ assert.strictEqual(prResult.status, 0, 'recognized PR creation must be host-deferred on Codex (currently exits 2: RED)');
121
+ assert.strictEqual(String(prResult.stdout || '').trim(), '', 'recognized PR creation must not emit permissionDecision allow/ask');
122
+ assert.ok(!/HARNESS APPROVE/i.test(`${prResult.stdout || ''}\n${prResult.stderr || ''}`), 'recognized PR creation must not require a Harness approval phrase');
123
+
124
+ // Provider-neutral exact create_pull_request operation, if used, has the same contract.
125
+ const neutralResult = run(payload('mcp__demo__create_pull_request', {
126
+ repository: 'acme/harness',
127
+ head: 'feature/task-authorization',
128
+ base: 'main',
129
+ title: 'Phase complete PR',
130
+ body: 'Provider-neutral PR body.',
131
+ }));
132
+ assert.strictEqual(neutralResult.status, 0, 'exact create_pull_request operation must be host-deferred on Codex (currently exits 2: RED)');
133
+
134
+ // Genuine mutations remain blocked, but the fixed-format approval protocol is retired.
135
+ const legacyMutation = run(payload('mcp__demo__update_issue', { issue_id: 1, title: 'changed' }));
136
+ assert.strictEqual(legacyMutation.status, 2, 'genuine mutation must remain blocked on Codex');
137
+ assert.ok(!/HARNESS APPROVE/i.test(`${legacyMutation.stdout || ''}\n${legacyMutation.stderr || ''}`), 'genuine mutation must not expose the retired Harness approval phrase');
138
+
139
+ console.log('PASS: task git authorization Codex controls and host-deferred PR creation');
140
+ } catch (err) {
141
+ console.error(err && err.stack ? err.stack : err);
142
+ process.exitCode = 1;
143
+ } finally {
144
+ try { fs.rmSync(tempRoot, { recursive: true, force: true }); } catch (_) {}
145
+ }
@@ -124,3 +124,32 @@ Current Harness PreToolUse enforcement remains wired to the tool set explicitly
124
124
  - Codex configuration reference: https://developers.openai.com/codex/config-reference
125
125
 
126
126
  Related tracking: #82, #85, #98, #100, #109, #110.
127
+
128
+ ## Prompt handling and authorization
129
+
130
+ Codex `UserPromptSubmit` prompts pass through to the normal router unchanged.
131
+ Harness does not parse, bind, or intercept any fixed approval syntax in user
132
+ prompts. Native agent task-scope authorization and native permissions remain
133
+ authoritative.
134
+
135
+ Routine exact `create_pull_request` operations are host-deferred (below). All
136
+ other risky actions (merge/delete/deploy/publish/send/force-push and unrelated
137
+ genuine mutations) remain hard PreToolUse blocks; an explicitly required
138
+ `actionGate` hint still gates. These are deterministic mechanism changes, not
139
+ live-host proof.
140
+
141
+ ## Routine task Git operations (host-deferred)
142
+
143
+ Ordinary exact `create_pull_request` operations (including
144
+ `mcp__codex_apps__github__create_pull_request` and provider-neutral
145
+ `mcp__*__create_pull_request`) are host-deferred: Harness does not issue an
146
+ additional challenge merely because the generic `create` heuristic classifies
147
+ them as `mutate`. Their action descriptor stays `mutate` and non-safe; Harness
148
+ does not pretend a write is a read or emit `permissionDecision` allow/ask.
149
+
150
+ Native agent task-scope authorization and native permissions remain authoritative.
151
+ An explicitly supplied required `actionGate` hint still gates the call. All other
152
+ risky actions (merge/delete/deploy/publish/send/force-push and unrelated genuine
153
+ mutations) remain gated; exact operation matching is used, with no substring
154
+ exemption, so misleading names such as `create_pull_requests` or
155
+ `foo_create_pull_request` still block.
@@ -5,13 +5,13 @@ status: active
5
5
 
6
6
  # Document workflow reconciliation — Phase 3b S1 runtime
7
7
 
8
- **Status: S1 runtime ships default OFF; opt in with `HARNESS_DOC_RECONCILIATION_PILOT=1`.
9
- S1 grants no positive document verification credit. S2/S3 are pending; E1 has not started.**
10
- This document refines the [document lifecycle contract](document-lifecycle.md#phase-3b--narrow-integration-contract-s1-shipped-default-off-positive-reconciliation-not-shipped).
8
+ **Status: the runtime ships default OFF; opt in with `HARNESS_DOC_RECONCILIATION_PILOT=1`.
9
+ With the pilot ON, F4 ([#358](https://github.com/dyphn1/Harness-everything/pull/358)) grants limited positive credit for exactly `doc-lint` and `link-check`, from a host-captured run of the installed doc-check on Claude Code 2.1.309 (live runs 4–5). S3 provider integration is pending; E1 has not started.**
10
+ This document refines the [document lifecycle contract](document-lifecycle.md#phase-3b--narrow-integration-contract-s1-default-off-f4-limited-credit).
11
11
  The [accepted E0](https://github.com/dyphn1/Harness-everything/issues/341#issuecomment-6308604128)
12
12
  permits this limited, reversible consumer under the
13
13
  [owner's E0/E1 decision](https://github.com/dyphn1/Harness-everything/issues/325#issuecomment-6283237637).
14
- Current evidence establishes deterministic mechanism/package behavior, not live-host loading or E1 GO.
14
+ Current evidence establishes deterministic mechanism/package behavior and bounded Claude Code 2.1.309 live credit runs; it is not E1 GO.
15
15
 
16
16
  ## Entry gates and ownership
17
17
 
@@ -336,12 +336,12 @@ Preparation assertion rows do not carry `reportOrigin`; the S1 runtime report ca
336
336
  marker is never presented as a production report.
337
337
  ## S1 — switch, trusted scope and fail-closed no-credit gate
338
338
 
339
- **S1 runtime ships default OFF (`HARNESS_DOC_RECONCILIATION_PILOT=1` opts in). It grants no positive document verification credit; S2/S3 are pending and E1 has not started.**
339
+ **S1 runtime ships default OFF (`HARNESS_DOC_RECONCILIATION_PILOT=1` opts in). With the pilot ON, F4 (#358) grants limited positive credit for `doc-lint` and `link-check` only; S3 provider integration is pending and E1 has not started.**
340
340
  The [accepted E0](https://github.com/dyphn1/Harness-everything/issues/341#issuecomment-6308604128)
341
341
  permits this limited implementation; the [posted execution plan](https://github.com/dyphn1/Harness-everything/issues/325)
342
- orders Docs → RED → GREEN → independent review → green CI for each slice. S1 is a temporary
343
- negative-credit slice. S2 supplies trusted producer records, independent review provenance,
344
- positive reconciliation and freshness; only then can E1 observation begin. S3 adds optional
342
+ orders Docs → RED → GREEN → independent review → green CI for each slice. S1 was the temporary
343
+ negative-credit slice; F4 added the limited positive credit. The S2 requirements (trusted producer records, independent review provenance,
344
+ positive reconciliation and freshness) gate E1 observation. S3 adds optional
345
345
  provider suggestions. None of these slices authorizes wider #320 integration.
346
346
 
347
347
  ### Switch and scope decision
@@ -1080,8 +1080,8 @@ provider, discovery execution, public checker change or new attestation service/
1080
1080
  ## Implementation and acceptance after E0
1081
1081
 
1082
1082
  S1 ships the default-OFF switch, narrow disposition/completion no-credit integration and runtime
1083
- status reporting. S2's trusted producer, positive reconciliation and freshness remain pending;
1084
- S3's optional provider adapter remains pending. Require every expected applicable MUST and
1083
+ status reporting. F4 adds limited positive reconciliation behind the same switch. S3's optional
1084
+ provider adapter remains pending. Require every expected applicable MUST and
1085
1085
  freshness check before positive credit, plus source/package compatibility, negative cases and
1086
1086
  relevant green CI including Windows. The S1 suites establish their tested mechanism/package
1087
1087
  contracts; they do not establish independent host provenance, live-host loading or architectural
@@ -1090,7 +1090,7 @@ remain separate gates.
1090
1090
 
1091
1091
  ## F4: credited reconciliation (Claude-only, pilot ON)
1092
1092
 
1093
- Status: **implemented behind the pilot switch; mechanism and package evidence, plus one live field-availability capture.** Credit works for `doc-lint` and `link-check` (a registered `doc-check` run) and for an independent `quality-review` (a host reviewer with bound path digests). One live capture (Claude Code 2.1.308, macOS, one session) proves field availability, not live-host credit. There is no live-host credit run yet. Deterministic mechanism tests prove the chain. Design source: the A3 advisor report (`.worktrees/packets/feat-path/A3-advisor-report.md`, a local coordination file, not a shipped doc).
1093
+ Status: **implemented behind the pilot switch; mechanism and package evidence, plus live credit runs on Claude Code 2.1.309 (runs 4–5), which credited exactly `doc-lint` and `link-check`.** Mechanism tests cover credit for `doc-lint` and `link-check` (a registered `doc-check` run) and for an independent `quality-review` (a host reviewer with bound path digests). The earlier Claude Code 2.1.308 capture (macOS, one session) proves field availability only. Deterministic mechanism tests prove the chain. Design source: the A3 advisor report (`.worktrees/packets/feat-path/A3-advisor-report.md`, a local coordination file, not a shipped doc).
1094
1094
 
1095
1095
  ### Whole path
1096
1096
 
@@ -1200,8 +1200,8 @@ claude -p "<fixed capture prompt, containing the exact check command>" \
1200
1200
  ### Scope and limits
1201
1201
 
1202
1202
  - The first positive scope is a body-only edit to an existing governed reference, with no derived declaration, code, test, PRD or ADR normative change. Its applicable MUSTs are lint and links.
1203
- - Simulated replay and one live field capture are not E1 GO, and they do not permit a wider rollout.
1204
- - Not claimed: cryptographic attestation, a sandbox, a forced review provider, default-ON, live-host behaviour beyond the capture, or support for OpenCode or Codex.
1203
+ - Simulated replay and the recorded live credit runs are not E1 GO, and they do not permit a wider rollout.
1204
+ - Not claimed: cryptographic attestation, a sandbox, a forced review provider, default-ON, live-host behaviour beyond the recorded runs, or support for OpenCode or Codex.
1205
1205
 
1206
1206
  **Executor rationale.** The owner's text (#325, 2026-10-21) says the reviewer "must differ from the author's `session_id` and from every executor principal". An executor principal is the principal of execution evidence used in this assessment. A reviewer that read or computed digests with plain tool calls is not an executor. A reviewer whose registered doc-check run is refused still counts, which keeps the rule fail-closed.
1207
1207
 
@@ -15,19 +15,21 @@ This table distinguishes merged mechanism code from end-to-end workflow integrat
15
15
  | 2 — roles, IDs, aliases, legacy, supersession, index, D6.9 PRD revisions | **Merged** in #327; do not rebuild this lifecycle logic for Phase 3 |
16
16
  | Step-type prerequisite (#326) | **Complete:** PR #328 merged; Harness-owned `doc-change` is explicitly declared, has no implicit TDD binding, and is not auto-inserted by router/System One |
17
17
  | 3a — document facts, obligations and evidence producer | **Merged** in #334: `harness-everything/scripts/doc-obligations.js` and `doc-evidence.js`. H3/H4 have *partial deterministic mechanism* coverage, not integrated workflow evidence. Known limitations: caller-attested envelope; `configDigest` presence-only; `candidateDigest` restricted to `docs/prd/`; missing independent task-scope binding. Deleted and old rename paths are bound to trusted per-path baseline evidence ([doc-evidence-baseline-binding.md](doc-evidence-baseline-binding.md), #335 fixed by #342), with workflow-disposition reconciliation left out of scope |
18
- | 3b — pilot-local provider suggestions and workflow evidence reconciliation | **S1 runtime ships default OFF** (opt in with `HARNESS_DOC_RECONCILIATION_PILOT=1`), with trusted scope assessment and fail-closed withholding; **no positive document verification credit**. See [S1 switch, trusted scope and no-credit contract](doc-workflow-reconciliation.md#s1--switch-trusted-scope-and-fail-closed-no-credit-gate). **S3 pure advisory provider suggestions shipped in 0.37.0 (not wired); S2 trusted producer/positive reconciliation/freshness is pending; E1 has not started.** Accepted #341 E0 permits this limited reversible consumer. Current evidence is deterministic mechanism/package coverage, not live-host loading or E1 GO; preserve existing required-binding compatibility |
18
+ | 3b — pilot-local provider suggestions and workflow evidence reconciliation | **S1 runtime ships default OFF** (opt in with `HARNESS_DOC_RECONCILIATION_PILOT=1`), with trusted scope assessment and fail-closed withholding. **F4 (#358) adds limited positive credit** for exactly `doc-lint` and `link-check` from a host-captured run of the installed doc-check (Claude Code only; live runs on 2.1.309). See [S1 switch, trusted scope and no-credit contract](doc-workflow-reconciliation.md#s1--switch-trusted-scope-and-fail-closed-no-credit-gate). S3 pure advisory provider suggestions shipped in 0.37.0 (not wired); E1 has not started. Accepted #341 E0 permits this limited reversible consumer; the live runs are not E1 GO; preserve existing required-binding compatibility |
19
19
  | 3c — H1–H4 and paired-task evaluation | **Not started.** Mechanism tests alone are insufficient for architecture promotion; compare frozen baseline and treatment on real bounded tasks, success/failure first, then conditional cost and latency |
20
- | 1b — shared documentation craft and family metadata | **Splittable.** Shared references and thin skill consumers can proceed independently. A small optional logical-family metadata seam may be piloted; physical skill directory moves remain blocked on #320 identity/path separation |
21
- | 4 — derived-view generators and `describes:` drift | **Not started.** Preserve generator authorization, deterministic regenerate-equals and scoped reminder semantics |
20
+ | 1b — shared documentation craft and family metadata | **Shared craft delivered** in F2 (#356): shared references with thin skill consumers. Family metadata is not established. A small optional logical-family metadata seam may be piloted; physical skill directory moves remain blocked on #320 identity/path separation |
21
+ | 4 — derived-view generators and `describes:` drift | **Regenerate-equals delivered** in F3 (#357) for authorized generators; `describes:` drift reminders and broader maintenance are not established. Preserve generator authorization, deterministic regenerate-equals and scoped reminder semantics |
22
22
 
23
- ### Phase 3b — narrow integration contract (S1 shipped default OFF; positive reconciliation not shipped)
23
+ <a id="phase-3b--narrow-integration-contract-s1-shipped-default-off-positive-reconciliation-not-shipped"></a>
24
+
25
+ ### Phase 3b — narrow integration contract (S1 default OFF; F4 limited credit)
24
26
 
25
27
  The preparation design, test-only provider/trust contracts, and qualified RED replay are complete:
26
28
  [Document workflow reconciliation](doc-workflow-reconciliation.md),
27
29
  [case corpus](../ci/fixtures/doc-obligations/reconciliation-prep/cases.json),
28
30
  [pending-RED registry](../ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json), and
29
31
  [preparation suite](../ci/mechanism-325-3b-preparation.test.js). #341 E0 is accepted; S1 ships the
30
- default-OFF no-credit gate only. Positive reconciliation (S2) remains unshipped.
32
+ default-OFF gate, and F4 (#358) adds limited positive credit for `doc-lint` and `link-check` behind the same switch. S3 provider integration remains pending.
31
33
 
32
34
  - **Inputs:** explicit active `doc-change` step, D6.5 diff facts, Phase 3a obligations/evidence, available provider *metadata*, and host capabilities.
33
35
  - **Entry and activation gates (#341):** parallel preparatory work — provider metadata, adapter/interface design and RED fixtures — may proceed while #341 Phase 0A–0C runs, but the **runtime** Evidence Reconciliation must not merge or activate until **#341 E0** is reviewed and accepted (initial evidence read/binding/outcome conformance, mutation and read-order compatibility, source/package parity, zero false MUST PASS on applicable adversarial cases, and applicable green CI including Windows). After E0, only the **bounded, opt-in, reversible** documentation consumer is eligible for runtime integration at the existing verification/disposition path (no second router, forced provider, new lock, or weakened authorization, with a documented rollback), and it supplies the observed exposure for **#341 E1** (initial target 2–3 genuine development/review cycles with sufficient case mix, not elapsed time). **#341 E1 GO / REVISE / NO-GO gates expansion beyond the limited pilot, not the pilot's own observation** (missing or unrepresentative exposure is `UNCHECKED`, never an automatic GO). The H1–H4 paired-task architectural gate below stays required for any #320 promotion in addition to E1. Shared evidence-read/binding/decision defects route to #341 (fixture-first correction, regression replay, re-observation); documentation policy and the trusted host/VCS producer remain #325 deliverables here.
@@ -25,6 +25,15 @@ The repository routing contract uses **step-scoped workflow selection**. Normali
25
25
 
26
26
  This is a **repository/agent contract**, not an automatic upgrade to any platform row below. A hook can prove that the contract was injected and record an agent's binding disposition; neither proves that a live host actually loaded or followed the referenced content. Instruction-only surfaces can carry the same rule without mechanically enforcing it. Promoting behavior to live-host compliance requires retained host/session evidence under the #82 compatibility program. Claude and local OpenAI packages carry workflow entry/completion **reminders** plus separate Rule-of-3/permission boundaries, but this package/mechanism change does not upgrade `platform-compatibility.json` live-host status values. See [workflow runtime](workflow-runtime.md) for scope, worktree isolation, and failure behavior. OpenCode and instruction-only/public Skills-only surfaces do not acquire equivalent runtime gating. Shell inspection is not a filesystem sandbox; absent hooks, agent-writable state, and indirect script effects remain limits.
27
27
 
28
+ ### Action-gate mechanism vs. host semantic boundary
29
+
30
+ Routine exact `create_pull_request` operations are host-deferred at the mechanism level:
31
+ their descriptor remains a mutation (not read/safe), and no Harness consent or
32
+ `permissionDecision` is emitted. Native agent task-scope authorization and native
33
+ permissions stay authoritative; an explicit required `actionGate` hint still gates.
34
+ This is a deterministic mechanism change, not live-host proof, and does not
35
+ upgrade any platform row's live-host status.
36
+
28
37
  ## Current capability summary
29
38
 
30
39
  | Platform surface | Standalone skills | Plugin support | Hooks/lifecycle | Project scope | Global scope | Current evidence boundary |
@@ -126,6 +135,18 @@ The compatibility path registers hook commands in Codex user configuration and c
126
135
 
127
136
  The public OpenAI Skills-only plugin is narrower again. Its ZIP contains the packaged `skills/` tree and referenced files, but not the local `.codex-plugin` lifecycle hooks. Public submission readiness is therefore a package/form contract, not evidence of public-directory approval or live hook execution.
128
137
 
138
+ ### Codex prompt pass-through and authorization (mechanism only)
139
+
140
+ Codex `UserPromptSubmit` prompts pass through to the normal router unchanged;
141
+ Harness does not parse or intercept a fixed approval syntax. Native agent
142
+ task-scope authorization and native permissions remain authoritative. Routine
143
+ exact `create_pull_request` operations are host-deferred at the mechanism level:
144
+ their descriptor remains a mutation (not read/safe), and no Harness consent or
145
+ `permissionDecision` is emitted. All other risky actions remain hard PreToolUse
146
+ blocks; an explicitly required `actionGate` hint still gates. These are
147
+ deterministic mechanism changes, not live-host proof. Codex ask-style decisions
148
+ are not supported.
149
+
129
150
  ## Install/uninstall ownership boundary
130
151
 
131
152
  The general installer merges shared files only through Harness-owned markers and removes only manifest-tracked artifacts. Malformed configuration and pre-existing user-owned files fail closed. Install/uninstall round-trip coverage protects user skills and runs on Linux, Windows, and macOS fixtures.
@@ -9,7 +9,6 @@ const {
9
9
  getWorkspaceRoot,
10
10
  getSessionDir,
11
11
  } = require('./lib/harness-state');
12
-
13
12
  const RULES_PATH = process.env.HARNESS_ACTION_GATE_RULES_PATH || path.join(__dirname, 'action-gate-rules.json');
14
13
  const ASK_CAPABLE_HOSTS = new Set(['claude']);
15
14
  const VALID_SCOPES = new Set(['always', 'outside-scratch']);
@@ -52,10 +51,12 @@ function toolInputOf(payload) {
52
51
  function stableValue(value) {
53
52
  if (Array.isArray(value)) return value.map(stableValue);
54
53
  if (!value || typeof value !== 'object') return value;
54
+ // Use a null-prototype accumulator so an own `__proto__` key is stored as data
55
+ // instead of mutating the accumulator's prototype (which would drop it).
55
56
  return Object.keys(value).sort().reduce((acc, key) => {
56
57
  acc[key] = stableValue(value[key]);
57
58
  return acc;
58
- }, {});
59
+ }, Object.create(null));
59
60
  }
60
61
 
61
62
  function exactPayloadText(payload) {
@@ -132,10 +133,25 @@ function normalizeActionDescriptor(payload) {
132
133
  };
133
134
  }
134
135
 
136
+ function isRoutinePullRequestCreation(toolName) {
137
+ const name = String(toolName || '');
138
+ const segments = name.split('__');
139
+ const last = segments[segments.length - 1];
140
+ return (last === 'create_pull_request' || name === 'create_pull_request');
141
+ }
142
+
135
143
  function structuredRule(payload) {
136
144
  const descriptor = normalizeActionDescriptor(payload);
137
145
  if (descriptor.toolFamily === 'shell') return { descriptor, rule: null };
138
146
  if (descriptor.classification === 'safe') return { descriptor, rule: null };
147
+ // Routine PR creation is a host-deferred mutation, not a Harness-gated one:
148
+ // the generic create=>mutate heuristic must not add an extra challenge. The
149
+ // descriptor stays a mutation (not read/safe). An explicit required
150
+ // actionGate hint still gates it.
151
+ const hint = routerActionGateHint(payload);
152
+ if (isRoutinePullRequestCreation(descriptor.toolName) && !(hint && hint.required)) {
153
+ return { descriptor, rule: null };
154
+ }
139
155
  if (descriptor.classification === 'gated') {
140
156
  return {
141
157
  descriptor,
@@ -145,7 +161,6 @@ function structuredRule(payload) {
145
161
  },
146
162
  };
147
163
  }
148
- const hint = routerActionGateHint(payload);
149
164
  if (hint && hint.required) {
150
165
  return {
151
166
  descriptor,
@@ -445,11 +460,17 @@ function recordDecision(payload, rule, table, host, disposition, options = {}, e
445
460
  return record;
446
461
  }
447
462
 
463
+
464
+
465
+
466
+
448
467
  function evaluatePreToolUse(payload, options = {}) {
449
468
  const context = sessionContext(payload, options.sessionDir);
450
469
  const table = options.ruleTable || loadRuleTable(options.rulePath || RULES_PATH);
451
470
  const structured = structuredRule(payload);
452
471
  const rule = structured.rule || classify(payload, table.rules, context.sessionDir);
472
+ const host = detectHost(payload, options.host);
473
+
453
474
  if (!rule) {
454
475
  return {
455
476
  kind: 'allow',
@@ -462,7 +483,6 @@ function evaluatePreToolUse(payload, options = {}) {
462
483
  };
463
484
  }
464
485
 
465
- const host = detectHost(payload, options.host);
466
486
  const reason = decisionReason(rule, table);
467
487
  if (hostSupportsAsk(host) && gatePolicy(options) === ALWAYS_ASK_POLICY) {
468
488
  const record = recordDecision(payload, rule, table, host, 'pending-approval', { ...options, sessionDir: context.sessionDir });
@@ -655,4 +675,5 @@ module.exports = {
655
675
  validateRuleTable,
656
676
  normalizeActionDescriptor,
657
677
  structuredRule,
678
+ isRoutinePullRequestCreation,
658
679
  };
@@ -22,6 +22,14 @@ function finish() {
22
22
  if (finished) return;
23
23
  finished = true;
24
24
  clearTimeout(timer);
25
+
26
+ let payload = null;
27
+ try { payload = JSON.parse(input || '{}'); } catch (_) { payload = null; }
28
+
29
+ // All prompts, including any legacy approval spelling, pass through unchanged
30
+ // to the normal router. Harness does not parse or intercept authorization
31
+ // syntax in user prompts; native agent/task scope and native permissions
32
+ // remain authoritative.
25
33
  if (!ROUTER) process.exit(0);
26
34
  const result = spawnSync(process.execPath, [ROUTER], { input, encoding: 'utf8', env: process.env, timeout: 25000 });
27
35
  const stderr = String(result.stderr || '');
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-enforcement",
3
3
  "description": "Repository inventory for the opencode enforcement plugin and its standalone helper",
4
- "version": "0.41.1",
4
+ "version": "0.41.3",
5
5
  "runtime": "index.mjs",
6
6
  "hooks": {
7
7
  "verify": "hooks/verify.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.41.1",
3
+ "version": "0.41.3",
4
4
  "description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
5
5
  "main": "harness-everything/scripts/bootstrap.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.41.1",
3
+ "version": "0.41.3",
4
4
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
5
5
  "author": {
6
6
  "name": "Miya Daniel",
@@ -9,7 +9,6 @@ const {
9
9
  getWorkspaceRoot,
10
10
  getSessionDir,
11
11
  } = require('./lib/harness-state');
12
-
13
12
  const RULES_PATH = process.env.HARNESS_ACTION_GATE_RULES_PATH || path.join(__dirname, 'action-gate-rules.json');
14
13
  const ASK_CAPABLE_HOSTS = new Set(['claude']);
15
14
  const VALID_SCOPES = new Set(['always', 'outside-scratch']);
@@ -52,10 +51,12 @@ function toolInputOf(payload) {
52
51
  function stableValue(value) {
53
52
  if (Array.isArray(value)) return value.map(stableValue);
54
53
  if (!value || typeof value !== 'object') return value;
54
+ // Use a null-prototype accumulator so an own `__proto__` key is stored as data
55
+ // instead of mutating the accumulator's prototype (which would drop it).
55
56
  return Object.keys(value).sort().reduce((acc, key) => {
56
57
  acc[key] = stableValue(value[key]);
57
58
  return acc;
58
- }, {});
59
+ }, Object.create(null));
59
60
  }
60
61
 
61
62
  function exactPayloadText(payload) {
@@ -132,10 +133,25 @@ function normalizeActionDescriptor(payload) {
132
133
  };
133
134
  }
134
135
 
136
+ function isRoutinePullRequestCreation(toolName) {
137
+ const name = String(toolName || '');
138
+ const segments = name.split('__');
139
+ const last = segments[segments.length - 1];
140
+ return (last === 'create_pull_request' || name === 'create_pull_request');
141
+ }
142
+
135
143
  function structuredRule(payload) {
136
144
  const descriptor = normalizeActionDescriptor(payload);
137
145
  if (descriptor.toolFamily === 'shell') return { descriptor, rule: null };
138
146
  if (descriptor.classification === 'safe') return { descriptor, rule: null };
147
+ // Routine PR creation is a host-deferred mutation, not a Harness-gated one:
148
+ // the generic create=>mutate heuristic must not add an extra challenge. The
149
+ // descriptor stays a mutation (not read/safe). An explicit required
150
+ // actionGate hint still gates it.
151
+ const hint = routerActionGateHint(payload);
152
+ if (isRoutinePullRequestCreation(descriptor.toolName) && !(hint && hint.required)) {
153
+ return { descriptor, rule: null };
154
+ }
139
155
  if (descriptor.classification === 'gated') {
140
156
  return {
141
157
  descriptor,
@@ -145,7 +161,6 @@ function structuredRule(payload) {
145
161
  },
146
162
  };
147
163
  }
148
- const hint = routerActionGateHint(payload);
149
164
  if (hint && hint.required) {
150
165
  return {
151
166
  descriptor,
@@ -445,11 +460,17 @@ function recordDecision(payload, rule, table, host, disposition, options = {}, e
445
460
  return record;
446
461
  }
447
462
 
463
+
464
+
465
+
466
+
448
467
  function evaluatePreToolUse(payload, options = {}) {
449
468
  const context = sessionContext(payload, options.sessionDir);
450
469
  const table = options.ruleTable || loadRuleTable(options.rulePath || RULES_PATH);
451
470
  const structured = structuredRule(payload);
452
471
  const rule = structured.rule || classify(payload, table.rules, context.sessionDir);
472
+ const host = detectHost(payload, options.host);
473
+
453
474
  if (!rule) {
454
475
  return {
455
476
  kind: 'allow',
@@ -462,7 +483,6 @@ function evaluatePreToolUse(payload, options = {}) {
462
483
  };
463
484
  }
464
485
 
465
- const host = detectHost(payload, options.host);
466
486
  const reason = decisionReason(rule, table);
467
487
  if (hostSupportsAsk(host) && gatePolicy(options) === ALWAYS_ASK_POLICY) {
468
488
  const record = recordDecision(payload, rule, table, host, 'pending-approval', { ...options, sessionDir: context.sessionDir });
@@ -655,4 +675,5 @@ module.exports = {
655
675
  validateRuleTable,
656
676
  normalizeActionDescriptor,
657
677
  structuredRule,
678
+ isRoutinePullRequestCreation,
658
679
  };
@@ -22,6 +22,14 @@ function finish() {
22
22
  if (finished) return;
23
23
  finished = true;
24
24
  clearTimeout(timer);
25
+
26
+ let payload = null;
27
+ try { payload = JSON.parse(input || '{}'); } catch (_) { payload = null; }
28
+
29
+ // All prompts, including any legacy approval spelling, pass through unchanged
30
+ // to the normal router. Harness does not parse or intercept authorization
31
+ // syntax in user prompts; native agent/task scope and native permissions
32
+ // remain authoritative.
25
33
  if (!ROUTER) process.exit(0);
26
34
  const result = spawnSync(process.execPath, [ROUTER], { input, encoding: 'utf8', env: process.env, timeout: 25000 });
27
35
  const stderr = String(result.stderr || '');
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "harness-everything",
4
- "version": "0.41.1",
4
+ "version": "0.41.3",
5
5
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
6
6
  "author": {
7
7
  "name": "Miya Daniel",
package/sbom.cdx.json CHANGED
@@ -2,23 +2,23 @@
2
2
  "$schema": "https://cyclonedx.org/schema/bom-1.7.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.7",
5
- "serialNumber": "urn:uuid:c067e2e8-259d-5b73-a1bf-780a0d33156e",
5
+ "serialNumber": "urn:uuid:e4948d5f-cae4-5b4e-84bc-19997b97c420",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-10-23T19:34:50.360Z",
8
+ "timestamp": "2026-10-25T06:53:03.342Z",
9
9
  "tools": [
10
10
  {
11
11
  "vendor": "Harness Everything",
12
12
  "name": "generate-sbom.js",
13
- "version": "0.41.1"
13
+ "version": "0.41.3"
14
14
  }
15
15
  ],
16
16
  "component": {
17
17
  "type": "application",
18
- "bom-ref": "pkg:npm/harness-everything@0.41.1",
18
+ "bom-ref": "pkg:npm/harness-everything@0.41.3",
19
19
  "name": "harness-everything",
20
- "version": "0.41.1",
21
- "purl": "pkg:npm/harness-everything@0.41.1"
20
+ "version": "0.41.3",
21
+ "purl": "pkg:npm/harness-everything@0.41.3"
22
22
  }
23
23
  },
24
24
  "components": [
@@ -2033,7 +2033,7 @@
2033
2033
  ]
2034
2034
  },
2035
2035
  {
2036
- "ref": "pkg:npm/harness-everything@0.41.1",
2036
+ "ref": "pkg:npm/harness-everything@0.41.3",
2037
2037
  "dependsOn": [
2038
2038
  "pkg:npm/%40commitlint/cli@19.8.1",
2039
2039
  "pkg:npm/%40commitlint/config-conventional@19.8.1",