Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ae3a6e1
Merge pull request #274 from beyondnetcode/develop
beyondnetPeru Jul 29, 2026
10b5251
feat(cli): prove the machine channel across the registry, and govern …
beyondnetPeru Jul 29, 2026
b319a6b
fix(cli): make exit code agree with envelope.success across the taxonomy
beyondnetPeru Jul 29, 2026
ad51f24
docs(board): GT-580 criteria 2+3 closed for the JSON channel
beyondnetPeru Jul 29, 2026
7f3fd09
chore(maturity): reconcile after GT-580's new ruleset
beyondnetPeru Jul 29, 2026
e21e162
fix(cli): correct the sweep's own invariant, and a fifth taxonomy mis…
beyondnetPeru Jul 29, 2026
457d50f
Merge pull request #278 from beyondnetcode/claude/affectionate-heisen…
beyondnetPeru Jul 29, 2026
74fd2f9
fix(cli): use real --arch flag in E2E architecture validation test
beyondnetPeru Jul 29, 2026
b0824a6
fix(standards): make the evaluability guard read Core, and capture th…
beyondnetPeru Jul 29, 2026
c856752
Merge pull request #276 from beyondnetcode/claude/fervent-cohen-08ea01
beyondnetPeru Jul 29, 2026
345d4ab
Merge pull request #280 from beyondnetcode/claude/relaxed-chebyshev-c…
beyondnetPeru Jul 29, 2026
0bd58ff
fix(evidence): qualify five recorded test commands with the path they…
beyondnetPeru Jul 29, 2026
89366b4
chore(ci): ratchet the dead-reference budget 305 -> 304, from the cou…
beyondnetPeru Jul 29, 2026
beb6f50
Merge pull request #281 from beyondnetcode/fix/ratchet-budget
beyondnetPeru Jul 29, 2026
f34a10f
fix(evidence): migrate the recorded closure commands onto the paths t…
beyondnetPeru Jul 29, 2026
600dd7a
chore(ci): ratchet the dead-reference budget 304 -> 40, from the coun…
beyondnetPeru Jul 29, 2026
104fd7a
Merge pull request #283 from beyondnetcode/fix/evidence-path-migration
beyondnetPeru Jul 30, 2026
e8e618c
fix(deps)!: the CLI and MCP must resolve the SDK that carries the sec…
beyondnetPeru Jul 29, 2026
86d3707
fix(release): carry the two pins that move with the CLI version (GT-634)
beyondnetPeru Jul 29, 2026
dacc317
fix(release): regenerate the product inventory, which pins the CLI ve…
beyondnetPeru Jul 29, 2026
7ee31cf
fix(release): the install-smoke gate cannot run under a dry-run publi…
beyondnetPeru Jul 29, 2026
d1ea72a
fix(guard): stop the evidence guard blaming the evidence for two pars…
beyondnetPeru Jul 30, 2026
f84fe73
Merge pull request #285 from beyondnetcode/release/npm-1.2.2-to-main
beyondnetPeru Jul 30, 2026
bf173be
chore(ci): ratchet the dead-reference budget 40 -> 38, from the count…
beyondnetPeru Jul 30, 2026
0aee3eb
Merge pull request #286 from beyondnetcode/fix/evidence-guard-parser
beyondnetPeru Jul 30, 2026
6f0ee00
feat(ci): fail when a security fix is committed and unpublished, clos…
beyondnetPeru Jul 30, 2026
faf49dc
feat(guard): declare the capture -> mapping order in the derived-arti…
beyondnetPeru Jul 30, 2026
29e940d
Merge pull request #287 from beyondnetcode/fix/chain-links-evaluability
beyondnetPeru Jul 30, 2026
6c30a78
Merge pull request #288 from beyondnetcode/feat/gt-624-security-publi…
beyondnetPeru Jul 30, 2026
c322127
docs(gaps): register GT-633, GT-634 and GT-635 on main — the rows, an…
beyondnetPeru Jul 30, 2026
09580c0
Merge pull request #289 from beyondnetcode/docs/gt-633-634-635-rows
beyondnetPeru Jul 30, 2026
f590189
docs(harness): record the fixture-vacuity case in the anti-vacuous do…
beyondnetPeru Jul 30, 2026
ff03a22
docs(gaps): close GT-634 — 1.2.2 is published, and the row's security…
beyondnetPeru Jul 30, 2026
a7639e1
Merge pull request #293 from beyondnetcode/docs/gt-634-closure
beyondnetPeru Jul 30, 2026
ad09d71
Merge pull request #292 from beyondnetcode/docs/anti-vacuous-fixture-…
beyondnetPeru Jul 30, 2026
a34b686
fix(standards): bring the one-renderer design to main, and fix a stic…
beyondnetPeru Jul 30, 2026
42f4b5c
Merge pull request #295 from beyondnetcode/fix/standards-one-renderer…
beyondnetPeru Jul 30, 2026
c2a763d
docs(gaps): register GT-638 — the board has no id allocator
beyondnetPeru Jul 30, 2026
b75522f
Merge pull request #298 from beyondnetcode/docs/gt-638-id-allocator
beyondnetPeru Jul 30, 2026
96019d1
docs(gaps): register GT-639 — the same gap gets fixed twice, because …
beyondnetPeru Jul 30, 2026
a2d66a3
Merge pull request #299 from beyondnetcode/docs/gt-639-duplicated-work
beyondnetPeru Jul 30, 2026
8c96bda
feat(ci): one gap id, one gap — the allocator guard, closing GT-638
beyondnetPeru Jul 30, 2026
a34548a
Merge pull request #300 from beyondnetcode/feat/gt-638-id-allocator-g…
beyondnetPeru Jul 30, 2026
2d41c2b
docs(gaps): renumber GT-633 -> GT-640, the collision the new guard found
beyondnetPeru Jul 30, 2026
981496c
Merge pull request #301 from beyondnetcode/docs/renumber-gt633-to-gt640
beyondnetPeru Jul 30, 2026
1b807f0
Merge branch 'main' into develop — resolve the 17-file divergence
beyondnetPeru Jul 30, 2026
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
27 changes: 25 additions & 2 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,32 @@ jobs:
- name: A port inventory does not read as a capability claim
run: node .harness/scripts/ci/45-validate-port-inventory-honesty.mjs --verbose

# GT-624: the 2026-07-23 security wave sat unpublished until 2026-07-27
# while SECURITY.md declared the 1.1.x line "actively patched". Nothing
# detected it; an audit did. This asks the registry what is published, finds
# the commit where each package's published version was SET, and fails on any
# commit after it whose type or scope marks it as security. It needs the
# network — a registry answer is the only honest source for "published", as
# the newest v* tag here is v1.1.0 while npm serves 1.2.2.
- name: No security fix is committed but unpublished
run: node .harness/scripts/ci/48-validate-security-publish-lag.mjs --verbose

# GT-638: a gap id is chosen by reading the highest GT-* on whichever branch
# you are on, so two parallel sessions allocate the same number and find out
# at merge time. `8449af3d` had to renumber GT-634 -> GT-637 for exactly this.
# The comparison needs the BASE branch, which a shallow checkout does not
# have — fetch it explicitly rather than letting the guard fail for the wrong
# reason.
- name: One gap id, one gap
run: |
git fetch --no-tags --depth=1 origin "${{ github.base_ref || github.event.repository.default_branch }}"
node .harness/scripts/ci/49-validate-gap-id-allocation.mjs --verbose

- name: Self-tests for the governance guards
run: |
node --test .harness/scripts/ci/42-validate-guard-denominators.test.mjs
node --test .harness/scripts/ci/48-validate-security-publish-lag.test.mjs
node --test .harness/scripts/ci/49-validate-gap-id-allocation.test.mjs
node --test .harness/scripts/ci/41-validate-evidence-commands.test.mjs
node --test .harness/scripts/ci/43-validate-guard-negative-fixtures.test.mjs
node --test .harness/scripts/ci/44-validate-adr-implementation-status.test.mjs
Expand All @@ -459,7 +482,7 @@ jobs:
- name: Execute the board's recorded validationCommands (report mode)
run: node .harness/scripts/ci/41-validate-evidence-commands.mjs --execute --verbose

# Ratchet, not a cliff. The budget is 21, which is what THIS RUNNER counts.
# Ratchet, not a cliff. The budget is 19, which is what THIS RUNNER counts.
#
# It was briefly lowered to 303 on the strength of the guard's computed
# "ratchet basis" — 288 dead locally plus 15 that resolve only through
Expand Down Expand Up @@ -490,7 +513,7 @@ jobs:
# Lower it as records are repaired, from a number this job printed; at 0,
# delete the flag and this becomes the strict gate.
- name: Dead-reference ratchet (must not grow)
run: node .harness/scripts/ci/41-validate-evidence-commands.mjs --strict --max-dead 21
run: node .harness/scripts/ci/41-validate-evidence-commands.mjs --strict --max-dead 19

# RETIRED 2026-07-27 (GT-570): the `publish-npm` job lived here and published
# ONLY `src/sdk/cli`, with `--tag beta` and no provenance, writing the token
Expand Down
18 changes: 11 additions & 7 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,17 @@ jobs:
- name: Self-tests for the ADR ruleset generator
run: node --test .harness/scripts/generate-adr-rulesets.test.mjs

# GT-598 / GT-633: `native-evaluability-snapshot.json` says of itself that it
# is a capture, and for a long time nothing captured it — it was written by
# hand and drifted, while its guard compared the file against six numbers
# typed into the test: the same six the file contained. Drift is not
# contained either, because `build-iso-5055-mapping.mjs` stamps
# nativeEvaluability onto all 388 mapping rows FROM this file. The order of
# these two steps is the order of the chain.
# 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

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
70 changes: 56 additions & 14 deletions .harness/scripts/ci/41-validate-evidence-commands.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,56 @@ const SAFE_GIT_SUBS = new Set([
/** `npm <sub>` classification. */
const NPM_MUTATING = new Set(['publish', 'ci', 'install', 'i', 'link', 'version', 'pack', 'audit']);

/**
* Flags whose VALUE is the next token, so the value is not an operand.
*
* Without this, `npm run --workspace src/sdk/cli test` reports that the script
* `"src/sdk/cli"` is not declared: the first non-dash token after `run` is the
* flag's value, not the script name. That form is valid npm, so the recorded
* command was right and the parser was wrong — and the failure LOOKED like stale
* evidence, which is the worst way for a guard to be wrong.
*/
const VALUE_TAKING_FLAGS = new Set([
'--workspace', '-w', '--prefix', '-C', '--filter', '--registry', '--loglevel', '--tag',
]);

/**
* Built-in subcommands of npm / pnpm / yarn that are NOT package scripts.
*
* `pnpm info <pkg>` and `yarn info <pkg>` query the registry. Reading `info` as a
* script name and then failing because no `"info"` script is declared blames the
* evidence for a command that runs perfectly well.
*/
const PM_BUILTIN_SUBCOMMANDS = new Set([
'info', 'view', 'ls', 'list', 'exec', 'why', 'outdated', 'dlx', 'create', 'init',
'ping', 'search', 'whoami', 'config', 'licenses', 'dedupe', 'store', 'root', 'bin',
'prefix', 'explain', 'doctor', 'help', 'add', 'remove', 'update', 'up', 'dist-tag',
'deprecate', 'owner', 'access', 'team', 'star', 'unpublish', 'login', 'logout',
]);

/**
* Index of the first true operand at or after `from`, skipping flags AND the
* values they consume. -1 when the token list is nothing but flags.
*/
function firstOperandIndex(tokens, from) {
for (let i = from; i < tokens.length; i += 1) {
const tok = tokens[i];
if (tok === '--') return -1; // everything after `--` belongs to the script
if (tok.startsWith('-')) {
if (VALUE_TAKING_FLAGS.has(tok)) i += 1; // its value is not an operand
continue;
}
return i;
}
return -1;
}

/** The operand itself, or null. */
function firstOperand(tokens, from) {
const i = firstOperandIndex(tokens, from);
return i === -1 ? null : tokens[i];
}

/**
* Scripts this runner refuses to spawn even though they are `node` and
* resolvable. Each entry needs a reason: a bare denylist rots into a no-op.
Expand Down Expand Up @@ -512,17 +562,6 @@ function pathOperands(cmd) {
* workspace's own VALUE token (which does not start with `-`) is mistaken for
* the script name.
*/
function npmScriptOperand(tokens) {
for (let i = 0; i < tokens.length; i++) {
const tok = tokens[i];
if (tok === '--workspace' || tok === '-w') { i += 1; continue; }
if (tok.startsWith('--workspace=')) continue;
if (tok.startsWith('-')) continue;
return tok;
}
return undefined;
}

/**
* Resolve a command's referent.
* @returns {{ status: 'resolved'|'dead'|'unchecked', detail: string }}
Expand Down Expand Up @@ -558,11 +597,14 @@ export function resolveCommand(cmd, ctx) {
if (!ws) return { status: 'dead', detail: `npm workspace does not exist: ${wsRef}` };
}
}
const sub = t[1];
// The subcommand is the first OPERAND, not `t[1]`: `npm --workspace w run test`
// puts a flag there. Same skipping rule as the script name below.
const subIdx = firstOperandIndex(t, 1);
const sub = subIdx === -1 ? null : t[subIdx];
let scriptName = null;
if (sub === 'run' || sub === 'run-script') scriptName = npmScriptOperand(t.slice(2));
if (sub === 'run' || sub === 'run-script') scriptName = firstOperand(t, subIdx + 1);
else if (sub === 'test') scriptName = 'test';
else if (sub && !sub.startsWith('-') && !NPM_MUTATING.has(sub) && sub !== 'exec' && sub !== 'ls' && sub !== 'view') scriptName = sub;
else if (sub && !NPM_MUTATING.has(sub) && !PM_BUILTIN_SUBCOMMANDS.has(sub)) scriptName = sub;

const pkg = ws ? ws.pkg : readJson(join(root, cmd.cwd || '.', 'package.json'));
if (scriptName && pkg?.scripts && !Object.prototype.hasOwnProperty.call(pkg.scripts, scriptName)) {
Expand Down
52 changes: 52 additions & 0 deletions .harness/scripts/ci/41-validate-evidence-commands.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ before(() => {
mkdirSync(join(fixtureRoot, 'scripts'), { recursive: true });
mkdirSync(join(fixtureRoot, 'pkgs', 'alpha'), { recursive: true });

// `scripts` must be PRESENT and must not contain the names under test. The
// undeclared-script branch is guarded by `pkg?.scripts`, so a root with no
// scripts block skips it entirely — and every assertion about an undeclared
// root script would pass without exercising anything.
writeFileSync(join(fixtureRoot, 'package.json'), JSON.stringify({
name: 'fixture-root', private: true, workspaces: ['pkgs/*'],
scripts: { lint: 'echo lint' },
}));
writeFileSync(join(fixtureRoot, 'pkgs', 'alpha', 'package.json'), JSON.stringify({
name: '@fixture/alpha', version: '1.0.0', scripts: { test: 'echo ok' },
Expand Down Expand Up @@ -167,6 +172,53 @@ describe('resolveCommand', () => {
assert.equal(r.status, 'resolved');
});

// --- GT-598 follow-on: two parser defects that BLAMED THE EVIDENCE ---------
//
// Both produced a `dead` verdict for a command that runs perfectly well, which
// is the worst way for this guard to be wrong: it reads as stale evidence and
// invites someone to "repair" a record that was already correct.

test('the script name is not the value of --workspace (flag order must not matter)', () => {
// `npm run --workspace <path> <script>` is valid npm. The parser used to take
// the first non-dash token after `run`, which is the flag's VALUE, and then
// report that the script "pkgs/alpha" is not declared.
const r = resolveCommand(extractCommand('npm run --workspace pkgs/alpha test'), ctx());
assert.equal(r.status, 'resolved', r.detail);
assert.match(r.detail, /script test/);
});

test('…and the same when the flag precedes the subcommand entirely', () => {
const r = resolveCommand(extractCommand('npm --workspace pkgs/alpha run test'), ctx());
assert.equal(r.status, 'resolved', r.detail);
assert.match(r.detail, /script test/);
});

test('a genuinely undeclared script is STILL dead when the flag comes first', () => {
// The fix must not turn the check off: same shape, script that does not exist.
const r = resolveCommand(extractCommand('npm run --workspace pkgs/alpha nonexistent'), ctx());
assert.equal(r.status, 'dead');
assert.match(r.detail, /"nonexistent" is not declared/);
});

test('`pnpm info <pkg>` is a registry query, not an undeclared script', () => {
// Was reported as: npm script "info" is not declared in <root>/package.json.
const r = resolveCommand(extractCommand('pnpm info @beyondnet/evolith-cli'), ctx());
assert.notEqual(r.status, 'dead');
});

test('`yarn info <pkg>` likewise', () => {
const r = resolveCommand(extractCommand('yarn info @beyondnet/evolith-cli'), ctx());
assert.notEqual(r.status, 'dead');
});

test('a built-in subcommand does not mask a real workspace error', () => {
// `info` stops being read as a script name; it must NOT stop the workspace
// itself from being checked.
const r = resolveCommand(extractCommand('pnpm info --workspace pkgs/gone'), ctx());
assert.equal(r.status, 'dead');
assert.match(r.detail, /workspace does not exist/);
});

test('does not invent a verdict for `node -e`', () => {
const r = resolveCommand(extractCommand('node -e "process.exit(0)"'), ctx());
assert.equal(r.status, 'unchecked');
Expand Down
Loading
Loading