# Claims review — 0.2.37

- base: v0.2.36
- head: ef7b1dd
- surface: sha256:ccfef14ad8a5dddddd579822a9513c16680ef757c4e3007ed55c6858936357ef
- verdict: pass
- reviewer: Claude (Opus 5)

Six surfaces changed. The release began as a claim with no call site and a
result object that decided on bounds nobody had declared; two of the five
findings below were found while reviewing the fixes for the other three, which
is the only reason they are in this record rather than in the next one. The
shape that recurs: a sentence corrected once, in one of the places it was
written.

## Findings

### 1. docs/RECEIPT-SPEC.md — "Receipts are anchored automatically"

- Claim, as of 0.2.36: *"Receipts are anchored automatically. Documents are
  not…"* — unconditional, on a surface that ships in the tarball. The same claim
  was repeated in `README.md` (*"Opt-in anchoring: `CONARIUM_ANCHOR_SINK=…`"*,
  which reads as: setting the variable stamps receipts), in `LIMITATIONS.md`
  (*"Receipts already show `pending`"*), in `docs/ARCHITECTURE.md` (*"that path
  submits a digest"*), and in the header of `bin/conarium-stamp.mjs`.
- Mechanism: `AnchorScheduler.maybeAnchor`, `createAnchorSinkFromEnv` and
  `appendAnchorSidecar` in `src/anchor.ts`.
- Why it fell short: none of them has a caller. `src/audit.ts` contains no
  occurrence of the string `anchor`; `src/receipt.ts:399` gives every receipt
  `anchor: null`; the only production files importing `./anchor.js` are
  `src/console.ts`, which classifies an anchor for display, and
  `src/anchor-service.ts`, which is the hosted endpoint. Measured end to end
  with `CONARIUM_ANCHOR_SINK=opentimestamps` and a receipt sink configured: the
  receipt carried `anchor: null` and no `<sink>.anchors.jsonl` was written. The
  variable selects a calendar client for the manual tools; it does not put the
  write path in touch with a calendar.
- Disposition: the text is narrowed to the mechanism in all five places, and the
  scheduler was deliberately **not** wired. Wiring it adds an outbound network
  hop to the receipt path and changes the two-connection list in
  `docs/ARCHITECTURE.md`; that is a product decision, and making a sentence true
  by adding a network call to satisfy it is the wrong direction of fit.
- Guard: `test/anchor_wiring.mjs` — an automatic-anchoring claim on a claim
  surface requires a write call in `src/` or `bin/`, excluding tests and
  excluding the definitions themselves. Demonstrated in both directions rather
  than one: with the claim restored and no call it exits 1 and prints the file
  and line; with the claim present and a call added to `src/console.ts` it exits
  0 and prints the call site. A guard that has only been shown to fail proves it
  can fail.
- Residual, and it is the same residual the guard has by construction: the rules
  are phrasings. Finding 3 is what that costs.

### 2. bin/conarium-reconcile.mjs — an undeclared exclusion decided the outcome

- Claim: the `/2` object implements `coverage-reconciliation/2` as
  `draft-dogru-scitt-disclosure-evidence-04` defines it.
- Mechanism: `projectResultV2`, and §Exclusions of that draft: *"Exclusion rules
  MUST be stated in the Mapping Profile and are therefore covered by its digest…
  An implementation MUST NOT exclude items by a rule that is not in the
  profile."*
- Why it fell short: with no profile, an infrastructure pattern was emitted as
  `excluded` with `rule.version: "hardcoded"`, and the run reported
  `outcome: no-exceptions` while `bounds.exclusion` said `undeclared` on the same
  object. It named the bound as undeclared and then decided with it. The
  inversion is what makes it a defect rather than a rough edge: measured on one
  input, a profile that merely omitted the rule produced `indeterminate` and
  `exceptions`, while **no profile at all** produced `excluded` and
  `no-exceptions`. Strictly less declaration bought a strictly cleaner verdict,
  which is Proposition 2b run backwards, on the outcome the draft calls the one
  through which a reconciliation can be made to come out clean.
- Disposition: with no profile, an infrastructure pattern is `indeterminate`
  (`reason: exclusion-undeclared`), and `bounds.exclusion` is
  `operator-declared` only when the profile lists at least one exclusion rule —
  a profile carrying no exclusions declares nothing about exclusion. Measured
  after the fix on the same input: no profile → `indeterminate` / `exceptions`;
  profile without the rule → `indeterminate` / `exceptions`; profile with the
  rule → `excluded` / `no-exceptions`. Declaration and verdict now move in the
  same direction.
- Second finding, inside the first: `conformance/cases/reconciliation/v2-excluded.json`
  had pinned the defect. Its `expectOutcome` was `no-exceptions` with
  `expectCounts.excluded: 1`, and its own `rationale` field said *"Without a
  Mapping Profile the exclusion bound is undeclared."* The fixture stated the
  undeclared bound and asserted the decision taken with it. A green vector over
  a defect is worse than no vector: it converts the defect into a contract. The
  case now runs against a profile that declares the rule, and the profile-less
  input is a negative case (`v2-neg-undeclared-exclusion`).

### 3. LIMITATIONS.md — the anchoring correction had a second copy

- Claim: *"Bitcoin confirmation still takes hours; receipts still show `pending`
  until upgrade."* (`LIMITATIONS.md`, OpenTimestamps client section.)
- Mechanism: the same one as finding 1 — a receipt is born `anchor: null`.
- Why it fell short: the file contradicted itself. Its anchors section, rewritten
  earlier in this same release, says `pending` appears only after an operator
  submits a stamp; two hundred lines down the old sentence was still there, and
  it is the more specific of the two, so a reader resolving the contradiction
  would resolve it the wrong way. `LIMITATIONS.tr.md` had both places corrected,
  so the two languages had also come apart — with the English, the surface that
  ships to npm, holding the stale half.
- Why the guard did not catch it: the rule was `receipts already show
  `pending``. The sentence says *still*. A rule pinned to one adverb guards one
  sentence, not the claim the sentence makes.
- Disposition: sentence corrected to match the mechanism; the rule now matches
  *already* / *still* / *are*, and was shown red against the live tree before the
  sentence was touched — the check named `LIMITATIONS.md:249` and exited 1.
- Recorded because of when it was found: during the release review, not by the
  guard written in the same release for exactly this claim. The guard is worth
  keeping and its limit is worth stating rather than describing it as coverage.

### 4. Two hand-written copies of the claim-surface list

- Claim: the release gate reviews the documents that carry promises, and the
  retracted-phrasing scan reads the same documents.
- Mechanism: `SURFACES` in `test/denetci.mjs` (22 entries) and `SURFACES` in
  `test/claim_discipline.mjs` (12 entries).
- Why it fell short: the second was a silent subset of the first. Measured: a
  strict subset, no entry in the second that was absent from the first, so ten
  documents — including `standards/README.md` and
  `docs/security/THREAT-MODEL.md`, the two that carried stale sentences into
  0.2.35 — were exempt from the phrasing scan for no stated reason. 0.2.36's
  record named this and deferred it to its own release; this is that release.
- Disposition: one file, `docs/claims/surfaces.json`, read by both. Every entry
  carries the reason it is a surface, and the phrasing exemption is an explicit
  per-entry field with a required justification — measured after the change: 22
  entries, 22 reasons, zero exemptions. Widening the scan from 12 to 22 produced
  one new red, `docs/PRIOR-ART.md` matching the Turkish-letter check on a
  registry field name (*Yayın Numarası*); it was resolved by naming the example,
  not by narrowing the list back.

### 5. LIMITATIONS.tr.md — three limitations reached one language and not the other

- Claim: the Turkish limitations file is the Turkish limitations file.
- Mechanism: nothing. Two files, written by hand, with no relation between them.
- Why it fell short: 24 sections against 21. Missing in Turkish: the Node 20
  end-of-life floor, the audit sink hash not being JCS, and reconciliation
  establishing object attribution rather than per-statement coverage. All three
  are limits a reader is entitled to before choosing this package; a reader in
  one language was told and a reader in the other was not. The same shape lost
  the SOC 2 answer for a release in 0.2.26.
- Disposition: the three sections written, and `test/limitations_locale.mjs`
  compares the two files by a section key carried in an HTML comment — not by
  heading text, which is translated and cannot be the join. Shown red first: with
  one key removed the check exits 1, names the missing key, and names the
  orphaned heading beside it.

## What this release adds rather than corrects

`--profile` reads a Mapping Profile that declares the multiplicity bound, the
exclusion rules, and the two clocks. The clock fields are **declarations and
nothing more**: the tool does not verify that `clocks.observation` is the clock
that stamped the snapshots or that `clocks.receipt` is the one that stamped the
receipts, and `/2` therefore reports them at `operator-declared`, never
`measured`. What the declaration does change is the comparison — measured, not
asserted: against a receipt 3s outside the window, a declared bound of 5s leaves
the item `indeterminate` (a declaration does not invent a match) and a declared
bound of 1s moves it to `observed-without-receipt`. `--skew` and `clocks.skew`
are the same kind of statement, so disagreement between them fails the run rather
than resolving silently; `5s` and `5000` are the same declaration.

The field list and its duration grammar are written out in
`docs/RECEIPT-SPEC.md` because another specification in the same working group
asked to adopt the shape rather than invent a second one. That is the reason the
encoding is stated to the millisecond and the reason `0ms` is documented as a
declaration rather than a default.

## What did not change

The policy gate, masking, the receipt format, `conarium-verify`, and the `/1`
reconciliation body and its exit codes. `/1` was re-measured on the same input
after each change and is byte-identical; `test/spec_exitcode_drift.mjs` reports
the same set. `standards/` is untouched — the published `-04` is immutable, and
the promises made to that list were kept in code and in `docs/`, not by editing
a posted draft. `-04`'s Implementation Status now understates this
implementation in four places; correcting it is `-05`'s work and is not done
here, because a draft describing an unpublished tree would be the same defect
pointed the other way.

Measured and not a finding, recorded so it is not re-raised: `package-lock.json`
carries `version: 0.2.24`. The published SBOM does not inherit it — `npm sbom
--package-lock-only` takes the root component from `package.json` and reports
`@conarium-ai/core@0.2.37`, 81 components, CycloneDX 1.5.
