Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ jobs:
- name: Self-tests for the ADR ruleset generator
run: node --test .harness/scripts/generate-adr-rulesets.test.mjs

# GT-598: `native-evaluability-snapshot.json` says of itself that it is a
# capture, and until now nothing captured it — it was written by hand, and
# it drifted. It still declared `documentation-only: 129` after Core moved
# to 136 (the same seven ADR rulesets the step above exists for), and its
# guard could not see that, because it compared the snapshot against six
# numbers hardcoded in the test: the same six the snapshot contained.
#
# Drift here is not contained. `build-iso-5055-mapping.mjs` stamps
# nativeEvaluability onto all 388 rows of the mapping FROM this file, so a
# stale class is laundered into the larger artifact and overstates the
# handler backlog. The order of these two steps is the order of the chain.
- name: Native evaluability snapshot is a faithful capture of Core's triage
run: node src/rulesets/standards/capture-native-evaluability-snapshot.mjs --check

- name: ISO/IEC 5055 mapping guard
run: node --test src/rulesets/standards/iso-5055-mapping.test.mjs

# GT-630: the derived artifacts have a dependency ORDER — the executive
# summary is built FROM the maturity reconciliation, which is built from
# the board. Generate the summary first and it captures a value the
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ bower_components
build/Release

# Dependency directories
node_modules/
# No trailing slash on purpose: `node_modules/` matches directories ONLY, and a
# fresh worktree has no node_modules of its own — the documented workaround is to
# symlink the main checkout's. A symlink is not a directory, so the slashed form
# left it untracked-but-visible, and `git add -A` staged a link to an absolute
# path on one machine.
node_modules
.harness/bin/
jspm_packages/

Expand Down
33 changes: 33 additions & 0 deletions .harness/scripts/ci/46-validate-derived-artifact-order.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,39 @@ export const CHAIN = [
consumes: ['src/packages/mcp-server/src/mcp/abac-evaluator.ts'],
writes: ['src/rulesets/opa/abac-mcp-tool-access.rego'],
},
{
name: 'native evaluability snapshot',
producer: 'src/rulesets/standards/capture-native-evaluability-snapshot.mjs',
checkArgs: ['--check'],
// Same shape as the ABAC link: derived from the RUNTIME, not from a data
// file. The producer runs Core's real triage through ts-node, so its real
// inputs are the triage entry point and the two sources that decide a
// rule's class. Before GT-633 there was no producer at all — the file said
// it was a capture, nothing captured it, and it drifted 129 vs Core's 136.
// Replay-safe: `capturedOn` is deliberately sticky while the classification
// is unchanged, so the fixed-point pass below stays byte-identical instead
// of rewriting a date on every run.
consumes: [
'src/packages/core-domain/test/rule-corpus-triage.ts',
'src/packages/core-domain/src/application/validators/rule-evaluability.ts',
'src/packages/core-domain/src/application/validators/evaluators/native-evaluator.ts',
],
writes: ['src/rulesets/standards/native-evaluability-snapshot.json'],
},
{
name: 'ISO/IEC 5055 corpus mapping',
producer: 'src/rulesets/standards/build-iso-5055-mapping.mjs',
checkArgs: ['--check'],
// The edge this order exists for: the builder stamps `nativeEvaluability`
// onto all 388 mapping rows from the snapshot, so rebuilding BEFORE
// recapturing launders a stale class into an artifact five times the size
// of its input and overstates the handler backlog (GT-598, GT-633).
consumes: ['src/rulesets/standards/native-evaluability-snapshot.json'],
writes: [
'src/rulesets/standards/iso-5055-mapping.json',
'src/rulesets/standards/iso-5055-mapping.csv',
],
},
{
name: 'maturity reconciliation',
producer: '.harness/scripts/ci/09-reconcile-maturity.mjs',
Expand Down
52 changes: 38 additions & 14 deletions .harness/scripts/ci/46-validate-derived-artifact-order.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test('the real repository is current and at a fixed point', () => {
const { status, out } = run(resolve(__dirname, '../../..'));
assert.equal(status, 0, out);
assert.match(out, /at a fixed point/);
assert.match(out, /links declared \.+ 3/);
assert.match(out, /links declared \.+ 5/);
});

test('the guard leaves the real tree byte-identical', () => {
Expand All @@ -47,6 +47,9 @@ test('the guard leaves the real tree byte-identical', () => {
// pass for the wrong reason.
const repo = resolve(__dirname, '../../..');
const artifacts = [
'src/rulesets/standards/native-evaluability-snapshot.json',
'src/rulesets/standards/iso-5055-mapping.json',
'src/rulesets/standards/iso-5055-mapping.csv',
'reference/core/control-center/maturity-reports/maturity-reconciliation.json',
'reference/core/control-center/maturity-reports/executive-summary.md',
'reference/core/control-center/maturity-reports/executive-summary.es.md',
Expand All @@ -60,25 +63,45 @@ test('the guard leaves the real tree byte-identical', () => {


/**
* The chain gained a first link (the ABAC rego, GT-602). These fixtures are about
* ORDER, not about ABAC, so each mini-repo gets a trivial producer/artifact pair
* for it — otherwise the shape check trips before the behaviour under test runs.
* The chain leads with links these fixtures are not about — the ABAC rego
* (GT-602) and the two standards links (GT-633: capture the native-evaluability
* snapshot, then rebuild the ISO/IEC 5055 mapping from it). These fixtures are
* about ORDER, so each mini-repo gets a trivial producer/artifact pair for every
* one of them — otherwise the shape check trips before the behaviour under test
* runs.
*/
const ABAC_STUB = {
'.harness/scripts/generate-abac-tool-sets.mjs':
"import fs from 'node:fs';\nconst f = process.cwd() + '/src/rulesets/opa/abac-mcp-tool-access.rego';\n" +
"if (process.argv.includes('--check')) process.exit(fs.readFileSync(f, 'utf8') === 'stable\\n' ? 0 : 1);\n" +
"fs.writeFileSync(f, 'stable\\n');\n",
const stubProducer = (artifacts) =>
"import fs from 'node:fs';\n" +
`const files = ${JSON.stringify(artifacts)}.map((f) => process.cwd() + '/' + f);\n` +
"if (process.argv.includes('--check')) process.exit(files.every((f) => fs.readFileSync(f, 'utf8') === 'stable\\n') ? 0 : 1);\n" +
"for (const f of files) fs.writeFileSync(f, 'stable\\n');\n";

const UPSTREAM_STUBS = {
'.harness/scripts/generate-abac-tool-sets.mjs': stubProducer(['src/rulesets/opa/abac-mcp-tool-access.rego']),
'src/packages/mcp-server/src/mcp/abac-evaluator.ts': '// stub\n',
'src/rulesets/opa/abac-mcp-tool-access.rego': 'stable\n',

'src/rulesets/standards/capture-native-evaluability-snapshot.mjs':
stubProducer(['src/rulesets/standards/native-evaluability-snapshot.json']),
'src/packages/core-domain/test/rule-corpus-triage.ts': '// stub\n',
'src/packages/core-domain/src/application/validators/rule-evaluability.ts': '// stub\n',
'src/packages/core-domain/src/application/validators/evaluators/native-evaluator.ts': '// stub\n',
'src/rulesets/standards/native-evaluability-snapshot.json': 'stable\n',

'src/rulesets/standards/build-iso-5055-mapping.mjs': stubProducer([
'src/rulesets/standards/iso-5055-mapping.json',
'src/rulesets/standards/iso-5055-mapping.csv',
]),
'src/rulesets/standards/iso-5055-mapping.json': 'stable\n',
'src/rulesets/standards/iso-5055-mapping.csv': 'stable\n',
};

// --- the shape of the declaration itself ------------------------------------

describe('chain declaration (anti-vacuous)', () => {
const fixture = (name, files) => {
const root = join(sandbox, name);
for (const [rel, body] of Object.entries({ ...ABAC_STUB, ...files })) {
for (const [rel, body] of Object.entries({ ...UPSTREAM_STUBS, ...files })) {
mkdirSync(dirname(join(root, rel)), { recursive: true });
writeFileSync(join(root, rel), body);
if (rel.endsWith('.mjs')) chmodSync(join(root, rel), 0o755);
Expand Down Expand Up @@ -128,7 +151,7 @@ describe('stale versus out-of-order', () => {
mkdirSync(dirname(join(root, rel)), { recursive: true });
writeFileSync(join(root, rel), body);
};
for (const [rel, body] of Object.entries(ABAC_STUB)) w(rel, body);
for (const [rel, body] of Object.entries(UPSTREAM_STUBS)) w(rel, body);
w('reference/core/control-center/gaps/gap-tracking.md', `count: ${boardCount}\n`);
w('reference/core/control-center/maturity-reports/maturity-assessment.md', '# assessment\n');
w('reference/core/control-center/maturity-reports/maturity-reconciliation.json', `{"count":${reconCount}}\n`);
Expand Down Expand Up @@ -177,7 +200,7 @@ fs.writeFileSync(dir + 'executive-summary.es.md', es);
const root = miniRepo('stale-upstream', { boardCount: 9, reconCount: 7, summaryCount: 7 });
const { status, out } = run(root);
assert.equal(status, 1, out);
assert.match(out, /maturity reconciliation is STALE \(link 2 of 3\)/);
assert.match(out, /maturity reconciliation is STALE \(link 4 of 5\)/);
assert.match(out, /Stopping at the FIRST stale link on purpose/);
assert.doesNotMatch(out, /executive governance summary is STALE/);
});
Expand Down Expand Up @@ -239,7 +262,8 @@ fs.writeFileSync(dir + 'executive-summary.es.md', es);
assert.equal(status, 1, out);
assert.match(out, /differ after replaying the chain IN ORDER/);
assert.match(out, /Fix by regenerating in the declared order/);
// The reconciler is link 2 now that the ABAC rego leads the chain.
assert.match(out, /2\. node \.harness\/scripts\/ci\/09-reconcile-maturity\.mjs/);
// The reconciler is link 4 now that the ABAC rego and the two standards
// links (GT-633) lead the chain.
assert.match(out, /4\. node \.harness\/scripts\/ci\/09-reconcile-maturity\.mjs/);
});
});
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

## [Unreleased]

## [1.2.2] - 2026-07-29

**Which packages this version covers.** Only `@beyondnet/evolith-cli` and
`@beyondnet/evolith-mcp`, the two that declared the stale dependency range. No
other package changed, and republishing them would only add noise to the
registry.

### Bug Fixes

* **deps:** the CLI and the MCP server now resolve `@beyondnet/evolith-sdk@^2.0.0`
instead of `^1.1.0` (GT-634). The SDK publishes `1.0.0`, `1.1.0` and `2.0.0`
with nothing in between, so a caret range under 1.x pinned **exactly 1.1.0** —
published 2026-07-18, five days before the security wave that shipped in 2.0.0
on 2026-07-27. Both consumers were published on 2026-07-28, after 2.0.0 existed,
still pointing at the 1.x line, so `npm install @beyondnet/evolith-cli@latest`
installed a pre-wave SDK. **The exposure described in the 1.2.0 section below is
therefore not closed by 1.2.0 alone for this dependency; it is closed here.**

The lockfile made it concrete rather than cosmetic: it pinned the registry
tarball of `sdk@1.1.0`, with integrity hashes, at
`src/sdk/cli/node_modules/@beyondnet/evolith-sdk` and
`src/packages/mcp-server/node_modules/@beyondnet/evolith-sdk`. A clean `npm ci`
fetched the pre-wave SDK and nested it inside each consumer, where it shadows
the workspace link — the top-level symlink to the local 2.0.0 is what a
developer reads, and the nested 1.1.0 is what a fresh install resolves. Both
nested entries disappear once the range is `^2.0.0`.

Neither package re-exports the SDK, and both use only `EvolithRestClient`, so
2.0.0's breaking change (payload types re-exported from `core-domain`,
`.passed` → `.verdict`, `'info'` severity retired) reaches no public API of
either. That is why this is a patch and not a minor.

## [1.2.0] - 2026-07-27

Changes accumulated since the 1.0.0 release of 2026-06-28. Automated changelog
Expand Down
20 changes: 4 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading