Release v0.19.0 - xchain-sync - #16
Merged
Merged
Conversation
…tion schema The bridge tables and the ISSUE opt-in columns ride the wire at indexer schema 11.
…apply Wire the vendored train activation gate into every follower apply path (live block, catch-up window, full and from-height bootstrap) so a build lacking the rule set the signed release manifest requires stops before writing the boundary block instead of forking and halting on divergence one block later. The halt rides the existing durable sync_halt path and names the missing rule set and height, and the pending verdict is published on /status so the halt is announced before it fires.
…ne numbers The comment above the apply-time schema heal claimed schema fetch and apply run only at bootstrap and pinned that claim to line numbers that had drifted. It now names the bootstrap, bootstrap-from-height, resume and heal call sites by function, so it cannot go stale the same way again.
The anchor_actions and validator_rewards key rebuilds name a column only the source-DDL heal supplies, which runs later in startup and not at all on a replica holding a durable halt, so the rebuild could only defer to a next start that repeated the same order. The column step now carries those authoritative definitions and the rebuild adds its own precondition, so one startup converges both.
A repair page whose row cannot land because a local row of a superseded id generation holds its natural key now retires that row and lands the source's, bounded by the id window the page proves absent upstream. A collision with an id the source still serves keeps the loud throw.
Refreshes the lockfile so every package the security audit flags resolves to its patched release inside the existing version ranges. No source change.
…ead errors ClientRollback is a hand-copied twin of the indexer's rollback and three statements were never mirrored: the anchor restore sat inside a guard the indexer runs unconditionally, the delegations restore used a self-join that re-nulled revoked rows, and the attestation batch-head restore was absent. Boundary hash verification and the dispenser snapshot read through a fail-soft query default, so a swallowed error became an authoritative empty answer. From the 2026-09-12 review round.
…aised The halt-recovery, boundary-read and sync_meta retention log lines route through the shared observability logger instead of raw console calls, the four new methods take plain names with every caller repointed, and the retention interval comes from config.js rather than a second process.env read beside the one config.js already does. The five new unit files take snake_case <unit>.test.js names.
…column The indexer landed 2026-09-13-destroys-sends-leg-ordinal, which adds leg_ordinal to destroys and sends plus a composite (action_index, leg_ordinal) index on each. Both tables are stream:action wire-replicated, so a follower on version 11 has no column to receive the streamed leg position: the row either fails the block apply under the strict path or lands every leg on the implicit default, leaving the follower answering leg order from nothing while its source answers from the wire. The bump is mechanical rather than a consensus change. Both tables are DERIVED in tableLifecycle: a deterministic projection of already-hashed actions, in no hash class of their own, rolled back by action_index, which is agnostic to the row's column set. No block-hash preimage moves. The migration is mode=auto and self-heals on the restart the version mismatch forces. MIGRATION_FRONTIER.indexer moves to 2026-09-13 and accounts that one file; the 2026-09-12 tail it enumerated now sits below the cursor.
…b mixins The indexer split src/db.js into src/db/index.js plus per-feature mixins, so the six hardcoded sibling paths in the stake-weight and block-hash conformance guards no longer resolve and fail closed under XCHAIN_REQUIRE_SIBLINGS=1. Each guard now pins the exact mixin holding what it compares (stakes.js for the two stake-weight builders, shared.js for BLOCK_HASH_VERSION, actions.js for getBlockHashes and its canonicalization and fold tail), with the comparisons and their failure messages left exactly as strong as before.
… registry
The indexer folded its per-coin config shims into the coins registry and deleted
src/configs/, so this guard's require of src/configs/BTC.js threw and, under
XCHAIN_REQUIRE_SIBLINGS=1, hard-failed every xchain-sync push rather than
skipping. It now reads src/coins/BTC.js, whose module exports STAKING and
VALIDATOR_QUERY_LIMIT at top level instead of behind getConfig('mainnet').
No consensus drift was hiding behind the broken read: the capability set, all
five MIN_STAKE floors and the 1000-row query limit are identical on both sides,
so the repoint is cosmetic. Falsified against a throwaway copy of the registry,
where a changed floor, a dropped capability and a changed query limit each turn
the guard red by name.
The indexer moved its consensus, hub and chain modules under src/consensus, src/hub and src/chain, so the twin guards, the rollback-coverage loop and the vendored copies now resolve those paths. Where a twin requires a co-twinned sibling by relative path, a narrow depth mask covers only that require depth and every other byte still has to match.
The indexer renamed its copies of three twin test suites and a utf8mb4 column module, so the cross-repo guard now pairs each sync file with the indexer's new name while sync keeps its own file names. The three twin tests are re-vendored from the indexer so their content stays byte-identical apart from require depth.
bin/pins/at1-suite-titles.json holds the full test titles per file for the unit tier, and bin/pins/identity.json holds the armed-map fingerprint and the vendored coin hashes. A later restructure is compared against both through declared rename maps rather than against a test count.
Every tracked src file is reachable from what the service starts, so nothing is deleted. The ported reachability and sibling-reference tools that produce the report build every reference pattern from this repo's own name.
eslint.config.js carries the shared preset's core rules inline, and eslint joins devDependencies and the lockfile. The lint script stays out of ci until the rule families it reports have been swept.
src/db.js becomes src/db/index.js with no change to the class body, and every require of ./db still resolves. The only edits are the moved file's sibling requires and the test paths that read it as a file.
The query methods move verbatim into mixins named for the table family they read, installed non-enumerably on the prototype so every call site is unchanged. Source-text drift guards that read a method from its file now read the mixin that holds it.
The source and replica halves, the hub client, the replicated-schema layer, the HTTP surface and the pure helpers each get a directory with snake_case file names, and only entry files and files this repo carries but does not author stay at the top. Requires, proxyquire targets and path literals follow the moves, body requires are hoisted, and mixed export shapes fold onto the class.
The report is re-run rather than patched, and every src file is still reachable from what the service starts.
Service code logs through the vendored observability logger, which writes to console until the entry point patches it and then ships structured lines. Multi-argument calls fold through util.format, so the rendered text is unchanged.
… can reach Methods this repo authors lose the prefix together with their call sites, while methods in files it carries but does not author, and names a guard reads by string, keep it. One test require of the process entry point moves back inside the case that needs it, because loading that entry patches console for every suite.
Where every string naming a method lives in this repo's own tests, the method moves together with its stub targets, extractor patterns, comments and suite titles. The suite-title pin tool gains a declared symbol map beside its path map, so a title a rename legitimately changes is compared through it.
Suite files end in .test.js or .fuzz.js, and package scripts, workflow steps and cross-suite requires follow the renames, with every script collecting the same files as before. The twin test files whose canonical lives in another repo keep their names.
…db home The derived-reward, recovery-reward and cooldown-credit collectors call named methods on new validator_rewards and credits mixins, with the query text unchanged. Suites that fake the database take the real mixins, and the drift guards read each collector together with its mixin.
…it as SQL The file header states the rollback's reverse delete in words instead of a code span, and names the recovery-reward collector by its current file name.
A pinned-validators comment and the sibling-coverage docstring name the xchain-sdk paths those files now live at.
The table-existence check, the lookup-table id ceiling and the checkpoint roots read move onto their table family's mixins with the query text unchanged. A shared test helper installs the real query methods onto fake databases without overriding the fakes' own stubs.
The poller's id-list reads move onto a new index_lookups mixin and the table-generic mixin with the query text unchanged. The mock databases that stand in for the poller's source take every real query method through the shared helper.
…b home The checkpoint reads move onto a new state_checkpoints mixin and the catalog read onto the table-generic mixin, each emitting the same SQL and bind arguments. The range read's row cap stays in the route, because it is the API's paging policy.
Keep leading poll coverage at the existing path and move later suites into numerically ordered parts. Preserve all full test titles and their execution order.
Keep the leading block at the original path and move later blocks into numerically ordered parts. Share fixtures locally while preserving titles, assertions, comment coverage, and both execution modes.
…avior Keep the original entry paths and move later suites into numerically ordered part files. Preserve full test titles, execution order, hooks, assertions, and comments while clearing file and callback size limits.
Keep the leading apply and balance coverage in the original test entry. Move the remaining suites into ordered parts while preserving titles, assertions, comments, and execution order.
Keep the leading table-list block in the entry and move later blocks into numerically ordered parts. Preserve title order, hooks, comments, and assertions.
Keep the leading divergence cases at the original path and move later suites into numbered parts. Preserve title order, assertions, comments, and both execution modes.
Keep the coverage contract in the entry and move later blocks into numbered parts. Preserve the title multiset, execution order, and both sibling-loading modes.
Move hub config delta handling into a non-enumerable class mixin. Split endpoint and regression refetch paths into bounded helpers without changing the HubClient API.
Move the shared test harnesses created by the REST API and stampede splits under a helpers directory so they clear the test-file naming rule, and fix the relative requires the move broke.
Fold this batch's test-file splits into the split declaration so the suite-title compare can account for them.
Split the table-spec constants out of src/server/updated_rows.js into src/server/updated_rows/table_specs.js, keeping the same exported names from the entry file.
…and compare both parts
…nt/index.js, the path the indexer's entry took, so the twin suites carry one require text
… the entry's new path The i5 test-file split landed two new files under test/unit/state_commitment_batched_node_writes.test/ after this window's follower move, so they still required the retired flat src/stateCommitment.js and failed the whole suite load with MODULE_NOT_FOUND. Repoint both to src/state_commitment/index.js, the same path every other reader in this repo already follows.
…s and src/db/rollback/ statements The source indexer moved src/rollback.js to src/rollback/index.js and split its statements into src/db/rollback/. Every drift guard that compared the source's SQL or bespoke logic now reads one text in a fixed order (entry, parts sorted, statement files sorted) through readSourceText(), so each regex and marker block still sees exactly the literals it saw in the single file, and the table-list guards require the new entry path.
The indexer's rollback split exposed resetOrphanedAttestRequests and restoreStampedAttestHeads as db-layer attests writers, and its byte-identical twin guard requires this file to match. See xchain-indexer 558e5df4.
…walk SQL twins under src/db and its twin test splits The follower half of the indexer window: DbNodeStore's three statements and the orphan walk's three reads move into src/db/subtree/ (node_store_rows.js, orphan_stats_reads.js), byte twins of the indexer's files, so the node-store and reportOrphanStats blocks the conformance suites compare change identically on both sides. Each db function hands back the handle's own promise, so no await moved. The three twin test entries are the indexer's copies through the depth mask: the entry FakeDb's run() dispatches to one method per table, the escrow-leaf shadow test scratch-arms through a restoring helper, and every over-limit describe is re-opened under the same title, so each file's ordered full-title list is unchanged.
… state-tree metric cap through it The reportOrphanStats twin block read STATE_TREE_METRIC_MAX_NODES from the environment directly; the block is compared raw against the indexer's, so both sides now read it through one accessor by one name. readEnvNow answers with the environment as it is at the call, which the new config case proves by changing the variable between two reads.
…er src/db/subtree in the cross-repo twin loop
…ier logic pin)
bin/lib/carrier_logic_pin.js (byte-identical twin in indexer, sync and hub)
hashes the acorn token stream of every armed-map carrier and the rules
digest, comments and whitespace dropped and require paths masked, so a
comment restore, reformat or move leaves the pin still while any change to
a body, operator or map literal moves it. bin/pins/carrier-logic.json holds
one entry per module id; a deliberate change re-pins only through
--write --id --reason, which records {id, from, to, reason}. The repo_guards
test fails on a moved entry, on a bare regenerate, and (with
XCHAIN_REQUIRE_SIBLINGS=1) on a twin whose sibling pin differs. This is the
O5 replacement guard: it covers the logic bytes that fingerprint v2 stops
hashing (activation-registry row 27, section 4.6). acorn 8.18.0 is pinned as
an exact devDependency (D64); the vm keeps its own nested 8.16.0.
Sync also prints and pins carrier_logic_digest in bin/pin-identity.js.
…ools run under --omit=dev bin/lib/carrier_logic_pin.js parses carriers with acorn at require time, and the drift guards run the identity tools after npm ci --omit=dev, so acorn 8.18.0 moves from devDependencies to dependencies (activation-registry row 27, D64: one exact version in indexer, sync and hub). The sync drift-guards job also gains its npm ci --omit=dev step before the identity pin check, whose comment wrongly said the tool needed no install.
…v1 retired (provisional twin bytes) src/consensus/gate_registry.js holds the SHARED-GATES block (78 rows, the indexer's twin rows keyed as the manifest keys them) and the sync-only consensus-constants rows; the 8 activation files and 3 carriers become shims reading their tables through get(key), predicates untouched; the manifest's ENTRIES come from rows(); src/armedMapFingerprint.js and its test are deleted; api.js and pin-identity.js publish armed_map_fingerprint = v2, armed_map_fingerprint_version 2 and carrier_logic_digest; the logic pin is re-pinned once with reason. v2 457980e8 bare and the armed value re-read unchanged over the same 39 keys. The block and shim bytes are provisional until the barrier copies the indexer's canonical bytes over them (activation-registry row 14, D73).
…ARED part files (D76, D77) src/consensus/gate_registry/ mirrors the indexer's registry parts as whole-file twins beside the hub-authored consumer core; the 11 gate files are byte twins of the indexer's shims; the manifest keeps sync's own 39 keys so v2 457980e8 bare and the armed value re-read unchanged over the same rows (D77); the logic pin is re-pinned once (12 entries) and the identity pin's carrier_logic_digest written (D75). The 16 remaining failures compare against siblings that have not taken the conversion yet (activation-registry row 14).
The collector reads its three regtest arming variables through a named table, so the env-doc scanner sees the literal names in this repo too; bytes identical to the indexer's (activation-registry rows 13 to 15).
…image boots and publishes a hex src/api.js required bin/lib/carrier_logic_pin (and acorn) at load, and the Dockerfile ships only src/, so a W3 sync container threw MODULE_NOT_FOUND at boot. The /health field now comes from src/health/carrier_logic.js, which reads bin/pins/carrier-logic.json the way the indexer's health helper does and publishes UNREADABLE when the pin is absent; the Dockerfile copies that one pin file. Activation registry P2 (spec D82).
…epins records (activation registry P1) Byte-identical twin of the indexer's bin/lib/carrier_logic_pin.js plus the new bin/lib/carrier_logic_pin_ops.js twin (membership rules and the operations), so a W4 deletion or move in the canonical repo is a recorded event the pin test can check, never a silent re-pin; the three XCHAIN_<NAME>_DIR reads are by literal name. The sync's own pin file does not change. One comment in balance_helpers.js cites the registry row instead of a deleted indexer shim.
Version 0.19.0, the 0.19.0 changelog section, the README refresh measured on Linux, the registry part shared_rows_5.js re-vendored byte-identical from the indexer with the TRAIN_ACTIVATION 0.19.0 row (testnet 152716) and the three per-chain testnet XCHAIN_BRIDGE_ACTIVATION heights sized at the cut, and the identity pin re-derived (armed-map fingerprint 239720fa).
The first sizing (11:53Z) put the train boundary at TBTC 152716 and the chain reached it at 16:33Z on 2026-09-16 while the cut was still waiting on the e2e matrix, so the release gate's H_TRAIN > T_BTC no longer held. Re-cut from the tips and last-99-block cadences read in one sitting at 16:33Z with a 10 h lead (the origin at 3x): TRAIN_ACTIVATION 0.19.0 testnet 152787 (TBTC 152716 + 71 at 508.8 s/blk, about 10.0 h), XCHAIN_BRIDGE_ACTIVATION BTC:testnet 152929 (TBTC 152716 + 213, about 30.1 h, the origin, last), LTC:testnet 4887898 (TLTC 4887644 + 254 at 141.8 s/blk, about 10.0 h), DOGE:testnet 67902062 (TDOGE 67900748 + 1314 at 27.4 s/blk, about 10.0 h). Mainnet stays the sentinel and regtest 0. The pins and the tests that carry the literals follow.
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.
Bumps the package and lockfile to 0.19.0 and folds the Unreleased block into a dated 0.19.0 entry. The train carries the XBRIDGE cross-chain bridge, armed on testnet by the ladder and on mainnet at the sentinel, with the token bridge and policy code dark behind their sentinels, the rollcall and tracker fixes, the code-structure passes with the consensus identity pinned, and the activation registry. The testnet ladder is TRAIN 152716, BTC bridge 152795, LTC 4887694 and DOGE 67900889, sized 2026-09-16 11:53Z.
Platform notes ride xchain-node's Release.