feat(engine): REMOTE-SYNC — a tag that exists only in one clone

VERSION-TAG reads LOCAL tags, so a manifest claiming 1.0.0 against an unpushed
v1.0.0 reads as a clean pass while no stranger can resolve that version.
portfolio-optimiser read OK until this check existed; it now reads ERROR, which
is the finding the gate was blind to rather than a new demand on the repo.

Measured across all 21 registered clones: exactly one has an unpushed tag, and
none is behind the forge. One subject is what got BRANCH-STALE rejected — the
difference is that an unpushed tag is never one of two legitimate conventions
the way tag-only releasing is, the remedy moves no published ref, and it
recurs at every release rather than once.

The reverse direction is deliberately not a finding: a clone that has not
fetched lately is behind the forge and nothing about the repository is wrong.

211 tests (was 205).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWc4piM4QW6Jxfky2Rw4Z8
This commit is contained in:
Kjell Tore Guttormsen 2026-08-12 23:00:02 +02:00
commit aebd4d28a9
4 changed files with 124 additions and 1 deletions

View file

@ -820,6 +820,47 @@ export function checkReleaseCurrent({ forgeTagsSelf, releases }) {
}];
}
// A tag that exists only in the operator's clone is a version that exists for
// nobody. This is the blind spot in VERSION-TAG rather than a duplicate of it:
// VERSION-TAG reads LOCAL tags, so a manifest claiming 1.0.0 against an
// unpushed `v1.0.0` reads as a clean pass while no stranger can resolve it.
// Measured across all 21 registered clones (2026-08-12), exactly one repo is in
// that state — portfolio-optimiser — and none is behind the forge.
//
// One subject is what got BRANCH-STALE rejected. The difference is that an
// unpushed tag is never one of two legitimate conventions the way tag-only
// releasing is: nobody deliberately keeps a release tag private, the remedy
// (`git push origin <tag>`) is safe and moves no published ref, and the finding
// recurs at every release, not once.
//
// The reverse direction is deliberately NOT a finding. A clone that has not
// fetched lately is behind the forge, and nothing about the repository is
// wrong — firing there would fail correct repositories on the reader's machine
// state, which is the mechanism that gets gates switched off.
export function checkRemoteSync({ tags, forgeTagsSelf }) {
if (forgeTagsSelf === null || forgeTagsSelf === undefined) {
return [{ level: 'SKIP', skip: 'notRun', code: 'REMOTE-SYNC', msg: 'forge refs not readable — cannot tell whether the local tags were ever pushed' }];
}
const onForge = new Set(forgeTagsSelf);
const unpushed = [...(tags ?? [])].filter((t) => !onForge.has(t)).sort(compareTags);
if (unpushed.length === 0) {
return [{
level: 'OK',
code: 'REMOTE-SYNC',
msg: (tags ?? []).length === 0
? 'no local tags — nothing that could be unpushed'
: `all ${tags.length} local tag(s) exist on the forge`,
}];
}
const names = unpushed.map((t) => `\`${t}\``).join(', ');
return [{
level: 'ERROR',
code: 'REMOTE-SYNC',
bucket: 'broken',
msg: `${names} exist${unpushed.length === 1 ? 's' : ''} only in this clone — the forge has no such tag, so the version is unreachable for everyone else. Push it: \`git push origin ${unpushed.join(' ')}\`.`,
}];
}
// Counting badges needs a NARROWER rule than detecting a dishonest one. The
// claim check reads any image, any host, on purpose. Here the opposite error
// matters: counting a screenshot or an architecture diagram as clutter would
@ -1297,6 +1338,7 @@ export function classifyRepo(
...checkVersionConsistency({ pluginVersion, readmeBadge, changelogTop, tags }),
...checkTagIntegrity({ tagObjects, name }, register),
...checkReleaseCurrent({ forgeTagsSelf, releases }),
...checkRemoteSync({ tags, forgeTagsSelf }),
...checkDescription(description, register),
];