@mysten-incubation/devstack 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dashboard-ui/assets/{grpc-CpkDu4SA.js → grpc-s7Ztk9wv.js} +1 -1
  2. package/dashboard-ui/assets/{index-jLPRmjst.js → index-DrOd0m4F.js} +2 -2
  3. package/dashboard-ui/index.html +1 -1
  4. package/dist/contracts/routable.d.mts +1 -1
  5. package/dist/index.d.mts +2 -2
  6. package/dist/orchestrators/boot.mjs +2 -1
  7. package/dist/orchestrators/boot.mjs.map +1 -1
  8. package/dist/orchestrators/router/file-provider.mjs +11 -8
  9. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  10. package/dist/orchestrators/router/service.mjs +2 -2
  11. package/dist/orchestrators/router/service.mjs.map +1 -1
  12. package/dist/plugins/sui/index.d.mts +10 -10
  13. package/dist/plugins/walrus/client-services.d.mts +1 -0
  14. package/dist/plugins/walrus/client-services.mjs +115 -0
  15. package/dist/plugins/walrus/client-services.mjs.map +1 -0
  16. package/dist/plugins/walrus/deploy.mjs +11 -5
  17. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  18. package/dist/plugins/walrus/errors.d.mts +1 -1
  19. package/dist/plugins/walrus/errors.mjs.map +1 -1
  20. package/dist/plugins/walrus/index.d.mts +1 -1
  21. package/dist/plugins/walrus/index.mjs +27 -12
  22. package/dist/plugins/walrus/index.mjs.map +1 -1
  23. package/dist/plugins/walrus/mode/known-deploy.mjs +1 -1
  24. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  25. package/dist/plugins/walrus/mode/local-cluster.d.mts +14 -1
  26. package/dist/plugins/walrus/mode/local-cluster.mjs +54 -12
  27. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  28. package/dist/plugins/walrus/routable.mjs +12 -15
  29. package/dist/plugins/walrus/routable.mjs.map +1 -1
  30. package/dist/plugins/walrus/snapshot.mjs +3 -2
  31. package/dist/plugins/walrus/snapshot.mjs.map +1 -1
  32. package/dist/plugins/walrus/storage-nodes.mjs +31 -33
  33. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  34. package/dist/substrate/runtime/config-validation.mjs +1 -1
  35. package/images/walrus/Dockerfile +17 -4
  36. package/images/walrus/deploy-walrus.sh +7 -4
  37. package/images/walrus/run-walrus-client-service.sh +103 -0
  38. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.mjs","names":[],"sources":["../../../src/plugins/walrus/deploy.ts"],"sourcesContent":["// Walrus deploy one-shot — Move publish via ArtifactPublisher.\n//\n// Distilled-doc reference (06-walrus.md §\"Lifecycle phase 2\"):\n// the `walrus-deploy` one-shot:\n// - publishes the Walrus Move package on the local sui chain,\n// - mints a WAL exchange,\n// - emits per-node config files (`dryrun-node-<i>.yaml`,\n// `dryrun-node-<i>.keystore`) under `runtime/walrus/<name>/deploy/`.\n//\n// We route this through the substrate's `ArtifactPublisher`\n// primitive (architecture §10 — \"callable from any plugin; no\n// plugin-side contract to implement\"). The publisher owns:\n// - cache key derivation (folds `chainId`),\n// - verify probe (lenient — re-derives on transient RPC failure),\n// - produce-on-miss (runs `walrus-deploy`),\n// - register-on-every-cycle (so downstream consumers always see\n// the resolved state).\n//\n// Distilled-doc invariants honored here:\n// - 5: cache key folds `chainId`.\n// - 6: verify checks BOTH the on-disk `deploy` file AND on-chain\n// object existence (system + staking). Either failure\n// invalidates and re-deploys.\n// - 7: `runtime/walrus/<name>/deploy/` rides the snapshot tar\n// (declared via `Snapshotable.subtrees` in `snapshot.ts`).\n// - 10: deploy summary must contain `package_id` + `system_object`\n// + `staking_object`. We surface parse failure as\n// `WalrusPluginError{phase: 'deploy'}` (the publisher's\n// `produce-failed` reason).\n\nimport { Duration, Effect, Schema, type Scope } from 'effect';\nimport { access, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport type { ContainerRuntime, ImageRef } from '../../contracts/container-runtime.ts';\nimport {\n\tartifactPublishError,\n\ttype ArtifactPublishError,\n\ttype ArtifactPublisher,\n} from '../../primitives/artifact-publisher.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { ContentHash } from '../../substrate/brand.ts';\nimport { atomicWriteFileSync } from '../../substrate/runtime/atomic-write.ts';\nimport { hostBindMountOwner } from '../../substrate/runtime/host-bind-mount-owner.ts';\nimport { HOST_GATEWAY_EXTRA_HOSTS } from '../../substrate/runtime/host-gateway.ts';\nimport { probeManyLenient } from '../../substrate/runtime/probes.ts';\nimport {\n\tDEPLOY_BIND_SOURCE_RETRY_PROFILE,\n\tmakeSpacedRetrySchedule,\n} from '../../substrate/runtime/retry-policy.ts';\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.ts';\nimport { labelledExcerpt } from '../../substrate/runtime/observability/index.ts';\nimport type { SuiProbeKey } from '../sui/index.ts';\nimport { walrusDeployMountPaths } from './deploy-paths.ts';\nimport { walrusPluginError, type WalrusPluginError } from './errors.ts';\n\n/** Cache-stored payload — what verify re-confirms on every cycle\n * (06-walrus.md §\"State-store entries\"). */\nexport interface CachedDeployState {\n\treadonly walrusPackageId: string;\n\treadonly systemObject: string;\n\treadonly stakingObject: string;\n\treadonly upgradeManagerObject?: string;\n\treadonly treasuryObject?: string;\n\treadonly exchangeObject?: string;\n}\n\n/** Verify-schema: the publisher decodes the cached id's on-chain\n * object through this. Minimal — the substrate's `ChainProbe`\n * decodes against this; any decode failure surfaces structured.\n * Distilled-doc invariant 8: the probe MUST consume a stable\n * identifier (the object id), NOT a derived hash. */\nexport const WalrusDeployVerifyShape = Schema.Struct({\n\tsystemObjectId: Schema.String,\n\tstakingObjectId: Schema.String,\n});\nexport type WalrusDeployVerified = Schema.Schema.Type<typeof WalrusDeployVerifyShape>;\n\nconst SuiObjectExistsShape = Schema.Struct({\n\tobjectId: Schema.String,\n});\n\n/** Short readiness tolerance for a transient not-found on the cached\n * deploy objects (sui/system/staking).\n *\n * The big post-restore catch-up window — `sui start` re-executes its\n * committed checkpoint store from seq=0, and `getObject(systemObject)`\n * reads not-found until the replay reaches the object's checkpoint — is\n * now absorbed by the sui plugin's caught-up-to-head ready-gate\n * (`waitForCheckpointCatchUp` in plugins/sui/mode/local.ts): the\n * validator does not report ready until its head has stabilized to live\n * cadence, so by the time this verify runs the committed deploy objects\n * are already served. This budget only needs to cover the small RPC\n * index-visibility lag, NOT the whole replay.\n *\n * If it gives up too soon it reads the objects as not-found and triggers\n * a SPURIOUS redeploy, which mints FRESH walrus ids and orphans every\n * pre-snapshot blob (the snapshot-survival-matrix walrus-S2 failure:\n * \"Too many failures while writing blob to nodes\"). A genuinely-wiped\n * chain still redeploys once the budget lapses.\n *\n * Budget: 5 probes × 3s = 15s. */\nconst WALRUS_DEPLOY_VERIFY_READINESS_RETRIES = 5;\nconst WALRUS_DEPLOY_VERIFY_READINESS_DELAY = '3 seconds';\n\nconst requiredDeployOutputFiles = (inputs: DeployInputs): ReadonlyArray<string> => [\n\tjoin(inputs.outputDirHostPath, 'deploy'),\n\t...Array.from({ length: inputs.committeeSize }, (_, nodeIndex) => [\n\t\tjoin(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.yaml`),\n\t\tjoin(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}-sui.yaml`),\n\t\tjoin(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.keystore`),\n\t]).flat(),\n];\n\nconst deployOutputFilesComplete = (\n\tinputs: DeployInputs,\n): Effect.Effect<boolean, WalrusPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tawait Promise.all(requiredDeployOutputFiles(inputs).map((file) => access(file)));\n\t\t\treturn true;\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\twalrusPluginError(\n\t\t\t\t'deploy',\n\t\t\t\t`walrus deploy cache is missing local output files under ${inputs.outputDirHostPath}`,\n\t\t\t\t{ cause },\n\t\t\t),\n\t}).pipe(Effect.catch(() => Effect.succeed(false)));\n\n/** Inputs to one deploy round. */\nexport interface DeployInputs {\n\treadonly walrusName: string;\n\treadonly chainId: string;\n\treadonly contentHash: ContentHash;\n\t/** Pre-derived host output dir — substrate's `servicePath('walrus',\n\t * name, 'deploy')` equivalent. Persists across teardown\n\t * (distilled-doc §\"What survives teardown\"). */\n\treadonly outputDirHostPath: string;\n\t/** On-disk per-stack root from `StackPathsService.stackRoot`. The\n\t * bind-mount source is `stackRoot`; `outputDirHostPath` MUST be a\n\t * descendant. Threaded explicitly to remove the previous\n\t * `dirname(dirname(dirname(...)))` walk-up footgun in\n\t * `walrusDeployMountPaths`. */\n\treadonly stackRoot: string;\n\treadonly suiRpcUrlInNetwork: string;\n\treadonly walrusFaucetUrlInNetwork: string;\n\treadonly committeeSize: number;\n\treadonly shards: number;\n\treadonly epochDuration: string;\n\treadonly publicHostsCsv: string;\n\treadonly listeningIpsCsv: string;\n\t/** Wrapper image — the cargo-built walrus image (bootstrap asset).\n\t * The deploy one-shot is `docker run --rm <image> deploy …`. */\n\treadonly walrusImage: ImageRef;\n\t/** Docker network the deploy one-shot attaches to, so its in-network\n\t * Sui RPC + faucet hostnames resolve. */\n\treadonly suiNetworkName: string;\n\t/** Centralized Sui funding readiness gate. Local Sui exposes this so\n\t * callers don't race the faucet's socket-ready / funds-ready gap. */\n\treadonly waitForFundsReady?: Effect.Effect<void, unknown>;\n}\n\n/** Default deploy one-shot timeout. Walrus genesis publish runs the\n * Move publish + WAL exchange creation + per-node config emission;\n * observed wall-clock is 30-60s. 5-minute ceiling absorbs cold-cache\n * + slow CI runners. */\nconst DEPLOY_TIMEOUT_MS = 5 * 60_000;\n\nconst ensureDeployOutputDir = (inputs: DeployInputs): Effect.Effect<void, WalrusPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tawait mkdir(inputs.outputDirHostPath, { recursive: true });\n\t\t\t// Route the bind-source marker through the canonical atomic\n\t\t\t// primitive (STYLE_GUIDE §17) — tempfile + fsync + rename so a\n\t\t\t// crashed writer can't leave the bind-mount probe reading a\n\t\t\t// half-written marker. 0o644 so the in-container deploy user can\n\t\t\t// stat it. Sync variant: the marker is a tiny one-liner and it\n\t\t\t// keeps the durability boundary the raw `writeFile` lacked.\n\t\t\tatomicWriteFileSync(\n\t\t\t\tjoin(inputs.outputDirHostPath, '.devstack-bind-source'),\n\t\t\t\t'devstack walrus bind source\\n',\n\t\t\t\t{ mode: 0o644 },\n\t\t\t);\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\twalrusPluginError(\n\t\t\t\t'deploy',\n\t\t\t\t`walrus deploy failed to prepare output directory ${inputs.outputDirHostPath}`,\n\t\t\t\t{ cause },\n\t\t\t),\n\t});\n\nconst deployExitDetail = (\n\tresult: { readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\tinputs: DeployInputs,\n): string => {\n\tconst missingCommandHint =\n\t\tresult.exitCode === 127\n\t\t\t? ' exit 127 usually means the deploy image is missing walrus-deploy or an entrypoint command.'\n\t\t\t: '';\n\treturn (\n\t\t`walrus deploy exited with code ${result.exitCode}.` +\n\t\tmissingCommandHint +\n\t\t` outputDir=${inputs.outputDirHostPath} committee=${inputs.committeeSize} shards=${inputs.shards}` +\n\t\tlabelledExcerpt('stdout', result.stdout) +\n\t\tlabelledExcerpt('stderr', result.stderr)\n\t);\n};\n\nconst isBindSourceMissing = (result: {\n\treadonly exitCode: number;\n\treadonly stderr: string;\n}): boolean =>\n\tresult.exitCode === 125 &&\n\t/bind source path does not exist/i.test(result.stderr) &&\n\t/invalid mount config/i.test(result.stderr);\n\n/** Parse the walrus deploy output into a `CachedDeployState`.\n *\n * Expected output format (best-effort match against the walrus\n * binary's deploy stdout — its exact format may drift between\n * versions):\n *\n * walrus_package_id: 0x<hex>\n * system_object: 0x<hex>\n * staking_object: 0x<hex>\n * upgrade_manager_object: 0x<hex> (optional)\n * treasury_object: 0x<hex> (optional)\n * exchange_object: 0x<hex> (optional)\n *\n * Returns the parsed state OR a typed `WalrusPluginError('deploy')`\n * surfaced with stdout/stderr capture for debugging. */\nexport const parseDeployOutput = (stdout: string): CachedDeployState | null => {\n\t// Match `key: value` (or `key = value`) lines; treat `None` as\n\t// \"absent\" per the upstream walrus-deploy output convention.\n\t//\n\t// Value pattern: `0x<hex>` strictly. The previous `\\S+` regex\n\t// happily matched values like `None,` (with a trailing comma) or\n\t// `[unset]` and passed them through as fake object ids; downstream\n\t// `ChainProbe.get(...)` would then issue a `getObject` against\n\t// garbage, surfacing a confusing \"object not found\" instead of a\n\t// parse failure. Enforcing the hex shape here makes the parse-\n\t// failure surface (`walrusPluginError('deploy', 'parser could not\n\t// find ...')`) catch the bad value at its real source.\n\tconst pick = (k: string): string | undefined => {\n\t\tconst re = new RegExp(`(?:^|\\\\n)\\\\s*${k}\\\\s*[:=]\\\\s*(\\\\S+)`);\n\t\tconst m = re.exec(stdout);\n\t\tconst v = m?.[1];\n\t\tif (v === undefined || v === 'None') return undefined;\n\t\tif (!/^0x[0-9a-fA-F]+$/u.test(v)) return undefined;\n\t\treturn v;\n\t};\n\tconst walrusPackageId = pick('walrus_package_id') ?? pick('package_id');\n\tconst systemObject = pick('system_object') ?? pick('system_object_id');\n\tconst stakingObject =\n\t\tpick('staking_object') ?? pick('staking_pool_id') ?? pick('staking_object_id');\n\tif (!walrusPackageId || !systemObject || !stakingObject) return null;\n\treturn {\n\t\twalrusPackageId,\n\t\tsystemObject,\n\t\tstakingObject,\n\t\tupgradeManagerObject: pick('upgrade_manager_object'),\n\t\ttreasuryObject: pick('treasury_object'),\n\t\texchangeObject: pick('exchange_object'),\n\t};\n};\n\n/** Run the walrus-deploy one-shot and parse the output.\n *\n * Implementation:\n * 1. `runtime.runOneShot({ image, argv: ['deploy', ...] })` —\n * fresh `docker run --rm` container. Mount the host output dir\n * at `/opt/walrus/outputs` so the per-node config files persist.\n * 2. Parse stdout for the deploy summary.\n * 3. Surface non-zero exits + parse failures as\n * `WalrusPluginError('deploy')` with stdout/stderr capture. */\nexport const runDeployOneShot = (\n\truntime: ContainerRuntime,\n\tinputs: DeployInputs,\n): Effect.Effect<CachedDeployState, WalrusPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tif (inputs.waitForFundsReady !== undefined) {\n\t\t\tyield* inputs.waitForFundsReady.pipe(\n\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t`walrus deploy funding gate failed before walrus-deploy: ${formatUnknownError(cause)}`,\n\t\t\t\t\t\t{ cause },\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tyield* ensureDeployOutputDir(inputs);\n\n\t\tconst outputOwner = hostBindMountOwner();\n\t\tconst outputMount = walrusDeployMountPaths({\n\t\t\tstackRoot: inputs.stackRoot,\n\t\t\tdeployOutputDirHostPath: inputs.outputDirHostPath,\n\t\t\tmountTarget: '/opt/walrus/runtime',\n\t\t});\n\t\tconst argv: ReadonlyArray<string> = [\n\t\t\t'deploy',\n\t\t\t'--output-dir',\n\t\t\toutputMount.outputDirInContainer,\n\t\t\t'--committee-size',\n\t\t\tString(inputs.committeeSize),\n\t\t\t'--shards',\n\t\t\tString(inputs.shards),\n\t\t\t'--epoch-duration',\n\t\t\tinputs.epochDuration,\n\t\t\t'--sui-rpc-url',\n\t\t\tinputs.suiRpcUrlInNetwork,\n\t\t\t'--faucet-url',\n\t\t\tinputs.walrusFaucetUrlInNetwork,\n\t\t\t'--public-hosts',\n\t\t\tinputs.publicHostsCsv,\n\t\t\t'--listening-ips',\n\t\t\tinputs.listeningIpsCsv,\n\t\t];\n\n\t\tconst runAttempt = () =>\n\t\t\truntime\n\t\t\t\t.runOneShot({\n\t\t\t\t\timage: inputs.walrusImage,\n\t\t\t\t\targv,\n\t\t\t\t\tmounts: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsource: outputMount.sourceHostPath,\n\t\t\t\t\t\t\ttarget: outputMount.mountTarget,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\t...(outputOwner === undefined ? {} : { env: { DEVSTACK_HOST_UID_GID: outputOwner } }),\n\t\t\t\t\tnetwork: inputs.suiNetworkName,\n\t\t\t\t\t// Same `host-gateway` rationale as storage-nodes.ts —\n\t\t\t\t\t// deploy one-shot dials sui's host-bound RPC + faucet via\n\t\t\t\t\t// `host.docker.internal`. See substrate/runtime/host-gateway.ts\n\t\t\t\t\t// for the platform rationale.\n\t\t\t\t\textraHosts: HOST_GATEWAY_EXTRA_HOSTS,\n\t\t\t\t\ttimeoutMillis: DEPLOY_TIMEOUT_MS,\n\t\t\t\t})\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t\t\t`walrus deploy one-shot failed: ${cause.reason}: ${cause.detail}`,\n\t\t\t\t\t\t\t\t{ cause },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\tconst attempt = Effect.gen(function* () {\n\t\t\tconst result = yield* runAttempt();\n\t\t\tif (isBindSourceMissing(result)) {\n\t\t\t\tyield* ensureDeployOutputDir(inputs);\n\t\t\t}\n\t\t\treturn result;\n\t\t});\n\t\tconst result = yield* attempt.pipe(\n\t\t\tEffect.repeat({\n\t\t\t\tschedule: makeSpacedRetrySchedule(\n\t\t\t\t\tDEPLOY_BIND_SOURCE_RETRY_PROFILE.delayMs,\n\t\t\t\t\tDEPLOY_BIND_SOURCE_RETRY_PROFILE.attempts,\n\t\t\t\t),\n\t\t\t\tuntil: (r) => !isBindSourceMissing(r),\n\t\t\t}),\n\t\t);\n\n\t\t// `Effect.repeat` with a bounded `schedule` + `until` exits SUCCESS\n\t\t// in two cases: (a) `until(r)` returned true (bind source is now\n\t\t// visible — the happy path) OR (b) the schedule recurrence cap was\n\t\t// reached while `until(r)` still returned false (the bind source\n\t\t// remained missing through every retry). The follow-up\n\t\t// `result.exitCode !== 0` check below would already surface case\n\t\t// (b) as a deploy-failure exit, but the failure message would be\n\t\t// the bind-source stderr — not the more diagnostic \"we exhausted\n\t\t// the bind-source retry budget\" message. Surface that explicitly\n\t\t// here so operators see the retry budget in the failure shape.\n\t\tif (isBindSourceMissing(result)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalrusPluginError(\n\t\t\t\t\t'deploy',\n\t\t\t\t\t`walrus deploy: bind-source visibility race did not resolve after ` +\n\t\t\t\t\t\t`${DEPLOY_BIND_SOURCE_RETRY_PROFILE.attempts} retries ` +\n\t\t\t\t\t\t`(${DEPLOY_BIND_SOURCE_RETRY_PROFILE.delayMs}ms spacing). ` +\n\t\t\t\t\t\t`outputDir=${inputs.outputDirHostPath} — Docker Desktop's bind-source ` +\n\t\t\t\t\t\t`propagation lagged past the retry budget. ` +\n\t\t\t\t\t\tdeployExitDetail(result, inputs),\n\t\t\t\t\t{\n\t\t\t\t\t\texitCode: result.exitCode,\n\t\t\t\t\t\tstdout: result.stdout,\n\t\t\t\t\t\tstderr: result.stderr,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (result.exitCode !== 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalrusPluginError('deploy', deployExitDetail(result, inputs), {\n\t\t\t\t\texitCode: result.exitCode,\n\t\t\t\t\tstdout: result.stdout,\n\t\t\t\t\tstderr: result.stderr,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tconst parsed = parseDeployOutput(result.stdout);\n\t\tif (!parsed) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalrusPluginError(\n\t\t\t\t\t'deploy',\n\t\t\t\t\t`walrus deploy: parser could not find walrus_package_id / system_object / staking_object ` +\n\t\t\t\t\t\t`in deploy output. Confirm the walrus binary's output format and adjust ` +\n\t\t\t\t\t\t`\\`parseDeployOutput\\` regexes in deploy.ts. ` +\n\t\t\t\t\t\t`SEAM: see deploy.ts header for the expected format.`,\n\t\t\t\t\t{ stdout: result.stdout, stderr: result.stderr },\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\treturn parsed;\n\t}).pipe(\n\t\tEffect.timeoutOrElse({\n\t\t\tduration: Duration.millis(DEPLOY_TIMEOUT_MS + 5_000),\n\t\t\torElse: () =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t`walrus deploy: outer timeout ${DEPLOY_TIMEOUT_MS}ms exceeded`,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t}),\n\t);\n\n/** Outputs of one deploy round — surfaced to the plugin's\n * resolved value. */\nexport interface DeployOutputs {\n\treadonly state: CachedDeployState;\n}\n\n/** Compose the ArtifactSpec for a walrus deploy and dispatch\n * through the substrate primitive. The publisher handles the full\n * cache/verify/produce/register loop.\n *\n * Produce: real wiring — `runDeployOneShot` runs\n * `docker run --rm walrusImage deploy …` and parses the deploy stdout.\n *\n * The substrate's `ArtifactPublisher.publish` returns the full\n * `CachedDeployState` on EVERY path (decoded cached payload on hit,\n * fresh produce on miss) — no projection dance required. */\nexport const deployWalrusContracts = (\n\tpublisher: ArtifactPublisher,\n\tprobe: ChainProbe<SuiProbeKey>,\n\truntime: ContainerRuntime,\n\tinputs: DeployInputs,\n): Effect.Effect<DeployOutputs, WalrusPluginError | ArtifactPublishError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst state = yield* publisher.publish<CachedDeployState, WalrusDeployVerified>({\n\t\t\tnamespace: 'walrus-deploy',\n\t\t\tchain: inputs.chainId,\n\t\t\tcontentHash: inputs.contentHash,\n\t\t\t// Verify: lenient probes of the cached system + staking\n\t\t\t// objects. The Sui chain probe decodes raw `getObject`\n\t\t\t// responses, so the probe schema matches that envelope and\n\t\t\t// this closure returns the compact verified shape.\n\t\t\tverify: (cached) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tif (!(yield* deployOutputFilesComplete(inputs))) return null;\n\n\t\t\t\t\t// Both cached on-chain objects are independent lenient\n\t\t\t\t\t// probes — fan them out via `probeManyLenient` so the\n\t\t\t\t\t// substrate owns the iteration shape. Any null means\n\t\t\t\t\t// \"re-deploy\"; both non-null gives us the composite\n\t\t\t\t\t// verified payload. The short retry (see the constants\n\t\t\t\t\t// above) only covers RPC index-visibility lag — the\n\t\t\t\t\t// validator's post-restore catch-up is already gated by\n\t\t\t\t\t// the sui plugin's caught-up-to-head ready-gate — so a\n\t\t\t\t\t// transient not-found doesn't force a spurious redeploy.\n\t\t\t\t\tconst probeBoth = probeManyLenient([\n\t\t\t\t\t\tprobe.get(\n\t\t\t\t\t\t\t{ kind: 'object', objectId: cached.systemObject },\n\t\t\t\t\t\t\tSuiObjectExistsShape,\n\t\t\t\t\t\t\t'lenient',\n\t\t\t\t\t\t),\n\t\t\t\t\t\tprobe.get(\n\t\t\t\t\t\t\t{ kind: 'object', objectId: cached.stakingObject },\n\t\t\t\t\t\t\tSuiObjectExistsShape,\n\t\t\t\t\t\t\t'lenient',\n\t\t\t\t\t\t),\n\t\t\t\t\t]);\n\t\t\t\t\tlet [system, staking] = yield* probeBoth;\n\t\t\t\t\tfor (\n\t\t\t\t\t\tlet attempt = 0;\n\t\t\t\t\t\t(system == null || staking == null) && attempt < WALRUS_DEPLOY_VERIFY_READINESS_RETRIES;\n\t\t\t\t\t\tattempt++\n\t\t\t\t\t) {\n\t\t\t\t\t\tyield* Effect.sleep(WALRUS_DEPLOY_VERIFY_READINESS_DELAY);\n\t\t\t\t\t\t[system, staking] = yield* probeBoth;\n\t\t\t\t\t}\n\t\t\t\t\tif (system === undefined || system === null) return null;\n\t\t\t\t\tif (staking === undefined || staking === null) return null;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsystemObjectId: system.objectId,\n\t\t\t\t\t\tstakingObjectId: staking.objectId,\n\t\t\t\t\t} satisfies WalrusDeployVerified;\n\t\t\t\t}).pipe(\n\t\t\t\t\t// `verify` must satisfy `Effect<… | null, never>` per\n\t\t\t\t\t// the publisher contract. Narrow the cache-miss\n\t\t\t\t\t// collapse to genuine cache-corruption signals\n\t\t\t\t\t// (`decode-failed`) and missing-registration\n\t\t\t\t\t// (`no-probe-registered`). The lenient probe already\n\t\t\t\t\t// maps `not-found`/`transient` to `null`, so the\n\t\t\t\t\t// remaining ChainProbeError values that reach here\n\t\t\t\t\t// are authoritative — `decode-failed` is stale shape\n\t\t\t\t\t// (re-produce) and `no-probe-registered` means the\n\t\t\t\t\t// plugin booted before the chain probe was\n\t\t\t\t\t// contributed (treat as miss). Anything else is a\n\t\t\t\t\t// real RPC failure we want visible in the logs\n\t\t\t\t\t// rather than silently collapsed to \"re-deploy\".\n\t\t\t\t\tEffect.catchTag('ChainProbeError', (err) => {\n\t\t\t\t\t\tif (err.reason === 'decode-failed' || err.reason === 'no-probe-registered') {\n\t\t\t\t\t\t\treturn Effect.succeed(null as WalrusDeployVerified | null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Effect.logWarning(\n\t\t\t\t\t\t\t`walrus.deploy verify probe surfaced an authoritative error (` +\n\t\t\t\t\t\t\t\t`reason=${err.reason}, chain=${err.chainId}, detail=${err.detail}); ` +\n\t\t\t\t\t\t\t\t`re-deploying.`,\n\t\t\t\t\t\t).pipe(Effect.as(null as WalrusDeployVerified | null));\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t// Produce: real walrus-deploy one-shot.\n\t\t\tproduce: runDeployOneShot(runtime, inputs).pipe(\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): ArtifactPublishError =>\n\t\t\t\t\t\tartifactPublishError('produce-failed', `walrus.deploy ${err.phase}: ${err.message}`),\n\t\t\t\t),\n\t\t\t),\n\t\t\t// Register: fires on EVERY cycle. The plugin's outer body\n\t\t\t// performs the walrus-state / endpoint / package registry\n\t\t\t// publishes after both deploy + storage-nodes are up; this\n\t\t\t// closure is the publisher-side null-op so the substrate\n\t\t\t// satisfies its Invariant-6 contract.\n\t\t\tregister: () => Effect.void,\n\t\t});\n\n\t\treturn { state };\n\t});\n"],"mappings":"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"}
1
+ {"version":3,"file":"deploy.mjs","names":[],"sources":["../../../src/plugins/walrus/deploy.ts"],"sourcesContent":["// Walrus deploy one-shot — Move publish via ArtifactPublisher.\n//\n// Distilled-doc reference (06-walrus.md §\"Lifecycle phase 2\"):\n// the `walrus-deploy` one-shot:\n// - publishes the Walrus Move package on the local sui chain,\n// - mints a WAL exchange,\n// - emits per-node config files (`dryrun-node-<i>.yaml`,\n// `dryrun-node-<i>.keystore`) under `runtime/walrus/<name>/deploy/`.\n//\n// We route this through the substrate's `ArtifactPublisher`\n// primitive (architecture §10 — \"callable from any plugin; no\n// plugin-side contract to implement\"). The publisher owns:\n// - cache key derivation (folds `chainId`),\n// - verify probe (lenient — re-derives on transient RPC failure),\n// - produce-on-miss (runs `walrus-deploy`),\n// - register-on-every-cycle (so downstream consumers always see\n// the resolved state).\n//\n// Distilled-doc invariants honored here:\n// - 5: cache key folds `chainId`.\n// - 6: verify checks BOTH the on-disk `deploy` file AND on-chain\n// object existence (system + staking). Either failure\n// invalidates and re-deploys.\n// - 7: `runtime/walrus/<name>/deploy/` rides the snapshot tar\n// (declared via `Snapshotable.subtrees` in `snapshot.ts`).\n// - 10: deploy summary must contain `package_id` + `system_object`\n// + `staking_object`. We surface parse failure as\n// `WalrusPluginError{phase: 'deploy'}` (the publisher's\n// `produce-failed` reason).\n\nimport { Duration, Effect, Schema, type Scope } from 'effect';\nimport { access, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport type { ContainerRuntime, ImageRef } from '../../contracts/container-runtime.ts';\nimport {\n\tartifactPublishError,\n\ttype ArtifactPublishError,\n\ttype ArtifactPublisher,\n} from '../../primitives/artifact-publisher.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { ContentHash } from '../../substrate/brand.ts';\nimport { atomicWriteFileSync } from '../../substrate/runtime/atomic-write.ts';\nimport { hostBindMountOwner } from '../../substrate/runtime/host-bind-mount-owner.ts';\nimport { HOST_GATEWAY_EXTRA_HOSTS } from '../../substrate/runtime/host-gateway.ts';\nimport { probeManyLenient } from '../../substrate/runtime/probes.ts';\nimport {\n\tDEPLOY_BIND_SOURCE_RETRY_PROFILE,\n\tmakeSpacedRetrySchedule,\n} from '../../substrate/runtime/retry-policy.ts';\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.ts';\nimport { labelledExcerpt } from '../../substrate/runtime/observability/index.ts';\nimport type { SuiProbeKey } from '../sui/index.ts';\nimport { walrusDeployMountPaths } from './deploy-paths.ts';\nimport { walrusPluginError, type WalrusPluginError } from './errors.ts';\n\n/** Cache-stored payload — what verify re-confirms on every cycle\n * (06-walrus.md §\"State-store entries\"). */\nexport interface CachedDeployState {\n\treadonly walrusPackageId: string;\n\treadonly systemObject: string;\n\treadonly stakingObject: string;\n\treadonly upgradeManagerObject?: string;\n\treadonly treasuryObject?: string;\n\treadonly exchangeObject?: string;\n}\n\n/** Verify-schema: the publisher decodes the cached id's on-chain\n * object through this. Minimal — the substrate's `ChainProbe`\n * decodes against this; any decode failure surfaces structured.\n * Distilled-doc invariant 8: the probe MUST consume a stable\n * identifier (the object id), NOT a derived hash. */\nexport const WalrusDeployVerifyShape = Schema.Struct({\n\tsystemObjectId: Schema.String,\n\tstakingObjectId: Schema.String,\n});\nexport type WalrusDeployVerified = Schema.Schema.Type<typeof WalrusDeployVerifyShape>;\n\nconst SuiObjectExistsShape = Schema.Struct({\n\tobjectId: Schema.String,\n});\n\n/** Short readiness tolerance for a transient not-found on the cached\n * deploy objects (sui/system/staking).\n *\n * The big post-restore catch-up window — `sui start` re-executes its\n * committed checkpoint store from seq=0, and `getObject(systemObject)`\n * reads not-found until the replay reaches the object's checkpoint — is\n * now absorbed by the sui plugin's caught-up-to-head ready-gate\n * (`waitForCheckpointCatchUp` in plugins/sui/mode/local.ts): the\n * validator does not report ready until its head has stabilized to live\n * cadence, so by the time this verify runs the committed deploy objects\n * are already served. This budget only needs to cover the small RPC\n * index-visibility lag, NOT the whole replay.\n *\n * If it gives up too soon it reads the objects as not-found and triggers\n * a SPURIOUS redeploy, which mints FRESH walrus ids and orphans every\n * pre-snapshot blob (the snapshot-survival-matrix walrus-S2 failure:\n * \"Too many failures while writing blob to nodes\"). A genuinely-wiped\n * chain still redeploys once the budget lapses.\n *\n * Budget: 5 probes × 3s = 15s. */\nconst WALRUS_DEPLOY_VERIFY_READINESS_RETRIES = 5;\nconst WALRUS_DEPLOY_VERIFY_READINESS_DELAY = '3 seconds';\n\nconst requiredDeployOutputFiles = (inputs: DeployInputs): ReadonlyArray<string> => [\n\tjoin(inputs.outputDirHostPath, 'deploy'),\n\tjoin(inputs.outputDirHostPath, 'client_config.yaml'),\n\tjoin(inputs.outputDirHostPath, 'sui_client.yaml'),\n\tjoin(inputs.outputDirHostPath, 'sui_client.keystore'),\n\t...Array.from({ length: inputs.committeeSize }, (_, nodeIndex) => [\n\t\tjoin(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.yaml`),\n\t\tjoin(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}-sui.yaml`),\n\t\tjoin(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.keystore`),\n\t]).flat(),\n];\n\nconst deployOutputFilesComplete = (\n\tinputs: DeployInputs,\n): Effect.Effect<boolean, WalrusPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tawait Promise.all(requiredDeployOutputFiles(inputs).map((file) => access(file)));\n\t\t\treturn true;\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\twalrusPluginError(\n\t\t\t\t'deploy',\n\t\t\t\t`walrus deploy cache is missing local output files under ${inputs.outputDirHostPath}`,\n\t\t\t\t{ cause },\n\t\t\t),\n\t}).pipe(Effect.catch(() => Effect.succeed(false)));\n\n/** Inputs to one deploy round. */\nexport interface DeployInputs {\n\treadonly walrusName: string;\n\treadonly chainId: string;\n\treadonly contentHash: ContentHash;\n\t/** Pre-derived host output dir — substrate's `servicePath('walrus',\n\t * name, 'deploy')` equivalent. Persists across teardown\n\t * (distilled-doc §\"What survives teardown\"). */\n\treadonly outputDirHostPath: string;\n\t/** On-disk per-stack root from `StackPathsService.stackRoot`. The\n\t * bind-mount source is `stackRoot`; `outputDirHostPath` MUST be a\n\t * descendant. Threaded explicitly to remove the previous\n\t * `dirname(dirname(dirname(...)))` walk-up footgun in\n\t * `walrusDeployMountPaths`. */\n\treadonly stackRoot: string;\n\treadonly suiRpcUrlInNetwork: string;\n\treadonly walrusFaucetUrlInNetwork: string;\n\treadonly committeeSize: number;\n\treadonly shards: number;\n\treadonly epochDuration: string;\n\treadonly publicHostsCsv: string;\n\treadonly listeningIpsCsv: string;\n\t/** Wrapper image — the cargo-built walrus image (bootstrap asset).\n\t * The deploy one-shot is `docker run --rm <image> deploy …`. */\n\treadonly walrusImage: ImageRef;\n\t/** Docker network the deploy one-shot attaches to, so its in-network\n\t * Sui RPC + faucet hostnames resolve. */\n\treadonly suiNetworkName: string;\n\t/** Centralized Sui funding readiness gate. Local Sui exposes this so\n\t * callers don't race the faucet's socket-ready / funds-ready gap. */\n\treadonly waitForFundsReady?: Effect.Effect<void, unknown>;\n}\n\n/** Default deploy one-shot timeout. Walrus genesis publish runs the\n * Move publish + WAL exchange creation + per-node config emission;\n * observed wall-clock is 30-60s. 5-minute ceiling absorbs cold-cache\n * + slow CI runners. */\nconst DEPLOY_TIMEOUT_MS = 5 * 60_000;\n\nconst ensureDeployOutputDir = (inputs: DeployInputs): Effect.Effect<void, WalrusPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tawait mkdir(inputs.outputDirHostPath, { recursive: true });\n\t\t\t// Route the bind-source marker through the canonical atomic\n\t\t\t// primitive (STYLE_GUIDE §17) — tempfile + fsync + rename so a\n\t\t\t// crashed writer can't leave the bind-mount probe reading a\n\t\t\t// half-written marker. 0o644 so the in-container deploy user can\n\t\t\t// stat it. Sync variant: the marker is a tiny one-liner and it\n\t\t\t// keeps the durability boundary the raw `writeFile` lacked.\n\t\t\tatomicWriteFileSync(\n\t\t\t\tjoin(inputs.outputDirHostPath, '.devstack-bind-source'),\n\t\t\t\t'devstack walrus bind source\\n',\n\t\t\t\t{ mode: 0o644 },\n\t\t\t);\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\twalrusPluginError(\n\t\t\t\t'deploy',\n\t\t\t\t`walrus deploy failed to prepare output directory ${inputs.outputDirHostPath}`,\n\t\t\t\t{ cause },\n\t\t\t),\n\t});\n\nconst deployExitDetail = (\n\tresult: { readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\tinputs: DeployInputs,\n): string => {\n\tconst missingCommandHint =\n\t\tresult.exitCode === 127\n\t\t\t? ' exit 127 usually means the deploy image is missing walrus-deploy or an entrypoint command.'\n\t\t\t: '';\n\treturn (\n\t\t`walrus deploy exited with code ${result.exitCode}.` +\n\t\tmissingCommandHint +\n\t\t` outputDir=${inputs.outputDirHostPath} committee=${inputs.committeeSize} shards=${inputs.shards}` +\n\t\tlabelledExcerpt('stdout', result.stdout) +\n\t\tlabelledExcerpt('stderr', result.stderr)\n\t);\n};\n\nconst isBindSourceMissing = (result: {\n\treadonly exitCode: number;\n\treadonly stderr: string;\n}): boolean =>\n\tresult.exitCode === 125 &&\n\t/bind source path does not exist/i.test(result.stderr) &&\n\t/invalid mount config/i.test(result.stderr);\n\n/** Parse the walrus deploy output into a `CachedDeployState`.\n *\n * Expected output format (best-effort match against the walrus\n * binary's deploy stdout — its exact format may drift between\n * versions):\n *\n * walrus_package_id: 0x<hex>\n * system_object: 0x<hex>\n * staking_object: 0x<hex>\n * upgrade_manager_object: 0x<hex> (optional)\n * treasury_object: 0x<hex> (optional)\n * exchange_object: 0x<hex> (optional)\n *\n * Returns the parsed state OR a typed `WalrusPluginError('deploy')`\n * surfaced with stdout/stderr capture for debugging. */\nexport const parseDeployOutput = (stdout: string): CachedDeployState | null => {\n\t// Match `key: value` (or `key = value`) lines; treat `None` as\n\t// \"absent\" per the upstream walrus-deploy output convention.\n\t//\n\t// Value pattern: `0x<hex>` strictly. The previous `\\S+` regex\n\t// happily matched values like `None,` (with a trailing comma) or\n\t// `[unset]` and passed them through as fake object ids; downstream\n\t// `ChainProbe.get(...)` would then issue a `getObject` against\n\t// garbage, surfacing a confusing \"object not found\" instead of a\n\t// parse failure. Enforcing the hex shape here makes the parse-\n\t// failure surface (`walrusPluginError('deploy', 'parser could not\n\t// find ...')`) catch the bad value at its real source.\n\tconst pick = (k: string): string | undefined => {\n\t\tconst re = new RegExp(`(?:^|\\\\n)\\\\s*${k}\\\\s*[:=]\\\\s*(\\\\S+)`);\n\t\tconst m = re.exec(stdout);\n\t\tconst v = m?.[1];\n\t\tif (v === undefined || v === 'None') return undefined;\n\t\tif (!/^0x[0-9a-fA-F]+$/u.test(v)) return undefined;\n\t\treturn v;\n\t};\n\tconst walrusPackageId = pick('walrus_package_id') ?? pick('package_id');\n\tconst systemObject = pick('system_object') ?? pick('system_object_id');\n\tconst stakingObject =\n\t\tpick('staking_object') ?? pick('staking_pool_id') ?? pick('staking_object_id');\n\tif (!walrusPackageId || !systemObject || !stakingObject) return null;\n\treturn {\n\t\twalrusPackageId,\n\t\tsystemObject,\n\t\tstakingObject,\n\t\tupgradeManagerObject: pick('upgrade_manager_object'),\n\t\ttreasuryObject: pick('treasury_object'),\n\t\texchangeObject: pick('exchange_object'),\n\t};\n};\n\n/** Run the walrus-deploy one-shot and parse the output.\n *\n * Implementation:\n * 1. `runtime.runOneShot({ image, argv: ['deploy', ...] })` —\n * fresh `docker run --rm` container. Mount the host output dir\n * at `/opt/walrus/outputs` so the per-node config files persist.\n * 2. Parse stdout for the deploy summary.\n * 3. Surface non-zero exits + parse failures as\n * `WalrusPluginError('deploy')` with stdout/stderr capture. */\nexport const runDeployOneShot = (\n\truntime: ContainerRuntime,\n\tinputs: DeployInputs,\n): Effect.Effect<CachedDeployState, WalrusPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tif (inputs.waitForFundsReady !== undefined) {\n\t\t\tyield* inputs.waitForFundsReady.pipe(\n\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t`walrus deploy funding gate failed before walrus-deploy: ${formatUnknownError(cause)}`,\n\t\t\t\t\t\t{ cause },\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tyield* ensureDeployOutputDir(inputs);\n\n\t\tconst outputOwner = hostBindMountOwner();\n\t\tconst outputMount = walrusDeployMountPaths({\n\t\t\tstackRoot: inputs.stackRoot,\n\t\t\tdeployOutputDirHostPath: inputs.outputDirHostPath,\n\t\t\tmountTarget: '/opt/walrus/runtime',\n\t\t});\n\t\tconst argv: ReadonlyArray<string> = [\n\t\t\t'deploy',\n\t\t\t'--output-dir',\n\t\t\toutputMount.outputDirInContainer,\n\t\t\t'--committee-size',\n\t\t\tString(inputs.committeeSize),\n\t\t\t'--shards',\n\t\t\tString(inputs.shards),\n\t\t\t'--epoch-duration',\n\t\t\tinputs.epochDuration,\n\t\t\t'--sui-rpc-url',\n\t\t\tinputs.suiRpcUrlInNetwork,\n\t\t\t'--faucet-url',\n\t\t\tinputs.walrusFaucetUrlInNetwork,\n\t\t\t'--public-hosts',\n\t\t\tinputs.publicHostsCsv,\n\t\t\t'--listening-ips',\n\t\t\tinputs.listeningIpsCsv,\n\t\t];\n\n\t\tconst runAttempt = () =>\n\t\t\truntime\n\t\t\t\t.runOneShot({\n\t\t\t\t\timage: inputs.walrusImage,\n\t\t\t\t\targv,\n\t\t\t\t\tmounts: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsource: outputMount.sourceHostPath,\n\t\t\t\t\t\t\ttarget: outputMount.mountTarget,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\t...(outputOwner === undefined ? {} : { env: { DEVSTACK_HOST_UID_GID: outputOwner } }),\n\t\t\t\t\tnetwork: inputs.suiNetworkName,\n\t\t\t\t\t// Same `host-gateway` rationale as storage-nodes.ts —\n\t\t\t\t\t// deploy one-shot dials sui's host-bound RPC + faucet via\n\t\t\t\t\t// `host.docker.internal`. See substrate/runtime/host-gateway.ts\n\t\t\t\t\t// for the platform rationale.\n\t\t\t\t\textraHosts: HOST_GATEWAY_EXTRA_HOSTS,\n\t\t\t\t\ttimeoutMillis: DEPLOY_TIMEOUT_MS,\n\t\t\t\t})\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t\t\t`walrus deploy one-shot failed: ${cause.reason}: ${cause.detail}`,\n\t\t\t\t\t\t\t\t{ cause },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\tconst attempt = Effect.gen(function* () {\n\t\t\tconst result = yield* runAttempt();\n\t\t\tif (isBindSourceMissing(result)) {\n\t\t\t\tyield* ensureDeployOutputDir(inputs);\n\t\t\t}\n\t\t\treturn result;\n\t\t});\n\t\tconst result = yield* attempt.pipe(\n\t\t\tEffect.repeat({\n\t\t\t\tschedule: makeSpacedRetrySchedule(\n\t\t\t\t\tDEPLOY_BIND_SOURCE_RETRY_PROFILE.delayMs,\n\t\t\t\t\tDEPLOY_BIND_SOURCE_RETRY_PROFILE.attempts,\n\t\t\t\t),\n\t\t\t\tuntil: (r) => !isBindSourceMissing(r),\n\t\t\t}),\n\t\t);\n\n\t\t// `Effect.repeat` with a bounded `schedule` + `until` exits SUCCESS\n\t\t// in two cases: (a) `until(r)` returned true (bind source is now\n\t\t// visible — the happy path) OR (b) the schedule recurrence cap was\n\t\t// reached while `until(r)` still returned false (the bind source\n\t\t// remained missing through every retry). The follow-up\n\t\t// `result.exitCode !== 0` check below would already surface case\n\t\t// (b) as a deploy-failure exit, but the failure message would be\n\t\t// the bind-source stderr — not the more diagnostic \"we exhausted\n\t\t// the bind-source retry budget\" message. Surface that explicitly\n\t\t// here so operators see the retry budget in the failure shape.\n\t\tif (isBindSourceMissing(result)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalrusPluginError(\n\t\t\t\t\t'deploy',\n\t\t\t\t\t`walrus deploy: bind-source visibility race did not resolve after ` +\n\t\t\t\t\t\t`${DEPLOY_BIND_SOURCE_RETRY_PROFILE.attempts} retries ` +\n\t\t\t\t\t\t`(${DEPLOY_BIND_SOURCE_RETRY_PROFILE.delayMs}ms spacing). ` +\n\t\t\t\t\t\t`outputDir=${inputs.outputDirHostPath} — Docker Desktop's bind-source ` +\n\t\t\t\t\t\t`propagation lagged past the retry budget. ` +\n\t\t\t\t\t\tdeployExitDetail(result, inputs),\n\t\t\t\t\t{\n\t\t\t\t\t\texitCode: result.exitCode,\n\t\t\t\t\t\tstdout: result.stdout,\n\t\t\t\t\t\tstderr: result.stderr,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (result.exitCode !== 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalrusPluginError('deploy', deployExitDetail(result, inputs), {\n\t\t\t\t\texitCode: result.exitCode,\n\t\t\t\t\tstdout: result.stdout,\n\t\t\t\t\tstderr: result.stderr,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tconst parsed = parseDeployOutput(result.stdout);\n\t\tif (!parsed) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalrusPluginError(\n\t\t\t\t\t'deploy',\n\t\t\t\t\t`walrus deploy: parser could not find walrus_package_id / system_object / staking_object ` +\n\t\t\t\t\t\t`in deploy output. Confirm the walrus binary's output format and adjust ` +\n\t\t\t\t\t\t`\\`parseDeployOutput\\` regexes in deploy.ts. ` +\n\t\t\t\t\t\t`SEAM: see deploy.ts header for the expected format.`,\n\t\t\t\t\t{ stdout: result.stdout, stderr: result.stderr },\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\treturn parsed;\n\t}).pipe(\n\t\tEffect.timeoutOrElse({\n\t\t\tduration: Duration.millis(DEPLOY_TIMEOUT_MS + 5_000),\n\t\t\torElse: () =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t`walrus deploy: outer timeout ${DEPLOY_TIMEOUT_MS}ms exceeded`,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t}),\n\t);\n\n/** Outputs of one deploy round — surfaced to the plugin's\n * resolved value. */\nexport interface DeployOutputs {\n\treadonly state: CachedDeployState;\n}\n\n/** Compose the ArtifactSpec for a walrus deploy and dispatch\n * through the substrate primitive. The publisher handles the full\n * cache/verify/produce/register loop.\n *\n * Produce: real wiring — `runDeployOneShot` runs\n * `docker run --rm walrusImage deploy …` and parses the deploy stdout.\n *\n * The substrate's `ArtifactPublisher.publish` returns the full\n * `CachedDeployState` on EVERY path (decoded cached payload on hit,\n * fresh produce on miss) — no projection dance required. */\nexport const deployWalrusContracts = (\n\tpublisher: ArtifactPublisher,\n\tprobe: ChainProbe<SuiProbeKey>,\n\truntime: ContainerRuntime,\n\tinputs: DeployInputs,\n): Effect.Effect<DeployOutputs, WalrusPluginError | ArtifactPublishError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst state = yield* publisher.publish<CachedDeployState, WalrusDeployVerified>({\n\t\t\tnamespace: 'walrus-deploy',\n\t\t\tchain: inputs.chainId,\n\t\t\tcontentHash: inputs.contentHash,\n\t\t\t// Verify: lenient probes of the cached system + staking\n\t\t\t// objects. The Sui chain probe decodes raw `getObject`\n\t\t\t// responses, so the probe schema matches that envelope and\n\t\t\t// this closure returns the compact verified shape.\n\t\t\tverify: (cached) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tif (!(yield* deployOutputFilesComplete(inputs))) return null;\n\n\t\t\t\t\t// Both cached on-chain objects are independent lenient\n\t\t\t\t\t// probes — fan them out via `probeManyLenient` so the\n\t\t\t\t\t// substrate owns the iteration shape. Any null means\n\t\t\t\t\t// \"re-deploy\"; both non-null gives us the composite\n\t\t\t\t\t// verified payload. The short retry (see the constants\n\t\t\t\t\t// above) only covers RPC index-visibility lag — the\n\t\t\t\t\t// validator's post-restore catch-up is already gated by\n\t\t\t\t\t// the sui plugin's caught-up-to-head ready-gate — so a\n\t\t\t\t\t// transient not-found doesn't force a spurious redeploy.\n\t\t\t\t\tconst probeBoth = probeManyLenient([\n\t\t\t\t\t\tprobe.get(\n\t\t\t\t\t\t\t{ kind: 'object', objectId: cached.systemObject },\n\t\t\t\t\t\t\tSuiObjectExistsShape,\n\t\t\t\t\t\t\t'lenient',\n\t\t\t\t\t\t),\n\t\t\t\t\t\tprobe.get(\n\t\t\t\t\t\t\t{ kind: 'object', objectId: cached.stakingObject },\n\t\t\t\t\t\t\tSuiObjectExistsShape,\n\t\t\t\t\t\t\t'lenient',\n\t\t\t\t\t\t),\n\t\t\t\t\t]);\n\t\t\t\t\tlet [system, staking] = yield* probeBoth;\n\t\t\t\t\tfor (\n\t\t\t\t\t\tlet attempt = 0;\n\t\t\t\t\t\t(system == null || staking == null) && attempt < WALRUS_DEPLOY_VERIFY_READINESS_RETRIES;\n\t\t\t\t\t\tattempt++\n\t\t\t\t\t) {\n\t\t\t\t\t\tyield* Effect.sleep(WALRUS_DEPLOY_VERIFY_READINESS_DELAY);\n\t\t\t\t\t\t[system, staking] = yield* probeBoth;\n\t\t\t\t\t}\n\t\t\t\t\tif (system === undefined || system === null) return null;\n\t\t\t\t\tif (staking === undefined || staking === null) return null;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsystemObjectId: system.objectId,\n\t\t\t\t\t\tstakingObjectId: staking.objectId,\n\t\t\t\t\t} satisfies WalrusDeployVerified;\n\t\t\t\t}).pipe(\n\t\t\t\t\t// `verify` must satisfy `Effect<… | null, never>` per\n\t\t\t\t\t// the publisher contract. Narrow the cache-miss\n\t\t\t\t\t// collapse to genuine cache-corruption signals\n\t\t\t\t\t// (`decode-failed`) and missing-registration\n\t\t\t\t\t// (`no-probe-registered`). The lenient probe already\n\t\t\t\t\t// maps `not-found`/`transient` to `null`, so the\n\t\t\t\t\t// remaining ChainProbeError values that reach here\n\t\t\t\t\t// are authoritative — `decode-failed` is stale shape\n\t\t\t\t\t// (re-produce) and `no-probe-registered` means the\n\t\t\t\t\t// plugin booted before the chain probe was\n\t\t\t\t\t// contributed (treat as miss). Anything else is a\n\t\t\t\t\t// real RPC failure we want visible in the logs\n\t\t\t\t\t// rather than silently collapsed to \"re-deploy\".\n\t\t\t\t\tEffect.catchTag('ChainProbeError', (err) => {\n\t\t\t\t\t\tif (err.reason === 'decode-failed' || err.reason === 'no-probe-registered') {\n\t\t\t\t\t\t\treturn Effect.succeed(null as WalrusDeployVerified | null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Effect.logWarning(\n\t\t\t\t\t\t\t`walrus.deploy verify probe surfaced an authoritative error (` +\n\t\t\t\t\t\t\t\t`reason=${err.reason}, chain=${err.chainId}, detail=${err.detail}); ` +\n\t\t\t\t\t\t\t\t`re-deploying.`,\n\t\t\t\t\t\t).pipe(Effect.as(null as WalrusDeployVerified | null));\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t// Produce: real walrus-deploy one-shot.\n\t\t\tproduce: runDeployOneShot(runtime, inputs).pipe(\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): ArtifactPublishError =>\n\t\t\t\t\t\tartifactPublishError('produce-failed', `walrus.deploy ${err.phase}: ${err.message}`),\n\t\t\t\t),\n\t\t\t),\n\t\t\t// Register: fires on EVERY cycle. The plugin's outer body\n\t\t\t// performs the walrus-state / endpoint / package registry\n\t\t\t// publishes after both deploy + storage-nodes are up; this\n\t\t\t// closure is the publisher-side null-op so the substrate\n\t\t\t// satisfies its Invariant-6 contract.\n\t\t\tregister: () => Effect.void,\n\t\t});\n\n\t\treturn { state };\n\t});\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { ForkIncompatibleError } from "../../substrate/runtime/mode-errors.mjs";
9
9
  * creation now, so its errors surface as `ContainerRuntimeError`
10
10
  * upstream — we narrate the *phase* as `'cluster-network'` but the
11
11
  * fault tags through the substrate). */
12
- type WalrusPhase = 'image-build' | 'cluster-network' | 'deploy' | 'exchange' | 'storage-node' | 'proxy' | 'fund-wal' | 'register-known';
12
+ type WalrusPhase = 'image-build' | 'cluster-network' | 'deploy' | 'aggregator' | 'publisher' | 'exchange' | 'storage-node' | 'proxy' | 'fund-wal' | 'register-known';
13
13
  /** Generic Walrus plugin error. Raised by the plugin's acquire
14
14
  * body, WAL funding strategy, and the per-mode builders. */
15
15
  interface WalrusPluginError {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/plugins/walrus/errors.ts"],"sourcesContent":["// Walrus plugin — typed errors.\n//\n// Errors raised and consumed inside the Walrus plugin live here.\n// Cross-service errors that Walrus *consumes* but the substrate\n// raises (e.g. `ArtifactPublishError`) come from the substrate's\n// primitive — we don't redeclare those.\n//\n// `ForkIncompatibleError` is a cross-cutting mode-refusal shape\n// owned by `substrate/runtime/mode-errors.ts`; walrus contributes\n// the `walrusLocalCluster` variant via the factory below.\n//\n// Effect v4: plain interfaces with `_tag` discriminator (per\n// surrounding subsystem style). `Effect.catchTag` matches on `_tag`.\n\nimport { ForkIncompatibleError } from '../../substrate/runtime/mode-errors.ts';\nimport { defineConfigError, type ConfigIssue } from '../../substrate/runtime/config-validation.ts';\n\nexport { ForkIncompatibleError };\n\n/** Phases for `WalrusError`. Closed sum — keeps the cause-walker's\n * display table small. Matches the closed `WalrusPhases` from the\n * distilled doc §\"Cross-component references\" except `'network'`\n * is dropped (the substrate's `ContainerRuntime` owns docker network\n * creation now, so its errors surface as `ContainerRuntimeError`\n * upstream — we narrate the *phase* as `'cluster-network'` but the\n * fault tags through the substrate). */\nexport type WalrusPhase =\n\t| 'image-build'\n\t| 'cluster-network'\n\t| 'deploy'\n\t| 'exchange'\n\t| 'storage-node'\n\t| 'proxy'\n\t| 'fund-wal'\n\t| 'register-known';\n\n/** Generic Walrus plugin error. Raised by the plugin's acquire\n * body, WAL funding strategy, and the per-mode builders. */\nexport interface WalrusPluginError {\n\treadonly _tag: 'WalrusPluginError';\n\treadonly phase: WalrusPhase;\n\treadonly message: string;\n\treadonly cause?: unknown;\n\t/** Optional sub-process capture envelope — populated for deploy\n\t * one-shot + per-node container failures. */\n\treadonly stderr?: string;\n\treadonly stdout?: string;\n\treadonly exitCode?: number;\n}\n\nexport const walrusPluginError = (\n\tphase: WalrusPhase,\n\tmessage: string,\n\tparts: Omit<WalrusPluginError, '_tag' | 'phase' | 'message'> = {},\n): WalrusPluginError => ({ _tag: 'WalrusPluginError', phase, message, ...parts });\n\n/** Configuration error — synchronous factory-time guards\n * (`nodeCount >= 1`, `shards >= nodeCount`, missing required\n * fields on `.known(...)`). Surfaces as a thrown `Error` shaped\n * like this in the factory, mirroring the distilled-doc behavior\n * of synchronous configuration faults. */\nexport interface WalrusConfigError extends ConfigIssue {\n\treadonly _tag: 'WalrusConfigError';\n}\n\nconst makeWalrusConfigError = defineConfigError('WalrusConfigError');\n\nexport const walrusConfigError = (\n\tfield: string,\n\tmessage: string,\n\thint?: string,\n\tcause?: unknown,\n): WalrusConfigError => makeWalrusConfigError({ field, message, hint, cause });\n\n/** Union of every error a Walrus-plugin caller may encounter. */\nexport type WalrusError = WalrusPluginError | ForkIncompatibleError | WalrusConfigError;\n\n/** The catchable error tags this plugin exposes. Pinned against the\n * user-facing error catalog by the error-catalog-parity test. */\nexport const WALRUS_ERROR_TAGS: ReadonlyArray<WalrusError['_tag']> = [\n\t'WalrusPluginError',\n\t'ForkIncompatibleError',\n\t'WalrusConfigError',\n] as const;\n"],"mappings":";;;AAkDA,MAAa,qBACZ,OACA,SACA,QAA+D,CAAC,OACxC;CAAE,MAAM;CAAqB;CAAO;CAAS,GAAG;AAAM;AAW/E,MAAM,wBAAwB,kBAAkB,mBAAmB;AAEnE,MAAa,qBACZ,OACA,SACA,MACA,UACuB,sBAAsB;CAAE;CAAO;CAAS;CAAM;AAAM,CAAC"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/plugins/walrus/errors.ts"],"sourcesContent":["// Walrus plugin — typed errors.\n//\n// Errors raised and consumed inside the Walrus plugin live here.\n// Cross-service errors that Walrus *consumes* but the substrate\n// raises (e.g. `ArtifactPublishError`) come from the substrate's\n// primitive — we don't redeclare those.\n//\n// `ForkIncompatibleError` is a cross-cutting mode-refusal shape\n// owned by `substrate/runtime/mode-errors.ts`; walrus contributes\n// the `walrusLocalCluster` variant via the factory below.\n//\n// Effect v4: plain interfaces with `_tag` discriminator (per\n// surrounding subsystem style). `Effect.catchTag` matches on `_tag`.\n\nimport { ForkIncompatibleError } from '../../substrate/runtime/mode-errors.ts';\nimport { defineConfigError, type ConfigIssue } from '../../substrate/runtime/config-validation.ts';\n\nexport { ForkIncompatibleError };\n\n/** Phases for `WalrusError`. Closed sum — keeps the cause-walker's\n * display table small. Matches the closed `WalrusPhases` from the\n * distilled doc §\"Cross-component references\" except `'network'`\n * is dropped (the substrate's `ContainerRuntime` owns docker network\n * creation now, so its errors surface as `ContainerRuntimeError`\n * upstream — we narrate the *phase* as `'cluster-network'` but the\n * fault tags through the substrate). */\nexport type WalrusPhase =\n\t| 'image-build'\n\t| 'cluster-network'\n\t| 'deploy'\n\t| 'aggregator'\n\t| 'publisher'\n\t| 'exchange'\n\t| 'storage-node'\n\t| 'proxy'\n\t| 'fund-wal'\n\t| 'register-known';\n\n/** Generic Walrus plugin error. Raised by the plugin's acquire\n * body, WAL funding strategy, and the per-mode builders. */\nexport interface WalrusPluginError {\n\treadonly _tag: 'WalrusPluginError';\n\treadonly phase: WalrusPhase;\n\treadonly message: string;\n\treadonly cause?: unknown;\n\t/** Optional sub-process capture envelope — populated for deploy\n\t * one-shot + per-node container failures. */\n\treadonly stderr?: string;\n\treadonly stdout?: string;\n\treadonly exitCode?: number;\n}\n\nexport const walrusPluginError = (\n\tphase: WalrusPhase,\n\tmessage: string,\n\tparts: Omit<WalrusPluginError, '_tag' | 'phase' | 'message'> = {},\n): WalrusPluginError => ({ _tag: 'WalrusPluginError', phase, message, ...parts });\n\n/** Configuration error — synchronous factory-time guards\n * (`nodeCount >= 1`, `shards >= nodeCount`, missing required\n * fields on `.known(...)`). Surfaces as a thrown `Error` shaped\n * like this in the factory, mirroring the distilled-doc behavior\n * of synchronous configuration faults. */\nexport interface WalrusConfigError extends ConfigIssue {\n\treadonly _tag: 'WalrusConfigError';\n}\n\nconst makeWalrusConfigError = defineConfigError('WalrusConfigError');\n\nexport const walrusConfigError = (\n\tfield: string,\n\tmessage: string,\n\thint?: string,\n\tcause?: unknown,\n): WalrusConfigError => makeWalrusConfigError({ field, message, hint, cause });\n\n/** Union of every error a Walrus-plugin caller may encounter. */\nexport type WalrusError = WalrusPluginError | ForkIncompatibleError | WalrusConfigError;\n\n/** The catchable error tags this plugin exposes. Pinned against the\n * user-facing error catalog by the error-catalog-parity test. */\nexport const WALRUS_ERROR_TAGS: ReadonlyArray<WalrusError['_tag']> = [\n\t'WalrusPluginError',\n\t'ForkIncompatibleError',\n\t'WalrusConfigError',\n] as const;\n"],"mappings":";;;AAoDA,MAAa,qBACZ,OACA,SACA,QAA+D,CAAC,OACxC;CAAE,MAAM;CAAqB;CAAO;CAAS,GAAG;AAAM;AAW/E,MAAM,wBAAwB,kBAAkB,mBAAmB;AAEnE,MAAa,qBACZ,OACA,SACA,MACA,UACuB,sBAAsB;CAAE;CAAO;CAAS;CAAM;AAAM,CAAC"}
@@ -6,7 +6,7 @@ import { AccountFundingCoinValue } from "../account/funding.mjs";
6
6
  import { WalrusConfigError, WalrusError, WalrusPhase, WalrusPluginError } from "./errors.mjs";
7
7
  import { WalFaucetStrategy } from "./faucet-strategy.mjs";
8
8
  import { WalrusStorageNode } from "./storage-nodes.mjs";
9
- import { WalrusLocalClusterOptions } from "./mode/local-cluster.mjs";
9
+ import { WalrusLocalClusterOptions, WalrusLocalPublisherOptions, WalrusLocalServiceOptions } from "./mode/local-cluster.mjs";
10
10
  import { WalrusKnownDeploymentOptions, WalrusKnownNetwork } from "./mode/known-deploy.mjs";
11
11
  import { WalrusBindings, WalrusNodeBinding } from "./codegen.mjs";
12
12
  //#region src/plugins/walrus/index.d.ts
@@ -8,9 +8,10 @@ import { PluginContext, emitContributions } from "../../substrate/plugin-ctx.mjs
8
8
  import { chainProbeFor } from "../../substrate/runtime/strategy-registry/chain-probe-for.mjs";
9
9
  import "../../substrate/runtime/strategy-registry/index.mjs";
10
10
  import { LOCAL_NETWORK_NAME } from "../../api/inference-network.mjs";
11
+ import { renderUrl, routedHostname } from "../../substrate/runtime/routed-url.mjs";
11
12
  import { walrusPluginError } from "./errors.mjs";
12
- import { buildWalrusNetworkName } from "./storage-nodes.mjs";
13
- import { makeLocalRoutables } from "./routable.mjs";
13
+ import { WALRUS_ROUTER_PORT, buildWalrusNetworkName } from "./storage-nodes.mjs";
14
+ import { WALRUS_AGGREGATOR_ENDPOINT_NAME, WALRUS_PUBLISHER_ENDPOINT_NAME, makeLocalRoutables } from "./routable.mjs";
14
15
  import { defineModeNamespace } from "../../api/mode-narrowed-factory.mjs";
15
16
  import { suiResource } from "../sui/index.mjs";
16
17
  import { coinResourceId } from "../coin/index.mjs";
@@ -94,26 +95,38 @@ const buildLocalPlugin = (opts) => {
94
95
  stackRoot: stackPaths.stackRoot
95
96
  }, mode);
96
97
  if (boot.mode !== "local") return yield* Effect.die("walrus: mode mismatch in local plugin");
98
+ const packageConfig = boot.exchangeObjectId === void 0 ? {
99
+ systemObjectId: boot.deploy.systemObject,
100
+ stakingPoolId: boot.deploy.stakingObject
101
+ } : {
102
+ systemObjectId: boot.deploy.systemObject,
103
+ stakingPoolId: boot.deploy.stakingObject,
104
+ exchangeIds: [boot.exchangeObjectId]
105
+ };
106
+ const serviceUrl = (role) => routedHostname(identity, role).pipe(Effect.map((hostname) => renderUrl({
107
+ protocol: "http",
108
+ hostname,
109
+ port: WALRUS_ROUTER_PORT
110
+ })), Effect.mapError((cause) => walrusPluginError("proxy", `walrus route URL assembly failed for ${role}: ${cause.detail}`, { cause })));
111
+ const aggregatorUrl = boot.clientServices.aggregator === null ? null : yield* serviceUrl(WALRUS_AGGREGATOR_ENDPOINT_NAME);
112
+ const publisherUrl = boot.clientServices.publisher === null ? null : yield* serviceUrl(WALRUS_PUBLISHER_ENDPOINT_NAME);
97
113
  const resolvedValue = {
98
114
  mode: "local",
99
115
  network: identity.network,
100
116
  walrusPackageId: boot.walrusPackageId,
101
117
  walPackageId: boot.walPackageId,
102
- packageConfig: {
103
- systemObjectId: boot.deploy.systemObject,
104
- stakingPoolId: boot.deploy.stakingObject,
105
- exchangeIds: boot.exchangeObjectId ? [boot.exchangeObjectId] : void 0
106
- },
118
+ packageConfig,
107
119
  nodes: boot.nodes,
108
- proxyUrl: boot.proxyUrl,
109
- aggregatorUrl: boot.aggregatorUrl,
110
- publisherUrl: boot.publisherUrl,
120
+ proxyUrl: aggregatorUrl,
121
+ aggregatorUrl,
122
+ publisherUrl,
111
123
  walFaucetStrategy: boot.walFaucetStrategy,
112
124
  walCoinType: boot.walCoinType
113
125
  };
114
126
  const walFaucetContribution = resolvedValue.walFaucetStrategy === null || resolvedValue.walCoinType === null ? [] : [makeWalFaucetContribution(resolvedValue.walFaucetStrategy, resolvedValue.walCoinType)];
127
+ const clientServiceRoles = [...boot.clientServices.aggregator === null ? [] : ["aggregator"], ...boot.clientServices.publisher === null ? [] : ["publisher"]];
115
128
  emitContributions(ctx, [
116
- makeSnapshotable("local", identity.app, identity.stack, resolved.name, resolvedValue.network, resolved.nodeCount),
129
+ makeSnapshotable("local", identity.app, identity.stack, resolved.name, resolvedValue.network, resolved.nodeCount, clientServiceRoles),
117
130
  makeCodegenable({
118
131
  mode: "local",
119
132
  network: resolvedValue.network,
@@ -141,7 +154,9 @@ const buildLocalPlugin = (opts) => {
141
154
  walrusName: resolved.name,
142
155
  serviceKey: String(walrusKey),
143
156
  nodeCount: resolved.nodeCount,
144
- containerApiPort: resolved.containerApiPort
157
+ containerApiPort: resolved.containerApiPort,
158
+ aggregator: boot.clientServices.aggregator,
159
+ publisher: boot.clientServices.publisher
145
160
  })
146
161
  ]);
147
162
  return resolvedValue;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/walrus/index.ts"],"sourcesContent":["// Walrus plugin — barrel + factories.\n//\n// Architecture: Walrus is a service plugin that owns a local cluster\n// or describes a known deployment. The factory at this file folds the\n// four modes behind:\n//\n// - `walrus(opts?)` — local-cluster shorthand. No env\n// defaulting; use `walrusFor(...)`\n// for known deployments.\n// - `walrusFor(network)` — mode-narrowed factory namespace\n// (architecture Tension 11). Returns\n// `{ local: …, known: … }` narrowed\n// to the network's mode. Crucially,\n// the fork branch exposes ONLY\n// `.known` — `.local` is a compile\n// error on a fork-mode network.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs, per mode):\n//\n// Local mode (full local cluster):\n// 1. `ctx.snapshotExtra` — runtime/walrus/<name>/deploy/ subtree\n// + storage-node managed containers.\n// 2. `ctx.codegen` — `walrus-network` bindings.\n// 3. `ctx.endpoint` × (N+2) — per-node + aggregator + publisher.\n// 4. `ctx.provides` walrus-state-registry — local entry.\n// 5. `ctx.provides` endpoint-registry — N+2 entries.\n// 6. `ctx.provides` package-registry — `walrus.<name>`.\n// 7. `ctx.provides` coinType:<WAL fullCoinType> — WAL faucet\n// strategy\n// (when exchange exists).\n//\n// Known mode (read-only deployment):\n// 1. `ctx.snapshotExtra` — identity-guard only; no subtrees.\n// 2. `ctx.codegen` — `walrus-network` bindings (mode='known').\n// 3. `ctx.provides` walrus-state-registry — known entry.\n//\n// Resource id: `'walrus'` (singular). The plugin's substrate-level\n// plugin key is the same string.\n\nimport { Effect, Path } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, type ResourceRef } from '../../api/define-plugin.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport type { RoutableDecl } from '../../contracts/routable.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { deriveSubnetPrefix, withSubnetAddressing } from '../../substrate/runtime/subnet-broker.ts';\nimport type { AccountFundingCoinValue } from '../account/index.ts';\nimport { coinResourceId, type CoinResourceId } from '../coin/index.ts';\nimport { suiResource, type SuiProbeKey } from '../sui/index.ts';\n\nimport { chainProbeFor } from '../../substrate/runtime/strategy-registry/index.ts';\n\nimport { makeCodegenable, makeWalrusStaticCodegen } from './codegen.ts';\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport { walrusPluginKey } from './plugin-key.ts';\nimport { walrusPluginError, type WalrusPluginError } from './errors.ts';\nimport { makeWalFaucetContribution, type WalFaucetStrategy } from './faucet-strategy.ts';\nimport { bootWalrusService, type WalrusMode } from './service.ts';\nimport {\n\tresolveLocalClusterOptions,\n\ttype WalrusLocalClusterOptions,\n} from './mode/local-cluster.ts';\nimport {\n\tresolveKnownDeploymentOptions,\n\ttype WalrusKnownDeploymentOptions,\n} from './mode/known-deploy.ts';\nimport { makeSnapshotable, type WalrusSnapshotMode } from './snapshot.ts';\nimport { makeLocalRoutables } from './routable.ts';\nimport { WALRUS_STATE_REGISTRY_KEY, type WalrusStateEntry } from './registry-publish.ts';\nimport { buildWalrusNetworkName, type WalrusStorageNode } from './storage-nodes.ts';\n\n// ---------------------------------------------------------------------------\n// Resource — the resolved value all consumers read\n// ---------------------------------------------------------------------------\n\n/** The Walrus resolved value carried by the resource. */\nexport interface WalrusResolved {\n\treadonly mode: 'local' | 'known';\n\t/** Network name the walrus deployment targets (`localnet`/`testnet`/…). */\n\treadonly network: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\t/** SDK-ready `packageConfig` — structurally compatible with\n\t * `@mysten/walrus`'s `WalrusPackageConfig`. */\n\treadonly packageConfig: {\n\t\treadonly systemObjectId: string;\n\t\treadonly stakingPoolId: string;\n\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t};\n\treadonly nodes: ReadonlyArray<WalrusStorageNode>;\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly walFaucetStrategy: WalFaucetStrategy | null;\n\treadonly walCoinType: string | null;\n}\n\n/** Walrus plugin resource. */\nexport const walrusResource = resource<'walrus', WalrusResolved>('walrus');\n\nexport interface WalrusNetworkIdentity {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly walrusName: string;\n}\n\n/** Walrus deploy records storage-node listening IPs under this /24.\n * Docker network create requests the matching subnet explicitly, with\n * the prefix derived from the Walrus network identity so parallel\n * stacks don't all claim the same Docker IPAM range. Walrus claims\n * the `10.64.*` – `10.127.*` band; see substrate/runtime/subnet-broker.ts\n * for the algorithm and band coordination with seal. */\nexport const deriveWalrusSubnetPrefix = (identity: WalrusNetworkIdentity): string =>\n\tderiveSubnetPrefix(`${identity.app}\\0${identity.stack}\\0${identity.walrusName}`, 64);\n\nconst withWalrusNetworkAddressing = (\n\truntime: ContainerRuntime,\n\twalrusNetworkName: string,\n\tsubnetPrefix: string,\n): ContainerRuntime => ({\n\t...runtime,\n\tensureNetwork: (spec) =>\n\t\truntime.ensureNetwork(\n\t\t\tspec.name === walrusNetworkName ? withSubnetAddressing(spec, subnetPrefix) : spec,\n\t\t),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by walrus() + walrusFor())\n// ---------------------------------------------------------------------------\n\nconst buildLocalPlugin = (opts: WalrusLocalClusterOptions) => {\n\t// Synchronous factory-time validation (distilled-doc invariants\n\t// 11 — `nodeCount >= 1` + `shards >= nodeCount`).\n\tconst resolved = resolveLocalClusterOptions(opts);\n\n\tconst walrusKey = walrusPluginKey(resolved.name);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tpluginKey: walrusKey,\n\t\t// Stack-free codegen: a local walrus cluster's deploy ids / endpoint\n\t\t// URLs are LOADED CONFIG DATA -- the committed `walrus.ts` stub emits\n\t\t// `requireValue(dep, 'walrus', '<key>')`, never a baked object id / URL.\n\t\tstaticCodegen: () => [makeWalrusStaticCodegen({ mode: 'local', network: LOCAL_NETWORK_NAME })],\n\t\t// `deps` auto-infers the resolved `[sui]` tuple from the\n\t\t// `[suiResource] as const` dependency. `ctx` arrives via the\n\t\t// `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst [sui] = deps;\n\n\t\t\t\t// Substrate-context primitives:\n\t\t\t\t// - `ContainerRuntimeService` + `IdentityContext` arrive\n\t\t\t\t// via the supervisor's plugin runtime context.\n\t\t\t\t// - `ArtifactPublisher` is the substrate-level\n\t\t\t\t// publisher (cache → verify → produce → register cycle).\n\t\t\t\t// - `ChainProbe<SuiProbeKey>` is looked up via the\n\t\t\t\t// StrategyRegistry under `chain-probe:<chainId>`;\n\t\t\t\t// Sui registered itself there at its own acquire.\n\t\t\t\t// - `StackPathsService` resolves the per-stack on-disk\n\t\t\t\t// root so the deploy-output bind-mount source is a\n\t\t\t\t// real path (not the `<runtime>/...` template).\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\t\tconst path = yield* Path.Path;\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\n\t\t\t\t// Resolve the deploy-output bind-mount source from the\n\t\t\t\t// per-stack paths bundle. The deploy one-shot owns preparing\n\t\t\t\t// the directory immediately before its Docker bind mount.\n\t\t\t\tconst deployHostMountPath = path.join(\n\t\t\t\t\tstackPaths.stackRoot,\n\t\t\t\t\t'walrus',\n\t\t\t\t\tresolved.name,\n\t\t\t\t\t'deploy',\n\t\t\t\t);\n\n\t\t\t\t// Cross-container DNS: walrus containers (deploy one-shot\n\t\t\t\t// + N storage nodes) dial sui RPC + faucet via\n\t\t\t\t// `host.docker.internal`. The sui plugin binds brokered\n\t\t\t\t// host ports — no shared docker network needed.\n\t\t\t\t//\n\t\t\t\t// Architectural decision (B5): walrus owns its OWN docker\n\t\t\t\t// network for storage-node ↔ deploy connectivity; sui-side\n\t\t\t\t// hops go through the host gateway.\n\t\t\t\t// On Linux this requires Docker Desktop or the\n\t\t\t\t// `host.docker.internal:host-gateway` runtime hint (the\n\t\t\t\t// established devstack convention — see plugins/deepbook\n\t\t\t\t// which uses the same pattern).\n\t\t\t\tconst walrusNetworkName = buildWalrusNetworkName(\n\t\t\t\t\tidentity.app,\n\t\t\t\t\tidentity.stack,\n\t\t\t\t\tresolved.name,\n\t\t\t\t);\n\t\t\t\tconst walrusSubnetPrefix = deriveWalrusSubnetPrefix({\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t});\n\t\t\t\tconst walrusRuntime = withWalrusNetworkAddressing(\n\t\t\t\t\truntime,\n\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\twalrusSubnetPrefix,\n\t\t\t\t);\n\t\t\t\tconst suiRpcUrlInNetwork = sui.hostGateway.rpcUrl;\n\t\t\t\t// sui-faucet v2 endpoint — `/v2/gas` is the supported path\n\t\t\t\t// on devnet-v1.71.0+.\n\t\t\t\tconst suiFaucetUrlInNetwork = sui.hostGateway.faucetUrl;\n\t\t\t\tif (suiFaucetUrlInNetwork === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t\t'walrus local-cluster requires a Sui faucet URL for deploy funding.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst walrusFaucetUrlInNetwork = `${suiFaucetUrlInNetwork}/v2/gas`;\n\n\t\t\t\tconst mode: WalrusMode = { mode: 'local', opts: resolved };\n\t\t\t\tconst boot = yield* bootWalrusService(\n\t\t\t\t\t{\n\t\t\t\t\t\truntime: walrusRuntime,\n\t\t\t\t\t\tpublisher,\n\t\t\t\t\t\tprobe,\n\t\t\t\t\t\tsuiSdk: sui.sdk,\n\t\t\t\t\t\tsuiChainId: sui.chainId,\n\t\t\t\t\t\tsuiRpcUrlInNetwork,\n\t\t\t\t\t\twalrusFaucetUrlInNetwork,\n\t\t\t\t\t\twaitForFundsReady: sui.waitForTransactionsReady.wait,\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tsubnetPrefix: walrusSubnetPrefix,\n\t\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\t\t// Walrus has no `sui-net` to attach to (sui binds\n\t\t\t\t\t\t// host ports). Reuse the walrus network so the\n\t\t\t\t\t\t// deploy one-shot + storage nodes land on a single\n\t\t\t\t\t\t// network and reach sui via `host.docker.internal`.\n\t\t\t\t\t\tsuiNetworkName: walrusNetworkName,\n\t\t\t\t\t\tdeployHostMountPath,\n\t\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\t},\n\t\t\t\t\tmode,\n\t\t\t\t);\n\n\t\t\t\tif (boot.mode !== 'local') {\n\t\t\t\t\t// Should be unreachable — dispatch is by mode. Defense.\n\t\t\t\t\treturn yield* Effect.die('walrus: mode mismatch in local plugin');\n\t\t\t\t}\n\n\t\t\t\tconst resolvedValue: WalrusResolved = {\n\t\t\t\t\tmode: 'local',\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\twalrusPackageId: boot.walrusPackageId,\n\t\t\t\t\twalPackageId: boot.walPackageId,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: boot.deploy.systemObject,\n\t\t\t\t\t\tstakingPoolId: boot.deploy.stakingObject,\n\t\t\t\t\t\texchangeIds: boot.exchangeObjectId ? [boot.exchangeObjectId] : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnodes: boot.nodes,\n\t\t\t\t\tproxyUrl: boot.proxyUrl,\n\t\t\t\t\taggregatorUrl: boot.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: boot.publisherUrl,\n\t\t\t\t\twalFaucetStrategy: boot.walFaucetStrategy,\n\t\t\t\t\twalCoinType: boot.walCoinType,\n\t\t\t\t};\n\t\t\t\t// Emit the resolved contributions inline: snapshot → codegen →\n\t\t\t\t// state-registry → (optional WAL faucet) → routables. `identity`\n\t\t\t\t// (from `IdentityContext`) stamps the snapshot + routable\n\t\t\t\t// container names. ID-stability surfaces (deploy/blob ids via\n\t\t\t\t// ArtifactPublisher) live OUTSIDE this emission and are untouched.\n\t\t\t\tconst walFaucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t\t\tresolvedValue.walFaucetStrategy === null || resolvedValue.walCoinType === null\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\tmakeWalFaucetContribution(\n\t\t\t\t\t\t\t\t\tresolvedValue.walFaucetStrategy,\n\t\t\t\t\t\t\t\t\tresolvedValue.walCoinType,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t];\n\t\t\t\temitContributions(ctx, [\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'local' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\tresolved.name,\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t\tresolved.nodeCount,\n\t\t\t\t\t),\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'local',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\tstrategy: { noteName: resolved.name },\n\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\ttypeof WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\t{ readonly noteName: string }\n\t\t\t\t\t>,\n\t\t\t\t\t...walFaucetContribution,\n\t\t\t\t\t...(makeLocalRoutables({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t\t\tserviceKey: String(walrusKey),\n\t\t\t\t\t\tnodeCount: resolved.nodeCount,\n\t\t\t\t\t\tcontainerApiPort: resolved.containerApiPort,\n\t\t\t\t\t}) as readonly RoutableDecl[]),\n\t\t\t\t]);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nconst buildKnownPlugin = (opts: WalrusKnownDeploymentOptions) => {\n\t// Synchronous factory-time validation for required deployment ids.\n\tconst resolved = resolveKnownDeploymentOptions(opts);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\t// Known deployment is a pure value-producer — no containers,\n\t\t// no long-running children.\n\t\trole: 'task',\n\t\tsection: 'service',\n\t\t// Stack-free codegen: a known deployment's ids / URLs are DECLARED\n\t\t// config (not loaded-at-runtime data) — bake them as literals in the\n\t\t// committed `walrus.ts` (mirrors `knownPackage`). `walrusPackageId` /\n\t\t// `walPackageId` / `walCoinType` are null for a known deployment.\n\t\tstaticCodegen: () => [\n\t\t\tmakeWalrusStaticCodegen({\n\t\t\t\tmode: 'known',\n\t\t\t\tnetwork: resolved.network,\n\t\t\t\tknown: {\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\t...(resolved.exchangeIds.length > 0 ? { exchangeIds: [...resolved.exchangeIds] } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t\t// Known mode is a pure value-producer — `sui` is unused (the value\n\t\t// reads `resolved.network` from the deployment options) but the\n\t\t// `dependsOn` edge still orders boot, so `start` is zero-arg.\n\t\t// `ctx` arrives via the `PluginContext` service.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst resolvedValue = {\n\t\t\t\t\tmode: 'known',\n\t\t\t\t\tnetwork: resolved.network,\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolved.exchangeIds.length > 0 ? resolved.exchangeIds : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\twalFaucetStrategy: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t} satisfies WalrusResolved;\n\t\t\t\t// Emit inline: snapshot → codegen → state-registry. Known mode\n\t\t\t\t// emits no routable. `identity` (from `IdentityContext`) stamps\n\t\t\t\t// the snapshot scoping.\n\t\t\t\tctx.snapshotExtra(\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'known' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\t'walrusKnownDeployment',\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tctx.codegen(\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'known',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tctx.provides({\n\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\tstrategy: {\n\t\t\t\t\t\tname: 'walrusKnownDeployment',\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingObjectId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t},\n\t\t\t\t\tautoMounted: true,\n\t\t\t\t} satisfies StrategyContributorDecl<typeof WALRUS_STATE_REGISTRY_KEY, WalrusStateEntry>);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nexport interface WalCoinValue extends AccountFundingCoinValue {\n\treadonly symbol: 'WAL';\n\treadonly fullCoinType: `${string}::wal::WAL`;\n\treadonly decimals: 9;\n\treadonly source: 'walrus';\n}\n\n/** Resolve the local Walrus deployment's WAL coin as an account-funding\n * coin ref. Accounts that need WAL should use:\n *\n * funding: [{ coin: walCoin(localWalrus), amount }]\n *\n * The funding strategy itself is contributed by the Walrus service\n * once its local exchange exists. */\nexport const walCoin = (walrusMember: ResourceRef<'walrus', WalrusResolved>) => {\n\tconst coinRef = resource<CoinResourceId<'wal'>, WalCoinValue>(coinResourceId('wal'));\n\n\treturn definePlugin({\n\t\tid: coinRef.id,\n\t\tdependsOn: walrusMember,\n\t\trole: 'task',\n\t\tsection: 'action',\n\t\tstart: (resolved): Effect.Effect<WalCoinValue, WalrusPluginError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tif (resolved.walCoinType === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'exchange',\n\t\t\t\t\t\t\t'walCoin(...) requires a local Walrus deployment with a WAL package.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tsymbol: 'WAL',\n\t\t\t\t\tfullCoinType: resolved.walCoinType as `${string}::wal::WAL`,\n\t\t\t\t\tdecimals: 9,\n\t\t\t\t\tsource: 'walrus',\n\t\t\t\t} satisfies WalCoinValue;\n\t\t\t}),\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local-cluster shorthand. Known deployments are selected through the\n * per-network methods `walrusFor(network).testnet(...)` /\n * `.mainnet(...)` so network choice stays explicit; `.known(...)` is the\n * raw-id override form. */\nexport const walrus = (opts?: { readonly local?: WalrusLocalClusterOptions }) => {\n\treturn buildLocalPlugin(opts?.local ?? {});\n};\n\n/** Per-network factory options for `walrusFor(net).testnet()`/`.mainnet()`.\n * The `network` is injected by the namespace method, so the caller only\n * supplies the (still-required) `nodes` committee plus optional per-field\n * overrides — the on-chain ids default from the `@mysten/walrus` SDK package\n * config. Plugin-INTERNAL — deliberately NOT re-exported from `src/index.ts`. */\ntype WalrusKnownByNetworkOptions = Omit<WalrusKnownDeploymentOptions, 'network'>;\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local', network: 'localnet' } as const;\n * walrusFor(network).local({...}) // OK\n * walrusFor(liveNet).testnet({ nodes }) // OK — network injected\n * walrusFor(forkNet).local({...}) // compile error: no `.local` on fork\n *\n * Per-network methods (`.testnet`/`.mainnet`) inject the network into\n * `buildKnownPlugin`; `nodes` is still REQUIRED, so `.testnet({ nodes })` is\n * the minimal call. `.known(...)` is now the raw-id explicit-override form only\n * (`systemObjectId`/`stakingPoolId`/`nodes`) — the `network`-in-`.known()` path\n * was HARD CUT.\n *\n * Critically, the fork branch exposes NO `.local` — calling\n * `.local` on a fork-mode network is a **compile error** at the\n * call site (distilled-doc invariant 12 — type-level refusal). */\nexport const walrusFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: (opts: WalrusLocalClusterOptions = {}) => buildLocalPlugin(opts),\n\t\tknown: (opts: WalrusKnownByNetworkOptions) => buildKnownPlugin(opts),\n\t\ttestnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'testnet', ...opts }),\n\t\tmainnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'mainnet', ...opts }),\n\t},\n\tlive: {\n\t\tknown: (opts: WalrusKnownByNetworkOptions) => buildKnownPlugin(opts),\n\t\ttestnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'testnet', ...opts }),\n\t\tmainnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'mainnet', ...opts }),\n\t},\n\tfork: {\n\t\t// `.local` is intentionally absent — calling\n\t\t// `walrusFor(forkNetwork).local(...)` is a compile error.\n\t\tknown: (opts: WalrusKnownByNetworkOptions) => buildKnownPlugin(opts),\n\t\ttestnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'testnet', ...opts }),\n\t\tmainnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'mainnet', ...opts }),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers\n// ---------------------------------------------------------------------------\n\nexport type { WalrusLocalClusterOptions } from './mode/local-cluster.ts';\nexport type { WalrusKnownDeploymentOptions, WalrusKnownNetwork } from './mode/known-deploy.ts';\nexport type { WalrusStorageNode } from './storage-nodes.ts';\nexport type { WalrusBindings, WalrusNodeBinding } from './codegen.ts';\nexport type { WalrusError, WalrusPluginError, WalrusConfigError, WalrusPhase } from './errors.ts';\nexport { walCoinType, walFaucetStrategyKey, type WalFaucetStrategy } from './faucet-strategy.ts';\nexport {\n\tWALRUS_STATE_REGISTRY_KEY,\n\ttype WalrusStateEntry,\n\ttype WalrusLocalStateEntry,\n\ttype WalrusKnownStateEntry,\n} from './registry-publish.ts';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/walrus/index.ts"],"sourcesContent":["// Walrus plugin — barrel + factories.\n//\n// Architecture: Walrus is a service plugin that owns a local cluster\n// or describes a known deployment. The factory at this file folds the\n// four modes behind:\n//\n// - `walrus(opts?)` — local-cluster shorthand. No env\n// defaulting; use `walrusFor(...)`\n// for known deployments.\n// - `walrusFor(network)` — mode-narrowed factory namespace\n// (architecture Tension 11). Returns\n// `{ local: …, known: … }` narrowed\n// to the network's mode. Crucially,\n// the fork branch exposes ONLY\n// `.known` — `.local` is a compile\n// error on a fork-mode network.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs, per mode):\n//\n// Local mode (full local cluster):\n// 1. `ctx.snapshotExtra` — runtime/walrus/<name>/deploy/ subtree\n// + storage-node managed containers.\n// 2. `ctx.codegen` — `walrus-network` bindings.\n// 3. `ctx.endpoint` × (N+2) — per-node + aggregator + publisher.\n// 4. `ctx.provides` walrus-state-registry — local entry.\n// 5. `ctx.provides` endpoint-registry — N+2 entries.\n// 6. `ctx.provides` package-registry — `walrus.<name>`.\n// 7. `ctx.provides` coinType:<WAL fullCoinType> — WAL faucet\n// strategy\n// (when exchange exists).\n//\n// Known mode (read-only deployment):\n// 1. `ctx.snapshotExtra` — identity-guard only; no subtrees.\n// 2. `ctx.codegen` — `walrus-network` bindings (mode='known').\n// 3. `ctx.provides` walrus-state-registry — known entry.\n//\n// Resource id: `'walrus'` (singular). The plugin's substrate-level\n// plugin key is the same string.\n\nimport { Effect, Path } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, type ResourceRef } from '../../api/define-plugin.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport type { RoutableDecl } from '../../contracts/routable.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { deriveSubnetPrefix, withSubnetAddressing } from '../../substrate/runtime/subnet-broker.ts';\nimport { renderUrl, routedHostname } from '../../substrate/runtime/routed-url.ts';\nimport type { AccountFundingCoinValue } from '../account/index.ts';\nimport { coinResourceId, type CoinResourceId } from '../coin/index.ts';\nimport { suiResource, type SuiProbeKey } from '../sui/index.ts';\n\nimport { chainProbeFor } from '../../substrate/runtime/strategy-registry/index.ts';\n\nimport { makeCodegenable, makeWalrusStaticCodegen } from './codegen.ts';\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport { walrusPluginKey } from './plugin-key.ts';\nimport { walrusPluginError, type WalrusPluginError } from './errors.ts';\nimport { makeWalFaucetContribution, type WalFaucetStrategy } from './faucet-strategy.ts';\nimport { bootWalrusService, type WalrusMode } from './service.ts';\nimport {\n\tresolveLocalClusterOptions,\n\ttype WalrusLocalClusterOptions,\n} from './mode/local-cluster.ts';\nimport {\n\tresolveKnownDeploymentOptions,\n\ttype WalrusKnownDeploymentOptions,\n} from './mode/known-deploy.ts';\nimport { makeSnapshotable, type WalrusSnapshotMode } from './snapshot.ts';\nimport {\n\tWALRUS_AGGREGATOR_ENDPOINT_NAME,\n\tWALRUS_PUBLISHER_ENDPOINT_NAME,\n\tmakeLocalRoutables,\n} from './routable.ts';\nimport { WALRUS_STATE_REGISTRY_KEY, type WalrusStateEntry } from './registry-publish.ts';\nimport {\n\tWALRUS_ROUTER_PORT,\n\tbuildWalrusNetworkName,\n\ttype WalrusStorageNode,\n} from './storage-nodes.ts';\n\n// ---------------------------------------------------------------------------\n// Resource — the resolved value all consumers read\n// ---------------------------------------------------------------------------\n\n/** The Walrus resolved value carried by the resource. */\nexport interface WalrusResolved {\n\treadonly mode: 'local' | 'known';\n\t/** Network name the walrus deployment targets (`localnet`/`testnet`/…). */\n\treadonly network: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\t/** SDK-ready `packageConfig` — structurally compatible with\n\t * `@mysten/walrus`'s `WalrusPackageConfig`. */\n\treadonly packageConfig: {\n\t\treadonly systemObjectId: string;\n\t\treadonly stakingPoolId: string;\n\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t};\n\treadonly nodes: ReadonlyArray<WalrusStorageNode>;\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly walFaucetStrategy: WalFaucetStrategy | null;\n\treadonly walCoinType: string | null;\n}\n\n/** Walrus plugin resource. */\nexport const walrusResource = resource<'walrus', WalrusResolved>('walrus');\n\nexport interface WalrusNetworkIdentity {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly walrusName: string;\n}\n\n/** Walrus deploy records storage-node listening IPs under this /24.\n * Docker network create requests the matching subnet explicitly, with\n * the prefix derived from the Walrus network identity so parallel\n * stacks don't all claim the same Docker IPAM range. Walrus claims\n * the `10.64.*` – `10.127.*` band; see substrate/runtime/subnet-broker.ts\n * for the algorithm and band coordination with seal. */\nexport const deriveWalrusSubnetPrefix = (identity: WalrusNetworkIdentity): string =>\n\tderiveSubnetPrefix(`${identity.app}\\0${identity.stack}\\0${identity.walrusName}`, 64);\n\nconst withWalrusNetworkAddressing = (\n\truntime: ContainerRuntime,\n\twalrusNetworkName: string,\n\tsubnetPrefix: string,\n): ContainerRuntime => ({\n\t...runtime,\n\tensureNetwork: (spec) =>\n\t\truntime.ensureNetwork(\n\t\t\tspec.name === walrusNetworkName ? withSubnetAddressing(spec, subnetPrefix) : spec,\n\t\t),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by walrus() + walrusFor())\n// ---------------------------------------------------------------------------\n\nconst buildLocalPlugin = (opts: WalrusLocalClusterOptions) => {\n\t// Synchronous factory-time validation (distilled-doc invariants\n\t// 11 — `nodeCount >= 1` + `shards >= nodeCount`).\n\tconst resolved = resolveLocalClusterOptions(opts);\n\n\tconst walrusKey = walrusPluginKey(resolved.name);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tpluginKey: walrusKey,\n\t\t// Stack-free codegen: a local walrus cluster's deploy ids / endpoint\n\t\t// URLs are LOADED CONFIG DATA -- the committed `walrus.ts` stub emits\n\t\t// `requireValue(dep, 'walrus', '<key>')`, never a baked object id / URL.\n\t\tstaticCodegen: () => [makeWalrusStaticCodegen({ mode: 'local', network: LOCAL_NETWORK_NAME })],\n\t\t// `deps` auto-infers the resolved `[sui]` tuple from the\n\t\t// `[suiResource] as const` dependency. `ctx` arrives via the\n\t\t// `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst [sui] = deps;\n\n\t\t\t\t// Substrate-context primitives:\n\t\t\t\t// - `ContainerRuntimeService` + `IdentityContext` arrive\n\t\t\t\t// via the supervisor's plugin runtime context.\n\t\t\t\t// - `ArtifactPublisher` is the substrate-level\n\t\t\t\t// publisher (cache → verify → produce → register cycle).\n\t\t\t\t// - `ChainProbe<SuiProbeKey>` is looked up via the\n\t\t\t\t// StrategyRegistry under `chain-probe:<chainId>`;\n\t\t\t\t// Sui registered itself there at its own acquire.\n\t\t\t\t// - `StackPathsService` resolves the per-stack on-disk\n\t\t\t\t// root so the deploy-output bind-mount source is a\n\t\t\t\t// real path (not the `<runtime>/...` template).\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\t\tconst path = yield* Path.Path;\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\n\t\t\t\t// Resolve the deploy-output bind-mount source from the\n\t\t\t\t// per-stack paths bundle. The deploy one-shot owns preparing\n\t\t\t\t// the directory immediately before its Docker bind mount.\n\t\t\t\tconst deployHostMountPath = path.join(\n\t\t\t\t\tstackPaths.stackRoot,\n\t\t\t\t\t'walrus',\n\t\t\t\t\tresolved.name,\n\t\t\t\t\t'deploy',\n\t\t\t\t);\n\n\t\t\t\t// Cross-container DNS: walrus containers (deploy one-shot\n\t\t\t\t// + N storage nodes) dial sui RPC + faucet via\n\t\t\t\t// `host.docker.internal`. The sui plugin binds brokered\n\t\t\t\t// host ports — no shared docker network needed.\n\t\t\t\t//\n\t\t\t\t// Architectural decision (B5): walrus owns its OWN docker\n\t\t\t\t// network for storage-node ↔ deploy connectivity; sui-side\n\t\t\t\t// hops go through the host gateway.\n\t\t\t\t// On Linux this requires Docker Desktop or the\n\t\t\t\t// `host.docker.internal:host-gateway` runtime hint (the\n\t\t\t\t// established devstack convention — see plugins/deepbook\n\t\t\t\t// which uses the same pattern).\n\t\t\t\tconst walrusNetworkName = buildWalrusNetworkName(\n\t\t\t\t\tidentity.app,\n\t\t\t\t\tidentity.stack,\n\t\t\t\t\tresolved.name,\n\t\t\t\t);\n\t\t\t\tconst walrusSubnetPrefix = deriveWalrusSubnetPrefix({\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t});\n\t\t\t\tconst walrusRuntime = withWalrusNetworkAddressing(\n\t\t\t\t\truntime,\n\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\twalrusSubnetPrefix,\n\t\t\t\t);\n\t\t\t\tconst suiRpcUrlInNetwork = sui.hostGateway.rpcUrl;\n\t\t\t\t// sui-faucet v2 endpoint — `/v2/gas` is the supported path\n\t\t\t\t// on devnet-v1.71.0+.\n\t\t\t\tconst suiFaucetUrlInNetwork = sui.hostGateway.faucetUrl;\n\t\t\t\tif (suiFaucetUrlInNetwork === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t\t'walrus local-cluster requires a Sui faucet URL for deploy funding.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst walrusFaucetUrlInNetwork = `${suiFaucetUrlInNetwork}/v2/gas`;\n\n\t\t\t\tconst mode: WalrusMode = { mode: 'local', opts: resolved };\n\t\t\t\tconst boot = yield* bootWalrusService(\n\t\t\t\t\t{\n\t\t\t\t\t\truntime: walrusRuntime,\n\t\t\t\t\t\tpublisher,\n\t\t\t\t\t\tprobe,\n\t\t\t\t\t\tsuiSdk: sui.sdk,\n\t\t\t\t\t\tsuiChainId: sui.chainId,\n\t\t\t\t\t\tsuiRpcUrlInNetwork,\n\t\t\t\t\t\twalrusFaucetUrlInNetwork,\n\t\t\t\t\t\twaitForFundsReady: sui.waitForTransactionsReady.wait,\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tsubnetPrefix: walrusSubnetPrefix,\n\t\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\t\t// Walrus has no `sui-net` to attach to (sui binds\n\t\t\t\t\t\t// host ports). Reuse the walrus network so the\n\t\t\t\t\t\t// deploy one-shot + storage nodes land on a single\n\t\t\t\t\t\t// network and reach sui via `host.docker.internal`.\n\t\t\t\t\t\tsuiNetworkName: walrusNetworkName,\n\t\t\t\t\t\tdeployHostMountPath,\n\t\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\t},\n\t\t\t\t\tmode,\n\t\t\t\t);\n\n\t\t\t\tif (boot.mode !== 'local') {\n\t\t\t\t\t// Should be unreachable — dispatch is by mode. Defense.\n\t\t\t\t\treturn yield* Effect.die('walrus: mode mismatch in local plugin');\n\t\t\t\t}\n\n\t\t\t\tconst packageConfig: WalrusResolved['packageConfig'] =\n\t\t\t\t\tboot.exchangeObjectId === undefined\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tsystemObjectId: boot.deploy.systemObject,\n\t\t\t\t\t\t\t\tstakingPoolId: boot.deploy.stakingObject,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\tsystemObjectId: boot.deploy.systemObject,\n\t\t\t\t\t\t\t\tstakingPoolId: boot.deploy.stakingObject,\n\t\t\t\t\t\t\t\texchangeIds: [boot.exchangeObjectId],\n\t\t\t\t\t\t\t};\n\t\t\t\tconst serviceUrl = (role: string) =>\n\t\t\t\t\troutedHostname(identity, role).pipe(\n\t\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\t\tport: WALRUS_ROUTER_PORT,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t\t'proxy',\n\t\t\t\t\t\t\t\t`walrus route URL assembly failed for ${role}: ${cause.detail}`,\n\t\t\t\t\t\t\t\t{ cause },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\tconst aggregatorUrl =\n\t\t\t\t\tboot.clientServices.aggregator === null\n\t\t\t\t\t\t? null\n\t\t\t\t\t\t: yield* serviceUrl(WALRUS_AGGREGATOR_ENDPOINT_NAME);\n\t\t\t\tconst publisherUrl =\n\t\t\t\t\tboot.clientServices.publisher === null\n\t\t\t\t\t\t? null\n\t\t\t\t\t\t: yield* serviceUrl(WALRUS_PUBLISHER_ENDPOINT_NAME);\n\n\t\t\t\tconst resolvedValue: WalrusResolved = {\n\t\t\t\t\tmode: 'local',\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\twalrusPackageId: boot.walrusPackageId,\n\t\t\t\t\twalPackageId: boot.walPackageId,\n\t\t\t\t\tpackageConfig,\n\t\t\t\t\tnodes: boot.nodes,\n\t\t\t\t\tproxyUrl: aggregatorUrl,\n\t\t\t\t\taggregatorUrl,\n\t\t\t\t\tpublisherUrl,\n\t\t\t\t\twalFaucetStrategy: boot.walFaucetStrategy,\n\t\t\t\t\twalCoinType: boot.walCoinType,\n\t\t\t\t};\n\t\t\t\t// Emit the resolved contributions inline: snapshot → codegen →\n\t\t\t\t// state-registry → (optional WAL faucet) → routables. `identity`\n\t\t\t\t// (from `IdentityContext`) stamps the snapshot + routable\n\t\t\t\t// container names. ID-stability surfaces (deploy/blob ids via\n\t\t\t\t// ArtifactPublisher) live OUTSIDE this emission and are untouched.\n\t\t\t\tconst walFaucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t\t\tresolvedValue.walFaucetStrategy === null || resolvedValue.walCoinType === null\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\tmakeWalFaucetContribution(\n\t\t\t\t\t\t\t\t\tresolvedValue.walFaucetStrategy,\n\t\t\t\t\t\t\t\t\tresolvedValue.walCoinType,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t];\n\t\t\t\tconst clientServiceRoles = [\n\t\t\t\t\t...(boot.clientServices.aggregator === null ? [] : ['aggregator' as const]),\n\t\t\t\t\t...(boot.clientServices.publisher === null ? [] : ['publisher' as const]),\n\t\t\t\t];\n\t\t\t\temitContributions(ctx, [\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'local' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\tresolved.name,\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t\tresolved.nodeCount,\n\t\t\t\t\t\tclientServiceRoles,\n\t\t\t\t\t),\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'local',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\tstrategy: { noteName: resolved.name },\n\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\ttypeof WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\t{ readonly noteName: string }\n\t\t\t\t\t>,\n\t\t\t\t\t...walFaucetContribution,\n\t\t\t\t\t...(makeLocalRoutables({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t\t\tserviceKey: String(walrusKey),\n\t\t\t\t\t\tnodeCount: resolved.nodeCount,\n\t\t\t\t\t\tcontainerApiPort: resolved.containerApiPort,\n\t\t\t\t\t\taggregator: boot.clientServices.aggregator,\n\t\t\t\t\t\tpublisher: boot.clientServices.publisher,\n\t\t\t\t\t}) as readonly RoutableDecl[]),\n\t\t\t\t]);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nconst buildKnownPlugin = (opts: WalrusKnownDeploymentOptions) => {\n\t// Synchronous factory-time validation for required deployment ids.\n\tconst resolved = resolveKnownDeploymentOptions(opts);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\t// Known deployment is a pure value-producer — no containers,\n\t\t// no long-running children.\n\t\trole: 'task',\n\t\tsection: 'service',\n\t\t// Stack-free codegen: a known deployment's ids / URLs are DECLARED\n\t\t// config (not loaded-at-runtime data) — bake them as literals in the\n\t\t// committed `walrus.ts` (mirrors `knownPackage`). `walrusPackageId` /\n\t\t// `walPackageId` / `walCoinType` are null for a known deployment.\n\t\tstaticCodegen: () => [\n\t\t\tmakeWalrusStaticCodegen({\n\t\t\t\tmode: 'known',\n\t\t\t\tnetwork: resolved.network,\n\t\t\t\tknown: {\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\t...(resolved.exchangeIds.length > 0 ? { exchangeIds: [...resolved.exchangeIds] } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t\t// Known mode is a pure value-producer — `sui` is unused (the value\n\t\t// reads `resolved.network` from the deployment options) but the\n\t\t// `dependsOn` edge still orders boot, so `start` is zero-arg.\n\t\t// `ctx` arrives via the `PluginContext` service.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst resolvedValue = {\n\t\t\t\t\tmode: 'known',\n\t\t\t\t\tnetwork: resolved.network,\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolved.exchangeIds.length > 0 ? resolved.exchangeIds : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\twalFaucetStrategy: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t} satisfies WalrusResolved;\n\t\t\t\t// Emit inline: snapshot → codegen → state-registry. Known mode\n\t\t\t\t// emits no routable. `identity` (from `IdentityContext`) stamps\n\t\t\t\t// the snapshot scoping.\n\t\t\t\tctx.snapshotExtra(\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'known' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\t'walrusKnownDeployment',\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tctx.codegen(\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'known',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tctx.provides({\n\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\tstrategy: {\n\t\t\t\t\t\tname: 'walrusKnownDeployment',\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingObjectId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t},\n\t\t\t\t\tautoMounted: true,\n\t\t\t\t} satisfies StrategyContributorDecl<typeof WALRUS_STATE_REGISTRY_KEY, WalrusStateEntry>);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nexport interface WalCoinValue extends AccountFundingCoinValue {\n\treadonly symbol: 'WAL';\n\treadonly fullCoinType: `${string}::wal::WAL`;\n\treadonly decimals: 9;\n\treadonly source: 'walrus';\n}\n\n/** Resolve the local Walrus deployment's WAL coin as an account-funding\n * coin ref. Accounts that need WAL should use:\n *\n * funding: [{ coin: walCoin(localWalrus), amount }]\n *\n * The funding strategy itself is contributed by the Walrus service\n * once its local exchange exists. */\nexport const walCoin = (walrusMember: ResourceRef<'walrus', WalrusResolved>) => {\n\tconst coinRef = resource<CoinResourceId<'wal'>, WalCoinValue>(coinResourceId('wal'));\n\n\treturn definePlugin({\n\t\tid: coinRef.id,\n\t\tdependsOn: walrusMember,\n\t\trole: 'task',\n\t\tsection: 'action',\n\t\tstart: (resolved): Effect.Effect<WalCoinValue, WalrusPluginError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tif (resolved.walCoinType === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'exchange',\n\t\t\t\t\t\t\t'walCoin(...) requires a local Walrus deployment with a WAL package.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tsymbol: 'WAL',\n\t\t\t\t\tfullCoinType: resolved.walCoinType as `${string}::wal::WAL`,\n\t\t\t\t\tdecimals: 9,\n\t\t\t\t\tsource: 'walrus',\n\t\t\t\t} satisfies WalCoinValue;\n\t\t\t}),\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local-cluster shorthand. Known deployments are selected through the\n * per-network methods `walrusFor(network).testnet(...)` /\n * `.mainnet(...)` so network choice stays explicit; `.known(...)` is the\n * raw-id override form. */\nexport const walrus = (opts?: { readonly local?: WalrusLocalClusterOptions }) => {\n\treturn buildLocalPlugin(opts?.local ?? {});\n};\n\n/** Per-network factory options for `walrusFor(net).testnet()`/`.mainnet()`.\n * The `network` is injected by the namespace method, so the caller only\n * supplies the (still-required) `nodes` committee plus optional per-field\n * overrides — the on-chain ids default from the `@mysten/walrus` SDK package\n * config. Plugin-INTERNAL — deliberately NOT re-exported from `src/index.ts`. */\ntype WalrusKnownByNetworkOptions = Omit<WalrusKnownDeploymentOptions, 'network'>;\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local', network: 'localnet' } as const;\n * walrusFor(network).local({...}) // OK\n * walrusFor(liveNet).testnet({ nodes }) // OK — network injected\n * walrusFor(forkNet).local({...}) // compile error: no `.local` on fork\n *\n * Per-network methods (`.testnet`/`.mainnet`) inject the network into\n * `buildKnownPlugin`; `nodes` is still REQUIRED, so `.testnet({ nodes })` is\n * the minimal call. `.known(...)` is now the raw-id explicit-override form only\n * (`systemObjectId`/`stakingPoolId`/`nodes`) — the `network`-in-`.known()` path\n * was HARD CUT.\n *\n * Critically, the fork branch exposes NO `.local` — calling\n * `.local` on a fork-mode network is a **compile error** at the\n * call site (distilled-doc invariant 12 — type-level refusal). */\nexport const walrusFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: (opts: WalrusLocalClusterOptions = {}) => buildLocalPlugin(opts),\n\t\tknown: (opts: WalrusKnownByNetworkOptions) => buildKnownPlugin(opts),\n\t\ttestnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'testnet', ...opts }),\n\t\tmainnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'mainnet', ...opts }),\n\t},\n\tlive: {\n\t\tknown: (opts: WalrusKnownByNetworkOptions) => buildKnownPlugin(opts),\n\t\ttestnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'testnet', ...opts }),\n\t\tmainnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'mainnet', ...opts }),\n\t},\n\tfork: {\n\t\t// `.local` is intentionally absent — calling\n\t\t// `walrusFor(forkNetwork).local(...)` is a compile error.\n\t\tknown: (opts: WalrusKnownByNetworkOptions) => buildKnownPlugin(opts),\n\t\ttestnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'testnet', ...opts }),\n\t\tmainnet: (opts: WalrusKnownByNetworkOptions = {}) =>\n\t\t\tbuildKnownPlugin({ network: 'mainnet', ...opts }),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers\n// ---------------------------------------------------------------------------\n\nexport type {\n\tWalrusLocalClusterOptions,\n\tWalrusLocalPublisherOptions,\n\tWalrusLocalServiceOptions,\n} from './mode/local-cluster.ts';\nexport type { WalrusKnownDeploymentOptions, WalrusKnownNetwork } from './mode/known-deploy.ts';\nexport type { WalrusStorageNode } from './storage-nodes.ts';\nexport type { WalrusBindings, WalrusNodeBinding } from './codegen.ts';\nexport type { WalrusError, WalrusPluginError, WalrusConfigError, WalrusPhase } from './errors.ts';\nexport { walCoinType, walFaucetStrategyKey, type WalFaucetStrategy } from './faucet-strategy.ts';\nexport {\n\tWALRUS_STATE_REGISTRY_KEY,\n\ttype WalrusStateEntry,\n\ttype WalrusLocalStateEntry,\n\ttype WalrusKnownStateEntry,\n} from './registry-publish.ts';\n"],"mappings":"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"}
@@ -47,7 +47,7 @@ const resolveKnownDeploymentOptions = (opts) => {
47
47
  if (!nodes) throw walrusConfigError("nodes", `walrusKnownDeployment: explicit 'nodes' committee is required — Walrus ${opts.network ?? "custom"} has nodes fetched dynamically by the SDK`, "pass an empty array if you accept the SDK-driven committee lookup, or use walrus()/walrusFor(network).local({...}) for a local self-hosted cluster");
48
48
  const aggregatorUrl = opts.aggregatorUrl ?? reg?.aggregatorUrl ?? null;
49
49
  const publisherUrl = opts.publisherUrl ?? reg?.publisherUrl ?? null;
50
- const proxyUrl = opts.proxyUrl ?? reg?.proxyUrl ?? aggregatorUrl ?? publisherUrl ?? null;
50
+ const proxyUrl = opts.proxyUrl ?? reg?.proxyUrl ?? aggregatorUrl ?? null;
51
51
  return {
52
52
  mode: "known",
53
53
  network: reg?.network ?? "custom",
@@ -1 +1 @@
1
- {"version":3,"file":"known-deploy.mjs","names":[],"sources":["../../../../src/plugins/walrus/mode/known-deploy.ts"],"sourcesContent":["// Walrus mode — known deployment (testnet / mainnet / *-fork via\n// upstream translation).\n//\n// Distilled-doc reference (06-walrus.md §\"Lifecycle: Startup —\n// known deployment\"). Purely synchronous body — no ordered phases.\n// The factory validates required deployment fields and returns a\n// resolved value whose tags are eager `Layer.succeed(...)` constants.\n//\n// Distilled-doc invariants honored:\n// - 15: each of `proxyUrl / aggregatorUrl / publisherUrl` surfaces\n// INDEPENDENTLY. Encoded as `string | null` in the resolved\n// shape — a given field is null only when THAT specific URL\n// is unresolved (registry default + explicit override both\n// absent). The plugin's projection step publishes each tag\n// when its own URL is present; a missing publisher URL does\n// not suppress an available proxy/aggregator URL.\n// - 16: throw synchronously when `nodes` is missing for a\n// registered network. The on-chain ids\n// (`systemObjectId` / `stakingPoolId` / `exchangeIds`) ARE\n// stable and come from the `@mysten/walrus` SDK package config\n// — we default them from the SDK and let callers override.\n// Only the node committee stays explicit: testnet has 100+\n// nodes that the SDK fetches dynamically, so we don't pin them.\n\nimport { MAINNET_WALRUS_PACKAGE_CONFIG, TESTNET_WALRUS_PACKAGE_CONFIG } from '@mysten/walrus';\nimport { Effect, type Scope } from 'effect';\n\nimport { expectNonEmptyString } from '../../../substrate/runtime/config-validation.ts';\nimport { walrusConfigError, type WalrusConfigError } from '../errors.ts';\nimport type { WalrusStorageNode } from '../storage-nodes.ts';\n\n/** Known networks supported by the registry lookup. */\nexport type WalrusKnownNetwork = 'testnet' | 'mainnet' | 'devnet';\n\n/** Options for the known-deployment mode. */\nexport interface WalrusKnownDeploymentOptions {\n\t/** Network shortcut — looks up per-field defaults from a registry\n\t * baked into this package. */\n\treadonly network?: WalrusKnownNetwork;\n\t/** Per-field overrides for the on-chain ids. For `testnet`/`mainnet`\n\t * these default from the `@mysten/walrus` SDK package config, so\n\t * they're optional; for `devnet` (or when `network` is omitted)\n\t * `systemObjectId`/`stakingPoolId` are required. An explicit value\n\t * here always wins over the registry default. */\n\treadonly systemObjectId?: string;\n\treadonly stakingPoolId?: string;\n\treadonly exchangeIds?: ReadonlyArray<string>;\n\t/** Explicit storage-node committee. Required (throws if missing\n\t * even when `network` is set — distilled-doc invariant 16). */\n\treadonly nodes?: ReadonlyArray<WalrusStorageNode>;\n\treadonly aggregatorUrl?: string;\n\treadonly publisherUrl?: string;\n\treadonly proxyUrl?: string;\n}\n\n/** Resolved known-deployment boot artifacts. */\nexport interface KnownDeploymentBootResult {\n\treadonly mode: 'known';\n\t/** The known network's name (`testnet`/`mainnet`/`devnet`) — its stable\n\t * identity. A known remote deployment has no per-boot genesis digest, so\n\t * the network name IS its `chainId` for codegen/snapshot keying. */\n\treadonly network: string;\n\treadonly systemObjectId: string;\n\treadonly stakingPoolId: string;\n\treadonly exchangeIds: ReadonlyArray<string>;\n\treadonly nodes: ReadonlyArray<WalrusStorageNode>;\n\t/** Null only when the proxy URL itself is unresolved (no explicit\n\t * `proxyUrl` override and no registry/aggregator/publisher\n\t * fallback). Surfaces independently of `aggregatorUrl` /\n\t * `publisherUrl` — distilled-doc invariant 15. */\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n}\n\n/** Known-deployment registry — baked-in record per network. The\n * on-chain ids default from the `@mysten/walrus` SDK package config\n * (`{TESTNET,MAINNET}_WALRUS_PACKAGE_CONFIG`) so a caller only needs\n * to supply `nodes`; the aggregator/publisher/proxy URLs are\n * devstack-owned (the SDK ships none). Devnet has no canonical SDK\n * record today, so it keeps no id defaults. */\nconst KNOWN_DEPLOYMENT_REGISTRY: Readonly<\n\tRecord<\n\t\tWalrusKnownNetwork,\n\t\t{\n\t\t\treadonly network: string;\n\t\t\treadonly systemObjectId?: string;\n\t\t\treadonly stakingPoolId?: string;\n\t\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t\t\treadonly aggregatorUrl?: string;\n\t\t\treadonly publisherUrl?: string;\n\t\t\treadonly proxyUrl?: string;\n\t\t}\n\t>\n> = {\n\ttestnet: {\n\t\tnetwork: 'testnet',\n\t\t// On-chain ids default from the SDK package config; callers may\n\t\t// still override per-field. The URLs below are devstack-owned —\n\t\t// the SDK ships none.\n\t\tsystemObjectId: TESTNET_WALRUS_PACKAGE_CONFIG.systemObjectId,\n\t\tstakingPoolId: TESTNET_WALRUS_PACKAGE_CONFIG.stakingPoolId,\n\t\texchangeIds: TESTNET_WALRUS_PACKAGE_CONFIG.exchangeIds,\n\t\taggregatorUrl: 'https://aggregator.walrus-testnet.walrus.space',\n\t\tpublisherUrl: 'https://publisher.walrus-testnet.walrus.space',\n\t\tproxyUrl: 'https://aggregator.walrus-testnet.walrus.space',\n\t},\n\tmainnet: {\n\t\tnetwork: 'mainnet',\n\t\t// On-chain ids default from the SDK package config; callers may\n\t\t// still override per-field. Mainnet ships no `exchangeIds` in the\n\t\t// SDK config (testnet-only faucet exchange), so it stays absent.\n\t\tsystemObjectId: MAINNET_WALRUS_PACKAGE_CONFIG.systemObjectId,\n\t\tstakingPoolId: MAINNET_WALRUS_PACKAGE_CONFIG.stakingPoolId,\n\t\taggregatorUrl: 'https://aggregator.walrus.space',\n\t\tpublisherUrl: 'https://publisher.walrus.space',\n\t\tproxyUrl: 'https://aggregator.walrus.space',\n\t},\n\tdevnet: {\n\t\tnetwork: 'devnet',\n\t},\n};\n\n/** Synchronous factory-time validation + projection. Throws (NOT\n * Effect-fail) on missing required fields so misconfiguration trips\n * at the `defineDevstack` call site rather than at deferred\n * Layer.build time. Distilled-doc invariants 14 + 16. */\nexport const resolveKnownDeploymentOptions = (\n\topts: WalrusKnownDeploymentOptions,\n): KnownDeploymentBootResult => {\n\tconst reg = opts.network ? KNOWN_DEPLOYMENT_REGISTRY[opts.network] : undefined;\n\n\t// On-chain ids: caller override (`opts.*`) wins, else the registry\n\t// default sourced from the `@mysten/walrus` SDK package config.\n\t// Testnet/mainnet default from the SDK so only `nodes` is required;\n\t// devnet has no SDK record, so these stay required there.\n\tconst systemObjectId = expectNonEmptyString(opts.systemObjectId ?? reg?.systemObjectId, {\n\t\tfield: 'systemObjectId',\n\t\tmkError: ({ field }) =>\n\t\t\twalrusConfigError(\n\t\t\t\tfield,\n\t\t\t\t`walrusKnownDeployment: 'systemObjectId' is required (testnet/mainnet default from the @mysten/walrus SDK; pass it explicitly for devnet or networks without an SDK record)`,\n\t\t\t),\n\t});\n\n\tconst stakingPoolId = expectNonEmptyString(opts.stakingPoolId ?? reg?.stakingPoolId, {\n\t\tfield: 'stakingPoolId',\n\t\tmkError: ({ field }) =>\n\t\t\twalrusConfigError(\n\t\t\t\tfield,\n\t\t\t\t`walrusKnownDeployment: 'stakingPoolId' is required (testnet/mainnet default from the @mysten/walrus SDK; pass it explicitly for devnet or networks without an SDK record)`,\n\t\t\t),\n\t});\n\n\tconst nodes = opts.nodes;\n\tif (!nodes) {\n\t\t// Distilled-doc invariant 16: even when `network` is set, the\n\t\t// node committee must be explicit. The on-chain ids come from\n\t\t// the SDK package config, but the committee is dynamic — testnet\n\t\t// has 100+ nodes the SDK fetches at runtime, so we don't pin it.\n\t\tthrow walrusConfigError(\n\t\t\t'nodes',\n\t\t\t`walrusKnownDeployment: explicit 'nodes' committee is required — ` +\n\t\t\t\t`Walrus ${opts.network ?? 'custom'} has nodes fetched dynamically by the SDK`,\n\t\t\t`pass an empty array if you accept the SDK-driven committee lookup, ` +\n\t\t\t\t`or use walrus()/walrusFor(network).local({...}) for a local self-hosted cluster`,\n\t\t);\n\t}\n\n\tconst aggregatorUrl = opts.aggregatorUrl ?? reg?.aggregatorUrl ?? null;\n\tconst publisherUrl = opts.publisherUrl ?? reg?.publisherUrl ?? null;\n\tconst proxyUrl = opts.proxyUrl ?? reg?.proxyUrl ?? aggregatorUrl ?? publisherUrl ?? null;\n\n\t// Invariant 15: surface null per individual URL when missing so the\n\t// plugin's projection can conditionally publish each tag. Previously\n\t// any single missing URL nullified all three, dropping user-supplied\n\t// values for the URLs they did provide.\n\treturn {\n\t\tmode: 'known' as const,\n\t\tnetwork: reg?.network ?? 'custom',\n\t\tsystemObjectId,\n\t\tstakingPoolId,\n\t\texchangeIds: opts.exchangeIds ?? reg?.exchangeIds ?? [],\n\t\tnodes,\n\t\tproxyUrl,\n\t\taggregatorUrl,\n\t\tpublisherUrl,\n\t};\n};\n\n/** \"Boot\" the known deployment. Purely synchronous projection inside\n * Effect.gen — no I/O, no Scope work. Surfaces as Effect for\n * uniformity with the local-cluster boot signature. */\nexport const bootKnownDeployment = (\n\topts: WalrusKnownDeploymentOptions,\n): Effect.Effect<KnownDeploymentBootResult, WalrusConfigError, Scope.Scope> =>\n\tEffect.try({\n\t\ttry: () => resolveKnownDeploymentOptions(opts),\n\t\t// `resolveKnownDeploymentOptions` throws our typed config error\n\t\t// shape. STYLE_GUIDE §2 forbids bare error casts; runtime-guard\n\t\t// the `_tag` so an unexpected synchronous throw (e.g. a future\n\t\t// helper that throws a stock `TypeError`) is wrapped instead of\n\t\t// silently mis-tagged as `WalrusConfigError`.\n\t\tcatch: (err): WalrusConfigError => {\n\t\t\tif (\n\t\t\t\ttypeof err === 'object' &&\n\t\t\t\terr !== null &&\n\t\t\t\t'_tag' in err &&\n\t\t\t\t(err as { readonly _tag?: unknown })._tag === 'WalrusConfigError'\n\t\t\t) {\n\t\t\t\treturn err as WalrusConfigError;\n\t\t\t}\n\t\t\treturn walrusConfigError(\n\t\t\t\t'unknown',\n\t\t\t\t`walrusKnownDeployment: unexpected non-typed throw inside resolveKnownDeploymentOptions: ${\n\t\t\t\t\terr instanceof Error ? err.message : String(err)\n\t\t\t\t}`,\n\t\t\t\tundefined,\n\t\t\t\terr,\n\t\t\t);\n\t\t},\n\t});\n"],"mappings":";;;;;;;;;;;AAiFA,MAAM,4BAaF;CACH,SAAS;EACR,SAAS;EAIT,gBAAgB,8BAA8B;EAC9C,eAAe,8BAA8B;EAC7C,aAAa,8BAA8B;EAC3C,eAAe;EACf,cAAc;EACd,UAAU;CACX;CACA,SAAS;EACR,SAAS;EAIT,gBAAgB,8BAA8B;EAC9C,eAAe,8BAA8B;EAC7C,eAAe;EACf,cAAc;EACd,UAAU;CACX;CACA,QAAQ,EACP,SAAS,SACV;AACD;;;;;AAMA,MAAa,iCACZ,SAC+B;CAC/B,MAAM,MAAM,KAAK,UAAU,0BAA0B,KAAK,WAAW,KAAA;CAMrE,MAAM,iBAAiB,qBAAqB,KAAK,kBAAkB,KAAK,gBAAgB;EACvF,OAAO;EACP,UAAU,EAAE,YACX,kBACC,OACA,4KACD;CACF,CAAC;CAED,MAAM,gBAAgB,qBAAqB,KAAK,iBAAiB,KAAK,eAAe;EACpF,OAAO;EACP,UAAU,EAAE,YACX,kBACC,OACA,2KACD;CACF,CAAC;CAED,MAAM,QAAQ,KAAK;CACnB,IAAI,CAAC,OAKJ,MAAM,kBACL,SACA,0EACW,KAAK,WAAW,SAAS,4CACpC,oJAED;CAGD,MAAM,gBAAgB,KAAK,iBAAiB,KAAK,iBAAiB;CAClE,MAAM,eAAe,KAAK,gBAAgB,KAAK,gBAAgB;CAC/D,MAAM,WAAW,KAAK,YAAY,KAAK,YAAY,iBAAiB,gBAAgB;CAMpF,OAAO;EACN,MAAM;EACN,SAAS,KAAK,WAAW;EACzB;EACA;EACA,aAAa,KAAK,eAAe,KAAK,eAAe,CAAC;EACtD;EACA;EACA;EACA;CACD;AACD;;;;AAKA,MAAa,uBACZ,SAEA,OAAO,IAAI;CACV,WAAW,8BAA8B,IAAI;CAM7C,QAAQ,QAA2B;EAClC,IACC,OAAO,QAAQ,YACf,QAAQ,QACR,UAAU,OACT,IAAoC,SAAS,qBAE9C,OAAO;EAER,OAAO,kBACN,WACA,2FACC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KAEhD,KAAA,GACA,GACD;CACD;AACD,CAAC"}
1
+ {"version":3,"file":"known-deploy.mjs","names":[],"sources":["../../../../src/plugins/walrus/mode/known-deploy.ts"],"sourcesContent":["// Walrus mode — known deployment (testnet / mainnet / *-fork via\n// upstream translation).\n//\n// Distilled-doc reference (06-walrus.md §\"Lifecycle: Startup —\n// known deployment\"). Purely synchronous body — no ordered phases.\n// The factory validates required deployment fields and returns a\n// resolved value whose tags are eager `Layer.succeed(...)` constants.\n//\n// Distilled-doc invariants honored:\n// - 15: each of `proxyUrl / aggregatorUrl / publisherUrl` surfaces\n// INDEPENDENTLY. Encoded as `string | null` in the resolved\n// shape — a given field is null only when THAT specific URL\n// is unresolved (registry default + explicit override both\n// absent). The plugin's projection step publishes each tag\n// when its own URL is present; a missing publisher URL does\n// not suppress an available proxy/aggregator URL.\n// - 16: throw synchronously when `nodes` is missing for a\n// registered network. The on-chain ids\n// (`systemObjectId` / `stakingPoolId` / `exchangeIds`) ARE\n// stable and come from the `@mysten/walrus` SDK package config\n// — we default them from the SDK and let callers override.\n// Only the node committee stays explicit: testnet has 100+\n// nodes that the SDK fetches dynamically, so we don't pin them.\n\nimport { MAINNET_WALRUS_PACKAGE_CONFIG, TESTNET_WALRUS_PACKAGE_CONFIG } from '@mysten/walrus';\nimport { Effect, type Scope } from 'effect';\n\nimport { expectNonEmptyString } from '../../../substrate/runtime/config-validation.ts';\nimport { walrusConfigError, type WalrusConfigError } from '../errors.ts';\nimport type { WalrusStorageNode } from '../storage-nodes.ts';\n\n/** Known networks supported by the registry lookup. */\nexport type WalrusKnownNetwork = 'testnet' | 'mainnet' | 'devnet';\n\n/** Options for the known-deployment mode. */\nexport interface WalrusKnownDeploymentOptions {\n\t/** Network shortcut — looks up per-field defaults from a registry\n\t * baked into this package. */\n\treadonly network?: WalrusKnownNetwork;\n\t/** Per-field overrides for the on-chain ids. For `testnet`/`mainnet`\n\t * these default from the `@mysten/walrus` SDK package config, so\n\t * they're optional; for `devnet` (or when `network` is omitted)\n\t * `systemObjectId`/`stakingPoolId` are required. An explicit value\n\t * here always wins over the registry default. */\n\treadonly systemObjectId?: string;\n\treadonly stakingPoolId?: string;\n\treadonly exchangeIds?: ReadonlyArray<string>;\n\t/** Explicit storage-node committee. Required (throws if missing\n\t * even when `network` is set — distilled-doc invariant 16). */\n\treadonly nodes?: ReadonlyArray<WalrusStorageNode>;\n\treadonly aggregatorUrl?: string;\n\treadonly publisherUrl?: string;\n\treadonly proxyUrl?: string;\n}\n\n/** Resolved known-deployment boot artifacts. */\nexport interface KnownDeploymentBootResult {\n\treadonly mode: 'known';\n\t/** The known network's name (`testnet`/`mainnet`/`devnet`) — its stable\n\t * identity. A known remote deployment has no per-boot genesis digest, so\n\t * the network name IS its `chainId` for codegen/snapshot keying. */\n\treadonly network: string;\n\treadonly systemObjectId: string;\n\treadonly stakingPoolId: string;\n\treadonly exchangeIds: ReadonlyArray<string>;\n\treadonly nodes: ReadonlyArray<WalrusStorageNode>;\n\t/** Null only when the read/proxy URL itself is unresolved (no explicit\n\t * `proxyUrl` override, registry proxy URL, or aggregator URL). Surfaces\n\t * independently of `aggregatorUrl` / `publisherUrl` — distilled-doc\n\t * invariant 15. */\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n}\n\n/** Known-deployment registry — baked-in record per network. The\n * on-chain ids default from the `@mysten/walrus` SDK package config\n * (`{TESTNET,MAINNET}_WALRUS_PACKAGE_CONFIG`) so a caller only needs\n * to supply `nodes`; the aggregator/publisher/proxy URLs are\n * devstack-owned (the SDK ships none). Devnet has no canonical SDK\n * record today, so it keeps no id defaults. */\nconst KNOWN_DEPLOYMENT_REGISTRY: Readonly<\n\tRecord<\n\t\tWalrusKnownNetwork,\n\t\t{\n\t\t\treadonly network: string;\n\t\t\treadonly systemObjectId?: string;\n\t\t\treadonly stakingPoolId?: string;\n\t\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t\t\treadonly aggregatorUrl?: string;\n\t\t\treadonly publisherUrl?: string;\n\t\t\treadonly proxyUrl?: string;\n\t\t}\n\t>\n> = {\n\ttestnet: {\n\t\tnetwork: 'testnet',\n\t\t// On-chain ids default from the SDK package config; callers may\n\t\t// still override per-field. The URLs below are devstack-owned —\n\t\t// the SDK ships none.\n\t\tsystemObjectId: TESTNET_WALRUS_PACKAGE_CONFIG.systemObjectId,\n\t\tstakingPoolId: TESTNET_WALRUS_PACKAGE_CONFIG.stakingPoolId,\n\t\texchangeIds: TESTNET_WALRUS_PACKAGE_CONFIG.exchangeIds,\n\t\taggregatorUrl: 'https://aggregator.walrus-testnet.walrus.space',\n\t\tpublisherUrl: 'https://publisher.walrus-testnet.walrus.space',\n\t\tproxyUrl: 'https://aggregator.walrus-testnet.walrus.space',\n\t},\n\tmainnet: {\n\t\tnetwork: 'mainnet',\n\t\t// On-chain ids default from the SDK package config; callers may\n\t\t// still override per-field. Mainnet ships no `exchangeIds` in the\n\t\t// SDK config (testnet-only faucet exchange), so it stays absent.\n\t\tsystemObjectId: MAINNET_WALRUS_PACKAGE_CONFIG.systemObjectId,\n\t\tstakingPoolId: MAINNET_WALRUS_PACKAGE_CONFIG.stakingPoolId,\n\t\taggregatorUrl: 'https://aggregator.walrus.space',\n\t\tpublisherUrl: 'https://publisher.walrus.space',\n\t\tproxyUrl: 'https://aggregator.walrus.space',\n\t},\n\tdevnet: {\n\t\tnetwork: 'devnet',\n\t},\n};\n\n/** Synchronous factory-time validation + projection. Throws (NOT\n * Effect-fail) on missing required fields so misconfiguration trips\n * at the `defineDevstack` call site rather than at deferred\n * Layer.build time. Distilled-doc invariants 14 + 16. */\nexport const resolveKnownDeploymentOptions = (\n\topts: WalrusKnownDeploymentOptions,\n): KnownDeploymentBootResult => {\n\tconst reg = opts.network ? KNOWN_DEPLOYMENT_REGISTRY[opts.network] : undefined;\n\n\t// On-chain ids: caller override (`opts.*`) wins, else the registry\n\t// default sourced from the `@mysten/walrus` SDK package config.\n\t// Testnet/mainnet default from the SDK so only `nodes` is required;\n\t// devnet has no SDK record, so these stay required there.\n\tconst systemObjectId = expectNonEmptyString(opts.systemObjectId ?? reg?.systemObjectId, {\n\t\tfield: 'systemObjectId',\n\t\tmkError: ({ field }) =>\n\t\t\twalrusConfigError(\n\t\t\t\tfield,\n\t\t\t\t`walrusKnownDeployment: 'systemObjectId' is required (testnet/mainnet default from the @mysten/walrus SDK; pass it explicitly for devnet or networks without an SDK record)`,\n\t\t\t),\n\t});\n\n\tconst stakingPoolId = expectNonEmptyString(opts.stakingPoolId ?? reg?.stakingPoolId, {\n\t\tfield: 'stakingPoolId',\n\t\tmkError: ({ field }) =>\n\t\t\twalrusConfigError(\n\t\t\t\tfield,\n\t\t\t\t`walrusKnownDeployment: 'stakingPoolId' is required (testnet/mainnet default from the @mysten/walrus SDK; pass it explicitly for devnet or networks without an SDK record)`,\n\t\t\t),\n\t});\n\n\tconst nodes = opts.nodes;\n\tif (!nodes) {\n\t\t// Distilled-doc invariant 16: even when `network` is set, the\n\t\t// node committee must be explicit. The on-chain ids come from\n\t\t// the SDK package config, but the committee is dynamic — testnet\n\t\t// has 100+ nodes the SDK fetches at runtime, so we don't pin it.\n\t\tthrow walrusConfigError(\n\t\t\t'nodes',\n\t\t\t`walrusKnownDeployment: explicit 'nodes' committee is required — ` +\n\t\t\t\t`Walrus ${opts.network ?? 'custom'} has nodes fetched dynamically by the SDK`,\n\t\t\t`pass an empty array if you accept the SDK-driven committee lookup, ` +\n\t\t\t\t`or use walrus()/walrusFor(network).local({...}) for a local self-hosted cluster`,\n\t\t);\n\t}\n\n\tconst aggregatorUrl = opts.aggregatorUrl ?? reg?.aggregatorUrl ?? null;\n\tconst publisherUrl = opts.publisherUrl ?? reg?.publisherUrl ?? null;\n\tconst proxyUrl = opts.proxyUrl ?? reg?.proxyUrl ?? aggregatorUrl ?? null;\n\n\t// Invariant 15: surface null per individual URL when missing so the\n\t// plugin's projection can conditionally publish each tag. Previously\n\t// any single missing URL nullified all three, dropping user-supplied\n\t// values for the URLs they did provide.\n\treturn {\n\t\tmode: 'known' as const,\n\t\tnetwork: reg?.network ?? 'custom',\n\t\tsystemObjectId,\n\t\tstakingPoolId,\n\t\texchangeIds: opts.exchangeIds ?? reg?.exchangeIds ?? [],\n\t\tnodes,\n\t\tproxyUrl,\n\t\taggregatorUrl,\n\t\tpublisherUrl,\n\t};\n};\n\n/** \"Boot\" the known deployment. Purely synchronous projection inside\n * Effect.gen — no I/O, no Scope work. Surfaces as Effect for\n * uniformity with the local-cluster boot signature. */\nexport const bootKnownDeployment = (\n\topts: WalrusKnownDeploymentOptions,\n): Effect.Effect<KnownDeploymentBootResult, WalrusConfigError, Scope.Scope> =>\n\tEffect.try({\n\t\ttry: () => resolveKnownDeploymentOptions(opts),\n\t\t// `resolveKnownDeploymentOptions` throws our typed config error\n\t\t// shape. STYLE_GUIDE §2 forbids bare error casts; runtime-guard\n\t\t// the `_tag` so an unexpected synchronous throw (e.g. a future\n\t\t// helper that throws a stock `TypeError`) is wrapped instead of\n\t\t// silently mis-tagged as `WalrusConfigError`.\n\t\tcatch: (err): WalrusConfigError => {\n\t\t\tif (\n\t\t\t\ttypeof err === 'object' &&\n\t\t\t\terr !== null &&\n\t\t\t\t'_tag' in err &&\n\t\t\t\t(err as { readonly _tag?: unknown })._tag === 'WalrusConfigError'\n\t\t\t) {\n\t\t\t\treturn err as WalrusConfigError;\n\t\t\t}\n\t\t\treturn walrusConfigError(\n\t\t\t\t'unknown',\n\t\t\t\t`walrusKnownDeployment: unexpected non-typed throw inside resolveKnownDeploymentOptions: ${\n\t\t\t\t\terr instanceof Error ? err.message : String(err)\n\t\t\t\t}`,\n\t\t\t\tundefined,\n\t\t\t\terr,\n\t\t\t);\n\t\t},\n\t});\n"],"mappings":";;;;;;;;;;;AAiFA,MAAM,4BAaF;CACH,SAAS;EACR,SAAS;EAIT,gBAAgB,8BAA8B;EAC9C,eAAe,8BAA8B;EAC7C,aAAa,8BAA8B;EAC3C,eAAe;EACf,cAAc;EACd,UAAU;CACX;CACA,SAAS;EACR,SAAS;EAIT,gBAAgB,8BAA8B;EAC9C,eAAe,8BAA8B;EAC7C,eAAe;EACf,cAAc;EACd,UAAU;CACX;CACA,QAAQ,EACP,SAAS,SACV;AACD;;;;;AAMA,MAAa,iCACZ,SAC+B;CAC/B,MAAM,MAAM,KAAK,UAAU,0BAA0B,KAAK,WAAW,KAAA;CAMrE,MAAM,iBAAiB,qBAAqB,KAAK,kBAAkB,KAAK,gBAAgB;EACvF,OAAO;EACP,UAAU,EAAE,YACX,kBACC,OACA,4KACD;CACF,CAAC;CAED,MAAM,gBAAgB,qBAAqB,KAAK,iBAAiB,KAAK,eAAe;EACpF,OAAO;EACP,UAAU,EAAE,YACX,kBACC,OACA,2KACD;CACF,CAAC;CAED,MAAM,QAAQ,KAAK;CACnB,IAAI,CAAC,OAKJ,MAAM,kBACL,SACA,0EACW,KAAK,WAAW,SAAS,4CACpC,oJAED;CAGD,MAAM,gBAAgB,KAAK,iBAAiB,KAAK,iBAAiB;CAClE,MAAM,eAAe,KAAK,gBAAgB,KAAK,gBAAgB;CAC/D,MAAM,WAAW,KAAK,YAAY,KAAK,YAAY,iBAAiB;CAMpE,OAAO;EACN,MAAM;EACN,SAAS,KAAK,WAAW;EACzB;EACA;EACA,aAAa,KAAK,eAAe,KAAK,eAAe,CAAC;EACtD;EACA;EACA;EACA;CACD;AACD;;;;AAKA,MAAa,uBACZ,SAEA,OAAO,IAAI;CACV,WAAW,8BAA8B,IAAI;CAM7C,QAAQ,QAA2B;EAClC,IACC,OAAO,QAAQ,YACf,QAAQ,QACR,UAAU,OACT,IAAoC,SAAS,qBAE9C,OAAO;EAER,OAAO,kBACN,WACA,2FACC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KAEhD,KAAA,GACA,GACD;CACD;AACD,CAAC"}
@@ -28,7 +28,20 @@ interface WalrusLocalClusterOptions {
28
28
  /** Per-node TCP ready-probe timeout (ms). Defaults to
29
29
  * `DEFAULT_NODE_READY_TIMEOUT_MS`. */
30
30
  readonly readyTimeoutMs?: number;
31
+ /** Local app-facing aggregator service. Defaults to enabled. Set
32
+ * `false` to expose only storage-node routes. */
33
+ readonly aggregator?: boolean | WalrusLocalServiceOptions;
34
+ /** Local app-facing publisher service. Defaults to enabled. Set
35
+ * `false` to run storage nodes without the simple publish API. */
36
+ readonly publisher?: boolean | WalrusLocalPublisherOptions;
31
37
  }
38
+ interface WalrusLocalServiceOptions {
39
+ /** In-container port passed to the release service's
40
+ * `--bind-address 0.0.0.0:<port>`. Defaults to the Walrus CLI's
41
+ * publisher/aggregator port. */
42
+ readonly port?: number;
43
+ }
44
+ type WalrusLocalPublisherOptions = WalrusLocalServiceOptions;
32
45
  //#endregion
33
- export { WalrusLocalClusterOptions };
46
+ export { WalrusLocalClusterOptions, WalrusLocalPublisherOptions, WalrusLocalServiceOptions };
34
47
  //# sourceMappingURL=local-cluster.d.mts.map