npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vantageos/backend-doctor

v0.6.0

Published

A conformance instrument that measures the VantagePeers backend against the pinned backend standard. The THIRD doctor built ON the @vantageos/mcp-doctor engine — never a third engine. It consumes runDoctor/renderTable and adds only detectors.

Readme

backend-doctor

A conformance instrument. It scores a Convex backend against the VantagePeers backend standard — a pinned, frozen fixture, not a document authored here — and prints, per rule, the sites that fail it or the sites a human verdict is owed on. It consumes the @vantageos/mcp-doctor engine (runDoctor + renderTable) — it never re-implements it and touches no engine file; the contract types are derived from the engine's own signatures.

The standard is vendored under fixtures/backend-standard-<pin>.md, byte-identical to its upstream git blob; the canonical source lives in ElPi-Corp at vantagepeers/backend-standard/backend-standard.md (pin bacc54a, 56 rules — on ElPi-Corp main; it adds R-54..R-57 and the MCP rule S-28, merged as ElPi-Corp #239 = 7d7df89 before the 2026-10-10 history rewrite, same file blob 85d8530e; see PINS.txt). The doctor never authors the standard — it scores against the frozen copy.

What it checks — every rule, one bucket, derived not typed

Every mechanical rule of the standard carries an executable control. There is no deferral bucket: a rule is either CHECKED, or NOT_MECHANICALLY_CHECKABLE named with a written reason (never silently dropped). The rule count is whatever the pinned fixture derives — it is not typed in this README, because a typed count is stale the moment the pin moves (a rule with no detector yet must show as not-checkable against the true denominator, not vanish under a smaller one). Read the live coverage from the tool, never from prose:

# the coverage proof — prints "<N> CHECKED + <M> NOT_MECHANICALLY_CHECKABLE = <N+M> of <N+M>"
# and exits 1 on any gap. This line, not this README, is the count.
npx --yes @vantageos/backend-doctor@<version> --coverage   # or, from a checkout: npx tsx src/coverage-cli.ts

The detectors read four kinds of material, each a comparison target, never an authority:

| Kind | Reads | Rules (examples) | |---|---|---| | oracle-CSV (Axis-2) | the per-tool inventory the target's own .backend-doctor/vp-by-tool.csv (or --oracle <file>); absent => could-not-judge | R-1..R-13, R-7, R-8, R-37 | | writer-tier side-car | vp-writer-tier.csv beside the VP oracle (or --writer-tier <file>), joined on outil; absent => R-10 could-not-judge, file named | R-10 | | oracle-CSV (Axis-1) | VP oracle vs the VR inventory .backend-doctor/vr-by-tool.csv (or --vr-oracle <file>) | R-40 | | source-tree | the real convex/ source and mcp-server/src (live path, or fixtures/vp-convex/ in CI) | R-6, R-11, R-16..R-36, R-50..R-56 | | standard-document | the vendored standard + the four MCP-family docs | R-14, R-15, R-32, R-38, R-39, R-41, R-42 |

The acceptance property — surfacing, not deferring

A rule whose control has a mechanical detection half and a human/gate half does not report present ("held") or absent ("N/A") while its subject exists in the material. It runs the mechanical half in full and returns could-not-judge naming the concrete sites a human or delivery-gate verdict is owed on — a work list where there was silence. Green is the human half surfaced per site, never the detection merely running.

Run it

backend-doctor is its own tool (own package @vantageos/backend-doctor, own bin), like react-doctor / mcp-doctor. The supported invocation is the published, scoped package — so the instrument that judged is the instrument named, and every station runs the same pinned version without cloning:

npx --yes @vantageos/backend-doctor@<version> <path-to-a-convex-backend>
# <path> may be the repo root or its convex/ dir: both are normalised to the repo root
# (nearest package.json/.git) and produce identical results. --version / --help are supported.

Do not type a stale version as the only pin — read the current one from the registry, never from this README:

npm view @vantageos/backend-doctor version                 # the current published version
npx --yes @vantageos/backend-doctor@$(npm view @vantageos/backend-doctor version) <path-to-a-convex-backend>

Until the scoped package is first published (npm view @vantageos/backend-doctor returns 404 until then), the npx @vantageos/… form above cannot resolve — run from a checkout (below) in the meantime. Once published, the scoped package is the supported path from that version onward.

For development of the doctor itself, or to run it before the package is published (not the long-term supported path), from a checkout:

npm ci && npm run build
node dist/cli.js <path-to-a-convex-backend>       # the bin; the live verdict + named sites + tally
npx tsx src/cli.ts <path-to-a-convex-backend>     # same, straight from source
npx tsx src/coverage-cli.ts                       # the coverage proof; exits 1 on any gap
npm test                                          # the detector suite (RED-first, both poles, mutation bite proofs)
npx tsc --noEmit

node dist/cli.js refuses to run (exit 2) when the dist/ it runs from was compiled from different source than the checkout's src/: npm run build stamps dist/build-stamp.json with a sha256 of src/**/*.ts, and the CLI recomputes it before reading anything (src/build-stamp.ts). A gate that must not depend on a build step runs from source (npx tsx src/cli.ts). An installed package has no src/ and is identified by its version.

The verdict, the tally, and the exit code (D6)

The CLI prints a machine tally — the four rule states (present / non-conforming / could-not-judge / absent) counted by code from the detector results and asserted to sum to the derived rule count (the coverage denominator, never a typed number). No human ever counts rows (the eyeballed-distribution defect is closed). The tally is over rules (one worst-state per rule); the printed line also names the raw result count so the two are never confused.

It then prints a severity-mapped verdict in react-doctor's shape. Each rule's severity is derived from the standard's §13 objectives table (never hardcoded):

| objective tag (§13) | severity | |---|---| | multi-organisation multi-tenant (tenant-boundary) | critical | | highly secure (without multi-tenant) | major-high | | enterprise-grade / production-ready | major | | token-efficient only | minor |

Verdict mapping, computed from the tally (major = major-high + major):

  • 0 critical + 0 major violations → approve (exit 0)
  • 0 critical + 1..5 major violations → revise recommended
  • 0 critical + >5 major violations → revise mandatory
  • ≥1 critical violation → revise mandatory, no bypass

A could-not-judge rule is an abstention — a human verdict is owed, never a silent approve; any abstention blocks approve, and an abstention on a critical rule is reported first. The exit code preserves the engine's honesty (and cross-checks @vantageos/mcp-doctor's computeExitCode): 1 = a hard violation (≥1 critical, or any major violation); 2 = a human verdict owed with no hard violation (an abstention — never a clean 0); 0 = approve. approve is never printed on an exit that means abstention.

CR-1 — caller resolution on the exposed surface (doctor-owned, CRITICAL)

Alongside the numbered rules the CLI renders a CALLER RESOLUTION (CR-1) section answering one question per registration: does this public registration resolve its caller before serving or writing data?

  • Population is the exposed surface itself, derived from the target tree's own convex/_generated/api.d.ts — never a hand-written list and never a pattern over argument names. internal*/httpAction are excluded and the exclusion is proven per site from the builder token on that line. The count classified must equal the count exposed; a discrepancy is reported as CR-1's own bug.
  • Four classes, and every member is named: RESOLVES / FAIL-OPEN / NO-IDENTITY / NO-DATA. FAIL-OPEN and unjustified NO-IDENTITY/NO-DATA are mechanical violations and the gate refuses on them (exit 1).
  • The only exit is a token in the target source, allow-no-caller-resolution: <reason>, read at the registration. There is no exemption list, no baseline and no tolerated count — FAIL-OPEN has no token exit at all.
  • It asks whether the caller is RESOLVED, never whether a refusal is shaped correctly. That second question is R-50/R-51's; a handler with no guard has nothing to throw, so it cannot fail a rule about throwing.
  • On a tree that exposes nothing it reports no applicable site, never a count.

CR-1 is NOT one of the numbered rules in the pinned standard, so it is not in src/registry.ts and does not enter the coverage line: registering an id the vendored standard does not define would make coverage.ts report a phantom rule. Its upstream number is pending: the earlier proposal "R-52" is withdrawn because the standard at 2825122 gives R-52 to the cross-tenant-write rule and R-53 to identity-by-ID (§16); CR-1 takes the next free number when it is upstreamed, in the standard's own repository. Source and rationale: src/caller-resolution.ts.

CR-2 — no local identity primitive (doctor-owned, mechanical, ceiling 0)

Operator order 2026-10-09: "No application, no MCP server, and no starter defines its own primitive for: resolving a caller to a tenant, asserting membership of an organisation, filtering rows by a caller's scope, or validating a master credential." Identity lives only in @vantageos/cloud-identity. The CLI renders a LOCAL IDENTITY (CR-2) section; every site is named by file:line.

  • One site per function that defines a primitive (a helper, an MCP tool handler, or a Convex registration whose own handler does it) in convex/ and mcp-server/src. Each site names every kind it holds, with its line: tenant (resolves a caller to a tenant or agent), membership (a gate on a role / master field, a membership-table read, or an identity refusal the app builds itself: a RBAC_DENIED-style code or package refusal reason, the set read from the installed @vantageos/cloud-identity), scope (a read of a roster / allow-list member of the caller's scope, the member set derived from the target tree's own types, or a membership test over a caller's tenant / name / scope list), credential (timingSafeEqual, token hashing, jose / jsonwebtoken verification), name (an agent identified by name: an index keyed on a name field, a comparison through a name normaliser).
  • R-53 is reused, not re-derived: its (d) local identity layers, its doors whose principal is resolved by the handler itself, its clause-1 recipient-by-name sites and its (c) master-on-absence branches become CR-2 sites of the matching kind. What R-53 already sanctions (a getUserIdentity() value that reaches only a package resolver, the org-ID adapter read) is not a site.
  • The package's caller resolvers come from the package, not from the doctor. A getUserIdentity() value is sanctioned only when it reaches, as first argument, a function the file imports from @vantageos/cloud-identity AND that the installed package tags @callerResolver: the JSDoc block immediately above export declare function <name> in node_modules/@vantageos/cloud-identity/dist/**/*.d.ts, found at the scan root or its nearest ancestor (src/detectors/source/caller-resolvers.ts). cloud-identity 0.20.0 tags resolveActingPrincipal, resolvePersonPrincipal and resolveCallerFromClaims. A local function of the same name, or an untagged package export, is not a sink. When the package is absent, has no dist/, or tags nothing (every release before 0.20.0), the fixed pair resolveActingPrincipal / resolvePersonPrincipal is used. Either way the section prints caller-resolver sinks: DERIVED from … or caller-resolver sinks: FALLBACK to the fixed set … — <why> on every run. R-53 (including its recipient-by-name reading) and R-6's package-principal credit read the same set.
  • EXEMPT, and reported as exempt: a file of @vantageos/cloud-identity itself, and a site carrying allow-local-identity: <reason of 8+ characters> in its leading comment or body. Each exempt site is listed with its reason.
  • STORAGE FOR THE PACKAGE, reported and not counted (0.4.7): a membership-table read whose value flows only into an argument of a call imported from @vantageos/cloud-identity (directly, through a reader closure handed to such a call, or through a const that is only passed, mapped without a decision, or checked against its own .take(B + 1) budget). The same read also used locally (a role branch, an id comparison, a presence test, a local function) stays counted. Source: src/detectors/source/local-identity.ts, header "STORAGE FOR THE PACKAGE".
  • COULD-NOT-JUDGE: a file with a syntax error is named and judged nowhere (exit 2 when nothing else is red); an empty population is never a clean bill.
  • Ceiling 0: CR-2 is mechanical (fixtures/rule-classes-d02c931.json). Any violation is counted in mechanical_rule_counts / mechanical_rule_module_counts and exits 1.

CR-2 is the standard's R-57, reported under its doctor-owned key. It was authored before the standard numbered it; the standard at bacc54a defines the same rule as R-57. The count stays under the key CR-2 because the vantage-peers deploy-gate baseline (.claude/config/backend-doctor-baseline.json) reads CR-2; reporting it under both ids would count one defect as two rules, so R-57 itself is listed under NOT_MECHANICALLY_CHECKABLE with that reason. Renaming the key to R-57 is a follow-up that must move that baseline in the same delivery. Source and rationale: src/detectors/source/local-identity.ts.

CR-3 — write/read asymmetry for a scoped caller (doctor-owned, mechanical, ceiling 0)

A row a scoped caller can write must be readable back by that same caller. src/detectors/source/write-read-asymmetry.ts compares, per table with a tenant column in the scanned schema.ts, the public doors that insert into it with the public queries that read it. Three clauses, each site tagged:

  • [claims] — every public mutation inserting into table T accepts every transport-verified claim any public query reading T accepts. The claims are the three the standard names in R-54 clause 1 (verifiedActor, verifiedOrg, verifiedPerson; the same CLAIMS constant as R-54, read through the same args reader, so an args spread from a tree const is followed). A write door that cannot be told which organisation a service-account call is for cannot stamp it.
  • [fields] — the tenant columns an insert sets contain the tenant columns T's read doors filter on (.withIndex(_, q => q.eq("col", …)), .filter(q => q.eq(q.field("col"), …))).
  • [absent] — the tenant value an insert sets cannot be absent because of caller input without a refusal on its path: an unconditional undefined/null, an optional door argument (assigned, shorthand, or spread from args), or a conditional spread guarded by one. Absence that follows the caller's resolved scope (a nullable helper, a branch on isMaster) is the fleet row and is judged by [claims].

Routed writes. An insert behind ctx.runMutation(ref), ctx.runAction(ref), ctx.scheduler.runAfter(delay, ref) or ctx.scheduler.runAt(when, ref) is the calling door's own write: ref (api./internal./anyApi. path, makeFunctionReference("m:f"), or a const bound to one) is resolved to its registration and followed, transitively. runQuery is not followed (a query cannot insert).

Could-not-judge, always named: a run/scheduler call whose reference the scan cannot resolve to a registration in the tree (a dynamic reference, a components.* function, no such registration, a callee taken without a call or called through a bare identifier); a file with a parse error; no schema.ts in the tree; a query/insert table that is not a string literal; args spread from a validator the tree does not hold; an insert whose object or spread the walk cannot resolve (only when a required tenant column is then missing); a tenant parameter no call in the door's closure supplies; a read filter whose callback or column is not a literal. A tree with no applicable site is could-not-judge, never a clean bill. Doors that call requireServiceAccount, and tables no public query reads, are listed under OUT OF SCOPE in the section, not dropped.

The only exit is allow-write-read-asymmetry: <reason of 8+ characters> on a comment line of the door's statement; prose that merely quotes the token is not a declaration. Exempt doors are listed with their reason.

CR-3 is proposed as R-63 in the backend standard (ElPi-Corp). The key stays CR-3 until the doctor re-pins to a standard that defines it, as CR-2 did for R-57. Adopting this release makes mechanical_violations count CR-3 as a rule: a repository with asymmetric doors goes red until each is fixed or declared.

Proof: tests/source-cr3-write-read-asymmetry.test.ts (npx vitest run -t "write read asymmetry"), on material vendored byte-exact from vantage-peers 010edc9 (before PR #1577) and 83fa7ac (its head) (tests/fixtures/write-read-asymmetry-vp/.pin): the real tasks:create is refused before, passes after, and one landed mutation per claim form and per stamp form is refused on the fixed material.

R-31 — the read budget of a bulk mutation

  • R-31 (src/detectors/source/readbudget.ts): a .collect() inside a mutation is non-conforming unless it reads an index-equality range carrying // read-bound: <reason>; a bounded read (.take(n)/.paginate) conforms when the mutation self-reschedules with ctx.scheduler.runAfter, or when it is a caller-driven drain — the .paginate result flows to one return statement of the handler itself (a nested callback's return does not count) carrying both the continuation cursor and the isDone flag (any field names), so the caller re-invokes it page by page, and no ctx.runMutation/ctx.runAction in the same transaction carries that cursor or names the mutation; every other bounded read is could-not-judge.

R-54, R-55, R-56 — the door audit's causes 1, 4 and 5

Standard bacc54a §4. Each is mechanical, has no justification-token exit, and counts in mechanical_violations. Their material is the door audit of vantage-peers 16f0907 (docs/audit/2026-10-09-door-audit.md, branch argus/door-audit-16f0907); the real files are vendored byte-exact in tests/fixtures/vp-16f0907/ (.pin).

  • R-54 (src/detectors/source/service-account-claim.ts): a public door that resolves its caller through the organisation-scope resolver (withOrgScope, or any in-tree function that calls it) must declare a transport-verified claim argument (verifiedActor / verifiedOrg / verifiedPerson) AND refuse the call that is the service account and carries no claim. Red (A): no claim argument. Red (B): a claim argument but no such refusal, so its absence leaves the call master. A refusal is a throw (or a refusing return) whose path condition holds the service-account state and the claim's absence (a local derived from the claim counts; a refusal of the OTHER population does not). Exits, each listed and none silent: (a) service-account-only (requireServiceAccount, or requireResolvedCaller with masterOnly and mcpBoundOnly); (b) callers resolve only through a verified session (requireOrgAdmin, a bare identity read) and the door never reaches the scope resolver; (c) fleet-wide: <reason of 8+ characters>. Could-not-judge: an action whose scope arrives through a run call, a resolver-shaped callee defined outside the tree, a claim argument spread from a validator the scan cannot find, a door registered through a wrapper. A door that resolves no caller at all is outside the rule (CR-1 / R-6) and not counted. Clause 2 of the rule (the claim confines every row) is a dynamic property and is NOT decided here.
  • R-55 (src/detectors/source/narrow-then-bound.ts): in a public door and the helpers it reaches, a read bounded by .take/.paginate (or re-bounded by .slice(0, n)) whose rows are then narrowed by a caller-dependent predicate (a filter reading the door's arguments or the resolved scope, a filterBy… helper, an in-tree helper that narrows its parameter, a loop that skips on such a test) is red. Listed as covered, not silent: a fill loop that holds both the bound and the narrowing, and a refusal on a full window (a throw naming the cap, overflow or truncation) between the latest bound and the narrowing. An unbounded .collect() is R-11 / R-47's.
  • R-56 (src/detectors/source/credential-hygiene.ts), four clauses, one site tag each: (1) a credential-named column (code, token, secret, apiKey, …) stored without a digest, or a digest of a chosen secret (a door argument named like a secret, an environment secret) that reaches a stored row; (2) a table named like a refresh-token store, read by a hash index, with no function that reads it by hash AND marks the token consumed/revoked or deletes it; (3a) a table whose inserted rows carry tokenHash and expiresAt with no function that revokes or deletes a row of it, (3b) a mint into such a table (no code or refresh-token linkage) that neither revokes the principal's earlier bearers nor caps them; (4) a request-supplied commit/hash/token identifier matched to stored text by includes/startsWith/indexOf with no 40- or 64-digit check, and (4e) a verification response that echoes stored content. The entropy of an externally generated secret and the binding of a minted bearer to the session's claims are NOT decided here.
  • R-57 is measured by the doctor-owned CR-2 (src/detectors/source/local-identity.ts) and reported under the key CR-2 (see the CR-2 section). R-57 itself is listed under NOT_MECHANICALLY_CHECKABLE with that reason and is never reported present.

R-52 and R-53 — the write reaches the right row, and the actor is who it is by ID

  • R-52 (src/detectors/source/cross-tenant-write.ts): a write that reaches a row with no tenant value (no tenant column, or an optional one) through a value the caller chose — an argument, or a stored field a public write set from its arguments, such as task.project — must be dominated by a guard reading a server-stamped tenant: the target's own tenant when it has a column, a fleet-only decision when it has none. Tenant columns are the standard's orgId/tenantId/workspaceId/orgSlug, or a namespace checked against the caller's allowed prefixes (ElPi-Corp #228). The namespace condition is decided per table from its public doors (every query/mutation/action that reads or writes it): every door runs a prefix test (a function taking a namespace parameter and testing it with .startsWith, closed over calls — vantage-peers isNamespaceAllowedForScope — called on a namespace value: the argument at its namespace parameter derives from args.namespace, a row's .namespace or a binding of one, never a constant), refuses every non-master, or reads the table only on the true side of an isMaster test -> tenant column; no door does -> the table is untenanted; mixed, or no public door -> every R-52 site targeting it is could-not-judge, never green. On vantage-peers 2faa399, memories is mixed (nine doors checked, oauth.ts:provisionOrganization reads it through findSeatNameCollision unchecked), so its six sites are could-not-judge.
  • R-53 (src/detectors/source/identity-by-id.ts): every public write door resolves its principal through @vantageos/cloud-identity (never a client_org_mapping lookup in product code, save the one read the ruling of task k1742q0vm05x2f5pqawnz1eah18fp01p admits: keyed by the Clerk org ID — .withIndex("by_clerk_org_id", (q) => q.eq("clerkOrgId", K)) or .filter((q) => q.eq(q.field("clerkOrgId"), K)), then .unique()/.first() — where K is the org ID the package hands the orgKindOf adapter (its first parameter, or a helper parameter every call site fills with it), inside an orgKindOf or organisationById member of a lookups object passed to resolveActingPrincipal/assertTargetBelongsTo — inline, through a local, or as the return of a named builder that only assembles (no branch, no throw, no expression statement) — deciding nothing but its own return, an organisationById answering only { id, active } read from the row or null; a slug or name key, withOrgScope/lookupOrgMapping, and a read whose result reaches anything else stay red; a getUserIdentity() read is admitted only when its value, followed through locals, destructuring and helpers, ends solely as the first argument of resolveActingPrincipal/resolvePersonPrincipal, since the package verifies nothing about the credential itself), admits by comparing stored IDs (inline, or through the package's own assertTargetBelongsTo(principal, { orgId: row.orgId[, ownerId: row.ownerId] }, …) — synchronous at cloud-identity a6e5780 or awaited with a lookups argument at PR #21 — whose !ok branch throws or returns before the write; never a name field against a typed or credential-carried name), and refuses an unresolved principal; any helper or MCP tool handler that compares names to admit, or resolves identity itself, is a local identity layer.

Neither rule has a justification-token exit. Both are mechanical and count in mechanical_violations.

The deploy-gate evidence — --json-evidence and mechanical_violations

The vantage-peers deploy hook (enforce-backend-doctor-before-deploy.py) refuses a Convex deploy unless qa/backend-doctor-<sha>.json pins the deployed commit and carries an integer mechanical_violations == 0. It refuses on mechanical non-conformance only, never on a judgement/process rule. The CLI writes that file:

npx tsx src/cli.ts <repo>/convex --json-evidence <repo>/qa/    # a directory -> backend-doctor-<sha>.json

It writes exactly {"sha","cli_commit","convex_path","exit_code","checked","total","mechanical_violations","mechanical_rule_counts","mechanical_rule_module_counts"} (the last two are additive; the first seven are unchanged):

  • sha = git -C <convex_path>/.. rev-parse HEAD; no sha, no file (exit 2).
  • cli_commit = this checkout's HEAD (+dirty when src/ or fixtures/ differ from it), or npm @vantageos/backend-doctor@<version> for an installed copy.
  • exit_code = the code the process actually exits with; checked/total = the coverage line.
  • mechanical_violations = the number of rules whose reduced state is non-conforming and whose class is mechanical (CR-1 and CR-2 each count as one rule). The same count is printed as MECHANICAL VIOLATIONS: N right under the VERDICT line, with the per-rule breakdown.
  • mechanical_rule_counts = an object mapping each red mechanical rule id to its non-conforming count, the number its report line prints: sites for a source rule (R-53: 113 site(s) VIOLATE -> "R-53": 113), rows for an oracle rule (R-2: 6 of 106 applicable oracle rows VIOLATE -> "R-2": 6), registrations for CR-1, defining functions for CR-2. Its key count equals mechanical_violations; {} on a clean run. The count is attached by the reduction that formats the printed line, from the same variable (src/violation-count.ts), never parsed back from the text. A rule reduced from two red halves (R-31) sums its red halves; a single structural finding (R-14, R-38) counts 1. Printed as MECHANICAL RULE COUNTS: R-2 6, … under the MECHANICAL line. null when mechanical_violations is null, or when a red mechanical rule's producer attached no count (printed as MECHANICAL RULE COUNTS: COULD-NOT-JUDGE — no count attached for …): a partial map would read as complete.
  • mechanical_rule_module_counts = for each site-based red mechanical rule (source scans, R-46, CR-1, CR-2), { module: count } summing to that rule's mechanical_rule_counts entry. A convex/ file's module is its basename without extension (convex/tasks.ts -> tasks, convex/lib/auth.ts -> auth); a file outside convex/ keeps its path without extension (R-53 also reads mcp-server/src: mcp-server/src/tools). Row- and finding-based rules (R-2, R-8, R-13, R-28 and every other oracle rule, R-14, R-37, R-38, R-40, R-41, the standard-document rules) are absent. null with mechanical_rule_counts.

The class is data, not code. fixtures/rule-classes-d02c931.json gives every numbered rule plus CR-1 and CR-2 exactly one class with a one-phrase basis (42 mechanical = the standard's 40 + CR-1 + CR-2; 16 judgement = 13 judgement + 3 process). Each R- entry is cross-checked at run time against the pinned standard's own build kind (§15 table, which at bacc54a lists all 56 rules; a Detector spec's *kind:* token is the fallback for a rule the table omits), process mapping to judgement. Consequence: R-6, R-11, R-29, R-33, R-34, R-35 now COUNT (ElPi-Corp #225 settled them mechanical), as do R-52 to R-57; R-16..R-20 and the other §15 judgement rules print but are not counted, exactly as the standard classifies them.

Never a silent 0. Abstentions (could-not-judge) are not violations: they are listed apart on the MECHANICAL line and keep the exit code at 2 when no violation exists. The count becomes null (the hook reads it as INCOMPLETE and refuses) when it cannot be certified: a rule with no class, a sidecar class that contradicts the standard, a sidecar pinned to another standard, incomplete coverage, a failed CRUD self-check, or a judged convex/ tree that differs from the commit sha names. Each reason is printed as CANNOT CERTIFY: ….

Pins

Every input is pinned and, where it is a vendored document, proven byte-identical to its upstream git blob (an un-hashable vendored doc is an authority granted to oneself).

The single authority for which standard fixture is active is the loader constant STANDARD_FILE in src/detectors/standard/_doc.ts — currently backend-standard-d02c931.md (56 rules — adds R-54, a door the service account can reach acts inside a transport-verified organisation and refuses a claimless call; R-55, a read narrows before it bounds; R-56, credentials as digests, single-use refresh tokens, revocable bearers, full-length verification tokens; and R-57, no local identity primitive; canonical source: ElPi-Corp vantagepeers/backend-standard/backend-standard.md). bacc54a is on ElPi-Corp main (gh api repos/elpiarthera/ElPi-Corp/compare/bacc54a53d5f6f9f20e5254908e2b46807902faa...main -> ahead, behind 0); the PR #239 merge 7d7df89 carries the same blob but left main in the 2026-10-10 history rewrite (PINS.txt). The previous pin backend-standard-2825122.md (52 rules) and the older ones stay in the tree as provenance. Everything else is provenance, NOT the active pin: PINS.txt and any other fixtures/backend-standard-<sha>.md still in the tree (e.g. the superseded backend-standard-a9d7877b.md, 47 rules — it lacks R-48) are kept only to show what was scored before. If any of them disagrees with STANDARD_FILE, STANDARD_FILE wins. Read the active pin from STANDARD_FILE, never from PINS.txt or a number typed elsewhere — following a stale PINS.txt would silently score the superseded 47-rule document and never surface R-48. See PINS.txt for the engine, backend snapshot, and MCP-family doc + VR inventory pins.

Discipline (how a detector is proven)

Every detector is written RED-first against the real material (never the standard's prose illustration — the false-green law), proven to bite by a mutation injected on material it did not choose and grep-asserted as landed on an executing line before any verdict is read, and proven at both poles (a conforming site must pass; a detector that only ever fires is as broken as one that never does). Cross-document rules additionally carry a positive control per document so an unlisted identifier prefix cannot manufacture mass false positives.