harness-everything 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/ci.yml +0 -82
- package/.github/workflows/doc-governance.yml +91 -0
- package/.github/workflows/release.yml +5 -2
- package/CHANGELOG.md +10 -0
- package/ci/mechanism-2ax-doc-lifecycle.test.js +142 -12
- package/docs/document-lifecycle.md +19 -0
- package/docs/repository-contract.md +1 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/sbom.cdx.json +7 -7
- package/scripts/doc-governance-guard.js +31 -8
|
@@ -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.
|
|
13
|
+
"version": "0.50.0",
|
|
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.
|
|
4
|
+
"version": "0.50.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Miya Daniel"
|
|
7
7
|
},
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -21,88 +21,6 @@ jobs:
|
|
|
21
21
|
- name: Verify pilot instrumentation, sanitizer and offline replay
|
|
22
22
|
run: python3 benchmarks/issue290-pilot/check.py
|
|
23
23
|
|
|
24
|
-
doc-governance:
|
|
25
|
-
runs-on: ubuntu-latest
|
|
26
|
-
steps:
|
|
27
|
-
- uses: actions/checkout@v7
|
|
28
|
-
with:
|
|
29
|
-
fetch-depth: 0
|
|
30
|
-
- uses: actions/setup-node@v7
|
|
31
|
-
with:
|
|
32
|
-
node-version: 24
|
|
33
|
-
- name: Run base-owned document governance guard
|
|
34
|
-
env:
|
|
35
|
-
HARNESS_GUARD_EVENT: ${{ github.event_name }}
|
|
36
|
-
HARNESS_GUARD_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
37
|
-
HARNESS_GUARD_CANDIDATE_SHA: ${{ github.event.pull_request.head.sha }}
|
|
38
|
-
run: |
|
|
39
|
-
node -e 'const crypto = require("node:crypto");
|
|
40
|
-
const fs = require("node:fs");
|
|
41
|
-
const os = require("node:os");
|
|
42
|
-
const path = require("node:path");
|
|
43
|
-
const { spawnSync } = require("node:child_process");
|
|
44
|
-
(function main() {
|
|
45
|
-
const guardPath = "scripts/doc-governance-guard.js";
|
|
46
|
-
const event = process.env.HARNESS_GUARD_EVENT || "unknown";
|
|
47
|
-
const baseInput = process.env.HARNESS_GUARD_BASE_SHA || "";
|
|
48
|
-
const candidateInput = process.env.HARNESS_GUARD_CANDIDATE_SHA || "";
|
|
49
|
-
function warn(state, message) {
|
|
50
|
-
console.log("::warning title=" + state + "::" + message);
|
|
51
|
-
if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, "## Document governance guard\n\n**" + state + "**: " + message + "\n\n", "utf8");
|
|
52
|
-
}
|
|
53
|
-
function git(args) {
|
|
54
|
-
const result = spawnSync("git", args, { encoding: "buffer", maxBuffer: 16 * 1024 * 1024, env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1", GIT_NO_REPLACE_OBJECTS: "1", GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0" } });
|
|
55
|
-
if (result.error) throw result.error;
|
|
56
|
-
if (result.status !== 0) throw new Error("git " + args[0] + " failed: " + Buffer.from(result.stderr || result.stdout || "").toString("utf8").trim());
|
|
57
|
-
return Buffer.from(result.stdout || "");
|
|
58
|
-
}
|
|
59
|
-
function resolveCommit(input, label) {
|
|
60
|
-
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(input)) throw new Error(label + " is not a full commit SHA");
|
|
61
|
-
const resolved = git(["rev-parse", "--verify", "--quiet", "--end-of-options", input + "^{commit}"]).toString("ascii").trim().toLowerCase();
|
|
62
|
-
if (resolved !== input.toLowerCase()) throw new Error(label + " did not resolve to the supplied commit SHA");
|
|
63
|
-
return resolved;
|
|
64
|
-
}
|
|
65
|
-
if (event !== "pull_request") {
|
|
66
|
-
warn("UNCHECKED", "No externally selected PR base/head is available for event " + event + "; the base-owned guard was not executed.");
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
const base = resolveCommit(baseInput, "PR base SHA");
|
|
71
|
-
const candidate = resolveCommit(candidateInput, "PR head SHA");
|
|
72
|
-
const checkout = process.env.GITHUB_SHA || "unavailable";
|
|
73
|
-
console.log("PR head SHA used by the guard: " + candidate);
|
|
74
|
-
console.log("GITHUB_SHA used by checkout and the ordinary suite: " + checkout + " (the pull_request checkout may be a synthetic merge commit).");
|
|
75
|
-
const entries = git(["ls-tree", "-z", "--full-tree", base, "--", guardPath]);
|
|
76
|
-
if (entries.length === 0) {
|
|
77
|
-
warn("BOOTSTRAP_UNCHECKED", "Trusted base " + base + " has no " + guardPath + "; candidate guard was not executed. Independent review of the frozen candidate guard is required.");
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (entries[entries.length - 1] !== 0) throw new Error("base guard tree entry is not NUL terminated");
|
|
81
|
-
const entry = entries.subarray(0, entries.length - 1);
|
|
82
|
-
const tab = entry.indexOf(9);
|
|
83
|
-
if (tab < 0 || entry.subarray(tab + 1).toString("utf8") !== guardPath) throw new Error("base guard tree entry is malformed");
|
|
84
|
-
const [mode, type, oid] = entry.subarray(0, tab).toString("ascii").split(" ");
|
|
85
|
-
if (!(["100644", "100755"].includes(mode)) || type !== "blob" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(oid || "")) throw new Error("base guard is not a regular Git blob");
|
|
86
|
-
const guardBytes = git(["cat-file", "blob", oid]);
|
|
87
|
-
const digest = crypto.createHash("sha256").update(guardBytes).digest("hex");
|
|
88
|
-
console.log("Base guard blob: " + oid + "; SHA-256: " + digest);
|
|
89
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "harness-doc-governance-"));
|
|
90
|
-
const tempGuard = path.join(tempDir, "doc-governance-guard.js");
|
|
91
|
-
try {
|
|
92
|
-
fs.writeFileSync(tempGuard, guardBytes, { mode: 0o600 });
|
|
93
|
-
const result = spawnSync(process.execPath, [tempGuard, "--repo", process.cwd(), "--base", base, "--candidate", candidate], { encoding: "buffer", maxBuffer: 64 * 1024 * 1024, env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1", GIT_NO_REPLACE_OBJECTS: "1", GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0" } });
|
|
94
|
-
if (result.error) throw result.error;
|
|
95
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
96
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
97
|
-
process.exitCode = result.status === null ? 1 : result.status;
|
|
98
|
-
} finally {
|
|
99
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error("Document governance guard setup failed: " + (error.message || String(error)));
|
|
103
|
-
process.exitCode = 1;
|
|
104
|
-
}
|
|
105
|
-
})();'
|
|
106
24
|
self-regression:
|
|
107
25
|
strategy:
|
|
108
26
|
fail-fast: false
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
name: Document Governance
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_call:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
doc-governance:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v7
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
- uses: actions/setup-node@v7
|
|
17
|
+
with:
|
|
18
|
+
node-version: 24
|
|
19
|
+
- name: Run base-owned document governance guard
|
|
20
|
+
env:
|
|
21
|
+
HARNESS_GUARD_EVENT: ${{ github.event_name }}
|
|
22
|
+
HARNESS_GUARD_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
23
|
+
HARNESS_GUARD_CANDIDATE_SHA: ${{ github.event.pull_request.head.sha }}
|
|
24
|
+
run: |
|
|
25
|
+
node -e 'const crypto = require("node:crypto");
|
|
26
|
+
const fs = require("node:fs");
|
|
27
|
+
const os = require("node:os");
|
|
28
|
+
const path = require("node:path");
|
|
29
|
+
const { spawnSync } = require("node:child_process");
|
|
30
|
+
(function main() {
|
|
31
|
+
const guardPath = "scripts/doc-governance-guard.js";
|
|
32
|
+
const event = process.env.HARNESS_GUARD_EVENT || "unknown";
|
|
33
|
+
const baseInput = process.env.HARNESS_GUARD_BASE_SHA || "";
|
|
34
|
+
const candidateInput = process.env.HARNESS_GUARD_CANDIDATE_SHA || "";
|
|
35
|
+
function warn(state, message) {
|
|
36
|
+
console.log("::warning title=" + state + "::" + message);
|
|
37
|
+
if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, "## Document governance guard\n\n**" + state + "**: " + message + "\n\n", "utf8");
|
|
38
|
+
}
|
|
39
|
+
function git(args) {
|
|
40
|
+
const result = spawnSync("git", args, { encoding: "buffer", maxBuffer: 16 * 1024 * 1024, env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1", GIT_NO_REPLACE_OBJECTS: "1", GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0" } });
|
|
41
|
+
if (result.error) throw result.error;
|
|
42
|
+
if (result.status !== 0) throw new Error("git " + args[0] + " failed: " + Buffer.from(result.stderr || result.stdout || "").toString("utf8").trim());
|
|
43
|
+
return Buffer.from(result.stdout || "");
|
|
44
|
+
}
|
|
45
|
+
function resolveCommit(input, label) {
|
|
46
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(input)) throw new Error(label + " is not a full commit SHA");
|
|
47
|
+
const resolved = git(["rev-parse", "--verify", "--quiet", "--end-of-options", input + "^{commit}"]).toString("ascii").trim().toLowerCase();
|
|
48
|
+
if (resolved !== input.toLowerCase()) throw new Error(label + " did not resolve to the supplied commit SHA");
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
if (event !== "pull_request") {
|
|
52
|
+
warn("UNCHECKED", "No externally selected PR base/head is available for event " + event + "; the base-owned guard was not executed.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const base = resolveCommit(baseInput, "PR base SHA");
|
|
57
|
+
const candidate = resolveCommit(candidateInput, "PR head SHA");
|
|
58
|
+
const checkout = process.env.GITHUB_SHA || "unavailable";
|
|
59
|
+
console.log("PR head SHA used by the guard: " + candidate);
|
|
60
|
+
console.log("GITHUB_SHA used by checkout and the ordinary suite: " + checkout + " (the pull_request checkout may be a synthetic merge commit).");
|
|
61
|
+
const entries = git(["ls-tree", "-z", "--full-tree", base, "--", guardPath]);
|
|
62
|
+
if (entries.length === 0) {
|
|
63
|
+
warn("BOOTSTRAP_UNCHECKED", "Trusted base " + base + " has no " + guardPath + "; candidate guard was not executed. Independent review of the frozen candidate guard is required.");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (entries[entries.length - 1] !== 0) throw new Error("base guard tree entry is not NUL terminated");
|
|
67
|
+
const entry = entries.subarray(0, entries.length - 1);
|
|
68
|
+
const tab = entry.indexOf(9);
|
|
69
|
+
if (tab < 0 || entry.subarray(tab + 1).toString("utf8") !== guardPath) throw new Error("base guard tree entry is malformed");
|
|
70
|
+
const [mode, type, oid] = entry.subarray(0, tab).toString("ascii").split(" ");
|
|
71
|
+
if (!(["100644", "100755"].includes(mode)) || type !== "blob" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(oid || "")) throw new Error("base guard is not a regular Git blob");
|
|
72
|
+
const guardBytes = git(["cat-file", "blob", oid]);
|
|
73
|
+
const digest = crypto.createHash("sha256").update(guardBytes).digest("hex");
|
|
74
|
+
console.log("Base guard blob: " + oid + "; SHA-256: " + digest);
|
|
75
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "harness-doc-governance-"));
|
|
76
|
+
const tempGuard = path.join(tempDir, "doc-governance-guard.js");
|
|
77
|
+
try {
|
|
78
|
+
fs.writeFileSync(tempGuard, guardBytes, { mode: 0o600 });
|
|
79
|
+
const result = spawnSync(process.execPath, [tempGuard, "--repo", process.cwd(), "--base", base, "--candidate", candidate], { encoding: "buffer", maxBuffer: 64 * 1024 * 1024, env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1", GIT_NO_REPLACE_OBJECTS: "1", GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0" } });
|
|
80
|
+
if (result.error) throw result.error;
|
|
81
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
82
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
83
|
+
process.exitCode = result.status === null ? 1 : result.status;
|
|
84
|
+
} finally {
|
|
85
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Document governance guard setup failed: " + (error.message || String(error)));
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
}
|
|
91
|
+
})();'
|
|
@@ -19,12 +19,15 @@ permissions:
|
|
|
19
19
|
id-token: write
|
|
20
20
|
|
|
21
21
|
jobs:
|
|
22
|
-
# Release publication
|
|
22
|
+
# Release publication waits for the full CI matrix and document governance.
|
|
23
23
|
gate:
|
|
24
24
|
uses: ./.github/workflows/ci.yml
|
|
25
25
|
|
|
26
|
+
doc-governance:
|
|
27
|
+
uses: ./.github/workflows/doc-governance.yml
|
|
28
|
+
|
|
26
29
|
release:
|
|
27
|
-
needs: gate
|
|
30
|
+
needs: [gate, doc-governance]
|
|
28
31
|
runs-on: ubuntu-latest
|
|
29
32
|
steps:
|
|
30
33
|
- name: Checkout full release history
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.50.0](https://github.com/dyphn1/Harness-everything/compare/v0.49.0...v0.50.0) (2026-11-09)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **governance:** split the guard workflow and protect only authoritative doc membership ([5a46e67](https://github.com/dyphn1/Harness-everything/commit/5a46e673525a28f9df770b8eefe2dce4bf7ae9e8))
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **governance:** keep the extracted guard job in the release gate ([24c3b10](https://github.com/dyphn1/Harness-everything/commit/24c3b10a6530dd33fff22788a2219bd36ceb1447))
|
|
14
|
+
|
|
5
15
|
## [0.49.0](https://github.com/dyphn1/Harness-everything/compare/v0.48.0...v0.49.0) (2026-11-08)
|
|
6
16
|
|
|
7
17
|
### Features
|
|
@@ -1625,15 +1625,25 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
1625
1625
|
'plugins/harness-everything/skills/harness-everything/scripts/project-docs-resolver.js',
|
|
1626
1626
|
'.agents/harness-everything/project-docs.json',
|
|
1627
1627
|
'.agents/harness-everything/document-inventory.json',
|
|
1628
|
-
'.
|
|
1628
|
+
'.agents/harness-everything/errors/README.md',
|
|
1629
|
+
'.github/workflows/doc-governance.yml',
|
|
1629
1630
|
'package.json',
|
|
1630
1631
|
'self-evolve/scripts/self-regression.js',
|
|
1631
1632
|
'ci/mechanism-test.js',
|
|
1632
1633
|
'ci/mutation-check.js',
|
|
1633
1634
|
'ci/lib/isolated-test-process.js',
|
|
1634
1635
|
'ci/test-helper.js',
|
|
1636
|
+
'scripts/errors-sync.js',
|
|
1635
1637
|
'scripts/verify-all.js'
|
|
1636
1638
|
];
|
|
1639
|
+
const membershipPaths = ['AGENTS.md', 'SECURITY.md', 'CODE_OF_CONDUCT.md', 'PRIVACY.md', 'TERMS.md', 'docs/document-lifecycle.md'];
|
|
1640
|
+
const membershipPrefixPaths = ['docs/policy/existing.md', 'docs/prd/future.md'];
|
|
1641
|
+
const fixtureSupportPaths = [
|
|
1642
|
+
'.github/workflows/ci.yml',
|
|
1643
|
+
'benchmarks/issue290-pilot/README.md',
|
|
1644
|
+
'.agents/harness-everything/errors/E-0001-fixture.md',
|
|
1645
|
+
...membershipPrefixPaths
|
|
1646
|
+
];
|
|
1637
1647
|
const frozenGuard = fs.readFileSync(governanceGuardPath);
|
|
1638
1648
|
const frozenTest = fs.readFileSync(__filename);
|
|
1639
1649
|
const frozenGuardPath = path.join(tempRoot, 'frozen-doc-governance-guard.js');
|
|
@@ -1680,6 +1690,13 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
1680
1690
|
: fs.readFileSync(path.join(REPOSITORY_ROOT, ...relativePath.split('/')));
|
|
1681
1691
|
writeRelative(root, relativePath, bytes);
|
|
1682
1692
|
}
|
|
1693
|
+
for (const relativePath of membershipPaths) writeRelative(root, relativePath, fs.readFileSync(path.join(REPOSITORY_ROOT, relativePath)));
|
|
1694
|
+
for (const relativePath of fixtureSupportPaths) {
|
|
1695
|
+
const bytes = relativePath === '.github/workflows/ci.yml'
|
|
1696
|
+
? fs.readFileSync(path.join(REPOSITORY_ROOT, relativePath))
|
|
1697
|
+
: Buffer.from(`baseline ${relativePath}\n`);
|
|
1698
|
+
writeRelative(root, relativePath, bytes);
|
|
1699
|
+
}
|
|
1683
1700
|
const copyScenarioTree = (from, into) => {
|
|
1684
1701
|
fs.mkdirSync(into, { recursive: true });
|
|
1685
1702
|
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
@@ -1715,6 +1732,9 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
1715
1732
|
}
|
|
1716
1733
|
|
|
1717
1734
|
const fixture = createFixture();
|
|
1735
|
+
resetFixture(fixture);
|
|
1736
|
+
writeRelative(fixture.root, 'docs/adr/GRPH-001.md', 'baseline ADR\n');
|
|
1737
|
+
fixture.base = commitFixture(fixture.root, 'add ADR fixture member');
|
|
1718
1738
|
const ordinaryPath = path.join(fixture.root, 'docs', 'ordinary.md');
|
|
1719
1739
|
fs.writeFileSync(ordinaryPath, 'ordinary document edit\n');
|
|
1720
1740
|
// Git for Windows rejects LF paths even when they are added directly to the index.
|
|
@@ -1737,6 +1757,9 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
1737
1757
|
assert.strictEqual(positive.report.verifier.sha256, crypto.createHash('sha256').update(frozenGuard).digest('hex'));
|
|
1738
1758
|
assert.strictEqual(positive.report.verifier.matchesBaseObject, true, 'running verifier bytes match the baseline guard blob');
|
|
1739
1759
|
assert.deepStrictEqual(positive.report.trustSurface.paths.slice(0, trustedPaths.length).map(item => item.path), trustedPaths, 'fixed trust surface is explicit');
|
|
1760
|
+
for (const relativePath of [...membershipPaths, ...membershipPrefixPaths]) {
|
|
1761
|
+
assert.ok(positive.report.trustSurface.paths.some(item => item.path === relativePath), `${relativePath} is an explicit membership-only path`);
|
|
1762
|
+
}
|
|
1740
1763
|
const scenarioExpectedPath = 'ci/fixtures/doc-lifecycle/scenarios/active-to-active/expected.json';
|
|
1741
1764
|
const scenarioOracle = positive.report.trustSurface.paths.find(item => item.path === scenarioExpectedPath);
|
|
1742
1765
|
assert.ok(scenarioOracle, 'tracked scenario oracle is part of the baseline trust surface');
|
|
@@ -1784,6 +1807,90 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
1784
1807
|
assert.strictEqual(unchanged.status, 0, 'an unchanged tree passes');
|
|
1785
1808
|
assert.strictEqual(unchanged.report.verdict, 'PASS');
|
|
1786
1809
|
|
|
1810
|
+
resetFixture(fixture);
|
|
1811
|
+
const ciWorkflowPath = path.join(fixture.root, '.github/workflows/ci.yml');
|
|
1812
|
+
const ciWorkflow = fs.readFileSync(ciWorkflowPath, 'utf8');
|
|
1813
|
+
assert.ok(ciWorkflow.includes(' pilot-offline-evidence:\n runs-on: ubuntu-latest'), 'unrelated CI job fixture exists');
|
|
1814
|
+
writeRelative(fixture.root, '.github/workflows/ci.yml', ciWorkflow.replace(' pilot-offline-evidence:\n runs-on: ubuntu-latest', ' pilot-offline-evidence:\n name: Updated unrelated CI job\n runs-on: ubuntu-latest'));
|
|
1815
|
+
const unrelatedCiCandidate = commitFixture(fixture.root, 'change unrelated CI job');
|
|
1816
|
+
const unrelatedCi = runFrozenGuard(fixture, fixture.base, unrelatedCiCandidate);
|
|
1817
|
+
assert.strictEqual(unrelatedCi.status, 0, 'unrelated ci.yml job changes pass');
|
|
1818
|
+
assert.strictEqual(unrelatedCi.report.verdict, 'PASS');
|
|
1819
|
+
|
|
1820
|
+
resetFixture(fixture);
|
|
1821
|
+
writeRelative(fixture.root, '.github/workflows/doc-governance.yml', `${fs.readFileSync(path.join(fixture.root, '.github/workflows/doc-governance.yml'), 'utf8')}\n# guard workflow change\n`);
|
|
1822
|
+
const governanceWorkflowCandidate = commitFixture(fixture.root, 'change guard workflow');
|
|
1823
|
+
const governanceWorkflowChange = runFrozenGuard(fixture, fixture.base, governanceWorkflowCandidate);
|
|
1824
|
+
assert.strictEqual(governanceWorkflowChange.status, 2, 'the standalone guard workflow remains review-required');
|
|
1825
|
+
assert.strictEqual(governanceWorkflowChange.report.verdict, 'REVIEW_REQUIRED');
|
|
1826
|
+
|
|
1827
|
+
resetFixture(fixture);
|
|
1828
|
+
for (const relativePath of ['benchmarks/issue290-pilot/README.md', '.agents/harness-everything/errors/E-0001-fixture.md']) {
|
|
1829
|
+
fs.appendFileSync(path.join(fixture.root, ...relativePath.split('/')), '\nrecord update\n');
|
|
1830
|
+
}
|
|
1831
|
+
const recordsCandidate = commitFixture(fixture.root, 'change records only');
|
|
1832
|
+
const recordsOnly = runFrozenGuard(fixture, fixture.base, recordsCandidate);
|
|
1833
|
+
assert.strictEqual(recordsOnly.status, 0, 'benchmark and error-ledger records are outside the governance verdict');
|
|
1834
|
+
assert.strictEqual(recordsOnly.report.verdict, 'PASS');
|
|
1835
|
+
|
|
1836
|
+
resetFixture(fixture);
|
|
1837
|
+
writeRelative(fixture.root, '.github/workflows/doc-governance.yml', `${fs.readFileSync(path.join(fixture.root, '.github/workflows/doc-governance.yml'), 'utf8')}\n# guard workflow change\n`);
|
|
1838
|
+
fs.appendFileSync(path.join(fixture.root, '.agents/harness-everything/errors/E-0001-fixture.md'), '\nrecord update\n');
|
|
1839
|
+
const mixedCandidate = commitFixture(fixture.root, 'change T1 and T3');
|
|
1840
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, mixedCandidate).report.verdict, 'REVIEW_REQUIRED', 'a T1 change controls a mixed T1 and T3 change');
|
|
1841
|
+
|
|
1842
|
+
for (const relativePath of membershipPaths) {
|
|
1843
|
+
resetFixture(fixture);
|
|
1844
|
+
fs.appendFileSync(path.join(fixture.root, relativePath), '\nordinary content revision\n');
|
|
1845
|
+
const candidate = commitFixture(fixture.root, `revise ${relativePath}`);
|
|
1846
|
+
const result = runFrozenGuard(fixture, fixture.base, candidate);
|
|
1847
|
+
assert.strictEqual(result.report.verdict, 'PASS', `${relativePath} content revision does not require U5`);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
for (const relativePath of membershipPrefixPaths) {
|
|
1851
|
+
resetFixture(fixture);
|
|
1852
|
+
fs.appendFileSync(path.join(fixture.root, ...relativePath.split('/')), '\nordinary content revision\n');
|
|
1853
|
+
const candidate = commitFixture(fixture.root, `revise ${relativePath}`);
|
|
1854
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, candidate).report.verdict, 'PASS', `${relativePath} content revision does not require U5`);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
for (const relativePath of ['docs/policy/new.md', 'docs/prd/new.md']) {
|
|
1858
|
+
resetFixture(fixture);
|
|
1859
|
+
writeRelative(fixture.root, relativePath, 'new T1 member\n');
|
|
1860
|
+
const candidate = commitFixture(fixture.root, `add ${relativePath}`);
|
|
1861
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, candidate).report.verdict, 'REVIEW_REQUIRED', `adding ${relativePath} changes protected membership`);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
resetFixture(fixture);
|
|
1865
|
+
fixtureGit(fixture.root, ['rm', '--quiet', '--', 'AGENTS.md']);
|
|
1866
|
+
const deletedRootAgentsCandidate = commitFixture(fixture.root, 'delete root AGENTS');
|
|
1867
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, deletedRootAgentsCandidate).report.verdict, 'REVIEW_REQUIRED', 'deleting root AGENTS changes protected membership');
|
|
1868
|
+
|
|
1869
|
+
resetFixture(fixture);
|
|
1870
|
+
const changedRootAgentsModeCandidate = commitFixture(fixture.root, 'change root AGENTS mode', 'AGENTS.md');
|
|
1871
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, changedRootAgentsModeCandidate).report.verdict, 'REVIEW_REQUIRED', 'changing root AGENTS mode is not a content-only revision');
|
|
1872
|
+
|
|
1873
|
+
resetFixture(fixture);
|
|
1874
|
+
fixtureGit(fixture.root, ['mv', '--', 'AGENTS.md', 'docs/AGENTS.md']);
|
|
1875
|
+
const renamedRootAgentsCandidate = commitFixture(fixture.root, 'move root AGENTS');
|
|
1876
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, renamedRootAgentsCandidate).report.verdict, 'REVIEW_REQUIRED', 'moving root AGENTS out changes protected membership');
|
|
1877
|
+
|
|
1878
|
+
resetFixture(fixture);
|
|
1879
|
+
writeRelative(fixture.root, 'docs/adr/GRPH-001.md', 'revised ADR contents\n');
|
|
1880
|
+
const adrContentCandidate = commitFixture(fixture.root, 'revise ADR contents');
|
|
1881
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, adrContentCandidate).report.verdict, 'PASS', 'ADR content revisions do not require U5');
|
|
1882
|
+
|
|
1883
|
+
resetFixture(fixture);
|
|
1884
|
+
fixtureGit(fixture.root, ['rm', '--quiet', '--', 'docs/adr/GRPH-001.md']);
|
|
1885
|
+
const deletedAdrCandidate = commitFixture(fixture.root, 'delete ADR');
|
|
1886
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, deletedAdrCandidate).report.verdict, 'REVIEW_REQUIRED', 'deleting an ADR changes protected membership');
|
|
1887
|
+
|
|
1888
|
+
resetFixture(fixture);
|
|
1889
|
+
fs.mkdirSync(path.join(fixture.root, 'docs/records'), { recursive: true });
|
|
1890
|
+
fixtureGit(fixture.root, ['mv', '--', 'docs/adr/GRPH-001.md', 'docs/records/GRPH-001.md']);
|
|
1891
|
+
const movedAdrCandidate = commitFixture(fixture.root, 'move ADR out of governed folder');
|
|
1892
|
+
assert.strictEqual(runFrozenGuard(fixture, fixture.base, movedAdrCandidate).report.verdict, 'REVIEW_REQUIRED', 'moving an ADR out of the governed folder requires review');
|
|
1893
|
+
|
|
1787
1894
|
const rawIdentity = entry => ({ mode: entry.mode, type: entry.type, oid: entry.oid });
|
|
1788
1895
|
const configuredIdentity = (commit, relativePath) => {
|
|
1789
1896
|
const tree = parseTreeOracle(fixtureGit(fixture.root, ['ls-tree', '-r', '-z', '--full-tree', commit]));
|
|
@@ -1813,11 +1920,12 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
1813
1920
|
writeRelative(fixture.root, 'docs/governed-index.md', 'changed derived output\n');
|
|
1814
1921
|
const derivedCandidate = commitFixture(fixture.root, 'change derived document');
|
|
1815
1922
|
const derivedChange = runFrozenGuard(fixture, fixture.base, derivedCandidate);
|
|
1816
|
-
assert.strictEqual(derivedChange.status,
|
|
1817
|
-
assert.strictEqual(derivedChange.report.verdict, '
|
|
1818
|
-
const derivedEntry = derivedChange.report.policyPreservation.configuredPaths.
|
|
1819
|
-
assert.ok(derivedEntry, 'derived content change remains in
|
|
1820
|
-
assert.strictEqual(derivedEntry.state, '
|
|
1923
|
+
assert.strictEqual(derivedChange.status, 0, 'generated-view content changes are left to ordinary CI');
|
|
1924
|
+
assert.strictEqual(derivedChange.report.verdict, 'PASS');
|
|
1925
|
+
const derivedEntry = derivedChange.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/governed-index.md');
|
|
1926
|
+
assert.ok(derivedEntry, 'derived content change remains visible in the report');
|
|
1927
|
+
assert.strictEqual(derivedEntry.state, 'pass');
|
|
1928
|
+
assert.match(derivedEntry.reason, /regenerate-equals CI/);
|
|
1821
1929
|
assert.deepStrictEqual(derivedEntry.base, configuredIdentity(fixture.base, 'docs/governed-index.md'));
|
|
1822
1930
|
assert.deepStrictEqual(derivedEntry.candidate, configuredIdentity(derivedCandidate, 'docs/governed-index.md'));
|
|
1823
1931
|
|
|
@@ -1930,8 +2038,10 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
1930
2038
|
writeRelative(globFixture.root, 'generated/view.md', 'changed derived glob output\n');
|
|
1931
2039
|
const derivedGlobCandidate = commitFixture(globFixture.root, 'change derived glob member');
|
|
1932
2040
|
const derivedGlob = runFrozenGuard(globFixture, globFixture.base, derivedGlobCandidate);
|
|
1933
|
-
assert.strictEqual(derivedGlob.status,
|
|
1934
|
-
|
|
2041
|
+
assert.strictEqual(derivedGlob.status, 0, 'a generated-view revision is left to ordinary CI');
|
|
2042
|
+
const derivedGlobEntry = derivedGlob.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'generated/view.md');
|
|
2043
|
+
assert.strictEqual(derivedGlobEntry.state, 'pass');
|
|
2044
|
+
assert.match(derivedGlobEntry.reason, /regenerate-equals CI/);
|
|
1935
2045
|
|
|
1936
2046
|
if (process.platform !== 'win32') {
|
|
1937
2047
|
resetFixture(globFixture);
|
|
@@ -2119,11 +2229,20 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
2119
2229
|
assert.notStrictEqual(missingCommit.status, 0);
|
|
2120
2230
|
assert.notStrictEqual(missingCommit.report.verdict, 'PASS');
|
|
2121
2231
|
|
|
2122
|
-
checkCase('2ax.
|
|
2123
|
-
const workflowPath = path.join(REPOSITORY_ROOT, '.github/workflows/
|
|
2232
|
+
checkCase('2ax. guard has a standalone workflow outside the general CI workflow', () => {
|
|
2233
|
+
const workflowPath = path.join(REPOSITORY_ROOT, '.github/workflows/doc-governance.yml');
|
|
2234
|
+
assert.ok(fs.existsSync(workflowPath), 'dedicated document-governance workflow exists');
|
|
2235
|
+
});
|
|
2236
|
+
|
|
2237
|
+
checkCase('2ax. standalone workflow keeps the base-owned guard in a failing job', () => {
|
|
2238
|
+
const workflowPath = path.join(REPOSITORY_ROOT, '.github/workflows/doc-governance.yml');
|
|
2124
2239
|
const workflow = require('js-yaml').load(fs.readFileSync(workflowPath, 'utf8'));
|
|
2240
|
+
const ciWorkflow = require('js-yaml').load(fs.readFileSync(path.join(REPOSITORY_ROOT, '.github/workflows/ci.yml'), 'utf8'));
|
|
2125
2241
|
const job = workflow.jobs['doc-governance'];
|
|
2126
2242
|
assert.ok(job, 'standalone doc-governance job exists');
|
|
2243
|
+
assert.ok(!ciWorkflow.jobs['doc-governance'], 'general CI workflow no longer carries the governance runner');
|
|
2244
|
+
assert.deepStrictEqual(workflow.on, ciWorkflow.on, 'standalone guard keeps the original CI workflow triggers');
|
|
2245
|
+
assert.ok(!/"paths(?:-ignore)?"/.test(JSON.stringify(workflow.on)), 'the guard workflow is not path-filtered');
|
|
2127
2246
|
assert.strictEqual(job['runs-on'], 'ubuntu-latest');
|
|
2128
2247
|
assert.notStrictEqual(job['continue-on-error'], true, 'the job does not mask guard failures');
|
|
2129
2248
|
assert.ok(job.steps.some(step => step.uses === 'actions/checkout@v7' && step.with['fetch-depth'] === 0), 'job fetches base Git objects');
|
|
@@ -2131,7 +2250,6 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
2131
2250
|
const guardStep = job.steps.find(step => step.name === 'Run base-owned document governance guard');
|
|
2132
2251
|
assert.ok(guardStep, 'standalone job owns the guard step');
|
|
2133
2252
|
assert.notStrictEqual(guardStep['continue-on-error'], true, 'the guard step propagates nonzero exits');
|
|
2134
|
-
assert.ok(!workflow.jobs['self-regression'].steps.some(step => step.name === guardStep.name), 'self-regression no longer runs the guard');
|
|
2135
2253
|
const command = String(guardStep.run || '').trimEnd();
|
|
2136
2254
|
const inline = command.match(/^node -e '([\s\S]*)'$/);
|
|
2137
2255
|
assert.ok(inline, 'guard step remains a directly executable Node script');
|
|
@@ -2169,7 +2287,7 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
2169
2287
|
assert.match(passEvent.output, /"verdict": "PASS"/);
|
|
2170
2288
|
|
|
2171
2289
|
resetFixture(fixture);
|
|
2172
|
-
writeRelative(fixture.root, '
|
|
2290
|
+
writeRelative(fixture.root, '.github/workflows/doc-governance.yml', `${fs.readFileSync(path.join(fixture.root, '.github/workflows/doc-governance.yml'), 'utf8')}\n# guard workflow change\n`);
|
|
2173
2291
|
const reviewCandidate = commitFixture(fixture.root, 'simulate guard review failure');
|
|
2174
2292
|
const reviewEvent = simulate('pull_request', fixture.base, reviewCandidate);
|
|
2175
2293
|
assert.strictEqual(reviewEvent.status, 2, 'REVIEW_REQUIRED stays a failing workflow job');
|
|
@@ -2183,6 +2301,18 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
|
|
|
2183
2301
|
assert.strictEqual(result.status, exitCode, `the standalone job propagates guard exit ${exitCode}`);
|
|
2184
2302
|
}
|
|
2185
2303
|
});
|
|
2304
|
+
|
|
2305
|
+
checkCase('2ax. release gate calls and waits for the standalone document-governance workflow', () => {
|
|
2306
|
+
const releaseWorkflow = require('js-yaml').load(fs.readFileSync(path.join(REPOSITORY_ROOT, '.github/workflows/release.yml'), 'utf8'));
|
|
2307
|
+
const ciWorkflow = require('js-yaml').load(fs.readFileSync(path.join(REPOSITORY_ROOT, '.github/workflows/ci.yml'), 'utf8'));
|
|
2308
|
+
const governanceWorkflow = require('js-yaml').load(fs.readFileSync(path.join(REPOSITORY_ROOT, '.github/workflows/doc-governance.yml'), 'utf8'));
|
|
2309
|
+
|
|
2310
|
+
assert.strictEqual(releaseWorkflow.jobs.gate.uses, './.github/workflows/ci.yml', 'release keeps the CI reusable-workflow gate');
|
|
2311
|
+
assert.strictEqual(releaseWorkflow.jobs['doc-governance']?.uses, './.github/workflows/doc-governance.yml', 'release calls the extracted governance workflow');
|
|
2312
|
+
assert.deepStrictEqual(releaseWorkflow.jobs.release.needs, ['gate', 'doc-governance'], 'release waits for CI and document governance');
|
|
2313
|
+
assert.ok(!ciWorkflow.jobs['doc-governance'], 'CI does not duplicate the extracted governance runner');
|
|
2314
|
+
assert.ok(governanceWorkflow.jobs['doc-governance'], 'the called standalone workflow owns the governance job');
|
|
2315
|
+
});
|
|
2186
2316
|
});
|
|
2187
2317
|
|
|
2188
2318
|
helper.finish();
|
|
@@ -4,6 +4,25 @@ Contract for the documentation family: project docs configuration (D5), document
|
|
|
4
4
|
|
|
5
5
|
Documentation is a **consumer** of the #320 workflow/obligation abstractions. Nothing here is a second router, a standing maintenance loop or a cognitive lock: every check is a pure function that reports diagnostics.
|
|
6
6
|
|
|
7
|
+
## Governance tiers and placement (#401)
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TD
|
|
11
|
+
A["Document or path change"] --> B{"T1 scope?"}
|
|
12
|
+
B -->|membership, policy or guard implementation| C["REVIEW_REQUIRED"]
|
|
13
|
+
B -->|ordinary content revision| D["No U5; run lifecycle, conflict and link checks"]
|
|
14
|
+
B -->|no| E{"T3 evidence or notes?"}
|
|
15
|
+
E -->|yes| F["No document-governance verdict; ordinary CI still applies"]
|
|
16
|
+
E -->|no| G["T2: ordinary CI; generated views use regenerate-equals"]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Tier or convention | Scope and handling |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| **T1 — strict** | ADRs, `docs/policy/`, future `docs/prd/`, named authoritative docs, root `AGENTS.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `PRIVACY.md` and `TERMS.md`, the guard workflow, guard/doc-check/lifecycle/resolver code, policy JSON, the error-ledger schema/generator, `package.json`, generic runners/helpers and policy-bearing fixtures. Membership, delete/rename, policy-map and governance-implementation changes require review. Ordinary content revisions do not require U5; normal lifecycle, conflict and link checks still apply. Root files are membership-only. |
|
|
22
|
+
| **T2 — ordinary CI** | Other docs and skills, plus generated views checked by regenerate-equals. No document-governance verdict. |
|
|
23
|
+
| **T3 — records** | Benchmark/evidence payloads, non-policy fixtures, error-ledger records, `CHANGELOG.md` and notes receive no document-governance verdict; ordinary CI, security and provenance checks still apply. The ledger schema/generator and policy-bearing fixtures remain T1. |
|
|
24
|
+
| **Placement** | Put new authoritative docs in `docs/adr/`, `docs/policy/` or future `docs/prd/`; put observations in `docs/records/`. Keep existing authoritative files such as `docs/architecture.md`, `docs/doc-workflow-reconciliation.md` and this contract as named exceptions. No existing files move in this slice. |
|
|
25
|
+
|
|
7
26
|
## Scope of this slice (original 2026-10-20 checkpoint; Phase 3b/3c status updated 2026-10-25)
|
|
8
27
|
|
|
9
28
|
This table distinguishes merged mechanism code from end-to-end workflow integration. The last reviewed `main` commit is `29b1d65b398e2622af97bf067078373b7fa76b26` (v0.36.2). That release commit carries `[skip ci]` and only bumps package/distribution versions on top of `e5d22584e243d5b5e587ee0279625e97a9033160`, whose full-gate `CI` run [39374474269](https://github.com/dyphn1/Harness-everything/actions/runs/39374474269) completed with every job succeeded. The historical Windows System One resident teardown (`S1-RS15`) was observed in attempt 1 of [run 39175240370](https://github.com/dyphn1/Harness-everything/actions/runs/39175240370) on `main@75e04359ae5bf11a41739f8eb04d25a76ee8f3a7`; that run completed successfully on attempt 2 and says nothing about `29b1d65`. Treat the resident teardown as a known platform limitation only when a current run on the exact SHA reproduces it, and require an all-green full-gate run for that SHA before calling any baseline green.
|
|
@@ -20,6 +20,7 @@ This page is generated from checked-in runtime and workflow sources. Do not edit
|
|
|
20
20
|
| --- | --- | --- | --- |
|
|
21
21
|
| `behavioral-evals.yml` | 24 | schedule, workflow_dispatch | n/a |
|
|
22
22
|
| `ci.yml` | 22, 24 | workflow_call | `test`, `test:mutations`, `test:release:semantics`, `waza:check`, `test:consistency`, `test:references`, `test:release`, `test:collision`, `test:plugin:openai`, `test:plugin:submission`, `test:platform:compatibility`, `test:portability` |
|
|
23
|
+
| `doc-governance.yml` | 24 | workflow_call | n/a |
|
|
23
24
|
| `release.yml` | 24 | workflow_dispatch | n/a |
|
|
24
25
|
|
|
25
26
|
## Drift prevention
|
package/package.json
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "0.50.0",
|
|
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:
|
|
5
|
+
"serialNumber": "urn:uuid:d9117a7d-8bb7-597a-91d9-1bb5afbdcbb0",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-11-
|
|
8
|
+
"timestamp": "2026-11-09T00:33:16.486Z",
|
|
9
9
|
"tools": [
|
|
10
10
|
{
|
|
11
11
|
"vendor": "Harness Everything",
|
|
12
12
|
"name": "generate-sbom.js",
|
|
13
|
-
"version": "0.
|
|
13
|
+
"version": "0.50.0"
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "application",
|
|
18
|
-
"bom-ref": "pkg:npm/harness-everything@0.
|
|
18
|
+
"bom-ref": "pkg:npm/harness-everything@0.50.0",
|
|
19
19
|
"name": "harness-everything",
|
|
20
|
-
"version": "0.
|
|
21
|
-
"purl": "pkg:npm/harness-everything@0.
|
|
20
|
+
"version": "0.50.0",
|
|
21
|
+
"purl": "pkg:npm/harness-everything@0.50.0"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"components": [
|
|
@@ -2033,7 +2033,7 @@
|
|
|
2033
2033
|
]
|
|
2034
2034
|
},
|
|
2035
2035
|
{
|
|
2036
|
-
"ref": "pkg:npm/harness-everything@0.
|
|
2036
|
+
"ref": "pkg:npm/harness-everything@0.50.0",
|
|
2037
2037
|
"dependsOn": [
|
|
2038
2038
|
"pkg:npm/%40commitlint/cli@19.8.1",
|
|
2039
2039
|
"pkg:npm/%40commitlint/config-conventional@19.8.1",
|
|
@@ -17,15 +17,19 @@ const TRUST_SURFACE_PATHS = [
|
|
|
17
17
|
'plugins/harness-everything/skills/harness-everything/scripts/project-docs-resolver.js',
|
|
18
18
|
'.agents/harness-everything/project-docs.json',
|
|
19
19
|
'.agents/harness-everything/document-inventory.json',
|
|
20
|
-
'.
|
|
20
|
+
'.agents/harness-everything/errors/README.md',
|
|
21
|
+
'.github/workflows/doc-governance.yml',
|
|
21
22
|
'package.json',
|
|
22
23
|
'self-evolve/scripts/self-regression.js',
|
|
23
24
|
'ci/mechanism-test.js',
|
|
24
25
|
'ci/mutation-check.js',
|
|
25
26
|
'ci/lib/isolated-test-process.js',
|
|
26
27
|
'ci/test-helper.js',
|
|
28
|
+
'scripts/errors-sync.js',
|
|
27
29
|
'scripts/verify-all.js'
|
|
28
30
|
];
|
|
31
|
+
const MEMBERSHIP_ONLY_PATHS = ['AGENTS.md', 'SECURITY.md', 'CODE_OF_CONDUCT.md', 'PRIVACY.md', 'TERMS.md', 'docs/document-lifecycle.md'];
|
|
32
|
+
const MEMBERSHIP_ONLY_PREFIXES = ['docs/policy/', 'docs/prd/'];
|
|
29
33
|
const TRUST_SURFACE_PREFIXES = ['ci/fixtures/doc-lifecycle/scenarios/'];
|
|
30
34
|
|
|
31
35
|
function sha256(bytes) {
|
|
@@ -207,9 +211,25 @@ function sameIdentity(left, right) {
|
|
|
207
211
|
return stable(identity(left)) === stable(identity(right));
|
|
208
212
|
}
|
|
209
213
|
|
|
214
|
+
function sameMembership(left, right) {
|
|
215
|
+
if (!left || !right) return left === right;
|
|
216
|
+
return regularBlob(left) && regularBlob(right) && left.mode === right.mode;
|
|
217
|
+
}
|
|
218
|
+
|
|
210
219
|
function trustSurfacePaths(baseTree, candidateTree) {
|
|
211
220
|
const fixed = TRUST_SURFACE_PATHS.map(relativePath => ({ key: pathKey(relativePath), path: relativePath }));
|
|
212
|
-
const
|
|
221
|
+
const membershipPaths = new Map(MEMBERSHIP_ONLY_PATHS.map(relativePath => [pathKey(relativePath), relativePath]));
|
|
222
|
+
for (const sourceTree of [baseTree, candidateTree]) {
|
|
223
|
+
for (const [key, entry] of sourceTree) {
|
|
224
|
+
if (MEMBERSHIP_ONLY_PREFIXES.some(prefix => entry.pathBytes.subarray(0, Buffer.byteLength(prefix)).equals(Buffer.from(prefix)))) {
|
|
225
|
+
membershipPaths.set(key, pathText(entry.pathBytes));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const membership = [...membershipPaths]
|
|
230
|
+
.sort(([left], [right]) => Buffer.compare(Buffer.from(left, 'base64'), Buffer.from(right, 'base64')))
|
|
231
|
+
.map(([key, path]) => ({ key, path, membershipOnly: true }));
|
|
232
|
+
const fixedKeys = new Set([...fixed, ...membership].map(item => item.key));
|
|
213
233
|
const fixtureEntries = new Map();
|
|
214
234
|
for (const sourceTree of [baseTree, candidateTree]) {
|
|
215
235
|
for (const [key, entry] of sourceTree) {
|
|
@@ -220,7 +240,7 @@ function trustSurfacePaths(baseTree, candidateTree) {
|
|
|
220
240
|
const fixtures = [...fixtureEntries]
|
|
221
241
|
.sort(([left], [right]) => Buffer.compare(Buffer.from(left, 'base64'), Buffer.from(right, 'base64')))
|
|
222
242
|
.map(([key, path]) => ({ key, path }));
|
|
223
|
-
return [...fixed, ...fixtures];
|
|
243
|
+
return [...fixed, ...membership, ...fixtures];
|
|
224
244
|
}
|
|
225
245
|
|
|
226
246
|
function documentPath(pathBytes) {
|
|
@@ -278,7 +298,7 @@ function checkConfiguredPaths(baseTree, candidateTree, config) {
|
|
|
278
298
|
const after = candidateMembers.get(key) || null;
|
|
279
299
|
const entry = before || after;
|
|
280
300
|
const sameRegularMode = regularBlob(before) && regularBlob(after) && before.mode === after.mode;
|
|
281
|
-
const revised =
|
|
301
|
+
const revised = sameRegularMode && before.oid !== after.oid;
|
|
282
302
|
const identityPreserved = sameRegularMode && before.oid === after.oid;
|
|
283
303
|
const memberPath = pathText(entry.pathBytes);
|
|
284
304
|
details.push({
|
|
@@ -286,10 +306,12 @@ function checkConfiguredPaths(baseTree, candidateTree, config) {
|
|
|
286
306
|
...(glob ? { pattern: record.path } : {}),
|
|
287
307
|
path: memberPath,
|
|
288
308
|
...(memberPath === null ? { pathBase64: key } : {}),
|
|
289
|
-
state: revised ? 'revised' : identityPreserved ? 'pass' : 'review_required',
|
|
309
|
+
state: kind === 'derived' ? 'pass' : revised ? 'revised' : identityPreserved ? 'pass' : 'review_required',
|
|
290
310
|
base: identity(before),
|
|
291
311
|
candidate: identity(after),
|
|
292
|
-
...(
|
|
312
|
+
...(kind === 'derived' && !sameIdentity(before, after)
|
|
313
|
+
? { reason: 'Generated-view changes are left to ordinary regenerate-equals CI.' }
|
|
314
|
+
: revised ? { reason: 'Content revisions are tracked by Git identity but are not semantically validated by the guard.' } : {})
|
|
293
315
|
});
|
|
294
316
|
}
|
|
295
317
|
}
|
|
@@ -373,10 +395,11 @@ function run(argv) {
|
|
|
373
395
|
report.policyPreservation.state = [report.policyPreservation.governed, report.policyPreservation.derived, report.policyPreservation.configuredPaths]
|
|
374
396
|
.some(item => item.state !== 'pass') ? 'review_required' : 'pass';
|
|
375
397
|
|
|
376
|
-
report.trustSurface.paths = trustSurfacePaths(baseTree, candidateTree).map(({ key, path }) => {
|
|
398
|
+
report.trustSurface.paths = trustSurfacePaths(baseTree, candidateTree).map(({ key, path, membershipOnly }) => {
|
|
377
399
|
const before = baseTree.get(key);
|
|
378
400
|
const after = candidateTree.get(key);
|
|
379
|
-
|
|
401
|
+
const changed = membershipOnly ? !sameMembership(before, after) : !sameIdentity(before, after);
|
|
402
|
+
return { path, ...(path === null ? { pathBase64: key } : {}), base: identity(before), candidate: identity(after), changed };
|
|
380
403
|
});
|
|
381
404
|
report.trustSurface.changed = report.trustSurface.paths.filter(item => item.changed);
|
|
382
405
|
report.trustSurface.state = report.trustSurface.changed.length ? 'review_required' : 'pass';
|