feat(engine): TAG-ANNOTATED — a movable tag is a movable pin
First of the approved §5 checks. A lightweight tag is a branch-like ref: it can be moved to another commit with nothing recorded that it ever pointed elsewhere. The catalog pins every plugin to `ref: vX.Y.Z`, so this is a supply-chain property, not tidiness. The two levels come from a measurement, not from taste. Across all 19 clones: 155 tags, 14 lightweight, but only ONE repo whose NEWEST tag is lightweight. The newest is what a consumer resolves today and what an operator can re-cut at no cost -> ERROR. The older ones can only be "fixed" by force-moving an already published ref, which is the exact risk the check exists to name -> exposed once as a count, WARN, never as fourteen findings. A gate that demands an unsafe remedy gets switched off. No tags at all is the VERSION-NONE shape: the check ran, saw every tag there is, and found no subject. TAGS-NONE is an OK, not a skip. Newest is decided by version order, not by the order git returns. `git tag --list` sorts lexically, where v10.0.0 lands before v9.0.0 — which would misjudge exactly the repos with the longest history (repo-mailbox has 27 tags). Pinned in test. Read from local git objects via `for-each-ref %(objecttype)` — zero network, so the two-call budget is untouched. Measured on the corpus, and it matches the census exactly: 1 ERROR (ktg-plugin-marketplace v7.7.2), 3 WARN (catalog 7, okf 5, guard 1), 15 OK, 2 TAGS-NONE. No other repo moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lb7XmJGLnFSX9U7tgS7fKk
This commit is contained in:
parent
3955b10c4d
commit
ddfc628761
3 changed files with 164 additions and 2 deletions
|
|
@ -21,6 +21,7 @@ import {
|
|||
checkVersionConsistency,
|
||||
checkHeadings,
|
||||
checkBadges,
|
||||
checkTagIntegrity,
|
||||
checkReadmeLanguage,
|
||||
checkBoilerplate,
|
||||
checkLicenseClaim,
|
||||
|
|
@ -757,6 +758,67 @@ test('org-profile requires no headings at all', () => {
|
|||
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------- tag integrity
|
||||
|
||||
// A lightweight tag is a branch-like ref: it can be moved to a different commit
|
||||
// with no record that it ever pointed elsewhere. The catalog pins every plugin
|
||||
// to `ref: vX.Y.Z`, so a movable tag is a movable pin.
|
||||
//
|
||||
// Measured across all 19 clones before writing the rule: 155 tags, of which 14
|
||||
// are lightweight — 8 in catalog, 5 in okf, 1 in guard. Exactly ONE repo has a
|
||||
// lightweight NEWEST tag. That split is the rule: the newest tag is what a
|
||||
// consumer resolves today and what an operator can re-cut, so it is an ERROR;
|
||||
// the older ones can only be "fixed" by force-moving a published ref, which is
|
||||
// the very risk this check exists to name, so they are exposed as one WARN and
|
||||
// never as fourteen.
|
||||
test('a lightweight newest tag is an ERROR — a movable tag is a movable pin', () => {
|
||||
const f = checkTagIntegrity({ tagObjects: [{ name: 'v1.0.0', annotated: true }, { name: 'v1.1.0', annotated: false }] });
|
||||
const hit = f.find((x) => x.code === 'TAG-ANNOTATED');
|
||||
assert.equal(hit.level, 'ERROR');
|
||||
assert.equal(hit.bucket, 'broken');
|
||||
assert.match(hit.msg, /v1\.1\.0/);
|
||||
});
|
||||
|
||||
test('all-annotated tags are OK', () => {
|
||||
const f = checkTagIntegrity({ tagObjects: [{ name: 'v1.0.0', annotated: true }, { name: 'v1.1.0', annotated: true }] });
|
||||
assert.equal(f.some((x) => x.level === 'ERROR' || x.level === 'WARN'), false);
|
||||
assert.equal(f.some((x) => x.code === 'TAGS'), true);
|
||||
});
|
||||
|
||||
test('older lightweight tags are ONE aggregated WARN, never one finding per tag', () => {
|
||||
const tagObjects = [
|
||||
{ name: 'v0.1.0', annotated: false },
|
||||
{ name: 'v0.2.0', annotated: false },
|
||||
{ name: 'v0.3.0', annotated: false },
|
||||
{ name: 'v1.0.0', annotated: true },
|
||||
];
|
||||
const f = checkTagIntegrity({ tagObjects });
|
||||
const warns = f.filter((x) => x.code === 'TAG-ANNOTATED-HISTORY');
|
||||
assert.equal(warns.length, 1);
|
||||
assert.equal(warns[0].level, 'WARN');
|
||||
assert.match(warns[0].msg, /3/);
|
||||
assert.equal(f.some((x) => x.level === 'ERROR'), false);
|
||||
});
|
||||
|
||||
// Same shape as VERSION-NONE: the check ran, saw every tag there is, and found
|
||||
// no subject. Nothing here can be wrong, so it is a verdict — not a skip.
|
||||
test('a repo with no tags has nothing to judge — OK, not SKIP', () => {
|
||||
const f = checkTagIntegrity({ tagObjects: [] });
|
||||
assert.equal(f.length, 1);
|
||||
assert.equal(f[0].level, 'OK');
|
||||
assert.equal(f[0].code, 'TAGS-NONE');
|
||||
});
|
||||
|
||||
// The newest tag is decided by version order, not by the order git happened to
|
||||
// hand them over. `git tag --list` sorts lexically, where v10.0.0 sorts BEFORE
|
||||
// v9.0.0 — reading "newest" off an unsorted list would judge the wrong tag on
|
||||
// exactly the repos with the longest release history.
|
||||
test('newest is the highest version, not the last element of an unsorted list', () => {
|
||||
const f = checkTagIntegrity({ tagObjects: [{ name: 'v10.0.0', annotated: true }, { name: 'v9.0.0', annotated: false }] });
|
||||
assert.equal(f.some((x) => x.code === 'TAG-ANNOTATED'), false);
|
||||
assert.equal(f.some((x) => x.code === 'TAG-ANNOTATED-HISTORY'), true);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------ badge honesty
|
||||
|
||||
test('a static badge asserting test or build status is a finding', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue