Commit graph

8 commits

Author SHA1 Message Date
e6cd8983ca feat(engine): TAG-SIGNED — a tag that names a signer, not just write access
The check sat parked as "blocked on an operator decision" on the strength of
0 of 18 repos signing. That zero was never evidence against it: it is what a
practice nobody has adopted yet looks like, and reasoning from it makes the
gate ratify the status quo it exists to move. The rule that settled it asks
something else — would a public AAA+ repository do this? Yes, and unlike the
two conventions that killed VERSION-DRIFT, there is no second convention under
which release tags are better left unattributable.

The measurement then decided the SCOPE, not the yes. Across 20 clones: 158 v*
tags, 144 annotated, 14 lightweight, 0 signed, and not one dated after
2026-08-12. A boolean "this org signs" would have failed 20 correct
repositories on day one with force-moving 144 published refs as its only
remedy — the mechanism that gets gates switched off. So the policy is a DATE in
the register (never in the engine, the rule that keeps the org-profile
exemption a flag): nothing fires today, teeth at each repo's next release.

Verified both directions, because a sweep returning zero proves nothing on its
own — a dead check returns zero too. 21 repos: 18 PREPOLICY + 3 NONE, 0 ERROR,
0 WARN; then against real git objects an unsigned post-policy newest tag does
fire ERROR.

Two exclusions keep one defect from being counted twice: a lightweight tag has
no object to carry a signature (byDesign skip naming TAG-ANNOTATED as owner),
and a pre-policy tag was correct when cut (OK, said out loud, never silence).
It answers "is it signed", never "does the forge vouch for it".

The README row ships in this commit, not after it, and the stale test count in
CLAUDE.md (230 → 241) is corrected here rather than left for the next reader.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsPsVsvhrSaejK3cLPmnN2
2026-08-13 10:51:36 +02:00
fe0d039de2 feat(register): a decided YES about tag history, and app-creator registered
Two changes that belong together: both are the register learning to record a
decision the engine could otherwise only re-report forever.

`tags_lightweight_accepted` — TAG-ANNOTATED-HISTORY (WARN) fires on 13
historical lightweight tags in three repos. A lightweight tag is movable
without a trace and the catalog pins plugins by tag, so the finding is real.
But the only remedy for a published one is force-moving it — the exact act the
check warns about — so the WARN could never be cleared. That is the `titles`
defect one axis over: the gate could not tell "we decided this" from "nobody
looked".

Keyed on tag NAME, never a count: a count stays satisfied the moment one tag is
re-cut and a different, unaccepted one takes its place.

An accepted tag emits `TAG-ANNOTATED-ACCEPTED` at OK naming the tags — it is
not dropped. An exemption is a finding, the rule `readme_desc_match` already
follows; an exception nobody can see reads exactly like a check that silently
stopped running.

13 entries, not the 14 lightweight tags that exist. `ktg-plugin-marketplace
v7.7.2` is deliberately absent: it is that repo's NEWEST tag, the one
lightweight tag with a safe remedy, and an ERROR today. Pre-accepting it would
mean cutting v7.8.0 instead of fixing it makes the finding vanish silently.
The engine enforces this independently — the newest tag cannot be accepted
away even if named, and a test pins that.

`app-creator` → `standalone` — `--refresh` measured 22 on the forge against 21
registered. Unregistered meant zero checks against a repo published on `open/`.
The class is derived, not guessed: no `.claude-plugin/plugin.json`, absent from
the catalog, own remote on `open/` — identical in form to the four existing
`standalone`.

Measured before and after across all 21 clones, every finding code diffed:
- TAG-ANNOTATED-HISTORY 3 WARN -> 0, converted 1:1 to 3 OK. None disappeared.
- TAG-ANNOTATED unchanged at 1 ERROR (ktg-plugin-marketplace v7.7.2).
- app-creator: 1 SKIP -> 16 judged findings (3 ERROR, 2 WARN). Recorded, not
  fixed — it records, it does not fix.
- Nothing else moved.
- `--refresh`: 22/22, no divergence.

The accepted lists are claims about three OTHER repos, measured from LOCAL
clones — which is the gap REMOTE-SYNC exists to name. They go to those repos by
coord so a wrong name can be disputed.

187 tests (was 182).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AkHEqTSr1k3HbeiHu1ggW
2026-08-12 22:24:03 +02:00
5884a64e54 feat(register): a decided title, and an org-profile that stops lying
Three changes at the register/engine boundary, all agreed with org-ops
after census 05 and all about a check missing a place to record a
legitimate exception.

`titles`: an optional per-repo README title. Set, the H1 matches it and
is OK; unset, the WARN stands as before. A human title was already this
engine's stated position and rds-v1's prescription, but a decided YES had
nowhere to live, so the same 6 WARNs were reported three censuses running
and would have been reported forever. Five registered, each H1 read from
the repo rather than copied from the census; `ai-psychosis` deliberately
left out so the one repo where a reader cannot connect title to name
stands alone. Measured across 21 local clones: 6 WARN before, 1 after,
nothing else moved.

`readme_desc_match: false` on the org-profile class: for an ordinary repo
the README opening and the forge description describe the same subject
and equality is right; for this class they do not — the README is the
org's landing page, the forge text describes the repo. The equality is
what does not apply, not either text. Class data, not a hardcoded name,
and the exemption is RECORDED as an OK naming its reason, not dropped.
`.profile` went ERROR to 0 ERROR / 0 WARN; the same README under a plugin
class is still an ERROR.

`engineCommit`: the version names a file, only the sha names the code. A
sweep stamped 18 files 0.4.0 while four carried a 0.5.0-only finding —
feature and version bump are two commits, so the stamp lied without being
broken. Present-and-null when underivable, never absent: an absent key
means an older engine, null means this one ran without a HEAD to read.

135 to 147 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NELsvPY5gnJjN3esdhWYWC
2026-08-09 21:14:43 +02:00
a7276e6f78 feat(register): register llm-security-commons as shared-asset
The org's 21st repo (register: 20 → 21, forge: 21 — confirmed by --refresh
last session). Class verified structurally, mirroring c5c11f4: absent from
the catalog's marketplace.json, no .claude-plugin/, README.md + LICENSE
present, `## Non-goals` heading present, consumed by `llm-security` (which
already carries the `security` trait) as a git subtree per its own README
Install section. So `vendor` is the right install form.

Trait left unset: llm-security-commons' own STATE.md names this as an open
operator decision, and the register's trait definition keys traits to what
code DOES — this repo states "no runnable code, data/specifications/
fixtures only."

Measured, not assumed:
- llm-security-commons now gates ERROR (10x LINK-INTERNAL-MISSING) — its
  README documents the target directory layout (lexicon/, codepoints/,
  signatures/, calibration/, mapping/, schema/, spec/, conformance/) but
  `git ls-files` shows only signatures/active-content.json and
  docs/extraction-plan.md exist; neither was flagged, so the check found
  real gaps, not noise. Findings recorded, not fixed here — that repo's
  own session owns them.
- llm-security (the plugin that vendors this) still reports zero ERROR:
  no `open/llm-security-commons` reference exists anywhere in the org
  outside the commons repo itself, so no dependent verdict moved. Matches
  what STATE predicted before this session started.

Not tagged as a release: catalog's ref is still on v0.3.0, two releases
(v0.4.0, v0.5.0) behind and already asked for via coord (unactioned,
latest ask reply-expected: no). Stacking a third release on top would only
widen that gap; this commit reaches installed consumers on whichever
release picks it up next, same as portfolio-optimiser-commons did in
c5c11f4 -> 43a8d28.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjNqnudJnH4Q9ZyVWh8UDr
2026-08-09 14:45:52 +02:00
c5c11f4b13 feat(register): register portfolio-optimiser-commons as shared-asset
The org's 20th repo, missing since it was published on 2026-08-04 and
reported by three independent senders (the repo itself, org-ops census
03d, org-ops census 04).

Class verified structurally rather than accepted on the proposal:
absent from the catalog's marketplace.json, no .claude-plugin/,
README.md + LICENSE present, consumed by both reference
implementations as a git subtree. So `vendor` is the right install
form and requiring `## Install` would have been wrong.

The cost of the gap was not a gap. classifyRef returns `unknown` for
an unregistered name and `unknown` is the ERROR branch, so
portfolio-optimiser earned three false LINK-DEAD ERRORs against a repo
that exists — in the same round it fixed its three real ones. Verified
after: portfolio-optimiser has zero ERROR, and commons measures as
WARN with 9 checks run instead of SKIP with none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxP9N3p1fG6UYSB8rATBL6
2026-08-09 14:18:09 +02:00
dc386d4471 feat(gate): two presentation checks the evidence actually supports
Adversarial deep research (25 sources, 124 claims extracted, 25 verified:
11 confirmed / 14 refuted) plus direct measurement of all 18 cloned open/
repos. The useful half of the result is what it REFUSED to support, so the
research is recorded in docs/presentation-research-2026-08-03.md rather than
being spent and forgotten.

- BADGE-COUNT (WARN) — past five badges. Trockman et al., ICSE 2018
  (n=294,941 npm packages) measured a non-linear relationship with popularity
  inflecting at five, motivated by surveyed maintainers calling over-badged
  READMEs cluttered and "trying too hard". WARN and never ERROR: the
  coefficient sits in an appendix with no CI or p-value. Counting deliberately
  uses a NARROWER rule than the existing claim check, so a screenshot or an
  architecture diagram is never counted as clutter. Fires on 8 of 18.
- README-LANGUAGE (WARN) — prose not in the language this repo's readers were
  declared to speak, via a new `locales` axis in the register. Class is
  structural, a trait is what the code DOES, a locale is who it is FOR — the
  standard's own "who the reader is decides what is required". English is the
  default; ms-ai-architect and okr are declared nb, named by the operator as
  Norway-only in audience. Stopword-frequency comparison over prose with code
  stripped: a Norwegian flag name in a shell example cannot decide the
  document. Fires on exactly those two, silent on all sixteen English repos.

One design correction found mid-implementation: the first version returned
SKIP when a README had too little prose to judge, which broke a passing
fixture and would have stopped any terse repo from ever reaching OK. SKIP is
for a check that could not RUN; this one ran, saw everything and found no
prose to be in the wrong language — the same shape as "no licence claim to
back". Insufficient prose is now OK, and evenly bilingual prose is the SKIP,
because there the question is live and unanswered.

Deliberately NOT built, because the evidence does not reach: any rule about
images, diagrams or terminal recordings (every such claim refuted 0-3); a
README length bound (no evidence-based target exists); a section count (would
fire on 9 of 18 — textbook "suspect the CHECK"); Mermaid source length and
#gh-dark-mode-only (zero occurrences, and the instance limit is not readable
via the API, so any threshold would be a guess).

Verified live against the operator's own forge (15.0.6+gitea-1.22.0): Mermaid
DOES render in README.md — two div.mermaid-block iframes carrying real SVG —
while #gh-dark-mode-only landed only in Gitea 1.26.0 and is unavailable here.

103 tests green, up from 92. No version bump: the catalog ref still trails at
v0.1.1 against 0.1.3, and starting a second release chain over that is the
operator's call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCAGKZT8h9F46ygzSkhEee
2026-08-04 09:41:53 +02:00
720850a9ad feat(gate): buckets, traits, and the checks the brief calls load-bearing
Measured this build against a documentation brief for public repos. The
five original checks covered roughly one of its ten sections, so this
adds what a single repo can answer on its own.

New: required README headings per class (Non-goals is the cheapest
trust-builder there is), in-repo version consistency across manifest /
badge / CHANGELOG / tag, badge honesty, boilerplate, licence-claim,
and relative links. Findings now carry a BUCKET beside the level -
broken / missing / weakening - and output is grouped by it, because
that is the order the work gets done in.

Traits are a second axis beside class: class is structural and readable
off the catalog, a trait says what the code does. `security` attaches
SECURITY.md and a Known limitations section. The two names carrying it
are proposed, not measured - that list is the operator's.

Solo-maintained settles a category: CONTRIBUTING, CODE_OF_CONDUCT and
MAINTAINERS are required by no class. Consumer-facing documents are
untouched by that; SECURITY.md exists for the stranger who finds a hole.

Three bugs found by running against llm-security, not by reading:
- ~30 link findings, all noise. Regexes inside code spans are
  `[...](...)` to a naive scanner. Strip code first.
- `file:` and other schemes were treated as repo-relative paths.
- Relative links were resolved against the repo root instead of the
  file they sit in, calling two files missing that sat next to the
  README linking them.
Same fix applied to the boilerplate check: a document ABOUT placeholder
detection was tripping the placeholder detector.

Also removed this repo's own static tests badge. There is no CI - the
forge has zero Actions runners registered - so it could never become
real, and it is the exact anti-pattern the gate now flags.

67 tests. Against llm-security every remaining finding is real and
matches the census's independent hand-measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYJ3FHLtVgzFXMZ6UF598h
2026-07-27 16:06:33 +02:00
816ba97c63 feat(repo-standard): v0.1.0 - per-repo gate for the open/ standard
Five checks a single repository can answer on its own: README first
screen, install block, files required by its class, open/<name>
references, description length. Pure classifiers with I/O resolved into
their input, mirroring check-versions.mjs; ERROR/WARN/SKIP/OK, exit 1
on ERROR. 32 tests.

The reference check has THREE outcomes: "matches no repo" (ERROR) is
separate from "matches a known non-repo" (WARN). Sharing an outcome
would let real dead links hide inside correct text. Only names in URL
position count, and .git is normalised first - without that a raw scan
turns 3 dead names into ~20.

enabledPlugins is treated as a legitimate second install form; what the
gate requires in addition is a CLI command. The JSON form is never
reported as the defect.

STATE.md is gitignored from this first commit - public remote.
No hook yet: a blocking gate must first be precise enough not to fail a
correct repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYJ3FHLtVgzFXMZ6UF598h
2026-07-27 09:10:46 +02:00