Who owns a registry command¶
A contributor changing what one of the registry commands does — a flag,
a narrated step, a field of the observation JSON, a deliberate refusal —
wants to open the one module that owns that concern and have the review
ask one question. Before this division, each command was a single entry
file of up to 1417 lines, and a change to the journey's negative cases
was reviewed beside its identity manifest parser and its proof checks.
Now each command's Main.hs holds only its outer failure handler, and
named command-local modules own the options, the steps, the deliberate
controls and the observation. This page says which module to open, which
way the dependencies run, what may never change while the code moves,
who calls each command, and what the evidence does and does not show.
The story this serves¶
As an operator, I run the registry journey from a checkout, the two
archive commands insert-active and update-terminal from an extracted
release archive, and deployment against a node. I need the same
options, the same narration in the same order, the same observation
JSON, the same refusals and the same exit status after their entry
files are divided into owners, so the CI steps and release pages that
read those outputs keep meaning what they meant.
As a contributor, I want one visible owner for each option, scenario step and deliberate control, so I can change a command without searching a thousand-line file or accidentally changing another command's evidence.
Who calls each command¶
| Command | Entry file | Real caller | What the caller reads |
|---|---|---|---|
journey |
offchain/journey/Main.hs |
the registry workflow's journey job: a fresh plutus-blueprint build, then nix run .#journey from the checkout |
the [journey] narration and exit status |
insert-active |
offchain/insert-active/Main.hs |
the registry workflow's release-archive job, and the archive's INSERT-ACTIVE.md run page |
--observed JSON asserted with jq, narration, exit status |
update-terminal |
offchain/update-terminal/Main.hs |
the registry workflow's release-archive job, and the archive's UPDATE-TERMINAL.md run page |
--observed JSON asserted with jq, narration, exit status |
deployment |
offchain/deployment/Main.hs |
the supported identity check offchain/deployment-identity-check.sh, which CI runs; operators deploying and verifying a persistent registry |
narration, the manifest file, a count on standard output, refusals |
The release check requires both archive commands' Main.hs paths in the
on-chain archive, and the archive carries every tracked file under
offchain/, so the new modules travel with the entry files they serve.
The executable names and entry paths are unchanged.
Which way dependencies run¶
flowchart TD
Main[Each command's Main] -->|outer failure handler| Scenario[Scenario: the order steps run in]
Main -->|flags only| Options[Options: flags and environment]
Scenario -->|reads| Options
Scenario -->|runs| Steps[Steps: executed chain actions and reads]
Scenario -->|runs| Controls[Controls: deliberate refusals and their accepting controls]
Scenario -->|builds| Observation[Observation or narration]
Controls -->|uses| Steps
Steps -->|builders, node session, trie| Library[singular-registry library]
Controls -->|builders| Library
Every command follows this shape; no module imports its command's
Main, and no command imports another command's modules. Where two
commands carry the same small helper — the genesis submission, the
registry configuration for a seed, the wallet read — each keeps its own
copy: a shared owner across executables would be a library change,
which this division does not make. The library remains the owner of
every transaction the commands build, and its generated interface is
the off-chain API reference; the command
modules are executables, documented here with source links rather than
in that reference.
The journey, step by step¶
sequenceDiagram
participant S as Journey.Scenario
participant I as Journey.Identity
participant St as Journey.Steps
participant P as Journey.Proofs
participant C as Journey.Controls
S->>I: print the pinned unapplied identities
S->>St: boot, then publish reference outputs
S->>St: request — book the insert
S->>P: verify-absent against the chain root
S->>St: apply — fold it as the oracle
S->>I: derived-applied-identity
S->>P: verify-present, then the false claim is rejected
S->>St: read-back
S->>C: three refused transactions, then the unchanged-state control
| Module | Owns |
|---|---|
Journey.Options |
REGISTRY_BLUEPRINT (required) and REGISTRY_SCRIPT_IDENTITY (default ../onchain/script-identity.json) |
Journey.Identity |
the identity manifest, the identity header, and the derived-applied-identity check between pinned and carried scripts |
Journey.Steps |
boot; booking (narrated request) and folding (narrated apply) as separate steps; read-back |
Journey.Proofs |
proofs built from the in-memory mirror and compared with roots read from chain; the false-claim rejection |
Journey.Controls |
the negative section: each single-defect transaction must be refused in phase 2 by the named validator, then the state must be unchanged |
Journey.Malformations |
the pure rewrites: forged state reference, tampered state root, dropped proof witness |
Journey.Chain and Journey.Narration |
wallet, configuration, submission, the chain state read; the [journey] lines and the journey: failure prefix |
Booking and folding stay two narrated steps. This division introduces no narration hook and does not move the journey onto the driver; that decision belongs to issue #202 and remains open.
The two archive commands¶
sequenceDiagram
participant Sc as Scenario
participant R as Registry or Steps
participant Ct as Controls
participant O as Observation
Sc->>R: publish the state validator, then boot
Sc->>R: book and fold the story's key
Sc->>R: read the wallet, roots and holdings back
Sc->>Ct: accepting control through the same builder
Sc->>Ct: the refusal it controls
Sc->>O: write --observed JSON from the executed results
Sc-->>Sc: exit status repeats the observation's verdict
insert-active books and folds one insertActive at a wallet, reads
exactly one active token there, folds a fresh key as the accepting
control, then requires a second booking of the same key to be refused.
The control runs first because a refused request is never consumed and
would poison the fold after it. Its owners are
InsertActive.Options,
InsertActive.Steps,
InsertActive.Controls,
InsertActive.Observation
and
InsertActive.Scenario.
update-terminal boots two registries in one session before any fold.
In the first it inserts the story's key, retires it — burning the
witness from the wallet input that held it and committing a Terminal
leaf — and then requires a key bound absent to be refused retirement,
the story's retirement being that refusal's control. In the second it
retires an active control key, then requires a key the trie never bound
to be refused. Each refusal needs a registry of its own because a
refused fold leaves its request pending. Its owners are
UpdateTerminal.Registry
(session, boots, bookings, mirrored and deliberately inadmissible folds,
roots),
UpdateTerminal.Steps,
UpdateTerminal.Controls,
UpdateTerminal.Observation,
UpdateTerminal.Scenario
and the matching Options and Narration.
Every value in both observation documents is passed in from what the steps and controls executed or read back; the observation modules type no expected value.
The deployment verbs¶
flowchart LR
A[command line] -->|first argument that is not a flag| R{Main.run}
R -->|deploy| D[Deployment.Deploy]
R -->|verify| V[Deployment.Verify]
R -->|count| C[Deployment.Count]
R -->|genesis-skey| G[Deployment.GenesisKey]
R -->|anything else| U[usage refusal]
D -->|boot, credentials, publication| N[Deployment.Node]
V -->|claims against the node| N
D -->|manifest| M[Singular.Registry.Deployment]
V -->|manifest| M
C -->|manifest| M
Main keeps the outer handler and the router. Every verb reads its
flags through
Deployment.Options,
which accepts --name value and --name=value alike and refuses each
verb's missing required flag by name. The release's compiled halves and
the one pin derivation are
Deployment.Compiled;
node operations are
Deployment.Node;
each verb is its own module under
offchain/deployment/Deployment/.
The usage text still names deploy and verify only, as it always
did; the router accepts all four verbs. The manifest itself stays with
the library facade described in
Deployment record ownership.
What may not change¶
| Surface | Held unchanged |
|---|---|
| Executables | the names journey, insert-active, update-terminal, deployment and their Main.hs paths |
| Options | --observed PATH (first occurrence); REGISTRY_BLUEPRINT, REGISTRY_SCRIPT_IDENTITY, NAMING_BLUEPRINT; deployment's first-non-flag routing and both flag spellings; each refusal's text and order |
| Diagnostics | the journey: FAILED: journey:, insert-active: FAILED:, update-terminal: FAILED: and deployment: FAILED: user error (…) shapes, including the archive commands' second FAILED: ExitFailure 1 line after a named refusal, and exit status 1 |
| Narration | every line, in order: booking before folding, the accepting control before the refusal it controls |
| Observation JSON | every key and every value's derivation, including null where a refusal name cannot be read from the ledger's text |
| Ledger effects | the same transactions, built by the same library builders, in the same order |
Where to make a change¶
| To change | Open |
|---|---|
| a flag or environment variable | the command's Options module |
| what a step submits or reads back | the command's Steps (or UpdateTerminal.Registry) module |
| a deliberate refusal or its accepting control | the command's Controls module; the journey's transaction rewrites are in Journey.Malformations |
| a JSON field | the command's Observation module, with the value produced where it is executed |
| the order of steps | the command's Scenario module |
| a deployment verb | its own module; routing stays in Main |
| a transaction's shape or a model rule | the library, never a command module |
Decisions¶
| Chosen | Rejected | Why |
|---|---|---|
| Command-local modules under each executable's own source directory | one shared source directory for all four commands | sharing across executables is a new cross-command owner and a library-level change; the few duplicated helpers stay as they were |
| The journey's false claim as its own function beside the proof checks | moving it into the journey's controls | it is the proof verifier's own negative case and needs the proof it rejects; the controls module owns validator refusals |
| Deployment verbs receive the arguments the router read | each verb calling getArgs again |
the arguments are the same process-wide list; passing them makes each verb's input explicit |
count still judges --what and address bytes after the node session opens |
moving those checks before the session | that would change when an unknown --what is refused |
Evidence and limits¶
The checks that run on every pull request cover different claims. The
off-chain lint app formats every command module and runs HLint over
all of them, journey and update-terminal included, with no directory
exclusions (see Checking off-chain code). The
component build compiles all four
commands. The registry workflow runs the journey from the checkout with
a freshly built blueprint, and runs each archive command from an
extracted release archive with its jq assertion over the observation.
The supported deployment identity check runs genesis-skey, deploy
and verify on a local devnet, refuses a manifest whose
representative policy was changed, and now also checks the verb
router's refusals without a node, that --out=FILE writes the same key
as --out FILE, and that count finds one registry output and exactly
the reference outputs the manifest records.
Named limits: the journey is exercised from the checkout only — running
it from an extracted archive is issue #214 and is not shown here. No
earlier observation of a successful count exists to compare against;
its first executed evidence is this change's. The commands' narration,
flags and JSON are command contracts that command runs establish; the
registry transitions they submit are governed by the accepted Lean model
through the library builders, and a green command run is not a proof of
any model theorem. A build or a fixture does not establish an archive
or live-chain claim.