harness-everything 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +32 -0
- package/bin/cli.js +17 -0
- package/ci/contract-integrity.test.js +155 -1
- package/ci/mechanism-2al-codex-hooks-compat.test.js +286 -0
- package/contract-integrity/scripts/audit.js +225 -2
- package/docs/platform-capabilities.md +16 -0
- package/docs/platform-compatibility.json +4 -4
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +4 -2
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/contract-integrity/scripts/audit.js +225 -2
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/verification-loop/SKILL.md +2 -2
- package/plugins/harness-everything/skills/verification-loop/references/verification-phases.md +9 -1
- package/scripts/codex-hooks-compat.js +521 -0
- package/verification-loop/SKILL.md +2 -2
- package/verification-loop/references/verification-phases.md +9 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const crypto = require('crypto');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const path = require('path');
|
|
7
|
+
const { spawnSync } = require('child_process');
|
|
6
8
|
const { evaluateEvidence } = require('../../tdd/scripts/quality-gate');
|
|
7
9
|
|
|
8
10
|
const VERSION = '1.0.0';
|
|
@@ -57,6 +59,181 @@ function stable(value) {
|
|
|
57
59
|
return Object.fromEntries(Object.keys(value).sort().map(key => [key, stable(value[key])]));
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
function sha256File(file) {
|
|
63
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function realpath(file) {
|
|
67
|
+
return fs.realpathSync.native ? fs.realpathSync.native(file) : fs.realpathSync(file);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function containedPath(root, target) {
|
|
71
|
+
const relative = path.relative(root, target);
|
|
72
|
+
return relative === '' ||
|
|
73
|
+
(relative !== '..' && !relative.startsWith('..' + path.sep) && !path.isAbsolute(relative));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function gitRevision(workspace) {
|
|
77
|
+
if (!workspace) return null;
|
|
78
|
+
const result = spawnSync('git', ['rev-parse', 'HEAD'], {
|
|
79
|
+
cwd: workspace,
|
|
80
|
+
encoding: 'utf8',
|
|
81
|
+
windowsHide: true,
|
|
82
|
+
});
|
|
83
|
+
return result.status === 0 ? String(result.stdout || '').trim() || null : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function artifactFingerprints(trace, workspace) {
|
|
87
|
+
if (!workspace) return [];
|
|
88
|
+
const root = path.resolve(workspace);
|
|
89
|
+
let realRoot;
|
|
90
|
+
try {
|
|
91
|
+
if (fs.lstatSync(root).isSymbolicLink()) throw new Error('linked-root');
|
|
92
|
+
realRoot = realpath(root);
|
|
93
|
+
} catch (_) {
|
|
94
|
+
return (trace?.artifacts || []).map(artifact => ({
|
|
95
|
+
id: artifact.id, path: artifact.path, sha256: null, missing: true, reason: 'workspace-unavailable',
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
return (trace?.artifacts || []).map(artifact => {
|
|
99
|
+
const full = path.resolve(root, artifact.path);
|
|
100
|
+
if (!containedPath(root, full)) {
|
|
101
|
+
return { id: artifact.id, path: artifact.path, sha256: null, missing: true, reason: 'escaped-path' };
|
|
102
|
+
}
|
|
103
|
+
let current = root;
|
|
104
|
+
for (const segment of path.relative(root, full).split(path.sep).filter(Boolean)) {
|
|
105
|
+
current = path.join(current, segment);
|
|
106
|
+
if (!fs.existsSync(current)) {
|
|
107
|
+
return { id: artifact.id, path: artifact.path, sha256: null, missing: true, reason: 'missing' };
|
|
108
|
+
}
|
|
109
|
+
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
110
|
+
return { id: artifact.id, path: artifact.path, sha256: null, missing: true, reason: 'linked-path' };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
let physical;
|
|
114
|
+
try { physical = realpath(full); }
|
|
115
|
+
catch (_) {
|
|
116
|
+
return { id: artifact.id, path: artifact.path, sha256: null, missing: true, reason: 'missing' };
|
|
117
|
+
}
|
|
118
|
+
if (!containedPath(realRoot, physical) || !fs.lstatSync(physical).isFile()) {
|
|
119
|
+
return { id: artifact.id, path: artifact.path, sha256: null, missing: true, reason: 'escaped-path' };
|
|
120
|
+
}
|
|
121
|
+
return { id: artifact.id, path: artifact.path, sha256: sha256File(physical), missing: false };
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildProvenance(args, trace) {
|
|
126
|
+
const workspace = args.workspace ? path.resolve(args.workspace) : null;
|
|
127
|
+
return {
|
|
128
|
+
schemaVersion: 1,
|
|
129
|
+
generatedAt: new Date().toISOString(),
|
|
130
|
+
auditVersion: VERSION,
|
|
131
|
+
nodeVersion: process.version,
|
|
132
|
+
command: 'node contract-integrity/scripts/audit.js <trace> [--tdd-evidence <evidence>] [--workspace <workspace>]',
|
|
133
|
+
gitRevision: gitRevision(workspace || process.cwd()),
|
|
134
|
+
workspaceIsolation: unique((trace?.probes || []).map(probe => probe.workspaceIsolation).filter(Boolean)),
|
|
135
|
+
workspace: workspace ? { root: workspace } : null,
|
|
136
|
+
inputs: {
|
|
137
|
+
trace: { sha256: sha256File(path.resolve(args.input)) },
|
|
138
|
+
tddEvidence: args.tddEvidence ? { sha256: sha256File(path.resolve(args.tddEvidence)) } : null,
|
|
139
|
+
},
|
|
140
|
+
artifacts: artifactFingerprints(trace, workspace),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function verifyFreshReport(report, args, trace, currentAudit = null) {
|
|
145
|
+
const reasons = [];
|
|
146
|
+
if (!isObject(report?.provenance) || !isObject(report.provenance.inputs)) {
|
|
147
|
+
return { fresh: false, authorizesCompletion: false, reasons: ['report-provenance-missing'] };
|
|
148
|
+
}
|
|
149
|
+
const currentTraceHash = sha256File(path.resolve(args.input));
|
|
150
|
+
if (report.provenance.inputs.trace?.sha256 !== currentTraceHash) reasons.push('trace-changed-since-audit');
|
|
151
|
+
const currentTddHash = args.tddEvidence ? sha256File(path.resolve(args.tddEvidence)) : null;
|
|
152
|
+
const reportedTddHash = report.provenance.inputs.tddEvidence?.sha256 || null;
|
|
153
|
+
if (currentTddHash !== reportedTddHash) reasons.push('tdd-evidence-changed-since-audit');
|
|
154
|
+
if (!currentAudit || currentAudit.result !== 'PASS' || currentAudit.completionGate !== 'PASS' || currentAudit.errors?.length) {
|
|
155
|
+
reasons.push('current-audit-not-pass');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const tracedArtifacts = Array.isArray(trace?.artifacts) ? trace.artifacts : [];
|
|
159
|
+
if (tracedArtifacts.length > 0) {
|
|
160
|
+
if (!args.workspace) {
|
|
161
|
+
reasons.push('workspace-required-for-artifact-freshness');
|
|
162
|
+
} else {
|
|
163
|
+
const reportedRoot = report.provenance.workspace?.root;
|
|
164
|
+
if (!reportedRoot) {
|
|
165
|
+
reasons.push('report-workspace-unbound');
|
|
166
|
+
} else if (path.resolve(reportedRoot) !== path.resolve(args.workspace)) {
|
|
167
|
+
reasons.push('workspace-changed-since-audit');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const priorArtifacts = Array.isArray(report.provenance.artifacts) ? report.provenance.artifacts : [];
|
|
171
|
+
const prior = new Map();
|
|
172
|
+
for (const item of priorArtifacts) {
|
|
173
|
+
if (item?.id && !prior.has(item.id)) prior.set(item.id, item);
|
|
174
|
+
else if (item?.id) reasons.push(`artifact-fingerprint-duplicate:${item.id}`);
|
|
175
|
+
}
|
|
176
|
+
const current = new Map(artifactFingerprints(trace, args.workspace).map(item => [item.id, item]));
|
|
177
|
+
for (const artifact of tracedArtifacts) {
|
|
178
|
+
const before = prior.get(artifact.id);
|
|
179
|
+
const now = current.get(artifact.id);
|
|
180
|
+
if (!before || before.path !== artifact.path || before.missing || !before.sha256) {
|
|
181
|
+
reasons.push(`artifact-fingerprint-missing:${artifact.id}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (!now || now.missing || now.sha256 !== before.sha256) {
|
|
185
|
+
reasons.push(`artifact-changed-since-audit:${artifact.id}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (priorArtifacts.length !== tracedArtifacts.length) {
|
|
189
|
+
reasons.push('artifact-fingerprint-set-incomplete');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
fresh: reasons.length === 0,
|
|
195
|
+
authorizesCompletion: reasons.length === 0 && report.completionGate === 'PASS' && report.result === 'PASS',
|
|
196
|
+
reasons: unique(reasons),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function repairGuidance(requirements) {
|
|
201
|
+
const priorityOrder = { BLOCKER: 0, HIGH: 1, MEDIUM: 2 };
|
|
202
|
+
const recommendations = [];
|
|
203
|
+
const add = (priority, req, code, action) => recommendations.push({
|
|
204
|
+
priority,
|
|
205
|
+
requirementId: req.requirementId,
|
|
206
|
+
sourceRef: req.sourceRef || null,
|
|
207
|
+
code,
|
|
208
|
+
action,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
for (const req of requirements) {
|
|
212
|
+
if (req.lifecycleStatus !== 'CURRENT') continue;
|
|
213
|
+
if (req.drift.includes('SOURCE_CONFLICT')) add('BLOCKER', req, 'resolve-source-conflict', 'Resolve the authoritative source conflict before changing tests or implementation.');
|
|
214
|
+
if (req.drift.includes('SOURCE_DEFECT')) add('BLOCKER', req, 'repair-source-defect', 'Repair or explicitly supersede the defective authoritative source before completion.');
|
|
215
|
+
if (req.protection.status === 'SURVIVED') add('BLOCKER', req, 'strengthen-weak-oracle', 'Strengthen the requirement-linked observable assertion so every required contract probe is killed for the expected reason.');
|
|
216
|
+
if (req.drift.includes('STALE_SPEC')) add('HIGH', req, 'update-living-spec', 'Update the living specification and requirement revision before accepting behavior that moved ahead of the contract.');
|
|
217
|
+
if (req.drift.includes('STALE_TEST')) add('HIGH', req, 'refresh-requirement-tests', 'Update requirement-linked tests and RED evidence against the current living specification.');
|
|
218
|
+
if (req.drift.includes('STALE_IMPLEMENTATION')) add('HIGH', req, 'refresh-implementation-evidence', 'Attach implementation evidence for the current requirement revision and rerun verification.');
|
|
219
|
+
if (req.drift.includes('UNTRACED_CHANGE')) add('HIGH', req, 'repair-contract-lineage', 'Restore ADR/spec/ticket/test/implementation lineage for this requirement before completion.');
|
|
220
|
+
if (req.protection.status === 'NOT_EVALUATED') add('HIGH', req, 'execute-required-probes', 'Execute every predeclared required contract probe in an isolated supported adapter or retain NOT_EVALUATED.');
|
|
221
|
+
if (req.completeness.result !== 'PASS' || req.completeness.score < 100) add('HIGH', req, 'complete-tdd-evidence', 'Repair the existing #58 completeness evidence; do not compensate with protection score.');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const seen = new Set();
|
|
225
|
+
return recommendations
|
|
226
|
+
.filter(item => {
|
|
227
|
+
const key = `${item.requirementId}:${item.code}`;
|
|
228
|
+
if (seen.has(key)) return false;
|
|
229
|
+
seen.add(key);
|
|
230
|
+
return true;
|
|
231
|
+
})
|
|
232
|
+
.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority] ||
|
|
233
|
+
String(a.requirementId).localeCompare(String(b.requirementId)) ||
|
|
234
|
+
a.code.localeCompare(b.code));
|
|
235
|
+
}
|
|
236
|
+
|
|
60
237
|
function validateTrace(input) {
|
|
61
238
|
const errors = [];
|
|
62
239
|
if (!isObject(input)) return ['trace root must be an object'];
|
|
@@ -385,6 +562,7 @@ function evaluateTrace(trace, options = {}) {
|
|
|
385
562
|
requirementId: requirement.requirementId,
|
|
386
563
|
revision: requirement.revision,
|
|
387
564
|
lifecycleStatus: requirement.status,
|
|
565
|
+
sourceRef: currentSpec ? `${currentSpec.path}#${requirement.requirementId}` : null,
|
|
388
566
|
drift: unique(statuses),
|
|
389
567
|
reasonCodes: lineage.reasons,
|
|
390
568
|
completeness: quality,
|
|
@@ -429,6 +607,7 @@ function evaluateTrace(trace, options = {}) {
|
|
|
429
607
|
},
|
|
430
608
|
driftCounts,
|
|
431
609
|
requirements: reportRequirements,
|
|
610
|
+
repairGuidance: repairGuidance(reportRequirements),
|
|
432
611
|
graph: {
|
|
433
612
|
nodes: [
|
|
434
613
|
...(trace?.artifacts || []).map(artifact => ({ id: artifact.id, kind: artifact.kind, status: artifact.status, path: artifact.path })),
|
|
@@ -458,9 +637,22 @@ function markdown(report) {
|
|
|
458
637
|
for (const req of report.requirements) {
|
|
459
638
|
lines.push(`| ${req.requirementId} | ${req.revision} | ${req.drift.join(', ')} | ${req.completeness.score} | ${req.protection.status} | ${req.gateEligible ? 'PASS' : 'FAIL'} |`);
|
|
460
639
|
}
|
|
640
|
+
if (report.repairGuidance?.length) {
|
|
641
|
+
lines.push('', '## Repair Guidance', '');
|
|
642
|
+
for (const item of report.repairGuidance) {
|
|
643
|
+
lines.push(`- **${item.priority}** ${item.requirementId}${item.sourceRef ? ` (${item.sourceRef})` : ''}: ${item.action} [${item.code}]`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
461
646
|
if (report.errors.length) {
|
|
462
647
|
lines.push('', '## Errors', '', ...report.errors.map(error => `- ${error}`));
|
|
463
648
|
}
|
|
649
|
+
if (report.provenance) {
|
|
650
|
+
lines.push('', '## Provenance', '');
|
|
651
|
+
lines.push(`- Audit version: ${report.provenance.auditVersion}`);
|
|
652
|
+
lines.push(`- Git revision: ${report.provenance.gitRevision || 'unknown'}`);
|
|
653
|
+
lines.push(`- Trace SHA-256: ${report.provenance.inputs?.trace?.sha256 || 'unknown'}`);
|
|
654
|
+
lines.push(`- TDD evidence SHA-256: ${report.provenance.inputs?.tddEvidence?.sha256 || 'none'}`);
|
|
655
|
+
}
|
|
464
656
|
lines.push('', '## Evidence boundary', '');
|
|
465
657
|
lines.push('- Completeness is derived by the existing #58 evaluator; aggregate scores supplied by agents are ignored.');
|
|
466
658
|
lines.push('- Protection counts only predeclared required probes. Extra/padded probes do not raise a requirement score.');
|
|
@@ -470,13 +662,15 @@ function markdown(report) {
|
|
|
470
662
|
}
|
|
471
663
|
|
|
472
664
|
function parseArgs(argv) {
|
|
473
|
-
const args = { input: null, tddEvidence: null, output: null, markdown: null };
|
|
665
|
+
const args = { input: null, tddEvidence: null, output: null, markdown: null, workspace: null, verifyFresh: null };
|
|
474
666
|
for (let i = 0; i < argv.length; i++) {
|
|
475
667
|
const token = argv[i];
|
|
476
668
|
if (!args.input && !token.startsWith('--')) args.input = token;
|
|
477
669
|
else if (token === '--tdd-evidence') args.tddEvidence = argv[++i];
|
|
478
670
|
else if (token === '--output') args.output = argv[++i];
|
|
479
671
|
else if (token === '--markdown') args.markdown = argv[++i];
|
|
672
|
+
else if (token === '--workspace') args.workspace = argv[++i];
|
|
673
|
+
else if (token === '--verify-fresh') args.verifyFresh = argv[++i];
|
|
480
674
|
else throw new Error(`unknown argument: ${token}`);
|
|
481
675
|
}
|
|
482
676
|
return args;
|
|
@@ -487,7 +681,7 @@ function runCli(argv) {
|
|
|
487
681
|
try { args = parseArgs(argv.slice(2)); }
|
|
488
682
|
catch (error) { console.error(`Contract Integrity: FAIL\n- ${error.message}`); return 2; }
|
|
489
683
|
if (!args.input) {
|
|
490
|
-
console.error('Usage: node contract-integrity/scripts/audit.js <trace.json> [--tdd-evidence <#58-evidence.json>] [--output report.json] [--markdown report.md]');
|
|
684
|
+
console.error('Usage: node contract-integrity/scripts/audit.js <trace.json> [--tdd-evidence <#58-evidence.json>] [--workspace <root>] [--output report.json] [--markdown report.md] [--verify-fresh prior-report.json]');
|
|
491
685
|
return 2;
|
|
492
686
|
}
|
|
493
687
|
|
|
@@ -501,7 +695,33 @@ function runCli(argv) {
|
|
|
501
695
|
return 2;
|
|
502
696
|
}
|
|
503
697
|
|
|
698
|
+
if (args.verifyFresh) {
|
|
699
|
+
let prior;
|
|
700
|
+
try { prior = readJson(args.verifyFresh); }
|
|
701
|
+
catch (error) {
|
|
702
|
+
console.error(`Contract Integrity Freshness: FAIL\n- ${error.message}`);
|
|
703
|
+
return 2;
|
|
704
|
+
}
|
|
705
|
+
const currentAudit = evaluateTrace(trace, { tddEvidence });
|
|
706
|
+
const freshness = verifyFreshReport(prior, args, trace, currentAudit);
|
|
707
|
+
console.log(`Contract Integrity Freshness: ${freshness.authorizesCompletion ? 'PASS' : 'FAIL'}`);
|
|
708
|
+
for (const reason of freshness.reasons) console.error(`- ${reason}`);
|
|
709
|
+
return freshness.authorizesCompletion ? 0 : 1;
|
|
710
|
+
}
|
|
711
|
+
|
|
504
712
|
const report = evaluateTrace(trace, { tddEvidence });
|
|
713
|
+
report.provenance = buildProvenance(args, trace);
|
|
714
|
+
if (args.workspace) {
|
|
715
|
+
const missing = report.provenance.artifacts.filter(item => item.missing);
|
|
716
|
+
if (trace.mode === 'strict' && missing.length) {
|
|
717
|
+
report.result = 'FAIL';
|
|
718
|
+
report.completionGate = 'FAIL';
|
|
719
|
+
report.gateEligible = false;
|
|
720
|
+
report.errors.push(...missing.map(item => item.reason === 'linked-path'
|
|
721
|
+
? `provenance artifact linked-path: ${item.id} -> ${item.path}`
|
|
722
|
+
: `provenance artifact missing from workspace: ${item.id} -> ${item.path}`));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
505
725
|
console.log(`Contract Integrity: ${report.result}`);
|
|
506
726
|
console.log(`Scores: completeness=${report.scores.completenessScore} protection=${report.scores.protectionScore} integrity=${report.scores.contractIntegrityScore}`);
|
|
507
727
|
console.log(`Gate: ${report.completionGate}`);
|
|
@@ -527,10 +747,13 @@ if (require.main === module) process.exit(runCli(process.argv));
|
|
|
527
747
|
module.exports = {
|
|
528
748
|
DRIFT,
|
|
529
749
|
VERSION,
|
|
750
|
+
buildProvenance,
|
|
530
751
|
classifyLineage,
|
|
531
752
|
evaluateTrace,
|
|
532
753
|
markdown,
|
|
754
|
+
repairGuidance,
|
|
533
755
|
protectionForRequirement,
|
|
534
756
|
runCli,
|
|
535
757
|
validateTrace,
|
|
758
|
+
verifyFreshReport,
|
|
536
759
|
};
|
|
@@ -64,6 +64,22 @@ Codex has two paths that must not be collapsed:
|
|
|
64
64
|
|
|
65
65
|
The package is tested at the mechanism layer and remains subject to the host’s hook review/trust flow. A fresh host session is still required before claiming that a particular ChatGPT/Codex installation loaded and fired the hooks or that the model followed read-before-skip.
|
|
66
66
|
|
|
67
|
+
### Explicit Codex user-hook compatibility fallback
|
|
68
|
+
|
|
69
|
+
Current Codex documentation supports plugin-bundled hooks, so native plugin mounting remains the preferred path. Some host/source combinations have nevertheless failed to expose enabled plugin hooks for review/entry attribution (#122 and upstream Codex reports). Harness therefore provides an **explicit compatibility fallback**, not an automatic replacement for native plugin hooks:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
harness codex-hooks install
|
|
73
|
+
harness codex-hooks status
|
|
74
|
+
harness codex-hooks uninstall
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The fallback copies the current packaged runtime into `$CODEX_HOME/harness-everything/compat-hooks/` and merges absolute-path entries into `$CODEX_HOME/hooks.json`. It does **not** modify `config.toml`, trust hashes, approval policy, or hook enablement. Codex must still show/review/trust the resulting non-managed hooks through its normal `/hooks` flow.
|
|
78
|
+
|
|
79
|
+
Ownership is deliberately strict: update/uninstall removes only exact entries recorded in the Harness compatibility manifest. If an installed Harness entry was edited after installation, the operation fails closed rather than overwriting or deleting that user-modified entry. Unrelated pre-existing and later-added user hooks are preserved.
|
|
80
|
+
|
|
81
|
+
This fallback solves only plugin-source discovery/mounting. It does not claim to repair a Codex carrier that fails to dispatch `PreToolUse` at all; those host-level execution-path limitations remain a live compatibility boundary.
|
|
82
|
+
|
|
67
83
|
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.
|
|
68
84
|
|
|
69
85
|
## Install/uninstall ownership boundary
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "harness-platform-compatibility-v1",
|
|
3
|
-
"last_reviewed": "2026-09-
|
|
3
|
+
"last_reviewed": "2026-09-19",
|
|
4
4
|
"status_vocabulary": {
|
|
5
5
|
"Supported": "An official or native mechanism exists and the Harness behavior is verified.",
|
|
6
6
|
"Mechanism verified": "Harness package, adapter, or installer checks pass, but a real host run is not confirmed.",
|
|
@@ -109,13 +109,13 @@
|
|
|
109
109
|
"standaloneSkills": {"status": "Supported", "paths": {"project": [".agents/skills"], "global": ["~/.agents/skills"]}},
|
|
110
110
|
"pluginSupport": {"status": "Mechanism verified", "paths": {"manifest": ["plugin.json", ".codex-plugin/plugin.json"]}},
|
|
111
111
|
"bundledSkills": {"status": "Mechanism verified", "notes": "The local package contains a root skills/ tree."},
|
|
112
|
-
"hooksLifecycle": {"status": "Mechanism verified", "notes": "Codex
|
|
112
|
+
"hooksLifecycle": {"status": "Mechanism verified", "notes": "Codex officially supports plugin-bundled hooks and the local package is mechanism-tested. The plugin intentionally does not declare unsupported PostToolUseFailure; Codex uses documented PostToolUse semantics instead. Harness also provides an explicit user-config compatibility fallback that copies the packaged runtime and merges exact-owned entries into $CODEX_HOME/hooks.json without changing trust/config.toml. Neither path is promoted to live verified until a real host exposes and executes the hooks."},
|
|
113
113
|
"projectScope": {"status": "Mechanism verified", "paths": {"skills": [".agents/skills"], "instructions": ["AGENTS.md"]}},
|
|
114
114
|
"globalScope": {"status": "Mechanism verified", "paths": {"skills": ["~/.agents/skills"]}},
|
|
115
|
-
"installUninstallSymmetry": {"status": "Mechanism verified", "notes": "Harness installer
|
|
115
|
+
"installUninstallSymmetry": {"status": "Mechanism verified", "notes": "General Harness installer ownership is mechanism-tested. The optional codex-hooks compatibility fallback separately has exact-entry update/uninstall ownership tests and preserves unrelated user hooks; live Codex trust/dispatch still requires host evidence."},
|
|
116
116
|
"assetsReferences": {"status": "Mechanism verified", "notes": "Packaged skill references are checked and remain inside the package."},
|
|
117
117
|
"updateSync": {"status": "Unknown", "notes": "A real marketplace refresh has not been run in a Codex host."},
|
|
118
|
-
"liveHostVerification": {"status": "Unknown", "notes": "No preserved Codex plugin/session artifact is
|
|
118
|
+
"liveHostVerification": {"status": "Unknown", "notes": "No preserved Codex plugin/session artifact proves current bundled-hook entry. Native plugin mounting is preferred; the explicit user-hook fallback is mechanism-verified only and does not solve host carriers that bypass hook dispatch."}
|
|
119
119
|
}
|
|
120
120
|
},
|
|
121
121
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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": {
|
|
@@ -54,7 +54,9 @@
|
|
|
54
54
|
"test:contract-integrity": "node ci/contract-integrity.test.js",
|
|
55
55
|
"contract:integrity": "node contract-integrity/scripts/audit.js",
|
|
56
56
|
"waza:check": "node scripts/waza-harness-check.js",
|
|
57
|
-
"test:waza:compat": "node ci/mechanism-2aj-waza-reference-adapter.test.js"
|
|
57
|
+
"test:waza:compat": "node ci/mechanism-2aj-waza-reference-adapter.test.js",
|
|
58
|
+
"codex:hooks": "node scripts/codex-hooks-compat.js",
|
|
59
|
+
"test:codex:hooks-compat": "node ci/mechanism-2al-codex-hooks-compat.test.js"
|
|
58
60
|
},
|
|
59
61
|
"engines": {
|
|
60
62
|
"node": ">=22"
|