fix(engine): a schemaless host is still a host, and OK is a real outcome

Two findings from org-ops census 03, both measured against the parser
rather than read out of the regex.

(a) URL_REF required :// or @host:, so `git.fromaitochitta.com/open/<name>`
— the form a subtree instruction routinely uses — extracted nothing at
all (llm-security/V3-UPGRADE.md:343). What makes a name resolvable is
its position after a host, not the scheme in front of it.

Two guards keep the widening from becoming the noise the scheme was
masking: the host must end in a TLD-shaped label, and a candidate
preceded by / is a path segment that merely contains a dot, not a host
— so docs/v1.2/open/ and test/nav.golden/open/ stay silent and the
API-endpoint rule from 0.3.0 is untouched.

Measured before shipping across 1501 tracked Markdown files in 20
local clones: 14 lines changed verdict. 13 were references that had
been invisible. The 14th was a defect this widening introduced — a
markdown link whose display text repeats its own URL matched on both
halves, printing one dead reference twice and inflating the count the
new OK line offers as evidence. References are now deduplicated per
name-and-line, so two names on one line, or one name on two lines,
still count as two.

(b) checkLinks emitted nothing on success, so "no dead references" and
"the check never ran" were identical in the output — a sweep could not
tell 19 clean repos from 19 unread ones. The file already applies
"three outcomes, never two" to classifyRef; it now applies it to its
own result. Zero enumerated files is LINKS-OPEN-REFS-UNAVAILABLE
(SKIP), which is the honest name for what used to look like a pass.

Caught by the gate against itself: the first draft of this release's
CHANGELOG entry used a literal open/<name> placeholder and became a
real LINK-DEAD ERROR. Rewritten, not exempted.

116 -> 129 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxP9N3p1fG6UYSB8rATBL6
This commit is contained in:
Kjell Tore Guttormsen 2026-08-09 14:18:24 +02:00
commit c8cbeb97a5
2 changed files with 155 additions and 2 deletions

View file

@ -79,7 +79,18 @@ export function normalizeRepoRef(raw) {
// restriction, an API endpoint like `/api/v1/orgs/open/repos` also matches —
// `open` there is the org argument to the API, and `repos` is the literal
// resource segment, not a repo name (measured: catalog RUNBOOK.md:39, :114).
const URL_REF = /(?::\/\/[^\s)\]"'`/]+\/|@[^\s:]+:)open\/([A-Za-z0-9._-]+)/g;
//
// The third alternative is the SCHEMELESS host: `git.fromaitochitta.com/open/x`
// written without `https://`, which a subtree instruction routinely is
// (measured false negative: llm-security/V3-UPGRADE.md:343 — the scheme was
// doing work it was never entitled to, and its absence hid a WARN). What makes
// a name resolvable is its position after a HOST, not the scheme in front of
// it. Two guards keep that from becoming the noise the scheme was masking: the
// host must end in a TLD-shaped label, and the lookbehind refuses a host
// preceded by `/`, `.` or a word character — because that is a PATH segment
// that merely contains a dot (`docs/v1.2/open/`, `test/nav.golden/open/`), not
// a host. The API-endpoint rule survives untouched: `orgs` carries no dot.
const URL_REF = /(?::\/\/[^\s)\]"'`/]+\/|@[^\s:]+:|(?<![A-Za-z0-9._/-])[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}\/)open\/([A-Za-z0-9._-]+)/g;
export function extractOpenRefs(text) {
const out = [];
@ -101,10 +112,34 @@ export function classifyRef(name, register) {
return 'unknown';
}
// The same "three outcomes, never two" rule this file applies to classifyRef,
// applied to the check's own result. Emitting nothing on success made "no dead
// references" and "the check never ran" identical in the output, so a sweep
// across the org could not tell 19 clean repos from 19 unread ones (measured:
// org-ops census 03b). Silence is not a pass here either.
export function checkLinks({ files }, register) {
const entries = Object.entries(files ?? {});
if (entries.length === 0) {
return [{
level: 'SKIP',
code: 'LINKS-OPEN-REFS-UNAVAILABLE',
msg: 'no files were enumerated — the `open/` reference check did not run',
}];
}
const findings = [];
for (const [path, text] of Object.entries(files ?? {})) {
// One name, one line, one reference. `[host/open/x](https://host/open/x)`
// puts the same reference on both halves of a markdown link and matched
// twice once the schemaless host became legible (measured:
// llm-security/V3-ANNOUNCEMENT.md:124). The key keeps name AND line, so two
// different names on one line — or the same name on two lines — stay two.
const seen = new Set();
let checked = 0;
for (const [path, text] of entries) {
for (const ref of extractOpenRefs(text)) {
const key = `${path}\n${ref.line}\n${ref.name}`;
if (seen.has(key)) continue;
seen.add(key);
checked += 1;
const kind = classifyRef(ref.name, register);
if (kind === 'repo') continue;
if (kind === 'non-repo') {
@ -124,6 +159,17 @@ export function checkLinks({ files }, register) {
}
}
}
// The count IS the evidence. An OK that cannot say how many references it
// resolved is the same silence wearing a different level.
if (findings.length === 0) {
findings.push({
level: 'OK',
code: 'LINKS-OPEN-REFS',
msg: checked === 0
? `no \`open/\` references found in ${entries.length} scanned file(s)`
: `${checked} \`open/\` reference(s) checked — every one resolves to a registered repo`,
});
}
return findings;
}

View file

@ -139,6 +139,34 @@ test('extractOpenRefs handles the ssh scp-style form', () => {
assert.deepEqual(refs.map((r) => r.name), ['llm-security']);
});
test('extractOpenRefs finds a ref on a schemaless host — the scheme is not what makes it a URL', () => {
// Measured false negative (org-ops census 03, llm-security/V3-UPGRADE.md:343):
// a subtree instruction writes the host without a scheme. The name is still
// in URL position — `open` is the first segment after a dotted host — and it
// is still a name the register can resolve. Requiring `://` hid a WARN.
const refs = extractOpenRefs('- Subtree push to `git.fromaitochitta.com/open/claude-code-llm-security`');
assert.deepEqual(refs.map((r) => r.name), ['claude-code-llm-security']);
});
test('the schemaless form does not reopen the API-endpoint false positive', () => {
// The whole point of the host segment excluding `/` survives losing the
// scheme: `orgs` is the segment before `open`, so `open` is not first.
assert.deepEqual(extractOpenRefs('curl git.fromaitochitta.com/api/v1/orgs/open/repos'), []);
});
test('a dotted PATH segment is not a host — only a host puts a name in URL position', () => {
// The guard that keeps the schemaless branch from matching anywhere a dot
// happens to precede `open/`: a path segment is preceded by `/`, a host is not.
assert.deepEqual(extractOpenRefs('see https://git.fromaitochitta.com/docs/v1.2/open/notes'), []);
assert.deepEqual(extractOpenRefs('the fixture lives at test/nav.golden/open/index.md'), []);
});
test('a schemaless host is counted ONCE, not twice, when a scheme is present', () => {
// Two alternatives can match the same text; the leftmost consumes it.
const refs = extractOpenRefs('https://git.fromaitochitta.com/open/llm-security');
assert.deepEqual(refs.map((r) => r.name), ['llm-security']);
});
test('extractOpenRefs reports the 1-indexed line of each hit', () => {
const text = 'line one\nline two\nhttps://git.fromaitochitta.com/open/llm-security';
assert.equal(extractOpenRefs(text)[0].line, 3);
@ -191,6 +219,73 @@ test('the hidden .profile resolves — the enumerator sees what a glob misses',
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
});
test('checkLinks emits OK when every reference resolves — silence is not a pass', () => {
// Measured gap (org-ops census 03b): "no dead references" and "the check
// never ran" produced identical output, so a sweep across 19 repos could not
// tell 19 clean repos from 19 unread ones. The engine already applies "three
// outcomes, never two" to classifyRef; this applies it to its own result.
const f = checkLinks({ files: { 'README.md': 'https://git.fromaitochitta.com/open/llm-security' } }, REGISTER);
assert.equal(f.length, 1);
assert.equal(f[0].level, 'OK');
assert.equal(f[0].code, 'LINKS-OPEN-REFS');
// The count is the evidence: an OK that cannot say how many it checked is
// the same silence in a different colour.
assert.match(f[0].msg, /1 /);
});
test('a repo with no open/ references at all is still a ran check, not a blank', () => {
const f = checkLinks({ files: { 'README.md': '# title\n\nno references here' } }, REGISTER);
assert.equal(f.length, 1);
assert.equal(f[0].level, 'OK');
assert.match(f[0].msg, /no `open\/` references/);
});
test('checkLinks SKIPs when no files were enumerated — that is the "did not run" case', () => {
const f = checkLinks({ files: {} }, REGISTER);
assert.equal(f.length, 1);
assert.equal(f[0].level, 'SKIP');
assert.equal(f[0].code, 'LINKS-OPEN-REFS-UNAVAILABLE');
});
test('a link whose display text repeats its own URL is ONE reference, not two', () => {
// Introduced by the schemaless branch and measured before shipping it
// (llm-security/V3-ANNOUNCEMENT.md:124): `[host/open/x](https://host/open/x)`
// matches on both halves. For a dead name that is one defect printed twice;
// for a live one it inflates the very count the OK line offers as evidence.
const line = '[git.fromaitochitta.com/open/nonesuch](https://git.fromaitochitta.com/open/nonesuch)';
const f = checkLinks({ files: { 'README.md': line } }, REGISTER);
assert.equal(f.length, 1);
assert.equal(f[0].code, 'LINK-DEAD');
});
test('the OK count does not double-count a self-linking URL', () => {
const line = '[git.fromaitochitta.com/open/llm-security](https://git.fromaitochitta.com/open/llm-security)';
const f = checkLinks({ files: { 'README.md': line } }, REGISTER);
assert.equal(f[0].level, 'OK');
assert.match(f[0].msg, /^1 /);
});
test('two DIFFERENT names on one line stay two references', () => {
// The dedup key is name-and-line, not line — collapsing by line alone would
// hide the second dead name behind the first.
const line = 'https://git.fromaitochitta.com/open/nonesuch vs https://git.fromaitochitta.com/open/alsonone';
const f = checkLinks({ files: { 'README.md': line } }, REGISTER);
assert.equal(f.length, 2);
});
test('the same name on two DIFFERENT lines stays two references', () => {
const text = 'https://git.fromaitochitta.com/open/nonesuch\nand again https://git.fromaitochitta.com/open/nonesuch';
const f = checkLinks({ files: { 'README.md': text } }, REGISTER);
assert.equal(f.length, 2);
});
test('checkLinks prints no OK beside a finding, not even a WARN-only one', () => {
const dead = checkLinks({ files: { 'README.md': 'https://git.fromaitochitta.com/open/nonesuch' } }, REGISTER);
const warn = checkLinks({ files: { 'README.md': 'https://git.fromaitochitta.com/open/coord' } }, REGISTER);
assert.equal(dead.filter((x) => x.level === 'OK').length, 0);
assert.equal(warn.filter((x) => x.level === 'OK').length, 0);
});
// ------------------------------------------------------------- description
test('description length is bounded, measured in codepoints', () => {
@ -386,6 +481,18 @@ test('no class requires a ROADMAP — it is 0/18 and belongs to a later step', (
}
});
// A register row whose class does not exist reads as REGISTERED but runs the
// same zero checks as an unregistered one — the silent-loss shape this gate is
// built to catch, hiding inside a file that looks maintained. Reads the live
// register so every future row is guarded, not just today's.
test('every registered repo names a class that actually exists', () => {
const live = loadRegister();
const classes = Object.keys(live.classes);
for (const [repo, klass] of Object.entries(live.repos)) {
assert.equal(classes.includes(klass), true, `${repo} is registered as unknown class \`${klass}\``);
}
});
// -------------------------------------------------------------- aggregation
test('levelOf ranks ERROR above WARN above SKIP above OK', () => {