harness-everything 0.48.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 +30 -0
- package/ci/mechanism-2ax-doc-lifecycle.test.js +142 -12
- package/ci/mechanism-prb-outline-review.test.js +965 -47
- package/docs/doc-workflow-reconciliation.md +47 -1
- package/docs/document-lifecycle.md +19 -0
- package/docs/repository-contract.md +1 -0
- package/hooks/scripts/lib/doc-reconciliation-provider.js +333 -9
- package/hooks/scripts/state-persist.js +5 -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/hooks/scripts/lib/doc-reconciliation-provider.js +333 -9
- package/plugins/harness-everything/hooks/scripts/state-persist.js +5 -0
- 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,35 @@
|
|
|
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
|
+
|
|
15
|
+
## [0.49.0](https://github.com/dyphn1/Harness-everything/compare/v0.48.0...v0.49.0) (2026-11-08)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **doc-reconciliation:** attribute outline writers by phase under the approved e1 contract ([fe1a3e0](https://github.com/dyphn1/Harness-everything/commit/fe1a3e0bc2f73ab7789615d116ea27696d55860f)), closes [#341](https://github.com/dyphn1/Harness-everything/issues/341) [#341](https://github.com/dyphn1/Harness-everything/issues/341)
|
|
20
|
+
* **doc-reconciliation:** honor an owner-approved dependency-artifact inventory scope ([9c08c8a](https://github.com/dyphn1/Harness-everything/commit/9c08c8aa16cf876522afd554320e9b39e7db3bcb))
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **doc-reconciliation:** bind b-to-o git add to pinned targets and allow only simple commits ([20128c3](https://github.com/dyphn1/Harness-everything/commit/20128c353e545ca3070b70048678e6a4b02df36f))
|
|
25
|
+
* **doc-reconciliation:** bind the outline review to the git identity recorded at reviewer start ([85ecc8a](https://github.com/dyphn1/Harness-everything/commit/85ecc8abf272f921c3513d666c34cc75b602a43c)), closes [#341](https://github.com/dyphn1/Harness-everything/issues/341) [#341](https://github.com/dyphn1/Harness-everything/issues/341)
|
|
26
|
+
* **doc-reconciliation:** credit outline writers only for exact pinned-target spellings ([22dc944](https://github.com/dyphn1/Harness-everything/commit/22dc944ace6efc098ecf8b865ad0b82168b2f3fa))
|
|
27
|
+
* **doc-reconciliation:** fail closed on unsupported selector globs and self-contain husky fixture ([3732b13](https://github.com/dyphn1/Harness-everything/commit/3732b1346c97b7229426a587338c800f5a5e5968)), closes [#325](https://github.com/dyphn1/Harness-everything/issues/325) [#341](https://github.com/dyphn1/Harness-everything/issues/341) [#396](https://github.com/dyphn1/Harness-everything/issues/396)
|
|
28
|
+
* **doc-reconciliation:** keep task-pin identity out of the dependency-root overlap guard ([4962c5e](https://github.com/dyphn1/Harness-everything/commit/4962c5ed13942af00d9792e760ea88bbbb06370e))
|
|
29
|
+
* **doc-reconciliation:** require a unique outline reviewer lifecycle and per-commit outline scope ([06ee12d](https://github.com/dyphn1/Harness-everything/commit/06ee12dd0e22e077cdaed39de45681706339b09a)), closes [#341](https://github.com/dyphn1/Harness-everything/issues/341)
|
|
30
|
+
* **doc-reconciliation:** require regular in-root outline targets and regular-file o tree entries ([782e19f](https://github.com/dyphn1/Harness-everything/commit/782e19f7ee448090cfde6d328210a01be02ddb40))
|
|
31
|
+
* **doc-reconciliation:** resolve absolute selectors against the workspace root ([0a1c8b1](https://github.com/dyphn1/Harness-everything/commit/0a1c8b130c6c9c6d66383c69218c20133dd30f10))
|
|
32
|
+
* **doc-reconciliation:** treat brace, bracket and dot-dot selectors as overlapping ([59037db](https://github.com/dyphn1/Harness-everything/commit/59037db846ca3b3a93f8bbf8c373b03b275015dd)), closes [#341](https://github.com/dyphn1/Harness-everything/issues/341) [#396](https://github.com/dyphn1/Harness-everything/issues/396)
|
|
33
|
+
|
|
5
34
|
## [0.48.0](https://github.com/dyphn1/Harness-everything/compare/v0.47.5...v0.48.0) (2026-11-07)
|
|
6
35
|
|
|
7
36
|
### Features
|
|
@@ -13,6 +42,7 @@ All notable changes to this project are documented in this file.
|
|
|
13
42
|
### Bug Fixes
|
|
14
43
|
|
|
15
44
|
* **doc-reconciliation:** keep outline approvals durable and detect dependency-root selector overlaps conservatively, including unsupported glob syntax and parent traversal
|
|
45
|
+
* **doc-reconciliation:** replace the blanket outline writer rule with a phase-aware policy: a coordinator write or git add/commit of the pinned target before review earns credit, and every other captured writer stays ambiguous (#341)
|
|
16
46
|
|
|
17
47
|
## [0.47.5](https://github.com/dyphn1/Harness-everything/compare/v0.47.4...v0.47.5) (2026-11-03)
|
|
18
48
|
|
|
@@ -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();
|