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:
parent
a563035feb
commit
aebd4d28a9
4 changed files with 124 additions and 1 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -45,6 +45,24 @@ versioning is [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
question, whether this org publishes releases at all, by exiting 1 on its own
|
||||
author.
|
||||
|
||||
- **`REMOTE-SYNC` — a tag that exists only in the operator's clone.** 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 that version.
|
||||
`portfolio-optimiser` is the measured case, and it read `OK` until this check
|
||||
existed.
|
||||
|
||||
`ERROR`/`broken`, with the remedy named: `git push origin <tag>`. Measured
|
||||
across all 21 registered clones (2026-08-12): exactly one repo 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 the finding 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;
|
||||
firing there would fail correct repositories on the reader's machine state.
|
||||
|
||||
### Changed
|
||||
|
||||
- **The API-call count in `CLAUDE.md` is three, updated in the same commit as
|
||||
|
|
|
|||
13
CLAUDE.md
13
CLAUDE.md
|
|
@ -175,6 +175,17 @@ would recreate, in data, exactly the drift this plugin exists to remove.
|
|||
(v0.9.0 tagged, v0.3.0 published), and an `ERROR` would have let the gate
|
||||
decide an operator question — whether this org publishes releases at all —
|
||||
by exiting 1 on its own author.
|
||||
- **An unpushed tag is a version that exists for nobody, and it is
|
||||
`VERSION-TAG`'s blind spot, not its duplicate.** `VERSION-TAG` reads LOCAL
|
||||
tags, so a manifest claiming `1.0.0` against an unpushed `v1.0.0` reads as a
|
||||
clean pass — `portfolio-optimiser` read `OK` until `REMOTE-SYNC` existed.
|
||||
One subject in the corpus 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 (`git push origin <tag>`) moves no published
|
||||
ref, and the finding recurs at every release rather than once. The reverse
|
||||
direction — a clone behind the forge — is deliberately not a finding: that is
|
||||
the reader's machine state, not the repository's, and failing correct repos on
|
||||
it is how gates get switched off.
|
||||
- **Version order, in the measurement as well as in the code.** The shell that
|
||||
measured this check's baseline sorted tags with `sort -t. -k1,1V` and put
|
||||
`v0.9.0` above `v0.10.0` — the exact defect `compareTags` already exists to
|
||||
|
|
@ -262,7 +273,7 @@ would recreate, in data, exactly the drift this plugin exists to remove.
|
|||
## Commands
|
||||
|
||||
```bash
|
||||
npm test # 205 tests
|
||||
npm test # 211 tests
|
||||
node scripts/repo-standard-check.mjs --dir "$PWD" # gate one repo
|
||||
node scripts/repo-standard-check.mjs --offline # no network call
|
||||
node scripts/repo-standard-check.mjs --json # machine output
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import {
|
|||
groupSkips,
|
||||
skipsOf,
|
||||
checkReleaseCurrent,
|
||||
checkRemoteSync,
|
||||
} from './repo-standard-check.mjs';
|
||||
|
||||
const REGISTER = {
|
||||
|
|
@ -2084,3 +2085,54 @@ test('a release ahead of every known tag is not reported as stale', () => {
|
|||
const f = checkReleaseCurrent({ forgeTagsSelf: ['v1.0.0'], releases: ['v1.1.0'] });
|
||||
assert.equal(f[0].level, 'OK');
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------- REMOTE-SYNC
|
||||
//
|
||||
// Measured 2026-08-12 across all 21 registered clones: exactly one repo has a
|
||||
// local tag the forge does not (portfolio-optimiser, v1.0.0), and none is
|
||||
// behind. One subject is what got BRANCH-STALE rejected — the difference is
|
||||
// that an unpushed tag is never a legitimate convention, and that the gate
|
||||
// currently says the WRONG thing about it: VERSION-TAG reads LOCAL tags, so a
|
||||
// manifest claiming 1.0.0 against an unpushed v1.0.0 reads as OK while no
|
||||
// stranger can resolve it.
|
||||
|
||||
test('a local tag the forge does not have is an ERROR naming the tag', () => {
|
||||
const f = checkRemoteSync({ tags: ['v0.1.0', 'v1.0.0'], forgeTagsSelf: ['v0.1.0'] });
|
||||
assert.equal(f[0].level, 'ERROR');
|
||||
assert.equal(f[0].code, 'REMOTE-SYNC');
|
||||
assert.equal(f[0].bucket, 'broken');
|
||||
assert.match(f[0].msg, /v1\.0\.0/);
|
||||
});
|
||||
|
||||
test('being behind the forge is NOT a finding — a stale fetch is not a defect', () => {
|
||||
// The reverse direction must never fire. Every clone that has not fetched
|
||||
// lately would fail, and nothing is wrong with the repository.
|
||||
const f = checkRemoteSync({ tags: ['v0.1.0'], forgeTagsSelf: ['v0.1.0', 'v0.2.0'] });
|
||||
assert.equal(f[0].level, 'OK');
|
||||
});
|
||||
|
||||
test('refs that could not be read are a SKIP, never a pass', () => {
|
||||
const f = checkRemoteSync({ tags: ['v1.0.0'], forgeTagsSelf: null });
|
||||
assert.equal(f[0].level, 'SKIP');
|
||||
assert.equal(f[0].skip, 'notRun');
|
||||
});
|
||||
|
||||
test('no local tags is an OK — there is nothing that could be unpushed', () => {
|
||||
const f = checkRemoteSync({ tags: [], forgeTagsSelf: [] });
|
||||
assert.equal(f[0].level, 'OK');
|
||||
assert.equal(f[0].code, 'REMOTE-SYNC');
|
||||
});
|
||||
|
||||
test('every local tag present on the forge is an OK', () => {
|
||||
const f = checkRemoteSync({ tags: ['v0.1.0', 'v0.2.0'], forgeTagsSelf: ['v0.1.0', 'v0.2.0'] });
|
||||
assert.equal(f[0].level, 'OK');
|
||||
});
|
||||
|
||||
test('several unpushed tags are reported in version order, in one finding', () => {
|
||||
// The remedy is safe for all of them — pushing a tag moves no published ref —
|
||||
// so unlike TAG-ANNOTATED-HISTORY there is no reason to collapse them to a
|
||||
// count. Order is the engine's, so v0.10.0 does not sort under v0.9.0.
|
||||
const f = checkRemoteSync({ tags: ['v0.9.0', 'v0.10.0'], forgeTagsSelf: [] });
|
||||
assert.equal(f.length, 1);
|
||||
assert.match(f[0].msg, /v0\.9\.0.*v0\.10\.0/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue