Skip to content

fix(cluster-tool): decode each outbound-gate code by its own carrier - #103

Merged
heifner merged 1 commit into
masterfrom
fix/external-outpost-slug-reader
Sep 25, 2026
Merged

heifner merged 1 commit into
masterfrom
fix/external-outpost-slug-reader

Conversation

@heifner

@heifner heifner commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

The remaining chain-table reader flagged as [P1] on wire-sysio#619: "one live consumer bypasses it: ExternalOutpostSteps.ts still maps each getChains() row with String(code.value)."

Confirmed exactly as described. runOutboundEnvelopesQueued built its expected set with String(code.value) and compared spellings against String(row.chain_code). That worked only because both sides happened to land on the same decimal. Once the depot registers the slug_name ABI builtin, chains.code is "ETH", .value is undefined, every expected code becomes the literal "undefined", and the gate can never match — external-outpost bootstrap polls its full SingleHopBudgetMs and times out.

#101 did not expose it for two reasons: the reader bypasses slugValue entirely, so classifying slugValue call sites could not find it — and its fixture pinned { code: { value } }, so no test ever supplied the string carrier.

The fix

Both sides decode by declared type and compare packed values:

  • chains.code is a slug_name ABI field → slugValue, which handles the {value} wrapper before the builtin and the canonical spelling after.
  • outenvelopes.chain_code is declared uint64 → packedSlugValue, the decoder added in fix(cluster-tool): parse a bare slug cell as a slug, not a decimal #101 for precisely this carrier. It renders as a number, or a quoted decimal past 0xffffffff, which every real chain code exceeds.

The failure message keeps the human-readable form via SlugName.toString, so the enrichment context still names ETH / SOL rather than packed integers.

Fixture

chainRow is now parameterized over a SlugCarrier identity enum, and the gate's positive case runs under both carriers via it.each — the string spelling and the legacy {value} wrapper — so the rollout window is covered from both sides. Codes are real (ETH / SOL / WIRE) rather than small integers, so the spelling carrier is genuinely exercised.

I deliberately did not add a negative carrier case: the gate polls SingleHopBudgetMs (7 min) before failing, so a no-envelope test would stall the unit suite. The failure path is already covered by the existing enrichment test, which rejects through the fast getOutboundEnvelopes throw.

Verification

ExternalOutpostSteps + slugUtils: 30/30, 4.6s. Filtering to -t carrier gives 2 passed / 11 skipped, confirming both parameterized variants execute rather than silently collapsing to one. eslint clean on both files.

Order-independent, like #101: correct before and after #619.

runOutboundEnvelopesQueued read chains.code with String(code.value) and
compared spellings. Once the depot registers the slug_name ABI builtin the
cell is "ETH", .value is undefined, every expected code becomes
"undefined", and the gate can never match — external bootstrap times out.
The fixture pinned {code:{value}}, so no test saw it.

Both sides now decode by declared type and compare packed values:
chains.code is a slug_name field (slugValue handles the wrapper and the
spelling), outenvelopes.chain_code is uint64 (packedSlugValue). The
failure message keeps the spelling via SlugName.toString. Fixture covers
both carriers.

Change-Id: I481aa204ba3368b6d269488e36cab8637170fe9a
@heifner
heifner merged commit 98002a1 into master Sep 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants