harness-everything 0.43.0 → 0.44.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.
@@ -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.43.0",
13
+ "version": "0.44.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.43.0",
4
+ "version": "0.44.0",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
@@ -9,12 +9,8 @@ on:
9
9
  workflow_call:
10
10
 
11
11
  jobs:
12
- self-regression:
13
- strategy:
14
- fail-fast: false
15
- matrix:
16
- os: [ubuntu-latest, windows-latest, macos-latest]
17
- runs-on: ${{ matrix.os }}
12
+ doc-governance:
13
+ runs-on: ubuntu-latest
18
14
  steps:
19
15
  - uses: actions/checkout@v7
20
16
  with:
@@ -95,6 +91,19 @@ jobs:
95
91
  process.exitCode = 1;
96
92
  }
97
93
  })();'
94
+ self-regression:
95
+ strategy:
96
+ fail-fast: false
97
+ matrix:
98
+ os: [ubuntu-latest, windows-latest, macos-latest]
99
+ runs-on: ${{ matrix.os }}
100
+ steps:
101
+ - uses: actions/checkout@v7
102
+ with:
103
+ fetch-depth: 0
104
+ - uses: actions/setup-node@v7
105
+ with:
106
+ node-version: 24
98
107
  - name: Install dependencies for regression suite
99
108
  run: npm ci
100
109
  - name: Run hermetic self-regression suite
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.44.0](https://github.com/dyphn1/Harness-everything/compare/v0.43.1...v0.44.0) (2026-10-27)
6
+
7
+ ### Features
8
+
9
+ * **governance:** accept git-aware globs in the base-owned guard ([674a96d](https://github.com/dyphn1/Harness-everything/commit/674a96d7295555926ab3bb1b8ca0204cf59dbad5)), closes [#325](https://github.com/dyphn1/Harness-everything/issues/325)
10
+
11
+ ## [0.43.1](https://github.com/dyphn1/Harness-everything/compare/v0.43.0...v0.43.1) (2026-10-27)
12
+
13
+ ### Bug Fixes
14
+
15
+ * **governance:** let the guard pass governed content revisions ([31af03d](https://github.com/dyphn1/Harness-everything/commit/31af03d02475e150210cc6b5ba0e5b90fea0163e))
16
+
17
+ ## [Unreleased]
18
+
19
+ ### Fixed
20
+
21
+ - **doc-governance:** allow regular governed-content revisions and expand base-owned governed/derived globs against Git trees, while retaining review for membership, structural, policy, trust-surface, fixture, and derived-output changes; run the guard in its own CI job.
22
+
5
23
  ## [0.43.0](https://github.com/dyphn1/Harness-everything/compare/v0.42.2...v0.43.0) (2026-10-27)
6
24
 
7
25
  ### Features
@@ -1667,8 +1667,8 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
1667
1667
  return fixtureGit(root, ['rev-parse', 'HEAD']).toString('ascii').trim();
1668
1668
  }
1669
1669
 
1670
- function createFixture() {
1671
- const root = workspace('governance-guard-git');
1670
+ function createFixture(name = 'governance-guard-git') {
1671
+ const root = workspace(name);
1672
1672
  fs.mkdirSync(path.join(root, '.fixture-hooks'), { recursive: true });
1673
1673
  fixtureGit(root, ['init', '--quiet']);
1674
1674
  fixtureGit(root, ['config', 'user.name', 'Harness Guard Test']);
@@ -1779,22 +1779,256 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
1779
1779
  if (mutation === 'mode') assert.notStrictEqual(item.base.mode, item.candidate.mode, 'oracle mode change is reported');
1780
1780
  }
1781
1781
 
1782
- for (const relativePath of ['docs/architecture.md', 'docs/governed-index.md']) {
1782
+ const unchanged = runFrozenGuard(fixture, fixture.base, fixture.base);
1783
+ assert.strictEqual(unchanged.status, 0, 'an unchanged tree passes');
1784
+ assert.strictEqual(unchanged.report.verdict, 'PASS');
1785
+
1786
+ const rawIdentity = entry => ({ mode: entry.mode, type: entry.type, oid: entry.oid });
1787
+ const configuredIdentity = (commit, relativePath) => {
1788
+ const tree = parseTreeOracle(fixtureGit(fixture.root, ['ls-tree', '-r', '-z', '--full-tree', commit]));
1789
+ return rawIdentity(tree.get(Buffer.from(relativePath).toString('base64')));
1790
+ };
1791
+ resetFixture(fixture);
1792
+ writeRelative(fixture.root, 'docs/architecture.md', 'changed governed document content\n');
1793
+ const revisedGovernedCandidate = commitFixture(fixture.root, 'revise governed document');
1794
+ const revisedGoverned = runFrozenGuard(fixture, fixture.base, revisedGovernedCandidate);
1795
+ const fixtureStatusBeforeRepeat = fixtureGit(fixture.root, ['status', '--porcelain']);
1796
+ const revisedGovernedRepeat = runFrozenGuard(fixture, fixture.base, revisedGovernedCandidate);
1797
+ const fixtureStatusAfterRepeat = fixtureGit(fixture.root, ['status', '--porcelain']);
1798
+ assert.deepStrictEqual(revisedGovernedRepeat, revisedGoverned, 'identical Git inputs produce an identical report');
1799
+ assert.deepStrictEqual(fixtureStatusAfterRepeat, fixtureStatusBeforeRepeat, 'guard execution does not mutate the fixture repository');
1800
+ assert.strictEqual(revisedGoverned.status, 0, 'regular governed content revision passes');
1801
+ assert.strictEqual(revisedGoverned.report.verdict, 'PASS');
1802
+ const revisedEntry = revisedGoverned.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/architecture.md');
1803
+ assert.strictEqual(revisedEntry.state, 'revised', 'governed content revision is informational');
1804
+ assert.deepStrictEqual(revisedEntry.base, configuredIdentity(fixture.base, 'docs/architecture.md'));
1805
+ assert.deepStrictEqual(revisedEntry.candidate, configuredIdentity(revisedGovernedCandidate, 'docs/architecture.md'));
1806
+ assert.match(revisedEntry.reason, /not semantically validated by the guard/i);
1807
+ assert.ok(revisedGoverned.report.limits.some(limit => /content revisions.*not semantically validated by the guard/i.test(limit)));
1808
+ assert.strictEqual(revisedGoverned.report.policyPreservation.configuredPaths.state, 'pass');
1809
+ assert.deepStrictEqual(revisedGoverned.report.policyPreservation.configuredPaths.missingOrChanged, []);
1810
+
1811
+ resetFixture(fixture);
1812
+ writeRelative(fixture.root, 'docs/governed-index.md', 'changed derived output\n');
1813
+ const derivedCandidate = commitFixture(fixture.root, 'change derived document');
1814
+ const derivedChange = runFrozenGuard(fixture, fixture.base, derivedCandidate);
1815
+ assert.strictEqual(derivedChange.status, 2, 'derived content change still requires review');
1816
+ assert.strictEqual(derivedChange.report.verdict, 'REVIEW_REQUIRED');
1817
+ const derivedEntry = derivedChange.report.policyPreservation.configuredPaths.missingOrChanged.find(item => item.path === 'docs/governed-index.md');
1818
+ assert.ok(derivedEntry, 'derived content change remains in review-required paths');
1819
+ assert.strictEqual(derivedEntry.state, 'review_required');
1820
+ assert.deepStrictEqual(derivedEntry.base, configuredIdentity(fixture.base, 'docs/governed-index.md'));
1821
+ assert.deepStrictEqual(derivedEntry.candidate, configuredIdentity(derivedCandidate, 'docs/governed-index.md'));
1822
+
1823
+ const globPatterns = [
1824
+ 'docs/adr/*.md',
1825
+ 'empty/*.md',
1826
+ 'corpus/one*.md', // portability-lint: allow glob pattern string, never created as a file
1827
+ 'corpus/*.md',
1828
+ 'deep/**/*.md',
1829
+ 'repeat/**/repeat/*.md',
1830
+ 'literal.v1/**/*.md'
1831
+ ];
1832
+ const corpusPaths = [
1833
+ 'docs/adr/one.md', 'docs/adr/two.md', 'generated/view.md', 'generated/nested/view.md',
1834
+ 'corpus/one.md', 'corpus/many-a.md', 'corpus/many-b.md',
1835
+ 'deep/zero.md', 'deep/a/b.md',
1836
+ 'repeat/repeat/first.md', 'repeat/a/repeat/second.md',
1837
+ 'literal.v1/top.md', 'literal.v1/sub/nested.md', 'literalXv1/no.md'
1838
+ ];
1839
+ const globFixture = createFixture('governance-guard-glob-git');
1840
+ resetFixture(globFixture);
1841
+ const globPolicy = {
1842
+ governed: globPatterns.map(pattern => ({ path: pattern, role: 'reference' })),
1843
+ derived: [{ path: 'generated/**/*.md', generator: 'build' }]
1844
+ };
1845
+ writeRelative(globFixture.root, '.agents/harness-everything/project-docs.json', `${JSON.stringify(globPolicy, null, 2)}\n`);
1846
+ for (const relativePath of corpusPaths) writeRelative(globFixture.root, relativePath, `baseline ${relativePath}\n`);
1847
+ globFixture.base = commitFixture(globFixture.root, 'glob policy baseline');
1848
+
1849
+ const globAccepted = runFrozenGuard(globFixture, globFixture.base, globFixture.base);
1850
+ assert.strictEqual(globAccepted.report.verdict, 'PASS', globAccepted.report.errors[0]?.message);
1851
+ const globSummaries = globAccepted.report.policyPreservation.configuredPaths.patterns;
1852
+ assert.ok(globSummaries, 'glob records report their base and candidate member counts');
1853
+ assert.strictEqual(globAccepted.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/adr/one.md').state, 'pass', 'unchanged glob members pass');
1854
+ assert.deepStrictEqual(runFrozenGuard(globFixture, globFixture.base, globFixture.base), globAccepted, 'glob reports are deterministic for identical Git inputs');
1855
+ const lifecycleSource = fs.readFileSync(modulePath, 'utf8');
1856
+ const lifecycleMatcherSource = lifecycleSource.match(/function globToRegExp\(glob\) \{[\s\S]*?\n\}/);
1857
+ assert.ok(lifecycleMatcherSource, 'doc-lifecycle globToRegExp remains available for the consistency check');
1858
+ const lifecycleGlobToRegExp = new Function(`${lifecycleMatcherSource[0]}\nreturn globToRegExp;`)();
1859
+ for (const pattern of globPatterns) {
1860
+ const expected = corpusPaths.filter(relativePath => lifecycleGlobToRegExp(pattern).test(relativePath)).length;
1861
+ const summary = globSummaries.find(item => item.kind === 'governed' && item.path === pattern);
1862
+ assert.ok(summary, `glob summary exists for ${pattern}`);
1863
+ assert.strictEqual(summary.baseMembers, expected, `guard matcher agrees with doc-lifecycle for ${pattern}`);
1864
+ assert.strictEqual(summary.candidateMembers, expected, `candidate matcher agrees with doc-lifecycle for ${pattern}`);
1865
+ const expectedPaths = corpusPaths.filter(relativePath => lifecycleGlobToRegExp(pattern).test(relativePath)).sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
1866
+ const actualPaths = globAccepted.report.policyPreservation.configuredPaths.paths
1867
+ .filter(item => item.kind === 'governed' && item.pattern === pattern).map(item => item.path);
1868
+ assert.deepStrictEqual(actualPaths, expectedPaths, `guard matcher returns the same members as doc-lifecycle for ${pattern}`);
1869
+ }
1870
+ assert.strictEqual(globSummaries.find(item => item.path === 'empty/*.md').baseMembers, 0, 'an empty glob is legal and reports zero members');
1871
+
1872
+ resetFixture(globFixture);
1873
+ writeRelative(globFixture.root, 'docs/adr/one.md', 'revised governed glob member\n');
1874
+ const globRevisionCandidate = commitFixture(globFixture.root, 'revise glob member');
1875
+ const globRevision = runFrozenGuard(globFixture, globFixture.base, globRevisionCandidate);
1876
+ assert.strictEqual(globRevision.status, 0, 'a regular governed glob member revision passes');
1877
+ const globRevisionEntry = globRevision.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/adr/one.md');
1878
+ assert.strictEqual(globRevisionEntry.state, 'revised');
1879
+
1880
+ resetFixture(globFixture);
1881
+ writeRelative(globFixture.root, 'docs/adr/new.md', 'new governed glob member\n');
1882
+ const globAdditionCandidate = commitFixture(globFixture.root, 'add glob member');
1883
+ const globAddition = runFrozenGuard(globFixture, globFixture.base, globAdditionCandidate);
1884
+ assert.strictEqual(globAddition.status, 2, 'a new glob member requires review');
1885
+ assert.strictEqual(globAddition.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/adr/new.md').state, 'review_required');
1886
+
1887
+ resetFixture(globFixture);
1888
+ fixtureGit(globFixture.root, ['rm', '--quiet', '--', 'docs/adr/two.md']);
1889
+ const globDeletionCandidate = commitFixture(globFixture.root, 'delete glob member');
1890
+ const globDeletion = runFrozenGuard(globFixture, globFixture.base, globDeletionCandidate);
1891
+ assert.strictEqual(globDeletion.status, 2, 'a deleted glob member requires review');
1892
+ assert.strictEqual(globDeletion.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/adr/two.md').candidate, null);
1893
+
1894
+ resetFixture(globFixture);
1895
+ fixtureGit(globFixture.root, ['mv', '--', 'docs/adr/one.md', 'docs/adr/renamed.md']);
1896
+ const globRenameCandidate = commitFixture(globFixture.root, 'rename glob member');
1897
+ const globRename = runFrozenGuard(globFixture, globFixture.base, globRenameCandidate);
1898
+ const globRenamePaths = globRename.report.policyPreservation.configuredPaths.paths;
1899
+ assert.strictEqual(globRename.status, 2, 'a rename within a glob requires review');
1900
+ assert.strictEqual(globRenamePaths.find(item => item.path === 'docs/adr/one.md').candidate, null, 'rename retains the old endpoint');
1901
+ assert.strictEqual(globRenamePaths.find(item => item.path === 'docs/adr/renamed.md').base, null, 'rename retains the new endpoint');
1902
+
1903
+ for (const mutation of ['mode', 'symlink', 'gitlink']) {
1904
+ resetFixture(globFixture);
1905
+ if (mutation === 'mode') {
1906
+ const candidate = commitFixture(globFixture.root, 'change glob member mode', 'docs/adr/one.md');
1907
+ const result = runFrozenGuard(globFixture, globFixture.base, candidate);
1908
+ assert.strictEqual(result.status, 2, 'a glob member mode change requires review');
1909
+ assert.strictEqual(result.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/adr/one.md').state, 'review_required');
1910
+ continue;
1911
+ }
1912
+ fs.rmSync(path.join(globFixture.root, 'docs', 'adr', 'one.md'));
1913
+ if (mutation === 'symlink') {
1914
+ const oid = fixtureGit(globFixture.root, ['hash-object', '-w', '--stdin'], Buffer.from('../outside-target.md\n')).toString('ascii').trim();
1915
+ fixtureGit(globFixture.root, ['add', '-A']);
1916
+ fixtureGit(globFixture.root, ['update-index', '--add', '--cacheinfo', `120000,${oid},docs/adr/one.md`]);
1917
+ } else {
1918
+ fixtureGit(globFixture.root, ['add', '-A']);
1919
+ fixtureGit(globFixture.root, ['update-index', '--add', '--cacheinfo', `160000,${globFixture.base},docs/adr/one.md`]);
1920
+ }
1921
+ fixtureGit(globFixture.root, ['commit', '--quiet', '-m', `replace glob member with ${mutation}`]);
1922
+ const candidate = fixtureGit(globFixture.root, ['rev-parse', 'HEAD']).toString('ascii').trim();
1923
+ const result = runFrozenGuard(globFixture, globFixture.base, candidate);
1924
+ assert.strictEqual(result.status, 2, `a glob member ${mutation} requires review`);
1925
+ assert.strictEqual(result.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/adr/one.md').state, 'review_required');
1926
+ }
1927
+
1928
+ resetFixture(globFixture);
1929
+ writeRelative(globFixture.root, 'generated/view.md', 'changed derived glob output\n');
1930
+ const derivedGlobCandidate = commitFixture(globFixture.root, 'change derived glob member');
1931
+ const derivedGlob = runFrozenGuard(globFixture, globFixture.base, derivedGlobCandidate);
1932
+ assert.strictEqual(derivedGlob.status, 2, 'a derived glob member content change requires review');
1933
+ assert.strictEqual(derivedGlob.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'generated/view.md').state, 'review_required');
1934
+
1935
+ if (process.platform !== 'win32') {
1936
+ resetFixture(globFixture);
1937
+ const unusualPath = 'docs/adr/new\nfile.md';
1938
+ const oid = fixtureGit(globFixture.root, ['hash-object', '-w', '--stdin'], Buffer.from('unusual glob member\n')).toString('ascii').trim();
1939
+ fixtureGit(globFixture.root, ['update-index', '--add', '--cacheinfo', `100644,${oid},${unusualPath}`]);
1940
+ fixtureGit(globFixture.root, ['commit', '--quiet', '-m', 'add unusual glob member']);
1941
+ const unusualCandidate = fixtureGit(globFixture.root, ['rev-parse', 'HEAD']).toString('ascii').trim();
1942
+ const unusual = runFrozenGuard(globFixture, globFixture.base, unusualCandidate);
1943
+ assert.strictEqual(unusual.status, 2, 'a glob matches paths containing unusual LF bytes');
1944
+ assert.strictEqual(unusual.report.policyPreservation.configuredPaths.paths.find(item => item.path === unusualPath).state, 'review_required');
1945
+ }
1946
+
1947
+ if (process.platform !== 'win32') {
1948
+ // Known divergence: doc-lifecycle.js still excludes line terminators in glob segments.
1949
+ const newlineGlobFixture = createFixture('governance-guard-newline-glob-git');
1950
+ resetFixture(newlineGlobFixture);
1951
+ writeRelative(newlineGlobFixture.root, '.agents/harness-everything/project-docs.json', `${JSON.stringify({
1952
+ governed: [{ path: 'docs/**/*.md', role: 'reference' }],
1953
+ derived: []
1954
+ }, null, 2)}\n`);
1955
+ writeRelative(newlineGlobFixture.root, 'docs/anchor.md', 'baseline direct child\n');
1956
+ writeRelative(newlineGlobFixture.root, 'docs/a\nb/file.md', 'baseline newline directory member\n');
1957
+ writeRelative(newlineGlobFixture.root, 'docs/a/b/file.md', 'baseline regular directory member\n');
1958
+ newlineGlobFixture.base = commitFixture(newlineGlobFixture.root, 'newline glob policy baseline');
1959
+
1960
+ fixtureGit(newlineGlobFixture.root, ['rm', '--quiet', '--', 'docs/a\nb/file.md']);
1961
+ const newlineDeletionCandidate = commitFixture(newlineGlobFixture.root, 'delete newline directory member');
1962
+ const newlineDeletion = runFrozenGuard(newlineGlobFixture, newlineGlobFixture.base, newlineDeletionCandidate);
1963
+
1964
+ resetFixture(newlineGlobFixture);
1965
+ writeRelative(newlineGlobFixture.root, 'docs/a\nb/new.md', 'new newline directory member\n');
1966
+ const newlineAdditionCandidate = commitFixture(newlineGlobFixture.root, 'add newline directory member');
1967
+ const newlineAddition = runFrozenGuard(newlineGlobFixture, newlineGlobFixture.base, newlineAdditionCandidate);
1968
+
1969
+ resetFixture(newlineGlobFixture);
1970
+ fixtureGit(newlineGlobFixture.root, ['rm', '--quiet', '--', 'docs/a/b/file.md']);
1971
+ const regularDeletionCandidate = commitFixture(newlineGlobFixture.root, 'delete regular directory member');
1972
+ const regularDeletion = runFrozenGuard(newlineGlobFixture, newlineGlobFixture.base, regularDeletionCandidate);
1973
+
1974
+ assert.deepStrictEqual({
1975
+ newlineDirectoryDeletion: newlineDeletion.status,
1976
+ newlineDirectoryAddition: newlineAddition.status,
1977
+ regularDirectoryDeletion: regularDeletion.status
1978
+ }, {
1979
+ newlineDirectoryDeletion: 2,
1980
+ newlineDirectoryAddition: 2,
1981
+ regularDirectoryDeletion: 2
1982
+ }, 'newline-directory deletions and additions require review; regular-directory deletion is the positive control');
1983
+ }
1984
+
1985
+ resetFixture(globFixture);
1986
+ const narrowedPolicy = JSON.parse(fs.readFileSync(path.join(globFixture.root, '.agents/harness-everything/project-docs.json'), 'utf8'));
1987
+ narrowedPolicy.governed[0].path = 'docs/adr/one.md';
1988
+ writeRelative(globFixture.root, '.agents/harness-everything/project-docs.json', `${JSON.stringify(narrowedPolicy, null, 2)}\n`);
1989
+ fixtureGit(globFixture.root, ['rm', '--quiet', '--', 'docs/adr/two.md']);
1990
+ const narrowedCandidate = commitFixture(globFixture.root, 'narrow governed glob');
1991
+ const narrowed = runFrozenGuard(globFixture, globFixture.base, narrowedCandidate);
1992
+ assert.strictEqual(narrowed.status, 2, 'candidate policy cannot narrow base glob coverage');
1993
+ assert.strictEqual(narrowed.report.policyPreservation.governed.state, 'review_required', 'candidate glob changes remain policy review');
1994
+ assert.strictEqual(narrowed.report.policyPreservation.configuredPaths.paths.find(item => item.path === 'docs/adr/two.md').candidate, null);
1995
+
1996
+ for (const unsupported of ['docs/adr/*.md?', 'docs/adr/[a].md', 'docs/adr/{a}.md', '/docs/adr/*.md', 'docs/../*.md']) {
1997
+ resetFixture(globFixture);
1998
+ const invalidPolicy = JSON.parse(fs.readFileSync(path.join(globFixture.root, '.agents/harness-everything/project-docs.json'), 'utf8'));
1999
+ invalidPolicy.governed.push({ path: unsupported, role: 'reference' });
2000
+ writeRelative(globFixture.root, '.agents/harness-everything/project-docs.json', `${JSON.stringify(invalidPolicy, null, 2)}\n`);
2001
+ const invalidCandidate = commitFixture(globFixture.root, `unsupported pattern ${unsupported}`);
2002
+ const invalid = runFrozenGuard(globFixture, globFixture.base, invalidCandidate);
2003
+ assert.strictEqual(invalid.status, 1, `unsupported glob syntax fails closed: ${unsupported}`);
2004
+ assert.strictEqual(invalid.report.verdict, 'FAIL');
2005
+ }
2006
+
2007
+ for (const mutation of ['rename', 'mode', 'symlink']) {
1783
2008
  resetFixture(fixture);
1784
- writeRelative(fixture.root, relativePath, `changed configured document ${relativePath}\n`);
1785
- const candidate = commitFixture(fixture.root, `edit configured document ${relativePath}`);
2009
+ const relativePath = 'docs/architecture.md';
2010
+ if (mutation === 'rename') {
2011
+ fixtureGit(fixture.root, ['mv', '--', relativePath, 'docs/renamed-architecture.md']);
2012
+ } else if (mutation === 'symlink') {
2013
+ fs.rmSync(path.join(fixture.root, ...relativePath.split('/')));
2014
+ const oid = fixtureGit(fixture.root, ['hash-object', '-w', '--stdin'], Buffer.from('../outside-target.md\n')).toString('ascii').trim();
2015
+ fixtureGit(fixture.root, ['add', '-A']);
2016
+ fixtureGit(fixture.root, ['update-index', '--add', '--cacheinfo', `120000,${oid},${relativePath}`]);
2017
+ fixtureGit(fixture.root, ['commit', '--quiet', '-m', 'replace governed document with symlink']);
2018
+ }
2019
+ const candidate = mutation === 'symlink'
2020
+ ? fixtureGit(fixture.root, ['rev-parse', 'HEAD']).toString('ascii').trim()
2021
+ : commitFixture(fixture.root, `${mutation} governed document`, mutation === 'mode' ? relativePath : undefined);
1786
2022
  const result = runFrozenGuard(fixture, fixture.base, candidate);
1787
- assert.strictEqual(result.status, 2, `${relativePath} content change requires review: ${result.stderr}`);
2023
+ assert.strictEqual(result.status, 2, `${mutation} of a governed entry requires review`);
1788
2024
  assert.strictEqual(result.report.verdict, 'REVIEW_REQUIRED');
1789
2025
  const item = result.report.policyPreservation.configuredPaths.missingOrChanged.find(entry => entry.path === relativePath);
1790
- assert.ok(item, `${relativePath} identity change is reported`);
1791
- const baseTree = parseTreeOracle(fixtureGit(fixture.root, ['ls-tree', '-r', '-z', '--full-tree', fixture.base]));
1792
- const candidateTree = parseTreeOracle(fixtureGit(fixture.root, ['ls-tree', '-r', '-z', '--full-tree', candidate]));
1793
- const key = Buffer.from(relativePath).toString('base64');
1794
- const rawIdentity = entry => ({ mode: entry.mode, type: entry.type, oid: entry.oid });
1795
- assert.deepStrictEqual(item.base, rawIdentity(baseTree.get(key)), 'report includes exact baseline Git identity');
1796
- assert.deepStrictEqual(item.candidate, rawIdentity(candidateTree.get(key)), 'report includes exact candidate Git identity');
1797
- assert.notStrictEqual(item.base.oid, item.candidate.oid, 'content change is identified by blob OID');
2026
+ assert.ok(item, `${mutation} is reported for the original configured path`);
2027
+ assert.strictEqual(item.state, 'review_required');
2028
+ assert.deepStrictEqual(item.base, configuredIdentity(fixture.base, relativePath));
2029
+ if (mutation === 'rename') assert.strictEqual(item.candidate, null);
2030
+ if (mutation === 'mode') assert.notStrictEqual(item.base.mode, item.candidate.mode);
2031
+ if (mutation === 'symlink') assert.strictEqual(item.candidate.mode, '120000');
1798
2032
  }
1799
2033
 
1800
2034
  resetFixture(fixture);
@@ -1805,6 +2039,17 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
1805
2039
  assert.strictEqual(deletedGoverned.report.verdict, 'REVIEW_REQUIRED');
1806
2040
  assert.ok(deletedGoverned.report.policyPreservation.configuredPaths.missingOrChanged.some(item => item.path === 'docs/architecture.md'));
1807
2041
 
2042
+ resetFixture(fixture);
2043
+ const registeredPolicy = JSON.parse(fs.readFileSync(path.join(fixture.root, '.agents/harness-everything/project-docs.json'), 'utf8'));
2044
+ registeredPolicy.governed.push({ path: 'docs/new-governed.md', role: 'reference' });
2045
+ writeRelative(fixture.root, '.agents/harness-everything/project-docs.json', `${JSON.stringify(registeredPolicy, null, 2)}\n`);
2046
+ writeRelative(fixture.root, 'docs/new-governed.md', 'newly registered governed member\n');
2047
+ const registrationCandidate = commitFixture(fixture.root, 'register governed document');
2048
+ const registration = runFrozenGuard(fixture, fixture.base, registrationCandidate);
2049
+ assert.strictEqual(registration.status, 2, 'new-member registration requires review');
2050
+ assert.strictEqual(registration.report.verdict, 'REVIEW_REQUIRED');
2051
+ assert.ok(registration.report.policyPreservation.governed.added.some(item => item.path === 'docs/new-governed.md'));
2052
+
1808
2053
  for (const relativePath of trustedPaths) {
1809
2054
  resetFixture(fixture);
1810
2055
  if (relativePath === '.agents/harness-everything/project-docs.json') {
@@ -1872,6 +2117,71 @@ checkCase('2ax. frozen Git-object guard with bootstrap and tamper controls', ()
1872
2117
  const missingCommit = runFrozenGuard(fixture, fixture.base, '0'.repeat(40));
1873
2118
  assert.notStrictEqual(missingCommit.status, 0);
1874
2119
  assert.notStrictEqual(missingCommit.report.verdict, 'PASS');
2120
+
2121
+ checkCase('2ax. CI workflow keeps the base-owned guard in a failing standalone job', () => {
2122
+ const workflowPath = path.join(REPOSITORY_ROOT, '.github/workflows/ci.yml');
2123
+ const workflow = require('js-yaml').load(fs.readFileSync(workflowPath, 'utf8'));
2124
+ const job = workflow.jobs['doc-governance'];
2125
+ assert.ok(job, 'standalone doc-governance job exists');
2126
+ assert.strictEqual(job['runs-on'], 'ubuntu-latest');
2127
+ assert.notStrictEqual(job['continue-on-error'], true, 'the job does not mask guard failures');
2128
+ assert.ok(job.steps.some(step => step.uses === 'actions/checkout@v7' && step.with['fetch-depth'] === 0), 'job fetches base Git objects');
2129
+ assert.ok(job.steps.some(step => step.uses === 'actions/setup-node@v7' && step.with['node-version'] === 24), 'job provides Node 24');
2130
+ const guardStep = job.steps.find(step => step.name === 'Run base-owned document governance guard');
2131
+ assert.ok(guardStep, 'standalone job owns the guard step');
2132
+ assert.notStrictEqual(guardStep['continue-on-error'], true, 'the guard step propagates nonzero exits');
2133
+ assert.ok(!workflow.jobs['self-regression'].steps.some(step => step.name === guardStep.name), 'self-regression no longer runs the guard');
2134
+ const command = String(guardStep.run || '').trimEnd();
2135
+ const inline = command.match(/^node -e '([\s\S]*)'$/);
2136
+ assert.ok(inline, 'guard step remains a directly executable Node script');
2137
+
2138
+ function simulate(event, base, candidate) {
2139
+ const summaryPath = path.join(tempRoot, `guard-summary-${process.pid}-${Math.random().toString(16).slice(2)}.md`);
2140
+ const result = spawnSync(process.execPath, ['-e', inline[1]], {
2141
+ cwd: fixture.root,
2142
+ encoding: 'utf8',
2143
+ timeout: 20000,
2144
+ env: {
2145
+ ...process.env,
2146
+ HARNESS_GUARD_EVENT: event,
2147
+ HARNESS_GUARD_BASE_SHA: base || '',
2148
+ HARNESS_GUARD_CANDIDATE_SHA: candidate || '',
2149
+ GITHUB_SHA: 'checkout-control',
2150
+ GITHUB_STEP_SUMMARY: summaryPath
2151
+ }
2152
+ });
2153
+ const summary = fs.existsSync(summaryPath) ? fs.readFileSync(summaryPath, 'utf8') : '';
2154
+ fs.rmSync(summaryPath, { force: true });
2155
+ assert.ifError(result.error);
2156
+ return { status: result.status, output: result.stdout, summary };
2157
+ }
2158
+
2159
+ const uncheckedEvent = simulate('push');
2160
+ assert.strictEqual(uncheckedEvent.status, 0);
2161
+ assert.match(uncheckedEvent.output, /UNCHECKED/);
2162
+ const bootstrapEvent = simulate('pull_request', noGuardBase, noGuardCandidate);
2163
+ assert.strictEqual(bootstrapEvent.status, 0, 'missing base verifier remains bootstrap-warning only');
2164
+ assert.match(bootstrapEvent.output, /BOOTSTRAP_UNCHECKED/);
2165
+ assert.match(bootstrapEvent.summary, /BOOTSTRAP_UNCHECKED/);
2166
+ const passEvent = simulate('pull_request', fixture.base, fixture.base);
2167
+ assert.strictEqual(passEvent.status, 0);
2168
+ assert.match(passEvent.output, /"verdict": "PASS"/);
2169
+
2170
+ resetFixture(fixture);
2171
+ writeRelative(fixture.root, 'docs/governed-index.md', 'changed derived output for CI simulation\n');
2172
+ const reviewCandidate = commitFixture(fixture.root, 'simulate guard review failure');
2173
+ const reviewEvent = simulate('pull_request', fixture.base, reviewCandidate);
2174
+ assert.strictEqual(reviewEvent.status, 2, 'REVIEW_REQUIRED stays a failing workflow job');
2175
+ assert.match(reviewEvent.output, /"verdict": "REVIEW_REQUIRED"/);
2176
+
2177
+ for (const exitCode of [1, 3]) {
2178
+ resetFixture(fixture);
2179
+ writeRelative(fixture.root, 'scripts/doc-governance-guard.js', `process.exit(${exitCode});\n`);
2180
+ const base = commitFixture(fixture.root, `simulate base guard exit ${exitCode}`);
2181
+ const result = simulate('pull_request', base, base);
2182
+ assert.strictEqual(result.status, exitCode, `the standalone job propagates guard exit ${exitCode}`);
2183
+ }
2184
+ });
1875
2185
  });
1876
2186
 
1877
2187
  helper.finish();
@@ -58,7 +58,7 @@ flowchart LR
58
58
  | E1 GO | A subsequent explicit owner decision based on the independent assessment, recording rationale, allowed scope, remaining uncertainty, and rollback. Neither elapsed time nor CI nor finishing E1 creates GO. |
59
59
  | #320 promotion | Still unauthorized. Generic CDG/Router migration requires E1 GO plus #325 H1–H4/paired evidence and a separate owner decision per proven interface. |
60
60
 
61
- Current disposition for #376: governance boundaries are documented in this plan; P1 inventory/disposition is implemented; and P2 includes a base-object guard wired before `npm ci` in candidate-controlled PR CI. The reviewed PR base `e40864fc24a711b96f583caa8e82f4707152f232` has no verifier, so its first invocation is `BOOTSTRAP_UNCHECKED` and does not execute candidate bytes. The guard now includes Git identities for the `ci/fixtures/doc-lifecycle/scenarios/` oracle tree; changes to those fixtures and to configured governed or derived blobs yield `REVIEW_REQUIRED`. That means independent semantic review is required, not that the content is automatically judged invalid. Ordinary ungoverned edits and new unknown documents remain allowed as `PASS` with unknown/unchecked content validation. The independent review found the earlier fixture gap and left semantic MUST preservation unresolved; re-review of the fixed guard at final exact SHAs is pending. The CI invocation remains candidate-controlled and can be removed; no external required-status check is enabled. E1 qualification is NOT YET ESTABLISHED (0 qualifying cycles); GO is NOT ISSUED. CI evidence is not E1 credit or live-host evidence.
61
+ Current disposition for #376: governance boundaries are documented in this plan; P1 inventory/disposition is implemented; and P2 runs the base-owned guard in a dedicated `doc-governance` job, separate from `self-regression`, so the other CI gates still run when the guard is non-green. The reviewed PR base `e40864fc24a711b96f583caa8e82f4707152f232` has no verifier, so its first invocation is `BOOTSTRAP_UNCHECKED` and does not execute candidate bytes. The guard includes Git identities for the `ci/fixtures/doc-lifecycle/scenarios/` oracle tree; fixture/trust-surface, policy/config, deletion/rename, mode/type, and derived-output changes yield `REVIEW_REQUIRED`. Regular governed-content revisions with the same mode are reported as `revised` with base/candidate identities and pass; the guard does not semantically validate them. That means independent semantic review is required, not that the content is automatically judged invalid. Ordinary ungoverned edits and new unknown documents remain allowed as `PASS` with unknown/unchecked content validation. The independent review found the earlier fixture gap and left semantic MUST preservation unresolved; re-review of the fixed guard at final exact SHAs is pending. The CI invocation remains candidate-controlled and can be removed; no external required-status check is enabled. E1 qualification is NOT YET ESTABLISHED (0 qualifying cycles); GO is NOT ISSUED. CI evidence is not E1 credit or live-host evidence.
62
62
 
63
63
  ## Candidate inventory and coverage
64
64
 
@@ -230,12 +230,14 @@ Private byte-identical backups of all three queue files and this turn's scope, p
230
230
 
231
231
  | Item | Status |
232
232
  |---|---|
233
- | Integration | On `pull_request`, `.github/workflows/ci.yml` extracts the guard blob from the event base and runs those bytes against the event base/head SHAs before `npm ci`; it logs the blob OID and SHA-256 and reports `GITHUB_SHA` separately. The reviewed base `e40864fc24a711b96f583caa8e82f4707152f232` has no verifier, so the current-base result is `BOOTSTRAP_UNCHECKED`; non-PR events without an external verifier report `UNCHECKED`. Neither path executes candidate guard bytes. |
234
- | Guarded identities and outcomes | The trust surface includes every Git entry under `ci/fixtures/doc-lifecycle/scenarios/`, compared by raw Git identity (`mode`, `type`, `oid`), so fixture edits, deletions, mode changes, and candidate-only additions yield `REVIEW_REQUIRED`. Configured governed and derived paths also require baseline mode and blob OID identity; a blob change yields `REVIEW_REQUIRED` and requires independent semantic review. This is not an automatic finding that the content is invalid, and the guard does not validate natural-language MUST preservation. Ordinary ungoverned edits and new unknown documents remain `PASS`, with new paths reported `unknown`/`unchecked`. |
235
- | Verification | The Node 24 focused `ci/mechanism-2ax-doc-lifecycle.test.js` passes the fixture edit/delete/mode/add cases, unchanged-fixture positive control, governed/derived blob-change cases, and ordinary ungoverned/new-unknown controls. Node 24 `--check` on both changed JavaScript files and `git diff --check` passed. Earlier reported checks also include `ci/mechanism-325-f1-doc-check.test.js` (76 PASS), `npm run test:repo-contract`, `npm run test:docs:capabilities`, and a parsed YAML step simulation for bootstrap, push `UNCHECKED`, extracted-guard `PASS`, and `REVIEW_REQUIRED` exit-2 propagation. |
233
+ | Integration | On `pull_request`, the standalone `doc-governance` job extracts the guard blob from the event base and runs those bytes against the exact event base/head SHAs; it logs the blob OID and SHA-256 and reports `GITHUB_SHA` separately. The job does not mask guard failures and runs independently of `self-regression`. The reviewed base `e40864fc24a711b96f583caa8e82f4707152f232` has no verifier, so the current-base result is `BOOTSTRAP_UNCHECKED`; non-PR events without an external verifier report `UNCHECKED`. Neither path executes candidate guard bytes. |
234
+ | Guarded identities and outcomes | The trust surface includes every Git entry under `ci/fixtures/doc-lifecycle/scenarios/`, compared by raw Git identity (`mode`, `type`, `oid`), so fixture edits, deletions, mode changes, and candidate-only additions yield `REVIEW_REQUIRED`. Regular governed-content revisions with the same mode return `PASS` as informational `revised` entries with both Git identities; the guard does not semantically validate revised content. Deletion/rename, mode/type, policy/config or new-member registration, trust-surface/fixture, and derived-output changes still return `REVIEW_REQUIRED`; derived changes retain their regeneration/parity evidence requirement. Ordinary ungoverned edits and new unknown documents remain `PASS`, with new paths reported `unknown`/`unchecked`. |
235
+ | Verification | The Node 24 focused `ci/mechanism-2ax-doc-lifecycle.test.js` covers fixture edit/delete/mode/add, governed revision pass, deletion/rename/mode/symlink review, derived-change review, unchanged-tree and ordinary ungoverned/new-unknown controls, plus the standalone-job step simulation. Node 24 `--check` on both changed JavaScript files and `git diff --check` passed. Earlier reported checks also include `ci/mechanism-325-f1-doc-check.test.js` (76 PASS), `npm run test:repo-contract`, `npm run test:docs:capabilities`, and a parsed YAML step simulation for bootstrap, push `UNCHECKED`, extracted-guard `PASS`, and `REVIEW_REQUIRED` exit-2 propagation. |
236
236
  | Evidence / limits | These are deterministic mechanism and local step-simulation results, not hosted GitHub Actions or live-host evidence. Full `npm test` and `verify:all` are not established here. The independent review of the earlier frozen guard found the fixture-oracle gap and left semantic MUST preservation unresolved; independent re-review of the fixed guard against the final exact SHAs remains pending. Candidate-controlled `.github/workflows/ci.yml` can remove the invocation, and no external required-status check is enabled. |
237
237
  | E1 / authority | P2 is CI engineering evidence only. E1 remains at 0 qualifying cycles; status is NOT GO / owner GO is NOT ISSUED, and #320 remains unauthorized. |
238
238
 
239
+ On 2026-10-27, the base-owned guard added a self-contained POSIX `*`/`**` matcher and expands governed and derived globs from the base and candidate Git trees. New or missing members and non-regular or mode-changed entries remain `REVIEW_REQUIRED`; regular governed revisions remain informational, derived object changes still require review, empty globs report zero members, and candidate policy changes remain review-required.
240
+
239
241
  ### Next session
240
242
 
241
243
  | Remaining item | Next action / boundary |
@@ -208,8 +208,8 @@ Credited reconciliation for F4 (Claude Code only, pilot ON) is **implemented, wi
208
208
 
209
209
  ### Own-repository governance guard (PR #376)
210
210
 
211
- On `pull_request`, the self-regression workflow extracts `scripts/doc-governance-guard.js` from the event base commit and runs those bytes against the exact event base/head SHAs before `npm ci`. The ordinary test suite may still run on GitHub's PR checkout SHA, which can be a synthetic merge commit; it is reported separately. The guard never falls back to the candidate copy. The reviewed PR base lacks the verifier, so the current-base result is `BOOTSTRAP_UNCHECKED` in a warning and step summary while the regular regression suite continues; push and workflow-call events without an external PR verifier report `UNCHECKED`. A nonzero result from an extracted verifier remains nonzero.
211
+ On `pull_request`, a standalone `doc-governance` job extracts `scripts/doc-governance-guard.js` from the event base commit and runs those bytes against the exact event base/head SHAs. Its nonzero result fails that job; `self-regression` and the other CI jobs run independently. The ordinary test suite may still run on GitHub's PR checkout SHA, which can be a synthetic merge commit; it is reported separately. The guard never falls back to the candidate copy. PR #376's reviewed base lacked the verifier; that historical bootstrap reported BOOTSTRAP_UNCHECKED without executing candidate bytes. Bases containing the guard (including main at 0ef0541c) execute the base-owned verifier. If a selected PR base lacks the guard, the wrapper retains the warning/step-summary-only bootstrap path with exit 0; this is unverified governance, not PASS. Every nonzero exit from an extracted verifier, including FAIL and BOOTSTRAP_UNCHECKED, fails the governance job. Other CI jobs run independently.
212
212
 
213
- The guard's trust surface includes every Git entry under `ci/fixtures/doc-lifecycle/scenarios/`, compared by raw Git identity (`mode`, `type`, `oid`). Configured governed and derived paths also require baseline mode and blob OID identity. Fixture or configured-blob changes return `REVIEW_REQUIRED`, meaning independent semantic review is required; the guard does not judge whether edited prose still satisfies its MUST obligations. Ordinary ungoverned edits and new unknown documents remain allowed as `PASS`, with unknown content validation visible.
213
+ The guard's trust surface includes every Git entry under `ci/fixtures/doc-lifecycle/scenarios/`, compared by raw Git identity (`mode`, `type`, `oid`). A regular governed-content revision with the same mode returns `PASS` as an informational `revised` entry with both Git identities; the guard does not semantically validate revised content. Deletion/rename, mode/type, policy/config or new-member registration, trust-surface/fixture, and derived-output changes still return `REVIEW_REQUIRED`. Derived changes retain their regeneration/parity evidence requirement. Ordinary ungoverned edits and new unknown documents remain allowed as `PASS`, with unknown content validation visible.
214
214
 
215
215
  This integration lives in candidate-controlled `.github/workflows/ci.yml` and can be removed by a candidate change. The independent review found the prior fixture-oracle gap; re-review of the fixed guard at final exact SHAs is pending. No external required-status check is enabled, and no live-host evidence is established. This is package and mechanism evidence only; it does not qualify E1. E1 remains at zero qualifying cycles and owner GO is not issued.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-enforcement",
3
3
  "description": "Repository inventory for the opencode enforcement plugin and its standalone helper",
4
- "version": "0.43.0",
4
+ "version": "0.44.0",
5
5
  "runtime": "index.mjs",
6
6
  "hooks": {
7
7
  "verify": "hooks/verify.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.43.0",
3
+ "version": "0.44.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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
5
5
  "author": {
6
6
  "name": "Miya Daniel",
@@ -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.43.0",
4
+ "version": "0.44.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:1bad1acf-3464-511b-b36b-492f1409c976",
5
+ "serialNumber": "urn:uuid:062d938e-8477-5564-9f89-fa21e9121182",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-10-27T06:46:51.060Z",
8
+ "timestamp": "2026-10-27T10:11:43.769Z",
9
9
  "tools": [
10
10
  {
11
11
  "vendor": "Harness Everything",
12
12
  "name": "generate-sbom.js",
13
- "version": "0.43.0"
13
+ "version": "0.44.0"
14
14
  }
15
15
  ],
16
16
  "component": {
17
17
  "type": "application",
18
- "bom-ref": "pkg:npm/harness-everything@0.43.0",
18
+ "bom-ref": "pkg:npm/harness-everything@0.44.0",
19
19
  "name": "harness-everything",
20
- "version": "0.43.0",
21
- "purl": "pkg:npm/harness-everything@0.43.0"
20
+ "version": "0.44.0",
21
+ "purl": "pkg:npm/harness-everything@0.44.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.43.0",
2036
+ "ref": "pkg:npm/harness-everything@0.44.0",
2037
2037
  "dependsOn": [
2038
2038
  "pkg:npm/%40commitlint/cli@19.8.1",
2039
2039
  "pkg:npm/%40commitlint/config-conventional@19.8.1",
@@ -121,9 +121,8 @@ function validateProjectDocs(value, label) {
121
121
  if (!isRecord(record) || typeof record.path !== 'string' || !record.path || typeof record[field] !== 'string' || !record[field]) {
122
122
  throw new Error(`${label} has a malformed ${kind} record`);
123
123
  }
124
- if (record.path.startsWith('/') || record.path.split('/').includes('..') || /[*?\[\]]/.test(record.path)) {
125
- // shortcut: the baseline registry uses exact Git paths; glob support needs a Git-aware matcher before adoption.
126
- throw new Error(`${label} has a non-exact governed or derived path: ${record.path}`);
124
+ if (record.path.startsWith('/') || record.path.split('/').includes('..') || ['?', '[', ']', '{', '}'].some(char => record.path.includes(char))) {
125
+ throw new Error(`${label} has an unsupported governed or derived path pattern: ${record.path}`);
127
126
  }
128
127
  const key = `${kind}:${record.path}`;
129
128
  if (seen.has(key)) throw new Error(`${label} has duplicate ${kind} path: ${record.path}`);
@@ -159,8 +158,9 @@ function policyChanges(base, candidate, kind) {
159
158
  path: record.path,
160
159
  base: record,
161
160
  candidate: byPath.get(record.path) || null
162
- }));
163
- const added = candidate[kind].filter(record => !basePaths.has(record.path));
161
+ })).sort((left, right) => Buffer.compare(Buffer.from(left.path), Buffer.from(right.path)));
162
+ const added = candidate[kind].filter(record => !basePaths.has(record.path))
163
+ .sort((left, right) => Buffer.compare(Buffer.from(left.path), Buffer.from(right.path)));
164
164
  return { state: missingOrChanged.length || added.length ? 'review_required' : 'pass', preserved: base[kind].length - missingOrChanged.length, missingOrChanged, added };
165
165
  }
166
166
 
@@ -173,6 +173,32 @@ function pathText(pathBytes) {
173
173
  return Buffer.from(value, 'utf8').equals(pathBytes) ? value : null;
174
174
  }
175
175
 
176
+ function globToRegExp(glob) {
177
+ const source = Buffer.from(String(glob).replace(/\\/g, '/'), 'utf8').toString('latin1');
178
+ let pattern = '^';
179
+ for (let index = 0; index < source.length; index++) {
180
+ const char = source[index];
181
+ if (char === '*' && source[index + 1] === '*') {
182
+ if (source[index + 2] === '/') {
183
+ pattern += '(?:[\\s\\S]*/)?';
184
+ index += 2;
185
+ } else {
186
+ pattern += '[\\s\\S]*';
187
+ index++;
188
+ }
189
+ } else if (char === '*') {
190
+ pattern += '[^/]*';
191
+ } else {
192
+ pattern += char.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
193
+ }
194
+ }
195
+ return new RegExp(`${pattern}$`);
196
+ }
197
+
198
+ function matchesConfiguredPath(record, pathBytes, matcher) {
199
+ return matcher ? matcher.test(pathBytes.toString('latin1')) : Buffer.from(record.path, 'utf8').equals(pathBytes);
200
+ }
201
+
176
202
  function identity(entry) {
177
203
  return entry ? { mode: entry.mode, type: entry.type, oid: entry.oid } : null;
178
204
  }
@@ -223,18 +249,57 @@ function unknownNewDocuments(baseTree, candidateTree) {
223
249
 
224
250
  function checkConfiguredPaths(baseTree, candidateTree, config) {
225
251
  const details = [];
252
+ const patterns = [];
226
253
  for (const kind of ['governed', 'derived']) {
227
254
  for (const record of config[kind]) {
228
- const key = pathKey(record.path);
229
- const before = baseTree.get(key);
230
- const after = candidateTree.get(key);
231
- if (!regularBlob(before)) throw new Error(`baseline ${kind} path is missing or not a regular file: ${record.path}`);
232
- const identityPreserved = regularBlob(after) && before.mode === after.mode && before.oid === after.oid;
233
- details.push({ kind, path: record.path, state: identityPreserved ? 'pass' : 'review_required', base: identity(before), candidate: identity(after) });
255
+ const glob = record.path.includes('*');
256
+ const matcher = glob ? globToRegExp(record.path) : null;
257
+ const collect = sourceTree => {
258
+ if (!glob) {
259
+ const key = pathKey(record.path);
260
+ const entry = sourceTree.get(key);
261
+ return new Map(entry ? [[key, entry]] : []);
262
+ }
263
+ const matches = new Map();
264
+ for (const [key, entry] of sourceTree) {
265
+ if (matchesConfiguredPath(record, entry.pathBytes, matcher)) matches.set(key, entry);
266
+ }
267
+ return matches;
268
+ };
269
+ const baseMembers = collect(baseTree);
270
+ const candidateMembers = collect(candidateTree);
271
+ if (!glob && !regularBlob(baseMembers.values().next().value)) {
272
+ throw new Error(`baseline ${kind} path is missing or not a regular file: ${record.path}`);
273
+ }
274
+ if (glob) patterns.push({ kind, path: record.path, baseMembers: baseMembers.size, candidateMembers: candidateMembers.size });
275
+ const keys = new Set([...baseMembers.keys(), ...candidateMembers.keys()]);
276
+ for (const key of keys) {
277
+ const before = baseMembers.get(key) || null;
278
+ const after = candidateMembers.get(key) || null;
279
+ const entry = before || after;
280
+ const sameRegularMode = regularBlob(before) && regularBlob(after) && before.mode === after.mode;
281
+ const revised = kind === 'governed' && sameRegularMode && before.oid !== after.oid;
282
+ const identityPreserved = sameRegularMode && before.oid === after.oid;
283
+ const memberPath = pathText(entry.pathBytes);
284
+ details.push({
285
+ kind,
286
+ ...(glob ? { pattern: record.path } : {}),
287
+ path: memberPath,
288
+ ...(memberPath === null ? { pathBase64: key } : {}),
289
+ state: revised ? 'revised' : identityPreserved ? 'pass' : 'review_required',
290
+ base: identity(before),
291
+ candidate: identity(after),
292
+ ...(revised ? { reason: 'Content revisions are tracked by Git identity but are not semantically validated by the guard.' } : {})
293
+ });
294
+ }
234
295
  }
235
296
  }
236
- const failures = details.filter(item => item.state !== 'pass');
237
- return { state: failures.length ? 'review_required' : 'pass', paths: details, missingOrChanged: failures };
297
+ patterns.sort((left, right) => Buffer.compare(Buffer.from(left.kind), Buffer.from(right.kind)) || Buffer.compare(Buffer.from(left.path), Buffer.from(right.path)));
298
+ details.sort((left, right) => Buffer.compare(Buffer.from(left.pathBase64 || Buffer.from(left.path, 'utf8').toString('base64'), 'base64'), Buffer.from(right.pathBase64 || Buffer.from(right.path, 'utf8').toString('base64'), 'base64'))
299
+ || Buffer.compare(Buffer.from(left.kind), Buffer.from(right.kind))
300
+ || Buffer.compare(Buffer.from(left.pattern || ''), Buffer.from(right.pattern || '')));
301
+ const failures = details.filter(item => item.state === 'review_required');
302
+ return { state: failures.length ? 'review_required' : 'pass', patterns, paths: details, missingOrChanged: failures };
238
303
  }
239
304
 
240
305
  function parseArgs(argv) {
@@ -270,7 +335,7 @@ function run(argv) {
270
335
  reviewAuthority: 'External reviewer only; candidate-controlled approval files or reports are never read.',
271
336
  limits: [
272
337
  'Candidate-owned CI is not independent enforcement; bootstrap remains unchecked until an external reviewer binds frozen guard bytes to final exact SHAs.',
273
- 'Document semantics and derived-view parity are not validated; configured documents are required to keep their baseline Git identities.'
338
+ 'Governed content revisions are visible by Git identity but are not semantically validated by the guard; derived-view parity is not validated.'
274
339
  ],
275
340
  errors: []
276
341
  };