Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
3a8ffcc
refactor(armed-map): rename fingerprint_v2.js to fingerprint.js and d…
jdogresorg Sep 16, 2026
345cbcc
refactor(activation): W5 consolidation, the sync's twin shims retired…
jdogresorg Sep 16, 2026
325c607
fix(guards): the carrier logic pin membership rule yields the sync ga…
jdogresorg Sep 16, 2026
6d9e9dc
chore(guards): restore_comments FROZEN regexes name the W5 gate and c…
jdogresorg Sep 16, 2026
215c4cd
chore(pins): re-pin the suite titles with the test environment presen…
jdogresorg Sep 16, 2026
1c6e5bc
chore: level develop with master after the v0.19.0 cut
jdogresorg Sep 16, 2026
38c14c9
chore(pins): re-pin identity on the levelled tree (v2 alias retired a…
jdogresorg Sep 16, 2026
ae23ef1
fix(db): resolve the SQL directory from src/sql after the db module m…
jdogresorg Sep 16, 2026
79c7c6e
Merge remote-tracking branch 'origin/master' into level-develop-maste…
jdogresorg Sep 16, 2026
6cbeff0
feat(pins): carrier_logic_pin_ops gains --move --repin (activation re…
jdogresorg Sep 17, 2026
ccfc243
chore(twins): vendor the barrier-family registry rows (shared_rows_1 …
jdogresorg Sep 17, 2026
707fe79
feat(token-bridge): vendor the per-chain token bridge and policy test…
jdogresorg Sep 17, 2026
abddc26
docs(changelog): v0.20.0 barrier family, per-chain token and tick nam…
jdogresorg Sep 17, 2026
18dd726
revert(token-bridge): drop the per-chain testnet arming from the v0.2…
jdogresorg Sep 17, 2026
50fc157
chore(release): bump to 0.20.0, refresh the README badge and record t…
jdogresorg Sep 17, 2026
6ade6cf
fix(consensus): re-cut the LTC and DOGE mirror-admission heights onto…
jdogresorg Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.20.0] - 2026-09-17

### Changed
- The vendored registry includes shipped mirror-admission and anchor-attestation barrier heights.
- `carrier_logic_pin_ops --move` can re-pin changed carrier logic and record path and hash transitions atomically.
- Activation modules and fingerprint tooling follow the consolidated consensus layout.

## [0.19.1] - 2026-09-16

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# XChain Sync

<p align="center">
<img src="https://img.shields.io/badge/version-0.19.1-blue" alt="Version">
<img src="https://img.shields.io/badge/version-0.20.0-blue" alt="Version">
<img src="https://img.shields.io/badge/tests-2%2C779%2B%20passing-brightgreen" alt="Tests">
<img src="https://img.shields.io/badge/node-%3E%3D22-green" alt="Node">
<img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-blue" alt="License">
Expand Down
44 changes: 29 additions & 15 deletions bin/lib/carrier_logic_pin_ops.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,17 @@ module.exports = function carrierLogicPinOps(core) {
* activation file at the top level and under src/lib, every SHARED_GATES
* carrier at src/<name>.js, and the digest module that computes the requires.
*/
function hubMembers(dir) {
function hubMembers(dir, pin) {
const source = fs.readFileSync(path.join(dir, 'src/consensus_rules_digest.js'), 'utf8');
const shared = constInit(source, 'SHARED_GATES').elements
.map((row) => `src/${row.elements[0].value}.js`);
return activationFiles(dir).concat(activationFiles(dir, 'lib'), shared, ['src/consensus_rules_digest.js'])
// A SHARED_GATES stem lives at its pinned path once W5 has moved it
// (src/consensus/gates/<stem>_gate.js, src/consensus/<carrier>.js or a
// hub-owned home such as src/attestation/); the pin already records
// that path per id, so read it there rather than restating the loader's
// move table. A stem with no pin entry yet (before --init) falls back
// to the pre-W5 flat path.
const ids = Array.from(new Set(constInit(source, 'SHARED_GATES').elements.map((row) => row.elements[0].value)));
const shared = ids.map((id) => (pin && pin.entries && pin.entries[id] ? pin.entries[id].path : `src/${id}.js`));
return activationFiles(dir).concat(activationFiles(dir, 'lib'), gateFiles(dir), shared, ['src/consensus_rules_digest.js'])
.filter((rel) => fs.existsSync(path.join(dir, rel)));
}

Expand All @@ -171,9 +177,9 @@ module.exports = function carrierLogicPinOps(core) {
.concat(gateFiles(dir), fixedCarrierPaths(dir, pin, FIXED_CARRIER_IDS[name]), ['src/consensus_rules_digest.js'])
.filter((rel) => fs.existsSync(path.join(dir, rel)));
} else if (name === 'xchain-sync') {
list = activationFiles(dir).concat(fixedCarrierPaths(dir, pin, FIXED_CARRIER_IDS[name]));
list = activationFiles(dir).concat(gateFiles(dir), fixedCarrierPaths(dir, pin, FIXED_CARRIER_IDS[name]));
} else if (name === 'xchain-hub') {
list = hubMembers(dir);
list = hubMembers(dir, pin);
} else {
throw new Error(`no membership rule for ${name}`);
}
Expand Down Expand Up @@ -245,22 +251,21 @@ module.exports = function carrierLogicPinOps(core) {
}

/**
* Repoint one entry at `rel`, keeping its id and hash. Refused, as a logic
* finding (exit 1), when the file at the new path hashes differently: a move
* carries the module, never a change to it, which goes through --write.
* Repoint one entry at `rel`, keeping its id. A changed hash is accepted
* only with a reason, and the one move record then proves both changes.
*/
function moveEntry(dir, pin, id, rel, reason) {
const before = pin.entries[id];
if (!before) throw new Error(`${id} is not pinned; use --add`);
if (before.path === rel) throw new Error(`${id} is already at ${rel}; nothing to move`);
const hash = hashFile(dir, rel);
if (hash === null) throw new Error(`${rel} does not exist under ${dir}`);
if (hash !== before.hash) {
const err = new Error(`${id}: the logic at ${rel} (${hash.slice(0, 8)}) is not the pinned logic (${before.hash.slice(0, 8)}); a move carries a module unchanged, re-pin a changed one with --write`);
if (hash !== before.hash && (typeof reason !== 'string' || reason.trim() === '')) {
const err = new Error(`${id}: moving to changed logic requires --reason`);
err.exitCode = 1;
throw err;
}
pin.entries[id] = Object.assign({}, before, { path: rel });
pin.entries[id] = Object.assign({}, before, { path: rel, hash });
const record = { id, from: before.hash, to: hash, path: { from: before.path, to: rel }, date: today() };
if (reason !== undefined) record.reason = reason;
pin.repins.push(record);
Expand All @@ -270,7 +275,8 @@ module.exports = function carrierLogicPinOps(core) {
* Every difference between the pin as committed and `pin` that carries no
* `repins` record of the right shape: a changed or added hash needs a record
* whose `to` is the new hash, a retired id one whose `to` is null, and a
* moved path one whose `path.to` is the new path under the unchanged hash.
* moved path one whose `path.to` is the new path. A hash-and-path move needs
* one record whose hash and path fields prove both the old and new states.
* @returns {string[]} `<id>: <what happened>` lines, empty when every change is recorded
*/
function unrecordedChanges(committed, pin) {
Expand All @@ -285,6 +291,12 @@ module.exports = function carrierLogicPinOps(core) {
}
continue;
}
if (before && before.path !== rel) {
const combined = repins.some((r) => r.id === id && r.from === before.hash && r.to === hash &&
r.path && r.path.from === before.path && r.path.to === rel);
if (!combined) out.push(`${id}: hash and path changed with no combined --move record`);
continue;
}
if (!repins.some((r) => r.id === id && r.to === hash)) {
out.push(`${id}: ${before ? 'hash changed' : 'added'} with no --write or --add record`);
}
Expand Down Expand Up @@ -343,8 +355,10 @@ module.exports = function carrierLogicPinOps(core) {
}
writePin(dir, pin);
const rec = pin.repins[pin.repins.length - 1];
if (verb === 'moved') console.log(`moved ${opts.id}: ${rec.path.from} -> ${rec.path.to} (${rec.to.slice(0, 8)} unchanged)`);
else if (verb === 'retired') console.log(`retired ${opts.id}: ${rec.from} -> (gone)`);
if (verb === 'moved') {
const hashMove = rec.from === rec.to ? `${rec.to.slice(0, 8)} unchanged` : `${rec.from.slice(0, 8)} -> ${rec.to.slice(0, 8)}`;
console.log(`moved ${opts.id}: ${rec.path.from} -> ${rec.path.to} (${hashMove})`);
} else if (verb === 'retired') console.log(`retired ${opts.id}: ${rec.from} -> (gone)`);
else console.log(`${verb} ${opts.id}: ${rec.from || '(new)'} -> ${rec.to}`);
return 0;
}
Expand Down
14 changes: 9 additions & 5 deletions bin/pin-identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ function sha256(rel) {

/** The whole identity of this build, as the pin stores it. */
function buildPin() {
const v2 = require(path.join(REPO_ROOT, 'src/consensus/armed_map/fingerprint_v2.js')).computeArmedMapFingerprintV2();
const v2 = require(path.join(REPO_ROOT, 'src/consensus/armed_map/fingerprint.js')).computeArmedMapFingerprintV2();
const logicPin = require(path.join(REPO_ROOT, 'bin/lib/carrier_logic_pin.js'));
const coins = {};
for (const rel of COIN_FILES) coins[rel] = sha256(rel);
return {
// The legacy field carries v2 and the version field says so; the _v2 alias of the
// W1 to W4 window left the pin at W5 (activation-registry C4, D103).
armed_map_fingerprint: v2.hex,
armed_map_fingerprint_v2: v2.hex,
armed_map_fingerprint_version: 2,
armedMapRows: v2.rows || null,
armed_map_rows: v2.count === undefined ? null : v2.count,
Expand All @@ -82,8 +83,7 @@ function buildPin() {
/** Pin against tree, field by field, so a failure names the file that moved. */
function compare(pin, fresh) {
const differences = [];
for (const field of ['armed_map_fingerprint', 'armed_map_fingerprint_v2',
'armed_map_fingerprint_version', 'armed_map_rows', 'carrier_logic_digest']) {
for (const field of ['armed_map_fingerprint', 'armed_map_fingerprint_version', 'armed_map_rows', 'carrier_logic_digest']) {
if (pin[field] !== fresh[field]) differences.push(`${field} ${pin[field]} became ${fresh[field]}`);
}
for (const group of ['armedMapRows', 'vendoredCoins']) {
Expand All @@ -97,6 +97,11 @@ function compare(pin, fresh) {
else differences.push(`${group}: ${name} changed bytes`);
}
}
// A pin field this tool no longer writes (the W1 to W4 _v2 alias, or any future
// retirement) is a pin taken by an older tool: it does not hold until re-pinned.
for (const field of Object.keys(pin)) {
if (!Object.prototype.hasOwnProperty.call(fresh, field)) differences.push(`${field} is no longer recorded; re-pin`);
}
return differences;
}

Expand Down Expand Up @@ -143,7 +148,6 @@ function main() {
}
console.log(`armed-map fingerprint ${fresh.armed_map_fingerprint}`);
console.log(`armed-map version ${fresh.armed_map_fingerprint_version}`);
console.log(`armed-map v2 ${fresh.armed_map_fingerprint_v2}`);
console.log(`armed-map v2 rows ${fresh.armed_map_rows}`);
console.log(`carrier logic digest ${fresh.carrier_logic_digest}`);
console.log(`vendored coin files ${Object.keys(fresh.vendoredCoins).length}`);
Expand Down
Loading
Loading