did-btcr2-js

ADR 115: The Vector Corpus Holds No Pipeline State, signals.json Records the Anchored Signals, and Two Recipes Cover a Duplicate Signal and a Removed Beacon Address

Context

ADR 113 fixed the layout of a vector set and stated that no state file leaves the submodule. The generator of ADR 113 wrote two more files into each set: scenario.json, a copy of the recipe, and funding.json, the anchors and the beacon addresses of the scenario. The pipeline steps after generation (funding, anchoring, the verifiers) read them. Both files describe the pipeline, not the DID operations.

On 2026-09-14 the maintainers of the Danubetech harness sent questions about the regenerated vectors. Their harness reads scenario.json and funding.json. A consumer that reads those files couples its harness to our tooling, and a change to our pipeline breaks it. The maintainers also asked for the chain data of each anchored update: the transaction, the block, and the block times. Without that data, a consumer cannot check its own signal discovery against the corpus, and a consumer that reads no chain cannot fulfill the signals of a sans-I/O resolver.

Two rules for the corpus follow from that exchange:

Three coverage gaps became visible at the same time:

The offline verifier of the pipeline keyed its synthetic signals by beacon service id. A beacon rotation keeps the service id and changes the address. The verifier delivered the signal of the rotated beacon to the old address in the first discovery round. The filter of ADR 114 ignores that tuple, so the rotation recipe failed offline while it resolves on a chain.

Decision

The corpus holds no pipeline state (R1, R2). A vector set is {network}/{k1|x1}/{hash}/ with create/, update/ (or update/NN/), resolve/ (or resolve/NN/), other.json, and signals.json. The generator writes no scenario.json and no funding.json. The state of a generated scenario (the DID, the anchors, the beacon addresses, the cohort) goes to lib/scenarios/<network>/state/<scenario-id>.json in this repository. The state holds no secret: the anchor step reads the keys from other.json. The vector index of the pipeline keys a set by the scenarioId of its other.json. The build state of a network (the cohort artifacts, the manifests, the funding summary, the state files) is committed with the pass of that network.

signals.json records the Beacon Signals of a set. The live verifier writes the file with --record for every set that has a Beacon Signal on the chain. The file is an array with one entry per signal:

The verifier reads the signals from the chain the way the api does, with the indexer discovery of the method package. A consumer compares its own signal discovery with the file, or takes the signals from the file when it reads no chain. The README of each network states the rule.

Two new entry forms in a recipe. An update with removedBeacon: true is announced at a beacon that an earlier update removed from the document. The generator takes the anchor address from the genesis document and refuses the flag when the source document still carries the beacon. The update is signed and stays in the sidecar, so a resolver finds its data and ignores its signal. The expected document does not advance. A duplicate entry { "duplicateOf": N, "beaconId": "#..." } announces the signed update of entry N again, in a later block. The entry has no update/NN/ directory and no sidecar entry. The update directories count the update entries only. An anchor of the pipeline state records the entry it announces and the update directory whose signed update is its signal.

Two new recipes on every network. 23-k1-duplicate-signal: update 1 (version 2) and update 2 (version 3) are anchored at one beacon, and a third entry announces the signed update of version 2 again at the same beacon, in a later block. The set resolves to version 3. A sub-vector asks for a versionTime after the block of version 3 and before the block of the duplicate, and expects version 3: a resolver that runs the versionTime stop before the duplicate check returns version 2. 24-k1-removed-beacon-signal: update 1 removes the #initialP2PKH service, and update 2 is announced at the removed address. The set resolves to version 2, and a sub-vector for versionId 3 expects NOT_FOUND.

The n03 negative uses a reserved network value. The tampered identifier carries the nibble 6. The recipe descriptions on every network name the reserved value.

The offline verifier keys its signals by address. The synthetic signals of a set sit at the anchor addresses, as they do on a chain. A discovery round for a rotated address finds the signals of that address, and the first round finds none there.

Scope boundary

Consequences

Positive. The corpus holds what a consumer needs and nothing that couples a consumer to our pipeline. A consumer can check its signal discovery and its block times against the recorded chain data. The corpus covers the duplicate rule of ADR 068 and the removed-beacon rule of ADR 114, and the n03 negative tests a rule that every implementation must apply. The pipeline state sits with the recipes that produced it, in one directory per network.

Negative. A consumer that reads scenario.json or funding.json must stop. The Danubetech maintainers received that directive on 2026-09-14. Every committed vector set changes in the next regeneration pass of its network, because the files leave and signals.json arrives. A duplicate entry adds an anchor and a block to a pass.

Unchanged. The generator, the signing path, the fixed vector layout of the operation files, the resolve output shape, and the one-pass rule of ADR 113. The four networks and the per-network secrets.

Superseded. From ADR 113: the statement that no state file leaves the submodule. The state files leave; the operation files stay. The rest of ADR 113 stands.

Implementation

References