Skip to content

No status behind a seal that didn't hold - #33

Merged
threeqube merged 4 commits into
mainfrom
status-row-behind-unheld-seal
Oct 24, 2026
Merged

threeqube merged 4 commits into
mainfrom
status-row-behind-unheld-seal

Conversation

@threeqube

@threeqube threeqube commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #31 — review and merge that first.

Closes #32.

What and why

Whether a credential has a status list, and which entry of it is its own, are the credential's own claims. Behind a seal that didn't hold they vouch for nothing. Yet the Status row read "the issuer set up no way to deactivate this" for a tampered credential with no status entry, and stripping that entry would buy exactly that.

  • The Status row now reads "not checked" behind an unheld seal, as Move to verifier-core 2.0.0-beta.1 #31 made the Dates row do. No new wording.
  • "Deactivated" and "on hold" are believed only behind a held seal, in the headline as well as the row. The library halts after a failed seal, so it can't produce those cases today, but the rules shouldn't depend on that.
  • notWithdrawn keeps the same guard even though no test can catch it today: everything that reads it already stands behind a held seal. The code comment explains why it stays.

What the earner sees differently: on the demo, Changed (tampered) and the website-down ?vc= link (badDidWeb.json) now show Status "not checked" instead of "the issuer set up no way to deactivate this". Nothing changes when the seal held.

Validation

  • 105,720 unit tests pass; typecheck and lint are clean. CI is green, including the browser tests and the tests against the published preview.
  • The consistency suite (test/consistency-combinations.ts, since Move to verifier-core 2.0.0-beta.1 #31 split it) gains a rule: behind an unheld seal, the Status row reads "not checked", with severity unchecked.
  • Mutation checks, re-run on the final commit (number of tests that fail when each guard is removed):
Guard removed Failures
The Status row's own guard 20,782
"Deactivated" 7,409
"On hold" 7,409
The action's name gate 1
The Status row's severity 90,739

notWithdrawn's guard fails no test on its own, as its comment explains; with the Status row's guard also removed, the clear-list test fails.

  • Smoke test (24 October 2026), pr-31 against pr-33 with the compare tool, expectations written first: exactly 4 of 21 cases differ — Changed, Not their seal, and the ?vc= links legacy-noStatus-noExpiry-tampered and badDidWeb — each only in the Status row, "the issuer set up no way to deactivate this" → "not checked".
  • In the wallet (24 October 2026): the wallet's own verification slot, with Move to verifier-core 2.0.0-beta.1 #31 and No status behind a seal that didn't hold #33 packaged as the release branch would be, against today's build, in its dev server and a production build (identical results in both): the same four cases change only in the Status row, and every other difference is one Move to verifier-core 2.0.0-beta.1 #31's smoke test predicted.

Reviews

Rebased twice onto #31 as it grew; the content of the first three commits is unchanged (checked line by line). Their IDs on this branch:

Commit Level Findings Outcome
41948db (was 67ee77a) medium, tests included 6 5 fixed in the next two commits. One deferred, see below
a8bc3d1 (was f564c02) medium, tests included no defects 2 clarity notes fixed in b2fece4
b2fece4 (was 2277015) low, test hunk read none —
Final review of the whole PR on #31 medium, tests included no defects; 3 notes 2 fixed in c3cb349; the third was this description, now refreshed
c3cb349 medium, tests included 2 comment notes both fixed in the same commit

The final review ran 164,232 inputs through the code against #31's: behind an unheld seal, no headline, detail, action or row says anything about status; where the seal held, nothing differs. c3cb349 adds a unit test for the "on hold" guard (it had none) and corrects two comments about what the tests pin.

The 41948db review found that the rewritten "names nobody" test could no longer fail. It now pins "built wrong" behind a missing seal, the verdict whose action would otherwise name the issuer.

Deferred

The seal guard is applied check by check (dates, status, the action's name). A single "trusted checks" view, which drops seal-dependent results when the seal didn't hold, would cover future readers in one place. That's a larger restructure than this PR should carry.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Try it: https://digitalcredentials.github.io/verifier-plugin/pr-33/

Updates on every push to this PR, and goes away when it closes. The browser tests run against this copy too.

@threeqube

Copy link
Copy Markdown
Collaborator Author
screencapture-file-Users-sunnylee-repos-verifier-plugin-compare-report-index-html-2026-10-22-10_26_59

@threeqube
threeqube force-pushed the status-row-behind-unheld-seal branch from 2277015 to e30f2d3 Compare October 22, 2026 18:56
threeqube added a commit that referenced this pull request Oct 23, 2026
Vitest fails any test file that runs longer than 60 seconds, whatever its
results ("Timeout calling onTaskUpdate"). The consistency suite grew to
105,499 tests in one file, and CI machines vary about threefold in speed:
it passed in 34 s on #31 and failed twice at 70 s on #33, every test
passing. Reproduced locally by slowing it past 60 s; two 40 s files run
back to back on one worker pass, so the limit is per file.

The combinations, their builders and the agreement rules move unchanged
to consistency-combinations.ts. consistency-1 to -4 each run every fourth
combination (26,358 each, 105,432 in all). consistency.test.ts keeps the
claim patterns, the nameless-issuer check (whole, about 10 s) and the
stopped-early cases, and gains one test that every part has its file, so
a lost file can't drop a quarter of the combinations unnoticed.

Checked: 105,698 tests (105,697 plus that one); four deliberate breaks
fail exactly as many tests as on the single file (29,147, 14,571, 1,010,
14,573); the guard fails with a part missing or one extra; the slowdown
that failed the single file at 73 s passes split, files run one at a
time (each at most 17 s). The whole run takes about 10 s, not 25.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@threeqube
threeqube force-pushed the status-row-behind-unheld-seal branch from e30f2d3 to 0439434 Compare October 24, 2026 14:49
threeqube and others added 4 commits October 24, 2026 09:43
Whether a credential has a status list, and which entry of it is its
own, are the credential's claims. Behind a seal that didn't hold they
vouch for nothing, yet the Status row said "the issuer set up no way to
deactivate this" for a tampered credential with no status entry, which
removing that entry would buy. It now reads "not checked", as the Dates
row does since #31.

The same holds for the headline: "deactivated" and "on hold" are
believed only behind a held seal. The library halts after a failed seal,
so it can't produce either today, but the rule must not depend on that.
notWithdrawn needs no guard: everything that reads it already stands
behind a held seal.

A test that pinned "withdrawn" behind a missing seal, to check the
action named nobody, now pins signature_unchecked and still checks that.
The consistency suite gains the rule that the Status row reads "not
checked" behind an unheld seal. Mutation: dropping the row guard fails
20782 tests, the withdrawn guard 1853, the on-hold guard 1852.

Closes #32

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The "names nobody" test pinned signature_unchecked, whose words never
  carry a name, so it could no longer fail. It now pins "built wrong"
  behind a missing seal, the verdict whose action would name the
  issuer; ungating `named` fails it. The withdrawal half is its own test.
- notWithdrawn is guarded like its siblings. No test can catch it today,
  since every reader stands behind a held seal; it guards the one claim
  that must never leak past a broken seal.
- The consistency rule checks the Status row's severity as well as its
  value (40860 failures when it reads success).
- Row-level tests for a tampered seal over a list that came back clear,
  says deactivated, or says on hold. A duplicate of an existing test goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason notWithdrawn keeps a guard no test can catch moves to its own
docblock, so it doesn't look removable. The tampered-seal row tests say
they pin the row's guard, and name where the predicate guards are
pinned; their headline assertion, which couldn't fail, goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the final review (24 October 2026):

- isSuspended's seal guard had no unit test; only the consistency suite
  caught its removal. "Believes no hold when the seal never reported"
  mirrors the withdrawal test. Mutation: the guard removed fails it (it
  failed no unit test before).
- The three tampered-list Status row tests said they pinned the row's own
  guard. They pass if either the row's guard or the matching predicate's
  is there (notWithdrawn's for a clear list); the comment now says so, and
  names what pins each alone. Checked: the row guard removed fails only
  the two "no list" cases; the withdrawal guard, "believes no withdrawal";
  the hold guard, the new test; the row and notWithdrawn guards together,
  the clear-list case.
- The consistency comment on the Status row behind a broken seal is in
  the present tense again: this PR is the verifier-plugin#32 fix.

Checked: lint, typecheck, 105,720 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@threeqube
threeqube force-pushed the status-row-behind-unheld-seal branch from 0439434 to c3cb349 Compare October 24, 2026 22:17
@threeqube

Copy link
Copy Markdown
Collaborator Author
screencapture-file-private-tmp-claude-501-Users-sunnylee-repos-verifier-plugin-9874c219-e69c-481f-bd2e-2f1b488c8fdd-scratchpad-report-smoke2-index-html-2026-10-24-16_34_37

Base automatically changed from verifier-core-2.0.0-beta.1 to main October 24, 2026 23:38
@threeqube
threeqube merged commit d575001 into main Oct 24, 2026
6 checks passed
@threeqube
threeqube deleted the status-row-behind-unheld-seal branch October 24, 2026 23:40
github-actions Bot added a commit that referenced this pull request Oct 24, 2026
@threeqube

Copy link
Copy Markdown
Collaborator Author

Post-merge check (29 October 2026)

Checked on the live demo and the release branch at main 713b8b4, which also includes #38. The expectations were written before checking.

  • Landed: d575001 (this PR's merge) is on main, and the live demo serves 713b8b4.
  • Release: the release branch's dist/ is byte-identical to a fresh build of 713b8b4.
  • Browser tests against the live demo: 58 passed, 3 skipped (the cross-site tests, which need a local second site).
  • On the live demo, Status reads "not checked" behind a seal that didn't hold, in all four cases this PR named:
Case Headline Status
Changed This credential has been tampered with ✅ not checked
Not their seal We couldn't confirm who signed this ✅ not checked
legacy-noStatus-noExpiry-tampered This credential has been tampered with ✅ not checked
badDidWeb We couldn't check this right now ✅ not checked
  • Nothing changed when the seal held: Verified still reads "the issuer set up no way to deactivate this", and Withdrawn "deactivated by the issuer".
  • Compare tool: the live demo against itself shows 0 of 21 cases differing.

The wallet still pins an earlier build (a4ca3e6), so none of this reaches it until that pin is bumped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Status row says "the issuer set up no way to deactivate this" behind a seal that didn't hold

1 participant