The upgrade boundary
Some things are frozen because changing them would rewrite history. Others can be added because they change nothing that already exists. The line between them is this page.
- The eight frozen surfaces, down to which constants and which reason code names are inside them.
- What an implementation is free to change without telling anyone, and why an additive field is not a removal.
- Why a new opcode or a new marker version splits implementations even though old parsers survive it.
- That every bit of the SEED flags byte must be zero, and why it is not a forward compatibility channel.
- The two conditions software must see before it will build a mainnet transaction.
Frozen
Nothing below may change. A change here would produce different state from the same chain, which means the artifacts people already hold would mean something different than they did when they were made.
| Surface | Includes |
|---|---|
| Identity | Magic PTNA, version byte 0x01, protocol slug |
| Derivations | The five domain tags, single SHA-256, the preimage layouts |
| Marker grammar | Single minimal push, 83 byte ceiling, lowest index selection, duplicate voiding |
| Opcodes | 0x01 SEED and 0x02 KEEP, and their payload layouts |
| Constants | 144, 4032, 4032, 100000, 10000, 10000, 8, 6, and the eight tier thresholds |
| Validity rules | The six SEED checks, KEEP entry rules, the default rule, relic terminality |
| Reason codes | All eighteen, by name |
| State semantics | Depth is computed, rings are append only, relic is terminal |
Free to change
None of these affect what a transaction means.
- Indexer internals: storage engine, schema, caching, concurrency, language.
- Sync speed, memory use, and deployment shape.
- Additive API surface: new endpoints, new fields on existing responses. Removing or repurposing a field is not additive.
- Everything about presentation: renders, share cards, wording, this documentation.
- Client tooling, planners, and wallet integrations.
- Operational policy: rate limits, retention of invalid events, monitoring.
On the boundary
Three things sit in between and need the process below.
A new opcode
The marker already reserves opcode space, and unknown opcodes are handled: they record
MARKER_UNKNOWN_OP and change nothing. That means a new opcode can be introduced without
breaking older indexers, but older indexers will disagree with newer ones about state from the moment
anyone uses it. Adding an opcode is a protocol change, not a feature.
A new marker version
Same shape. Version 0x02 markers would be ignored with
MARKER_UNKNOWN_VERSION by current parsers. Introducing one splits implementations the same
way.
The flags byte
SEED reserves one payload byte with no assigned meaning, and every bit of it must be zero at version 1. Assigning a meaning to it later would change how already confirmed transactions are interpreted, so it is a protocol change, and one to avoid.
A non zero flags byte is SEED_BAD_GRAMMAR, and an implementation must reject it. One that
waves it through creates artifacts no other implementation has, which surfaces as a state root mismatch
rather than a disagreement about presentation. A future version that wants flags will carry a different
version byte instead.
The process for anything frozen or on the boundary
Four steps, in this order. Nothing written at step one ships until step three has vectors that two implementations can both reproduce.
-
Write it down before writing code
A change to the baseline is proposed as a change to the baseline file, with the reason, the affected constants, and the effect on existing artifacts.
-
Record deviations honestly
Any implementation that already differs from the baseline records the difference in
docs/deviations.md, in this repo. A deviation that is not written down is a bug wearing a disguise. -
Extend the golden vectors first
A change is not real until vectors cover it. Two implementations must be able to prove they agree on the new behaviour before it ships. See Golden vectors and state roots.
-
Bind it to a deployment record
Deployment records carry
spec_sha256. A change to the spec changes that hash, andGET /patina/statuspublishes it. Anyone can tell which spec an indexer is running without asking.
Networks and activation
Deployments bind a network to the constants and window heights it uses:
{ network, protocol_id: "PTNA", spec_sha256,
h_open, h_close, grace_end,
min_carrier_founding, min_carrier_open, commit_min_age }
- Regtest and signet deployments ship in the repo.
- Mainnet values stay unset until activation is authorised.
- Software must refuse to construct mainnet transactions without both
PATINA_MAINNET_AUTHORIZED=trueand a mainnet deployment record naming at least two approvers.
This is a deliberate speed limit. It means an accidental deploy cannot open a founding window, and it means the window's heights are a published decision rather than a runtime surprise.
What those switches reach, and the things nobody holds a switch over at all, is set out on the public transparency page.
Naming rule
There are no version labels in any name: not in file names, folder names, package names, URLs, or
interface copy. The one number that exists is the marker version byte, which is protocol data inside a
transaction, not a product name. When you need to say which spec is running, quote
spec_sha256.