Release v0.15.0 - xchain-documentation - #26
Open
jdogresorg wants to merge 53 commits into
Open
Conversation
…pability slash suite exists
…e rollback budget it bounds
…TE an over-cap request reverts
The tree listing claimed a file tally taken from a working-tree scan, which runs ahead of the committed count whenever a lane holds unlanded suites. Register the committed number and drop the two count exceptions the ACTION set now covers on its own.
…rom the tree The published figures counted two slash suites that are not in the repo, so the e2e-test repo's own recount test refused them. The tree carries 30 ACTION test suites across 76 files with 48 helper modules; SLASH leaves the enumerated list with them.
…ents Covers the encoder/node maintenance-window sentinel pair, three hub oracle consensus/publishing knobs plus two operator drill-script RPC targets, and the four HUB_SYNC_* mirror-drain knobs on both the indexer and its explorer twin, documented from each component's own copy of hub_db_sync.js. Notes that the explorer never calls the resync path HUB_SYNC_BARRIER_HOLD_CEILING_S gates, unlike the indexer's block loop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133x7wrwS7q1num9bQ2PT3R
…blish ledger override Sixteenth variable added to this pass after the coordinator found it landed on origin (visible to the coverage checker's origin-pointed ref run but not the local checkout this pass started from). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133x7wrwS7q1num9bQ2PT3R
The teardown ledger adds one module under test/helpers, so the published figure moves from 48 to 49.
…age adds The capability slash suite adds one file under test/actions, so the published figure moves from 76 to 77.
…re on the tree The capability and contract slash suites bring SLASH back into the counted set, so the figure returns to 31 and the two count exceptions are no longer needed.
…le variables The hub's RollcallRound and StateCheckpointEngine and the indexer's hub client landed reads of nine variables on origin with no configuration rows, and the cross-repo coverage gate has been refusing every platform push since. Each gets a row (name, default, meaning) on its component's configuration page, plus the three ROLLCALL_*_BLOCKS tunables that the scanner cannot see because they are read by computed name. The computed-read baselines move to match the committed trees: hub 31 to 33 (the roll-call tunable resolver and the attest response forward override), node 17 to 20 (ConfigService passes the roll-call rail env through by name).
… through Four host-environment variables the node hands to the explorer: the overall request budget, the fee-quote and pre-flight budgets, and the tip-age freshness gate. Each note says what makes the public default wrong on a private venue - one tunnelled host is a single IP to the limiter, and a regtest chain that nobody is mining crosses the six-hour staleness gate while its lag is zero.
An attestation costs two on-chain transactions today. The request rides inside the EXECUTE the user already paid for, but the response is a whole ATTEST v1 transaction that a validator broadcasts and pays a fee for, and the contract callback fires only when it mines. A contract executed a thousand times is a thousand validator-paid transactions, each waiting on Bitcoin block time. This is the height at which that stops. At or above it the responsible set's finalized artifact reaches indexers through the hub mirror the way PRICE rounds already do, and the full history still lands on chain in periodic batches so a node replaying the chain re-derives every callback. Evaluated on the request's own block, like the other attestation gates, so the rule for a request is fixed the moment it is admitted. Mainnet and testnet both ship unratified.
Both are regtest-only test seams introduced with the response mirror, and both were invisible to the coverage gate: the hub's was read through a constant holding its own name, and the indexer's is resolved by a shared helper that indexes process.env by argument. Operator-settable variables with no row in any configuration table. The hub knob shifts when a mirrored response becomes applicable, and it exists because regtest blocks are stamped at roughly now, so without it no response could bind for two real minutes. The indexer knob is the barrier's grace, which only has to cover stream lag because the real forward margin travels inside the signed row. Both refuse to take effect off regtest, because two nodes resolving either differently settle blocks differently.
Two schema tables landed locally with no row naming them, on both sides of the mirror. The hub authors the table when a round reaches quorum; the indexer carries a local copy that hub_db_sync populates. The indexer's row says the two things a reader needs that the hub's does not: that the mirror is transport and never authority, since the applier re-verifies every row's signatures against the responsible set it resolves from its own local request row, and that the applied state lives in attests rather than here.
…ivation Regtest stays inert by default, because arming a network commits every BTC indexer on it to a wired DOGE peer and a single-coin venue would defer at its first epoch close. A venue that runs both chains sets XC_ROLLCALL_REGTEST_ACTIVATION on every hub and indexer to arm at height 0, or at a height it names.
A consensus change registered after the public testnet launch cannot be genesis-active there without re-deciding history outside nodes have committed, so it parks on the sentinel until an operator names an instant. The page now says which gates those are, rather than letting the genesis-active line stand for every gate.
Both actions charge a base plus a per-item or per-recipient amount from the unified fee gate, in place of the flat charge per database hit. The base is what keeps the smallest sweep or callback able to buy a native-coin fee output above the chain's dust threshold.
…ponse Names the database the quoted oracle prices were read from, so a tool that seeds prices writes to the one the node reads instead of inferring it from its own environment.
A consensus train that moves the node, hub and indexer, and points the install example at it. There is no v0.13.0: the number was skipped deliberately.
The tree carries 79 action test files and 51 helper modules.
The explorer serves 174 api patterns and 117 page routes, and the end-to-end tree carries 79 action test files.
Above the response-mirror activation height a response reaches every indexer through the hub mirror and fires its callback at the first block whose protocol time reaches the signed effective time, with no validator transaction; the batch head and continuation formats that put the window's responses on chain, the mirror-era signing canonical, the invalid status for an on-chain v1 above the height, and the escrow split without a broadcast reimbursement are all written down beside the existing lifecycle.
…mirror venue has one The tree carries 52 helper modules; both component pages published 51.
The batch publisher and its window seam read three variables that no configuration table described, so the coverage gate refused every push across the tree. The kill switch and the buffer path join the attestation publishing table. The window override goes beside the forward-margin override it shares a module and a rule set with, including the one way they differ: a batch window of zero seconds is a division by zero in the alignment arithmetic, so regtest demands a positive integer rather than merely a whole number.
… node read baseline xchain-node forwards the price, oracle and attestation-response grace windows to the indexer by name in one loop, so each is a computed read the coverage gate cannot see. All three now carry rows, and the baseline moves to match the committed tree.
Both reached the p2p config only recently, so an operator setting them saw no effect and neither carried a row.
…en window An operator retuning it cannot see from the name that both engines read the one value, or that raising it alone re-opens a pending request and pays for a duplicate provider fetch.
It is the same at-most-once guard the single-response marker beside it provides, one level up, covering a whole window rather than one response. The row records why it lives in the database and not in the publisher's buffer file: the batch spends real coin, and the buffer sits on the disk whose exhaustion is what makes a rewrite after the broadcast fail. The detail carries the four states, because only one of them is safe to act on automatically. A window left at intent is quarantined for a person, since the transaction may be waiting in a mempool this hub cannot see.
The two are different units on purpose, and an operator reading only the hour cannot see that tying them would move a chain-only node's coverage proof whenever price staleness is retuned.
The six wallet pages described a passphrase that was never stored and had to be typed at every unlock. It is now captured once at create or import and stored encrypted under the wallet password, so the pages say that, and say plainly what it does and does not protect: it covers a recovery phrase that leaks on its own, it does not cover someone holding the device and the password, and it is not a decoy mechanism. Import now warns that a phrase carries no record of whether a passphrase was used, so importing without one opens a different, empty wallet with nothing to signal it.
One wave of the review round on the xchain-platform board. Every change was re-derived from the code rather than applied from the finding recommended option, and each carries a control that reproduces the original failure. Review findings: 6393 6394 6397 6425 6426 6442 6447 6458 6459 6460 6461 6500 6502 6532 6558 6559 6560 6561 c234140a0bd9
…env passthroughs, and the origin identity change in the wallet's data disclosure The explorer's app-wide, action-proof and checkpoint-verify defaults are now 1080, 90 and 90 per minute; the node pages document the encoder trust-proxy and rate-limit passthroughs and the explorer's five per-route caps. The data disclosure states that a real-client-IP module is loaded for the rate limiters only and that the access logs still record the edge address, so no visitor IP is written to disk.
…effect, not by the web server's mechanism
…r a count The node page recommended a setup by hardware SKU, two pages referred to a specific hub role, and the Apple privacy labels counted the wallet's first-party API hosts. Each now states the requirement or the guarantee instead.
The canonical config reference documented CORS_ORIGIN as a single origin rather than the allowlist parseCorsOrigin implements, and betting.md omitted that controller-bound tokens are rejected as a market's wager token. The published helper-module count finding (#6813) was REJECTED: the monitor's own replacement numbers were wrong in the other direction, and applying them would have reddened xchain-e2e-test's ci chain for every lane running it with the docs sibling present. Review round 7 findings #6835, #6837, #6853, and the #6813 reject. Also carries review round 6's documentation work.
Review-round fixes, each checked against the handler that implements it. Max supply was described as a lifetime issuance ceiling when the handlers compare against OUTSTANDING supply, so a burn reopens headroom; LOCK_MINT was described as disabling issuer supply creation; BATCH issuance limits were still taught in their pre-activation form three weeks after the mainnet gate armed; and SLEEP was described as pausing a TICK the address ISSUED when the handler gates on the OWNER, which the glossary itself defines as sellable and separate from issuance. ORACLE_BATCH_SIGN_TIMEOUT_MS is one name over two rails with two defaults: the price rail reads the validator config table and defaults to 60000, the attestation batch rail reads the environment and defaults to 15000. Both rows now say so and name each other. XCHAIN_COINBASE_MATURITY is per chain rather than a universal 100: DOGE is 240 on mainnet and testnet, 60 on regtest. Suite: 441 passing. The 2 failures are a pre-existing endpoint-count drift gate; the explorer route count is identical before and after this work.
… read The fleet-wide coverage gate reads every process.env access in each service and requires a row on that component's own configuration page. Seven reads had none: explorer, indexer HUB_DB_SYNC_HTTP_DEADLINE (vendored in both, so both pages) hub ANCHOR_RATELIMIT_MAX_WAIT_MS, ANCHOR_RATELIMIT_MAX_WAITS hub LLM_SPEND_LOG_FALLBACK_PATH indexer HUB_CALL_DEADLINE_MS sdk ENCODER_API_KEY Each row states the meaning and asserts the default the code actually applies, so a later drift between the two is a gate failure rather than a silent lie. The encoder key is marked a credential, with the note that an unpinned encoder URL lets a hub overlay repoint the client and send the key to the host it named. Rows only. No existing row, heading or table shape changed.
The row described what the setting does and that it is regtest-only, but not the one thing an operator needs before they need it: on a chain whose blocks are stamped at about wall clock, the default cannot be satisfied at all. The barrier waits for the mirror watermark to reach the block's timestamp plus the grace, and where the watermark also tracks wall clock a freshly mined block can never be two minutes behind it. Every affected block then waits out the full timeout and carries on regardless, so the venue does not fail, it crawls. The numbers are in the row because they are the argument: 367 blocks in six hours at the default against two thousand in under two once lowered, with a hundred and sixty deferrals waiting on a condition that could not arrive. That cost a release matrix a full cycle and read as a wedged run rather than a misconfiguration, which is exactly the confusion a line of documentation prevents. Off regtest nothing changes: the value is a consensus input there and the frozen constant wins whatever the environment says.
MAX_DIRECT_TIP_AGE_S bounds how long the hub keeps trusting a direct getlatestblock height that has not advanced past the pushed tip it just rejected. It is separate from MAX_TIP_AGE_S on purpose: that gate falls through to one more HTTP call, while this one is terminal and reports no BTC tip at all, so its bound is sized for an ordinary long block gap on a healthy chain. A height that beats the pushed tip is accepted whatever the tip's age.
The encoder now refuses an envelope cancel whose commit outpoint a foreign reservation already holds, and returns it as an operational error naming the outpoint. Documented alongside the other reservation errors so a client can map the code rather than discover it.
…eclared here The decoder vendors this constant byte-equal, so the canonical declaration has to exist for that copy to be verified against anything. Keyed on block time with >= semantics and consensus-affecting. Mainnet is null, which fails closed and awaits a ratified per-network instant. Testnet and regtest run from genesis, matching the sibling expiry-realign gate under the pre-launch rule that every feature is active on testnet: the gate closes a defect that spends a payer's native coin and returns nothing, so a public testnet reaches it, and testnet decoder state is rebuilt from the chain before launch.
The e2e action suites gained a helper that decides whether a case depending on the legacy on-chain ATTEST response path can run at all, so the published counts move from 52 to 53. Both figures are asserted against the tree by the action suite-count guard, which is what caught the drift.
The indexer resolves XCHAIN_COINPAY_EXPIRATION_S through one function so the regtest-only rule for a consensus input (ignore with a warning off regtest, positive-integer check on it) lives in one place. That read is computed, so the env-var coverage scanner cannot see the name and the computed-read ratchet held the indexer at its old count. Add the row the variable needs, in the barriers table beside the two grace windows that share its mechanism, and raise the indexer baseline to match the committed tree.
Operator ruling 2026-09-07: the point of this train is to exercise the response mirror and roll call on testnet, and a train that ships them dark there is not worth cutting. The mirror is armed at block 151324, the chain tip when the ruling was made, so it is active the moment a node updates rather than waiting on a future height. Roll call needed no change: it was already armed at 151200, which the chain passed some time ago. Mainnet stays unratified for both, so its behaviour is byte for byte unchanged. On testnet this changes state derived from existing bytes, so the changelogs now carry an Activation section saying so, and every hub and the indexers following it must update together rather than one at a time. The activation map is mirrored in five places and all five move together: both service copies, the documented canonical, the vendored copy the test helper reads, and the assertion that used testnet as its example of an unratified network, which it no longer is.
…rmed there The activation page still described the response mirror as the one height-keyed rule unarmed on testnet, which stopped being true when the v0.15.0 train ratified it at block 151324. Mainnet is untouched and still carries the null that encodes never.
jdogresorg
force-pushed
the
release/v0.15.0
branch
from
September 7, 2026 04:46
de1398b to
283628a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cuts xchain-documentation for the v0.15.0 release train. 50 commits from master.
Added
attestation_responsesmirror table.protocol/constants.js, with mainnet shipping inert atnull.XROLLCALLjoinsENGINE_TAGSwith header vectors; it is namespacing only and deliberately not a SLASH family.rollcall_canonical.jsonfreezes the canonical and wire bytes with real Ed25519 signatures, including negative cases for a wrong ledger hash, network or epoch.ROLLCALL_ACTIVATION, and that a BTC indexer without it defers every block.Changed
Nand nothing on-chain penalises it; where ROLLCALL is active it is evicted by deactivation, never burned.ANCHOR_ACTIVATIONheight below which any version is invalid; the checkpoint bundle carries one anchor per network per cycle with every chain as its own section, and the pre-restart version numbers are retired.anchor_bundlerow per bundle instead of oneanchor_<chain>row per chain, across the COLLECT, hub and indexer reference pages.getanchorstatusdocumentsanchorsPublishedas a count of bundles, and adds thesectionsAnchoredandbundlesOversizefields.anchor_actionstable is documented as keyed on(action_index, section_index), one row per bundle section.parseAnchorV0andanchorBundleSectionin place ofparseAnchorV3, and explains that a bundle omitting a chain is normal.Added
operations/release-process.md: how a release train is cut, signed and published, the hotfix path, and the classification rule that decides whether a fix needs an activation point.Fixed
xchain-contractstemplates and omittedcounterpartyBridge; it now counts 14 and lists it, matching whatnpx xchain-contracts listreports.Removed