Consumer conformance record¶
As a Singular integrator, I can replay the current conformance interface, distinguish partial evidence from completed coverage, and trace older observations to the candidate that produced them. The connected naming release journey is documented separately in recovery and permanent retirement.
Permanent contract and preserved consumer evidence¶
The first milestone's fixed contract admits registration (insertActive) and
permanent termination (updateTerminal). All seven edge values remain encodable
in request datums and fold redeemers, but witnessTerminal, insertAbsent,
updateActive, deleteAbsent and deleteActive fail at the permanent state
validator, including independently constructed requests and mixed batches.
Application payload updates leave the registry leaf unchanged; they are not
the excluded updateActive registry transition.
The seven-edge interface and older receipts below describe the preserved broader consumer contract. They do not establish current permanent-contract successes. Its generic consumer expectations remain recorded, and the new permanent requirement remains uncovered without a bounded live consumer comparison receipt. The candidate evidence separates compiled refusal tests, connected CLI execution and their limits. Defined redeemers, missing CLI commands and setup failures are not evidence of behavioral refusal.
The contract and its binding¶
The rows answer to the consumer contract source-bound to
lambdasistemi/cardano-keri@14a64a4681d3e429fab5877062b5c476c2a4bfe2:
the registry design rulings (eleven operator rulings and fourteen registry theorems) and
docs/user/consumer-checklist.md. cardano-keri consumes the
generic registry — Insert, Update, Delete — not the naming
application, whose restriction refuses Delete and reuse by design.
No naming evidence can establish any row here, and none is claimed.
The canonical identity rows (#69) carry the correction epic 16
executed: cardano-keri's ruling is that the registry provides
identity uniqueness, but a permissionless ledger cannot prohibit a
rival registry — a consistent initialization from a second seed
passes every frozen check and the node accepts it
(offchain/naming-correspondence.md, "What t50 settled"). Canonical
identity is therefore a derivation the consumer authenticates,
not a refusal the chain performs: the canonical registry token's
name is SHA-256 of the canonical seed's outRef, that seed can never
be spent twice, and a rival from another seed can never carry that
name.
The denominator¶
conformance/rows.json carries 47 rows, 46 owned: one permanent contract requirement, five registry identity
requirements, twenty-four registry operation requirements, eight serialization
requirements, five consumer resolution requirements and three execution-limit
requirements, plus the checkpoint and treasury policy (bonds, poison,
the juvenility window W, the signature threshold) recorded as
out of scope — it belongs to cardano-keri's checkpoint machine
and treasury, explicitly never claimed by Singular. list prints the
owned denominator separately so the boundary stays visible. Superseded
and held rows stay in the denominator and visible to completeness
accounting: relabelling a row pays no coverage and shrinks no board.
rows.json never carries executed: the declared field is the
coverage plan and the parser rejects the string. A row prints as
executed only when a run receipt for it exists and matches the
current base (--receipts DIR or CONFORMANCE_RECEIPTS).
The registry-mode contract change¶
This is a contract change, declared here rather than discovered by a consumer through a failing decode. Anything that reads Singular's datums or builds its redeemers changes with it.
The state datum: six fields become eight¶
| # | before | after |
|---|---|---|
| 0 | root |
root |
| 1 | maxFee |
maxFee |
| 2 | processTime |
processTime |
| 3 | retractTime |
retractTime |
| 4 | repPolicy |
applicationPolicy |
| 5 | consumerPin |
activePolicy |
| 6 | — | absentPolicy |
| 7 | — | terminalPolicy |
consumerPin is deleted with the pinned consumer script it named; the
mandatory withdrawal that script required is deleted with it. repPolicy is
renamed activePolicy and keeps its meaning — the policy that mints the
token standing for a live registration. Three pins are new: the
application policy that certifies requests, and the absent and terminal token
policies.
A decoder that reads field 4 as the representative policy now reads the application policy, and a decoder that reads six fields refuses an eight-field datum. Both are the intended consequence, and both are exercised: the state round-trip row round-trips all four derived pins through a boot datum, and its armed control demands the old six-field encoding and fails.
None of the four is a literal. Each is derived, for the registry identity a
boot creates, from the two partitions' script-identity.json: the application
policy is the naming application script's applied hash, and the three token
policies are the one parametrised witness script applied at kinds 0, 1 and 2.
The operation: a fourth constructor¶
| index | before | after |
|---|---|---|
| 0 | Insert value |
Insert value |
| 1 | Delete value |
Delete value |
| 2 | Update old new |
Update old new |
| 3 | — | Read value |
The three published indices do not move. A read leaves its key exactly where it is, and on chain only the terminal codec byte is admitted for it.
The request: a destination, then an edge index¶
Request gained one field, appended last: destination, a pair of the binary
address bytes the minted token must land at and the inline datum that output
must carry, as an Option: Some carries the datum itself, None means a
datum-less output. The request carries the datum rather than its hash (#419),
so anyone can build the fold from the chain alone; an approval still binds the
datum by its BLAKE2b-256 hash, empty for none. A request that mints nothing
names no destination.
Issue #183 then re-cut the request itself. Field for field, old wire beside new:
| field | old wire | final wire (#183) |
|---|---|---|
| 0 | requestToken |
requestToken |
| 1 | requestOwner |
requestOwner |
| 2 | requestKey |
requestKey |
| 3 | requestValue: Operation — Insert v / Delete v / Update old new / Read v |
edge: Int — the seven-edge table index, 0-6 |
| 4 | tip: Int — must equal state.tip |
deposit: Int — must equal the request output's lovelace less state.tip |
| 5 | submitted_at |
submitted_at |
| 6 | destination |
destination |
The constructor index does not move: RequestDatum is 0 before and after,
and the four unchanged fields keep their positions. What changes is what
fields 3 and 4 mean.
The seven edges, with the trie move each names and the value bytes that used to carry it:
| edge | name | trie move | old wire |
|---|---|---|---|
| 0 | insertAbsent |
insert 0x00 |
Insert 0x00 |
| 1 | insertActive |
insert 0x01 |
Insert 0x01 |
| 2 | updateActive |
update 0x00→0x01 |
Update 0x00 0x01 |
| 3 | updateTerminal |
update 0x01→0x02 |
Update 0x01 0x02 |
| 4 | deleteAbsent |
delete 0x00 |
Delete 0x00 |
| 5 | deleteActive |
delete 0x01 |
Delete 0x01 |
| 6 | witnessTerminal |
read 0x02 |
Read 0x02 |
What a consumer has to change. Write the edge index where the operation payload used to go, and the deposit — the lovelace the request output holds, less the state's tip — where the tip used to go. An edge names its own leaf bytes, so a consumer no longer chooses them and can no longer get them wrong.
Refusals that go away, because no request can express them. leaf-codec
(a value byte outside the three-byte codec), insert-terminal,
tip-mismatch, update-to-absent, update-absent-to-terminal and
update-active-to-active are all faults of value bytes that the edge wire
does not carry.
Refusals that remain, and the two that are new. A read whose key does not
bind 0x02 is refused read-absent, read-non-terminal or key-unknown,
and a tree-changing edge whose key binds 0x02 is refused
edge-from-terminal — all read off the trie rather than off the request. New
with this wire: edge-inadmissible for a tag outside 0-6, and
deposit-mismatch for a deposit that is not the holding less the tip.
The cage datum: a third constructor¶
| index | before | after |
|---|---|---|
| 0 | RequestDatum |
RequestDatum |
| 1 | StateDatum |
StateDatum |
| 2 | — | AbsentCustody { refund } |
The absent custody key is the identity of the output's sole non-ADA asset,
which must have quantity one under the registry's pinned absent policy. It is
not duplicated in the datum. The retired two-field constructor-2 wire is
therefore incompatible and must not decode as AbsentCustody.
Redeemers that are gone¶
The naming application's spend redeemer becomes Maintain, Retire,
Recover; Fold and Cancel are removed with the pending-claim lifecycle
they served. Its mint redeemer becomes the single Approve { edge, key, owner,
destination }; WithdrawApproval and InsertApproval are removed. The
representative policy is retired in favour of the one parametrised witness
policy, instantiated three times.
What a reader should do¶
Read tokens, never the root. The presence of an active witness under the active policy, at the asset name equal to the registry key, is a live registration; the presence of a terminal witness is a name that is over. The asset name is the key itself — there is no hashed representative name any more.
Current registry-mode integration¶
The released interface follows the eight-field registry-mode state contract above. The historical reports below retain their original transaction identities, measurements and limitations; they are not current execution claims. Coverage requires fresh receipts bound to the source and blueprint being replayed. A build or a green check of expected debt grants no coverage.
| Surface | Current contract and remaining limit |
|---|---|
| State codec, blueprint-encoding-round-trip/submitted-datum-byte-round-trip/state-fields-chain-round-trip | The eight fields are root, maxFee, processTime, retractTime, applicationPolicy, activePolicy, absentPolicy and terminalPolicy. The state round-trip row boots two cages and compares all eight fields submitted versus chain-observed, including the four derived pins; its armed control demands the old six-field encoding and fails. Historical owner, stake_script and consumer-pin receipts do not establish this codec. |
| Blueprint encodings, blueprint-encoding-round-trip | Every encoder/decoder pair in the registry types is compared against the compiled blueprint's own declared schema, constructor index and field order — including the appended read operation, the appended request destination and the appended custody datum. The retired consumer encoder is gone with its script. |
| Parameters, script-parameter-application | State and staking declare zero parameters. Request retains two ordered parameters, statePolicyId and cageTokenName, and order discrimination. The witness policy declares two, kind and registry. The former state allowlist is absent. |
| Address derivation, applied-validator-identity | State uses the production derivation compared with the actual chain address. The same comparison rejects an erroneous extra application with every other input fixed. Its receipt records addresses, arity, decisions and the off-chain identity venue; it is not a phase-2 rejection. Request retains applied-versus-unapplied address discrimination. |
| Generic observations, empty-fold/request-value-and-refund-routing | Both require observe-and-report and remain held-q002; surplus-fold-actions has no counterpart in Singular's model and is unmet by ruling (lambdasistemi/singular#345). Current refusals need structural phase/script attribution and an accepting control; a missing named failure branch remains an explicit limit. An observation does not fulfill the consumer requirement. |
Historical execution reports¶
Historical scope: every report from here through “Historical limits” describes the earlier source revisions named in its provenance. Present-tense wording in those retained reports belongs to those revisions. The current contract above supersedes their owner fields, state arity, accepting-path expectations and pending-E17 descriptions. Do not use their measurements or receipts as evidence for the current candidate.
Devnet sessions run in order, each on an isolated node with its own published world: the generic registry rows first — the four issue-#63 rows plus the issue-#70 rows below — then the canonical identity rows, then the serialization boundary rows. Two further checks, blueprint-encoding-round-trip and script-parameter-application, never start a node at all: they compare Haskell values against the compiled blueprint read at run time, so a reader totalling "rows executed on a real devnet" counts the thirteen generic, five canonical-identity and five serialization rows below — never the local checks. The counts are kept visibly separate for exactly that reason.
The generic registry rows¶
Against a real devnet, one cage, one key, in order:
| row | outcome | evidence |
|---|---|---|
| update-existing-key generic Update v1→v2 | accept | inclusion proof for v2 implies the chain-read root; forged-value proof does not (control) |
| delete-existing-key generic Delete | accept | exclusion proof verifies against the chain-read root (empty trie); pre-delete proof bound to the deleted value does not (control) |
| reinsert-deleted-key re-Insert v3 | accept | inclusion proof for v3 implies the chain-read root; forged-value proof does not (control) |
| insert-occupied-key Insert on occupied key | refuse | hand-built fold submitted; node refuses in phase 2, attributed to the state script (874e476d…, CekError); fresh cage accepts a valid insert (control) |
insert-occupied-key's refusal is a node verdict on a submitted transaction, the
li-refusals bar: the fold is hand-built (the library builder cannot
emit a transaction whose scripts do not evaluate) and calibrated
against the library builder on every valid fold — same inputs,
state output, refund destinations and Modify proofs — so the refused
shape differs from a library fold only in the operation under test.
The eval-time refusal the builder reports first is kept as a log
line, never as the verdict. Each receipt records where its refusal
was observed (venue: node-submit for every row here), and
refusal reasons are trimmed to their attribution (failure class,
script hash, machine error) — receipts stay under a run-enforced
16KB bound, because a receipt nobody can open is weak evidence.
Had the chain accepted the occupied insert, that would be a finding reported with the accepted transaction — never relabelled as a refusal.
Model-compared registration and retirement candidate (#229)¶
A local clean candidate at 9b42458ffc1fab6bf9b7cf88ef44884e24333b8b
ran the registration and retirement chapters on a Cardano devnet. Its
retained receipts carry five and seven model-and-chain step records
respectively. The executable driver supplies the model outcome and all
nine declared observations for each accepted step; the reader-facing book
renders the story programs from those receipts. This is candidate evidence,
not merge, release or independent acceptance.
| User-visible condition | Recorded result |
|---|---|
| Two distinct active registrations | accepted and compared after each fold |
| Repeating an occupied key | refused by the state script, beside accepted registrations |
| Redirecting active-token delivery | refused by the state script; the same request without redirection was accepted |
| Retiring a token from the immediately preceding registration | accepted, with burn and terminal leaf compared |
| Retiring an Absent key or a never-registered key | each refused by the state script beside an accepted retirement control |
The node did not supply a Plutus trace name for the refused folds. The receipts retain the script hash and the empty trace rather than inferring a branch name. An accepting control establishes that the builder can fold a valid request in the same run.
The former two-request batch allocation check is no longer a current conformance claim. The driver evaluates one request per transaction, so the specified per-kind-and-key batch mint rule has no executable consumer in these chapters. The old batch evidence remains historical to its own revision. The former receipt-field appendix examples were removed with their schema; new appendix checks validate the generic step records.
The wire these rows and the archive verb speak¶
Everything on this page, and the release archive's packaged
insert-active command, runs on the final request encoding: the edge
index at field 3 and the deposit at field 4, as set out above.
The re-cut carried its own enforcing check rather than being asserted. For exactly one commit the cage compiled BOTH encodings, and every other-edge fixture was folded under each of them and required to give the same verdict with the same refusal reason, receipted per fixture. The commit after it removed the old codec. That pair — the receipt at the dual-codec commit, the removal at its successor — is the evidence a one-commit rewrite cannot produce, and it is what stands behind the side-by-side table above.
The issue-70 generic rows¶
The table preserves the eleven requirements and their historical observations. The current generic session runs ten rows, followed by its session receipt; the retraction-window requirement now runs separately as a three-step story compared with the model. The remaining bespoke refusal rows attribute the refusal to the script that failed and retain their accepting controls.
| row | outcome | evidence |
|---|---|---|
| Retract outside phase 2 | refuse before and after; accept inside | The dedicated story submits three owner-signed retractions with finite validity bounds. Both outside-window attempts must be refused by the request script and by the model for not-phase2; the same first request retracted inside its window is accepted by both. The second request is booked alongside the first in the same registry, with the same owner and edge, and shares its accepting control. The receipt records each comparison. |
| reject-before-deadline-consumer-requirement a folder must not reject a request during its processing window | accept — unmet-by-ruling (historically refuse) | the consumer requires that a folder must not reject a request during its processing window (cardano-keri R9_reject_needs_rejectable, 0e638fad, RegistryGoals.lean blob d38e81c0); Singular's model lets a folder reject a request with no timing admission, and since #320 the chain accepts that reject. By operator ruling 2026-10-01 the requirement is kept unmet: the row is recorded unmet-by-ruling and never counted as passing, and alignment is cardano-keri#468; the same reject refunding the owner one lovelace short is refused in-run first, attributed to the state script (control). Historically the node refused the reject at script validation, attributed to the state script (ce7615f6…), and the same request rejected by the library after its retraction window was accepted in-run |
| fold-against-superseded-root fold with stale proofs | refuse — unmet-by-ruling | stale proofs against a superseded root refused, attributed to the state script; the same shape folded against the live root is accepted in-run (control) — the refusal is the staleness, not the shape. The chain refuses; Lean has no counterpart to compare with. By operator ruling 2026-10-02 ("Narrow #287") the row is recorded unmet-by-ruling, never a pass; follow-up lambdasistemi/singular#346 |
| empty-fold empty fold | accept — held-q002 | the chain accepts a fold carrying no actions (tx b670c28e…); with one live request waiting, empty actions are refused in-run (control) — the acceptance is specific to the empty fold |
| surplus-fold-actions surplus action | refuse — unmet-by-ruling | two actions over one request are refused, attributed to the state script (traced reason surplus-actions); one action fewer than there are requests is refused too (missing-action), and an exact one-to-one fold is accepted in-run (control). The chain refuses; Lean has no counterpart to compare with. By operator ruling 2026-10-02 ("Narrow #287") the row is recorded unmet-by-ruling, never a pass; follow-up lambdasistemi/singular#345. Historically, before the cage repair, the surplus was accepted (tx facebfe6…) |
| historical-owner-change owner change via Modify | accept — defect evidence | the chain accepted a Modify changing the state owner to 0xab…ab, signed by the previous owner (tx 78e49eea…); resolved-by-ruling: the registry has no owner role at all, so the transfer is a privilege that must not exist — retained as defect evidence of the outstanding owner gate |
| historical-non-owner-sweep sweep by a non-owner | refuse — superseded claim | the sweep is refused, attributed to the request script (7f32c3e7…); the same sweep signed as the owner is accepted in-run (control). SUPERSEDED: it asserted registry-owner authority, which does not exist — observation preserved, conformance claim withdrawn |
| request-value-and-refund-routing crossed refunds | accept — held-q002 | bonds of 5 and 3 ada refunded crossed (4 and 2 ada, aggregate exactly the validator's ceiling), accepted (tx 566ddc80…); refunds totalling below the aggregate floor are refused in-run (control) — the range is real, and it is still not the requirement |
| historical-permissionless-fold permissionless fold | accept | after the #79 repair the fold with NO owner signer is accepted (tx 4142f7d6…, mem 717070 / cpu 231585673 / size 11442); the same fold WITH the owner signer is accepted in-run (control) — see F-002 below |
| retired-stake-hook-with-withdrawal / retired-stake-hook-without-withdrawal stake_script hook | retired | nothing runs them: the registry interface (registry mode) has no stake_script hook and the model has no withdrawal. History: the pinned staking credential could not register (MissingScriptWitnessesUTXOW without the witness, cert-purpose CekError with it), so the hook never reached a ledger |
The retraction story gives the registry thirty seconds for processing and thirty
more for retraction. Its waits follow the submission times read from the booked
requests and those registry durations. The model reads the witness from each
built transaction; the ledger refusal is attributed by the applied request
script hash, without claiming an observable live refusal name. Open validity
intervals remain a named gap: the model represents finite bounds only, while
the Aiken tests establish the open-interval not-phase2 refusal.
Four dispositions, never to be mistaken for one another.
Unmet by ruling (unmet-by-ruling; reject-before-deadline-consumer-requirement, fold-against-superseded-root, surplus-fold-actions, wrong-redeemer-constructor-index): executed,
and kept unmet by an operator ruling. The one verdict carries two meanings,
and every row states which. reject-before-deadline-consumer-requirement: the registry deliberately does not do what the consumer
requirement states, kept unmet by operator ruling 2026-10-01. The consumer requires that a folder must not reject a
request during its processing window (cardano-keri
R9_reject_needs_rejectable, 0e638fad, blob d38e81c0); Singular's
model lets a folder reject a request with no timing admission, and the chain
accepts that reject (#320). Alignment is
cardano-keri#468.
fold-against-superseded-root, surplus-fold-actions and wrong-redeemer-constructor-index: the chain refuses, Lean has no counterpart to compare
with, kept unmet by operator ruling 2026-10-02 ("Narrow #287"); follow-ups: fold-against-superseded-root lambdasistemi/singular#346,
surplus-fold-actions lambdasistemi/singular#345, wrong-redeemer-constructor-index lambdasistemi/singular#347.
The session ends non-zero while any row is unmet; it is never a pass.
Held (held-q002; empty-fold, request-value-and-refund-routing): executed, with the consumer
requirements still unmet. empty-fold recorded
a conflict with the pre-revision
Singular model; rejecting empty batches is now approved. request-value-and-refund-routing requires the
operation-specific value-routing repair. The rows move only after the
required implementation and execution evidence is accepted.
Resolved-by-ruling (historical-owner-change): a ruling settled the row's question;
the observation is retained as defect evidence of the outstanding
owner gate — never a pass, never an owner-semantics claim.
Superseded (historical-owner-signed-sweep, historical-non-owner-sweep, historical-registry-termination): the expectation asserted authority,
a sweep or a termination that does not exist at this commit; observations
are preserved, claims are withdrawn, and no execution credit attaches.
Retired (retired-stake-hook-with-withdrawal, retired-stake-hook-without-withdrawal): the registry interface has no stake_script
hook, so nothing runs them.
Evidence provenance. The receipts cited in this section are the
generic session's ship run, taken fresh at clean tip 1d98d51 after
the receipt-overwrite repair (blueprint
state:ce7615f6… request:8970c286…, cardano-node 10.7.0, every
receipt dirty: false, every held row carrying its acceptance with
transaction id and measurements). historical-permissionless-fold's first post-repair execution
was at clean tip 748c4a9 (txid fe54d3a6…); its ship-run repetition
at 1d98d51 is 4142f7d6…. The overwritten pre-repair receipts are
retained only as evidence of that defect.
Held rows (operator question (Q-002), story 2). empty-fold and request-value-and-refund-routing are executed holds,
never passes; surplus-fold-actions, held when this history was written, is now unmet by
ruling (above). Only empty-fold was established as a direct conflict
between the two model results. In the pinned pre-revision contract,
Singular's Lean permits the empty fold (foldItems, | s, [] => .ok — Model.lean
188–189) and pairs each request with exactly one action in its
FoldItem, so a surplus tail is unmodelled; State carries no owner
field and Action.fold routes no refunds. Singular.Statements.fold_iff
(Model.lean 250–256) makes its five conjuncts sufficient — native
spend, fold items, net-mint match, representative-mint and
application-mint witnesses — and no owner is among them: folding is
permissionless. The consumer's theorems require the empty batch refused
(R8_empty_fold_refused) and the 1:1 accounting. Its fold value
routing is not R11_contribute_value — that states the deposit
amount, and R11_retract_value states bond+tip on retract; fold
routing lives in the consumer's Registry.processBody, Registry.stepFn
and the Cage.delegated_is_registry equality, and it is
operation-specific: register/revive lock the deposit into a
checkpoint, goDormant/goConvicted/convict refund to the recorded owner,
rejection returns the operation bond — guarded by rejectable and
userPostable, not on demand — and the fold tip goes to the
folder. A blanket exact-refund repair would preserve the wrong value
mode and prevent checkpoint funding. Upstream
#100/#101 are referenced proposals, not a delivered partition fix: #101 is open, and Singular owns the required local repair. R5_plugin_pinned needs a distinction this page
previously collapsed. It is about the plugin, not a registry owner:
the bound Registry.Sys carries plugin, and the consumer's fold checks
pl = s.plugin. So registry-owner privileges and fields are
superseded — the operator ruled Singular has no registry-owner role,
and there is no owner to pin — but the application/plugin identity
correspondence is not waived by that. The required application and
native-witness bindings and the policy bindings remain required, and
must be established through the actual ownerless representation; this is
not a reason to reintroduce stake_script or to assert its old mechanism
is the required one. The remaining requirements stay unmet; the receipts carry
verdict held-q002 and the session ends non-zero while anything is
held — a hold can never read as a pass, and a verdict moves only by
execution. historical-owner-change is resolved-by-ruling, not a fourth unresolved hold:
its receipt carries resolved-by-ruling, its acceptance stays defect
evidence, and it is outside the held-set.
A repair this slice had to make to keep that sentence true: a refused
control used to submit through the same path as a refusal ROW and
wrote its refusal under the row's id, so empty-fold/surplus-fold-actions/request-value-and-refund-routing's held
receipts were being replaced by their controls' refusals in every
earlier receipts directory — receipts asserting the opposite of the
executed finding (historical-owner-change's survived only because its control was
retired, which is what isolated the cause). The control now writes
nothing: its outcome is run-log evidence under its own identity, it
cannot silently pass (an accepted control fails the run as a FINDING;
a refusal that does not attribute fails the run naming the mismatch),
and the receipt policy is unit-tested to discriminate — a control
never overwrites a held receipt, a refusal row always writes, a
non-attributing refusal writes nothing. The overwritten historical
receipts in /tmp/t70-receipts* are kept untouched as evidence of
the defect, never relabelled as fresh proof; the receipts cited by
this page are the ones taken fresh at the clean tip after the repair.
historical-permissionless-fold and F-002, both halves by execution. An independent audit
found that the runner itself always supplied the state owner's
signature, so no fold row in the suite could have observed the owner
gate; historical-permissionless-fold was built as the exact regression property. Executed
against the pre-#79 candidate it was REFUSED, attributed to the state
script, and recorded diverges-from-lean (receipt retained as
history). After epic 17's #79 repair it was executed again: the fold
with no owner signer is accepted (agrees-with-model, txid
4142f7d6… at the ship run, fe54d3a6… at the first post-repair
execution, blueprint state:ce7615f6… request:8970c286… both times),
and the same fold with the owner signer is accepted in-run. Both
acceptances are the post-repair evidence, not a weakened test: the two
transactions differ in the signer set alone, so the acceptance is
attributable to the removed gate. The expectation was always accept;
what moved is the candidate, and the verdict moved by execution only —
never by editing a row or flipping a stored receipt.
The owner-dependency extent. Before #79, every fold-based row
flowed through validateOwnership: update-existing-key–insert-occupied-key, reject-before-deadline-consumer-requirement–retired-stake-hook-without-withdrawal, historical-non-owner-sweep and
request-value-and-refund-routing depended on the state owner's signature, while retract-outside-window's retract
never did (it depends only on the request owner). #79 removed the gate
from Modify folding, and the 2026-09-12 operator ruling settles more:
the registry has no owner role whatsoever — not latent, not
transferable, not gating End or migration. state.ak's End and
validateMigration still call validateOwnership; those calls are
outstanding conformance defects owned by epic 17, and historical-owner-change's accepted
owner change is their executed evidence.
Superseded rows, observations preserved. historical-owner-change is
resolved-by-ruling: the owner-pinning question has no premise left to
be pending on, the acceptance is defect evidence, and the retired
control (previous-owner End refused, new-owner accepted) tested
behaviour that must not exist — it was never landed. historical-non-owner-sweep's refusal
and its owner-signed control stay in the record with the conformance
claim withdrawn: a gate that tests authority that does not exist
discriminates nothing Singular owes. historical-owner-signed-sweep and historical-registry-termination are
superseded the same way: the registry has no sweep and no termination,
the end-to-end examples they cited were removed on 2026-09-12
(f3a68b1b), nothing runs either row, and list prints both
uncovered. retired-stake-hook-with-withdrawal/retired-stake-hook-without-withdrawal are retired: the registry interface has no
stake_script hook, so nothing runs them. The
imported partition's State.stake_script hook supplies registry-owner
authority by another name, which is exactly why epic 17 removes the
field; the pinned staking credential cannot register anyway (the
cause above), so the hook never reached a ledger. Preserved history
stays unchanged, and changing applicability earns no execution credit.
execution-units-and-transaction-size for this session is written from the accepting folds of update-existing-key, delete-existing-key and reinsert-deleted-key, each read off the step that folded it in its row's receipt; the other rows' worst cases live in their own receipts.
The canonical identity rows¶
A designation split publishes the canonical seed; canonical-seed-identity boots the canonical registry from it; rival-seed-authentication initializes a rival from a second seed through the same bootstrap path; a second split publishes the rival seed, so no boot can ever consume the wrong UTxO as its funder.
| row | outcome | evidence |
|---|---|---|
| canonical-seed-identity derived name recomputed and matched | accept | the state UTxO carries exactly the SHA-256 of the published seed's outRef, quantity 1; a fabricated outRef's derivation does not match (control) |
| rival-seed-authentication rival registry from a second seed | rival accepted on chain; authentication rejects | the node accepts the rival's bootstrap tx; both token names read back from the chain differ (each SHA-256 of its own seed's outRef); the canonical registry is unaffected — same UTxO, value and datum bytes as the canonical-seed-identity snapshot; the derived name accepts the canonical and rejects the rival |
| policy-address-only-authentication-control policy+address-only authenticator | control must fail the run | the weak authenticator accepts the rival read back from the chain while the full authenticator rejects the same value — the name is the only discriminator; armed (naive-authenticator), the run fails naming the accepted rival |
| applied-validator-identity applied-address derivation | accept | the manifest pins unapplied d42860fa97… with 1 declared parameter; applied in Haskell to ce7615f6ba4…; the derived address equals the address the chain reports and the library's; the unapplied address does not pass (control; armed run fails) |
| tokenless-output-authentication forged output at the canonical address | authentication rejects; no script ran | a forged output carrying the canonical address and datum but no token is accepted by the ledger with no script executed — no witness, no redeemer, no mint, empty node evaluation — and the same detector fires on the boot tx (control); authentication rejects it on the missing token |
rival-seed-authentication asserts an acceptance: the ledger takes the rival, and the consumer's authentication is what excludes it. The receipt records the ledger's verdict (accepted, with the rival's txid and measurements); the rejection is asserted on chain-read assets and named in the run — never relabelled as a refusal, never skipped because the outcome reads oddly. The acceptance is the finding, and it is the accepted design. policy-address-only-authentication-control is what makes rival-seed-authentication worth anything: a check that cannot fail proves nothing, so the weak authenticator's acceptance of the rival is itself executed and required.
The serialization boundary rows (devnet session)¶
One fresh cage per row (two where a row needs two phases), each cage booted, exercised and — except where the row says otherwise — closed on the same isolated node, in canonical order:
| row | outcome | evidence |
|---|---|---|
| submitted-datum-byte-round-trip datum bytes submitted and read back | accept | boot StateDatum plus one request RequestDatum, byte-compared submitted Datum versus chain-observed Datum for both; units from the boot minting, size the larger of the two transactions; corrupted comparison fails the run (control) |
update-redeemer-constructor-witnesses every UpdateRedeemer constructor executed |
accept | one executing witness per constructor — End 0, Contribute 1, Modify 2, Retract 3, Sweep 4 — each read back from the redeemer of a submitted transaction the validator executed (the Modify fold carries Modify and Contribute together); four witness transactions named; a skipped witness fails the run (control) |
| wrong-redeemer-constructor-index redeemer at a wrong constructor index | refuse — unmet-by-ruling | valid fold retargeted to Constr 5 keeping its fields (same CBOR size, so fee and collateral stay sufficient and any refusal attributes to the script); node refuses in phase 2 with CekError, attributed to both cage scripts (state+request, ledger order, unstable — the tamper breaks fold consistency the request script also checks); fresh cage accepts a valid fold (control); impossible marker fails the run (control). The chain refuses; Lean has no counterpart to compare with. By operator ruling 2026-10-02 ("Narrow #287") the row is recorded unmet-by-ruling, never a pass; follow-up lambdasistemi/singular#347 |
request-and-mint-constructor-witnesses RequestAction and MintRedeemer coverage |
accept, with one recorded gap | Update, Rejected (phase-3 reject), Minting (boot), Burning (end) each executed and read back from its redeemer; Migrating is unreachable on the imported partition (previousPolicies=[], so has(previousPolicies, oldPolicy) fails at state.ak validateMigration FR1) and is recorded as a gap with that reason in gap-request-and-mint-constructor-witnesses-Migrating.txt — never a pass, never omitted; a skipped witness fails the run (control) |
state-fields-chain-round-trip OnChainTokenState six fields round trip |
accept, schema pending | two boots, stake_script None and Some (staking hash), all six fields byte-identical submitted versus chain-observed; corrupted comparison fails the run (control). The six fields include the owner field epic 17 is removing: the schema repair is pending, this expectation is superseded at that repair, and the row will be re-executed against the repaired blueprint |
The serialization checks that need no node¶
No transaction, no node, no execution units — hence mem/cpu zero
and no transactions named. What is measured is stated per row, and
these rows never appear in a devnet-executed count.
| row | outcome | evidence |
|---|---|---|
| blueprint-encoding-round-trip Haskell encodings against the blueprint schema | accept (blueprint-check), schema pending |
all thirteen ToData types round-trip and each constructor index and field order matches the compiled blueprint's declared schema read at run time (REGISTRY_BLUEPRINT), including field-title order; no type needed a gap; Constr 99 validates against nothing and index 99 demanded for End fails the run (control); txSize is the blueprint file size in bytes, 92048. The checked schema is the six-field owner-bearing state: superseded at epic 17's pending repair, re-executed against the repaired blueprint |
| script-parameter-application parameter application derived in Haskell | accept (param-check) |
parameter counts and encodings published from the blueprint — state 1 (previousPolicies), request 2 (statePolicyId, cageTokenName, in source order), staking 0 — unapplied hashes match the pinned blueprint hashes, the applied state hash is 874e476d…, non-empty allowlists and swapped request params discriminate; 2 demanded for state fails the run (control); txSize is the largest applied script size in bytes, 7805 |
proof-step-constructor-witnesses: unmarked and escalated¶
proof-step-constructor-witnesses carries no receipt and prints uncovered: Branch and Leaf
are witnessed on chain across many accepted folds, but no accepted
fold has ever carried a Fork, and two independently ground
lone-Fork absence proofs — each verified by the mts Haskell stack
against its own root — are refused by the compiled validator with a
bare CekError at build-time evaluation. The full bundle (keys, trie
shape, both verdicts, the refusal bytes, what could not be
determined, and the bounded consumer consequence) is filed as a user
story escalation; the offline oracle (find-fork-keys,
check-fork-exclusion) and shape probes (show-nibbles,
show-d-proof, show-all-proofs) reproduce every offline claim.
This is a finding held open, not a gap and not a pass.
Measurements¶
Ship run: base b3f4b5a, clean tree, cardano-node 10.7.0. The
issue-#70 generic session's ship run: base 1d98d51, clean tree,
cardano-node 10.7.0 — every generic receipt below names that base
with dirty: false, and the held rows carry their acceptances with
transaction ids and measurements because their controls write none.
Serialization ship run: base b2201c3, clean tree, cardano-node
10.7.0 — every receipt below names that base with dirty: false.
Each invocation observes the tree before creating its output, and
ships from a fresh output directory, so no run ever measures the
previous run's receipts. The defect this guards against reported
dirty: true on clean trees; its direction is conservative — it can
block a valid ship, it can never let a dirty tree pass as clean — and
no merged receipt ever claimed clean while dirty.
Maxima queried from the running node, never hardcoded:
maxTxExUnits 140000000 mem / 10000000000 cpu,
maxTxSize 16384 bytes.
| fold | mem (headroom) | cpu (headroom) | size (headroom) |
|---|---|---|---|
| update-existing-key Update | 649162 (139350838) | 217096102 (9782903898) | 11451 (4933) |
| delete-existing-key Delete | 638564 (139361436) | 213717823 (9786282177) | 11451 (4933) |
| reinsert-deleted-key re-Insert | 616554 (139383446) | 200156393 (9799843607) | 11451 (4933) |
| empty-fold empty fold (held) | 273449 (139726551) | 87149465 (9912850535) | 8388 (7996) |
| surplus-fold-actions surplus action (held) | 905068 (139094932) | 295812293 (9704187707) | 11555 (4829) |
| historical-owner-change owner change (defect) | 628372 (139371628) | 207905393 (9792094607) | 11403 (4981) |
| request-value-and-refund-routing crossed refunds (held) | 1122369 (138877631) | 376904236 (9623095764) | 11613 (4771) |
| historical-permissionless-fold permissionless fold | 717070 (139282930) | 231585673 (9768414327) | 11442 (4942) |
| canonical-seed-identity canonical boot | 143440 (139856560) | 46848478 (9953151522) | 8502 (7882) |
| rival-seed-authentication rival boot | 143440 (139856560) | 46848478 (9953151522) | 8502 (7882) |
| tokenless-output-authentication forged payment | 0 (140000000) | 0 (10000000000) | 333 (16051) |
| submitted-datum-byte-round-trip datum round trip | 143440 (139856560) | 46848478 (9953151522) | 8466 (7918) |
| update-redeemer-constructor-witnesses five witnesses | 629296 (139370704) | 204228993 (9795771007) | 11423 (4961) |
| request-and-mint-constructor-witnesses four witnesses | 649502 (139350498) | 217245659 (9782754341) | 11423 (4961) |
| state-fields-chain-round-trip state None+Some | 147634 (139852366) | 49473955 (9950526045) | 8497 (7887) |
Execution units stay under 3.8% of the maxima for every row (worst:
request-value-and-refund-routing's crossed-refunds fold at 3.77% cpu, still ~26× headroom; tokenless-output-authentication
reports zeros honestly: no script purpose exists to evaluate).
Serialized size is the tight dimension at ~71% of maxTxSize for
folds and ~52% for boots; larger batches (fold-batch-size-boundary) may press against it
first. The canonical session's worst case is recorded in its execution-units-and-transaction-size
receipt; the registry session's execution-units-and-transaction-size reads the worst accepting folds of
update-existing-key, delete-existing-key and reinsert-deleted-key off their receipts' steps, and its other rows'
worst cases live in their own receipts; the serialization rows carry theirs in the row receipts, and no serialization execution-units-and-transaction-size
is claimed yet. Refusal reasons keep every failing script hash in ledger
order — a tampered fold can fail two scripts, and trimming volume
never trims identities — under the same run-enforced 16KB bound.
Historical limits¶
- Programs with end-to-end references: insert-key, retract-inside-window and reject-after-window run as
programs in the registry session, their state computed from those
receipts; the epic-16
CageSpecexamples they cite remain supporting references. historical-owner-signed-sweep and historical-registry-termination cite none: the examples they cited were removed on 2026-09-12 (f3a68b1b), nothing runs either row, and both are superseded by the ownerless-registry ruling (no sweep, no termination) — history earns no conformance credit. - Executed, unmet by ruling, not a pass: reject-before-deadline-consumer-requirement records the early
rejection the consumer forbids and the chain accepts, kept unmet by
operator ruling 2026-10-01 (
unmet-by-ruling). fold-against-superseded-root, surplus-fold-actions and wrong-redeemer-constructor-index: the chain refuses, Lean has no counterpart to compare with, kept unmet by operator ruling 2026-10-02 ("Narrow #287") (lambdasistemi/singular#346, lambdasistemi/singular#345, lambdasistemi/singular#347). - Executed holds, not passes: empty-fold and request-value-and-refund-routing remain held by
execution. empty-fold records the
pre-revision empty-fold conflict with
R8_empty_fold_refused; request-value-and-refund-routing violates the consumer's required operation-specific fold
value routing (
Registry.processBody,Registry.stepFn,Cage.delegated_is_registry).R11_contribute_valuestates the deposit amount and is not that fold requirement. Upstream#100/#101are referenced proposals, not a delivered partition fix:#101is open, and Singular owns the required local repair.R5_plugin_pinnedconcerns the plugin, not a registry owner. The consumer requirements stay unmet; a held row is never a pass, and the verdicts move only by execution. historical-owner-change's owner change is retained as defect evidence of the outstanding owner gate, resolved-by-ruling, with its defect owned by epic 17. - Superseded, never pending: historical-owner-signed-sweep, historical-non-owner-sweep and historical-registry-termination asserted registry-owner authority, a sweep or a termination that does not exist — observations preserved, claims withdrawn, no execution credit for changed applicability. retired-stake-hook-with-withdrawal/retired-stake-hook-without-withdrawal are retired: the registry interface has no stake_script hook, so nothing runs them.
- Uncovered: proof-step-constructor-witnesses, the five consumer-resolution requirements and fold-batch-size-boundary–observed-toolchain-and-environment print
uncovered(as do retired-stake-hook-with-withdrawal/retired-stake-hook-without-withdrawal, retired, and historical-owner-signed-sweep/historical-registry-termination, superseded — no receipts). proof-step-constructor-witnesses is not merely uncovered: itsForkfinding is filed for a user story and the row stays unmarked until that story resolves — an unmarked row with a finding, never a gap and never a pass. request-and-mint-constructor-witnesses'sMigratinggap is of the recorded kind, with its validator-read reason beside the receipts. - Schema pending (epic 17): blueprint-encoding-round-trip, submitted-datum-byte-round-trip and state-fields-chain-round-trip describe the
six-field owner-bearing
OnChainTokenState. The ownerless schema repair is pending; their expectations are superseded at that repair, their receipts stay valid as history, and the rows will be re-executed with fresh receipts against the repaired blueprint — coverage is not inherited across a wire-format change. Epic 17's antecedent ownerless repair exists locally (f3a68b1) but is not accepted and not merged; nothing here cherry-picks it, and the affected runtime checks follow the definitive schema artifacts when the explicit handoff arrives — integration stays serialized behind it. - Green expected-debt CI grants no conformance credit: the workflow's generic-rows step asserts declared debt — exact receipt set, exact verdict per receipt, held set exactly empty-fold request-value-and-refund-routing, unmet set exactly reject-before-deadline-consumer-requirement fold-against-superseded-root surplus-fold-actions, nothing failing — and nothing more. It pays neither the held rows nor a full execution-units-and-transaction-size, and strict completion and release stay RED on that debt.
- Out of scope: checkpoint-and-treasury-policy (cardano-keri), the naming rows (epic 16 demonstration, not consumer evidence), LR/LT rows (epic 17).
- The registry-identity rows authenticate the canonical registry as the consumer derives it from the published seed. They do not evidence the serialization boundary (serialization), the constructor coverage (update-redeemer-constructor-witnesses–request-and-mint-constructor-witnesses) or the batch limits (fold-batch-size-boundary).
- The measurements above are devnet folds of single requests. They say nothing about batch limits (fold-batch-size-boundary) or any other environment (observed-toolchain-and-environment records the observed one below).
Current replay commands¶
Run from the repository or extracted source root with Nix available. Use a fresh directory outside the tracked tree for each family. The generic session runs ten rows and may also emit its execution-units-and-transaction-size measurement receipt; the serialization session runs five devnet rows and two local checks. Superseded rows remain listed and are not runtime commands.
nix run ./conformance#conformance -- list
blueprint="$(nix build --quiet --no-link --print-out-paths ./onchain#plutus-blueprint)"
receipts="$(mktemp -d /tmp/singular-conformance.XXXXXX)"
# The finite retraction window has its own three-step model-compared receipt.
REGISTRY_BLUEPRINT="$blueprint" nix run ./conformance#conformance -- run retract-outside-window --receipts-dir "$receipts/retraction-window"
# Expected exit 1: exact held set empty-fold, request-value-and-refund-routing; unmet set reject-before-deadline-consumer-requirement, fold-against-superseded-root, surplus-fold-actions; no failed rows.
REGISTRY_BLUEPRINT="$blueprint" nix run ./conformance#conformance -- run update-existing-key delete-existing-key reinsert-deleted-key insert-occupied-key reject-before-deadline-consumer-requirement fold-against-superseded-root empty-fold surplus-fold-actions request-value-and-refund-routing register-active-key --receipts-dir "$receipts/generic"
# Expected exit 0 after all five identity rows and their controls.
REGISTRY_BLUEPRINT="$blueprint" nix run ./conformance#conformance -- run canonical-seed-identity rival-seed-authentication policy-address-only-authentication-control applied-validator-identity tokenless-output-authentication --receipts-dir "$receipts/identity"
# Expected exit 1: update-redeemer-constructor-witnesses and request-and-mint-constructor-witnesses are partial; wrong-redeemer-constructor-index is unmet by ruling (#347); the other four agree.
REGISTRY_BLUEPRINT="$blueprint" nix run ./conformance#conformance -- run blueprint-encoding-round-trip submitted-datum-byte-round-trip update-redeemer-constructor-witnesses wrong-redeemer-constructor-index request-and-mint-constructor-witnesses script-parameter-application state-fields-chain-round-trip --receipts-dir "$receipts/serialization"
nix run ./conformance#conformance -- list --receipts "$receipts/generic"
nix run ./conformance#conformance -- list --receipts "$receipts/identity"
nix run ./conformance#conformance -- list --receipts "$receipts/serialization"
These are expected result contracts, not claims that an arbitrary nonzero exit is acceptable. Check the terminal row counts, exact receipt IDs, source/blueprint binding, per-row verdicts and named residuals. A crash, missing receipt or failed control fails the replay. The CI workflow checks those details against the actual output; partial and held rows remain debt even when that regression check is green.
Armed controls use a separate fresh receipt directory. Their nonzero
exit must name the intended failed assertion: the wrong refusal reason,
forged value or datum, weak authenticator, corrupted state derivation
(legacy flag unapplied-address), wrong constructor index, wrong
parameter count or deliberately missing witness. The normal partial
exit alone does not prove an armed control fired. See
conformance.yml for the executable session and receipt checks.
The Fork oracle and its probes remain available without a node or blueprint; their output is local proof evidence, not proof-step-constructor-witnesses completion:
nix run ./conformance#conformance -- find-fork-keys
nix run ./conformance#conformance -- check-fork-exclusion
nix run ./conformance#conformance -- show-all-proofs
The runner gives each node a unique temporary directory. Receipts are written under the selected output directory and are never inherited as current merely because an older run succeeded.
Historical observed environment¶
cardano-node 10.7.0, GHC 9.12.3, Aiken compiler v1.1.21
(blueprint hal/mpf 0.0.0), unapplied state script pinned
d42860fa972c8a325daae773adc372795c48cf365d0a72b137c749d3 with one
declared parameter, applied state script
ce7615f6ba4de80dfa9b9c6aef680666472ba4ed7e640ff55aad7c6e,
unapplied request script hash 8970c286… (re-pinned from
64d1afbfe585…/874e476d7408…/6b5ce7… by the issue-#79
imported-validator repair; onchain/REPAIR.patch records the change,
PROVENANCE.md the authority). The issue-#70 rows were executed
against blueprint state:ce7615f6ba4de80dfa9b9c6aef680666472ba4ed7e640ff55aad7c6e
request:8970c286…; the pre-#79 executions (historical-permissionless-fold's refusal,
the former bespoke retraction and the stale-request/proof refusals) are history at the earlier hashes. Environments other than
this devnet shape are explicitly not covered.