@mysten-incubation/devstack 0.4.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 (61) hide show
  1. package/README.md +9 -10
  2. package/dashboard-ui/assets/{grpc-CpkDu4SA.js → grpc-s7Ztk9wv.js} +1 -1
  3. package/dashboard-ui/assets/{index-jLPRmjst.js → index-DrOd0m4F.js} +2 -2
  4. package/dashboard-ui/index.html +1 -1
  5. package/dist/contracts/routable.d.mts +1 -1
  6. package/dist/index.d.mts +3 -3
  7. package/dist/orchestrators/boot.mjs +2 -1
  8. package/dist/orchestrators/boot.mjs.map +1 -1
  9. package/dist/orchestrators/router/file-provider.mjs +11 -8
  10. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  11. package/dist/orchestrators/router/service.mjs +2 -2
  12. package/dist/orchestrators/router/service.mjs.map +1 -1
  13. package/dist/plugins/deepbook/index.d.mts +36 -6
  14. package/dist/plugins/deepbook/index.mjs +52 -16
  15. package/dist/plugins/deepbook/index.mjs.map +1 -1
  16. package/dist/plugins/seal/codegen.d.mts +5 -0
  17. package/dist/plugins/seal/codegen.mjs +53 -10
  18. package/dist/plugins/seal/codegen.mjs.map +1 -1
  19. package/dist/plugins/seal/index.d.mts +64 -19
  20. package/dist/plugins/seal/index.mjs +26 -36
  21. package/dist/plugins/seal/index.mjs.map +1 -1
  22. package/dist/plugins/seal/mode/fork-known.d.mts +0 -2
  23. package/dist/plugins/seal/mode/fork-known.mjs +11 -22
  24. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  25. package/dist/plugins/seal/mode/live.d.mts +36 -13
  26. package/dist/plugins/seal/mode/live.mjs +104 -43
  27. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  28. package/dist/plugins/seal/mode/local-keygen.mjs +2 -1
  29. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  30. package/dist/plugins/seal/registry-publish.d.mts +15 -3
  31. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  32. package/dist/plugins/sui/index.d.mts +10 -10
  33. package/dist/plugins/walrus/client-services.d.mts +1 -0
  34. package/dist/plugins/walrus/client-services.mjs +115 -0
  35. package/dist/plugins/walrus/client-services.mjs.map +1 -0
  36. package/dist/plugins/walrus/deploy.mjs +11 -5
  37. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  38. package/dist/plugins/walrus/errors.d.mts +1 -1
  39. package/dist/plugins/walrus/errors.mjs.map +1 -1
  40. package/dist/plugins/walrus/index.d.mts +138 -9
  41. package/dist/plugins/walrus/index.mjs +72 -20
  42. package/dist/plugins/walrus/index.mjs.map +1 -1
  43. package/dist/plugins/walrus/mode/known-deploy.d.mts +5 -1
  44. package/dist/plugins/walrus/mode/known-deploy.mjs +14 -11
  45. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  46. package/dist/plugins/walrus/mode/local-cluster.d.mts +14 -1
  47. package/dist/plugins/walrus/mode/local-cluster.mjs +54 -12
  48. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  49. package/dist/plugins/walrus/routable.mjs +12 -15
  50. package/dist/plugins/walrus/routable.mjs.map +1 -1
  51. package/dist/plugins/walrus/snapshot.mjs +3 -2
  52. package/dist/plugins/walrus/snapshot.mjs.map +1 -1
  53. package/dist/plugins/walrus/storage-nodes.mjs +31 -33
  54. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  55. package/dist/substrate/runtime/config-validation.mjs +1 -1
  56. package/images/walrus/Dockerfile +17 -4
  57. package/images/walrus/deploy-walrus.sh +7 -4
  58. package/images/walrus/run-walrus-client-service.sh +103 -0
  59. package/package.json +2 -2
  60. package/dist/plugins/seal/service.mjs +0 -17
  61. package/dist/plugins/seal/service.mjs.map +0 -1
@@ -173,7 +173,8 @@ const startLocalKeygenContainer = (deps, opts, cargoImage, state) => Effect.gen(
173
173
  keyServer: {
174
174
  serverConfigs,
175
175
  keyServerUrl: deps.routedUrl,
176
- objectId: state.keyServerObjectId
176
+ objectId: state.keyServerObjectId,
177
+ verifyKeyServers: false
177
178
  },
178
179
  keyManager,
179
180
  packageId: state.packageId
@@ -1 +1 @@
1
- {"version":3,"file":"local-keygen.mjs","names":[],"sources":["../../../../src/plugins/seal/mode/local-keygen.ts"],"sourcesContent":["// Seal local-keygen mode — the full localnet path.\n//\n// Distilled-doc reference (07-seal.md §\"Lifecycle / Startup —\n// `sealLocalKeygen`\"). Eight ordered phases:\n//\n// 0. Yield deps (Sui, Identity, ContainerRuntime, artifact publisher publisher).\n// 1. Image build (bootstrap asset — parallel with sui's boot).\n// 1a. Move source resolve (bootstrap asset, conditional).\n// 2. BLS keygen one-shot (real `runtime.runOneShot` against cargo\n// image; output parsed via regex).\n// 3. Publish seal Move package (via artifact publisher primitive).\n// 4. Register on-chain KeyServer (via artifact publisher primitive).\n// 5. Render config yaml + stage master-key env-file (atomic\n// FileSystem\n// writes).\n// 6. Start long-running key-server container (real\n// `runtime.ensureContainer`\n// + exec-based ready\n// probe).\n// 7. Project the plugin's aggregate value.\n//\n// Distilled-doc invariants pinned by this file:\n//\n// #1 KeyServer.url == routed hostname — single mint point\n// (`routedUrl` passed\n// identically into\n// `registerKeyServer`\n// and `buildKeyServerSpec`).\n// #2 Boot order keygen → publish → register → config → container.\n// #4 env-file for secret loading (not -e MASTER_KEY=…).\n// #5 No host-port publish.\n// #6 Keypair B8 verify cascade (deploy.ts:buildKeypairVerify).\n// #7 Signal-forwarding entrypoint shell (image-side).\n// #8 Fork incompatibility throw — handled at the barrel\n// (`index.ts`), NOT here.\n// #9 Peer-dep structural assignability (compile-time check\n// lives in registry-publish.ts).\n//\n// This is the substrate-name-LEAK boundary. Inside this file we can\n// say \"seal\"; outside `src/plugins/seal/`, the substrate doesn't\n// know seal exists.\n\nimport { Effect, FileSystem, Path, Schema, type Scope } from 'effect';\n\nimport { decodeJsonText } from '../../../substrate/runtime/runtime-decode.ts';\n\nimport type { ChainProbe } from '../../../contracts/chain-probe.ts';\nimport type { ContainerLabelTuple } from '../../../contracts/snapshotable.ts';\nimport type { ContainerRuntime, ImageRef } from '../../../contracts/container-runtime.ts';\nimport type {\n\tArtifactPublishError,\n\tArtifactPublisher,\n} from '../../../primitives/artifact-publisher.ts';\nimport type { AccountValue } from '../../account/index.ts';\nimport {\n\tparseMasterKeyEnvFile,\n\trenderSealKeyServerConfig,\n\tstageSealConfig,\n} from '../config-render.ts';\nimport {\n\tpublishSealPackage,\n\tregisterKeyServer,\n\ttype SealObjectProbeKey,\n\ttype SealSuiSdk,\n} from '../deploy.ts';\nimport { sealError, type SealError } from '../errors.ts';\nimport { MASTER_KEY_ENVFILE_BASENAME, runSealKeygen, type PersistedBlsKeypair } from '../keygen.ts';\nimport { makeKeyManager } from '../key-manager.ts';\nimport { buildKeyServerSpec, startKeyServer, type KeyServerContainerSpec } from '../key-server.ts';\nimport type { SealKeyServerEntry, SealLocalKeygenResolved } from '../registry-publish.ts';\nimport { resolveDefaultSealCargoImage } from '../bootstrap-assets/cargo-image.ts';\nimport { resolveDefaultSealSource } from '../bootstrap-assets/source-fetch.ts';\nimport { atomicWriteFile } from '../../../substrate/runtime/atomic-write.ts';\nimport { versionedDocSchema } from '../../../substrate/versioned-doc-schema.ts';\n\n// ---------------------------------------------------------------------------\n// Options (factory-time)\n// ---------------------------------------------------------------------------\n\n/** Options the local-keygen mode accepts (07-seal.md\n * §\"Configuration\"). The\n * barrel (`index.ts`) folds the typed `SealLocalKeygenOptions`\n * surface into this internal shape after default resolution. */\nexport interface LocalKeygenOptions {\n\treadonly name?: string;\n\treadonly version?: string;\n\treadonly movePackagePath?: string;\n\treadonly readyTimeoutMs?: number;\n\treadonly keyServerName?: string;\n}\n\n/** Resolved options after defaults are applied. */\nexport interface ResolvedLocalKeygenOptions {\n\treadonly name: string;\n\treadonly version: string;\n\treadonly readyTimeoutMs: number;\n\treadonly keyServerName: string;\n\treadonly movePackagePath?: string;\n}\n\nconst DEFAULT_READY_TIMEOUT_MS = 60_000;\nconst DEFAULT_KEY_SERVER_NAME = 'devstack-local';\n\n/** Synchronous factory-time defaults application. Pure — no\n * validation throws because the localnet signer requirement is\n * enforced one layer up in the barrel (the type-narrowed\n * `sealFor(local).localKeygen({signer})` makes signer required). */\nexport const resolveLocalKeygenOptions = (\n\topts: LocalKeygenOptions,\n\tdefaultVersion: string,\n): ResolvedLocalKeygenOptions => ({\n\tname: opts.name ?? 'seal',\n\tversion: opts.version ?? defaultVersion,\n\treadyTimeoutMs: opts.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n\tkeyServerName: opts.keyServerName ?? DEFAULT_KEY_SERVER_NAME,\n\tmovePackagePath: opts.movePackagePath,\n});\n\n// ---------------------------------------------------------------------------\n// Inputs to the boot pipeline (acquire-time)\n// ---------------------------------------------------------------------------\n\n/** Acquire-time deps the boot pipeline consumes. Sourced by the\n * barrel (`index.ts`) from a mix of:\n *\n * - substrate-resolved values (`IdentityContext`,\n * `ContainerRuntimeService`, `ArtifactPublisher`);\n * - substrate-derived identifiers (chainId, servicePath,\n * subnet, etc.);\n * - router-resolved URL (single mint — distilled-doc invariant #1).\n *\n * The barrel composes these from substrate services\n * (ContainerRuntime, IdentityContext, ArtifactPublisher,\n * StackPathsService) at acquire-time. */\nexport interface LocalKeygenDeps {\n\treadonly runtime: ContainerRuntime;\n\treadonly publisher: ArtifactPublisher;\n\treadonly signer: AccountValue;\n\treadonly sdk: SealSuiSdk;\n\treadonly buildImage?: ImageRef;\n\treadonly chainProbe: ChainProbe<SealObjectProbeKey>;\n\treadonly chainId: string;\n\t/** Per-stack on-disk dir under `runtime/seal/`. Host path for the\n\t * config yaml + master-key env-file. */\n\treadonly servicePath: string;\n\t/** Container name composed by the substrate (app + stack + name +\n\t * role). */\n\treadonly containerName: string;\n\t/** Label tuple driving snapshot/inspect by labels. */\n\treadonly labels: ContainerLabelTuple;\n\t/** Seal's per-stack docker network — the key-server daemon attaches\n\t * to this network. */\n\treadonly suiNetworkName: string;\n\t/** In-network sui RPC URL — the daemon's CONFIG_PATH yaml carries\n\t * this. */\n\treadonly suiRpcUrlInNetwork: string;\n\t/** Router-resolved single mint (distilled-doc invariant #1). */\n\treadonly routedHostname: string;\n\treadonly routedUrl: string;\n}\n\ninterface PersistedLocalKeygenKeyMaterial {\n\treadonly masterKey: string;\n\treadonly publicKey: string;\n\treadonly masterKeyEnvFile: string;\n}\n\ninterface PersistedLocalKeygenState extends PersistedLocalKeygenKeyMaterial {\n\treadonly packageId: string;\n\treadonly keyServerObjectId: string;\n}\n\nconst LOCAL_KEYGEN_STATE_VERSION = 1;\nconst LOCAL_KEYGEN_STATE_BASENAME = 'local-keygen-state.v1.json';\n\nconst PersistedLocalKeygenMetadataSchema = versionedDocSchema(LOCAL_KEYGEN_STATE_VERSION, {\n\tpublicKey: Schema.String,\n});\n\ntype PersistedLocalKeygenMetadata = Schema.Schema.Type<typeof PersistedLocalKeygenMetadataSchema>;\n\nconst localKeygenMetadataPath = (servicePath: string): string =>\n\t`${servicePath}/${LOCAL_KEYGEN_STATE_BASENAME}`;\n\nconst readPersistedLocalKeygenMetadata = (\n\tservicePath: string,\n): Effect.Effect<PersistedLocalKeygenMetadata | null, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = localKeygenMetadataPath(servicePath);\n\t\tconst raw = yield* fs.readFileString(path).pipe(Effect.catch(() => Effect.succeed(null)));\n\t\tif (raw === null) return null;\n\t\t// Parse + decode failures here mean \"no usable persisted metadata\"\n\t\t// and the caller treats this as a miss; the typed\n\t\t// `RuntimeDecodeIssue` carries the parse/decode diagnostic for\n\t\t// debugging but is collapsed to `null` so the boot loop falls\n\t\t// through to a fresh keygen.\n\t\treturn yield* decodeJsonText(PersistedLocalKeygenMetadataSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) => issue,\n\t\t}).pipe(Effect.catch(() => Effect.succeed(null)));\n\t});\n\nconst stagePersistedLocalKeygenMetadata = (\n\tservicePath: string,\n\tname: string,\n\tmetadata: PersistedLocalKeygenMetadata,\n): Effect.Effect<void, SealError, FileSystem.FileSystem> =>\n\tatomicWriteFile(\n\t\tlocalKeygenMetadataPath(servicePath),\n\t\tnew TextEncoder().encode(`${JSON.stringify(metadata, null, 2)}\\n`),\n\t\t{\n\t\t\tmode: 0o644,\n\t\t\tparentMode: 0o700,\n\t\t},\n\t).pipe(\n\t\tEffect.catch((cause) =>\n\t\t\tEffect.fail(\n\t\t\t\tsealError('config-render', {\n\t\t\t\t\tname,\n\t\t\t\t\tmessage: `seal.config-render: failed to write ${LOCAL_KEYGEN_STATE_BASENAME} (${cause.stage})`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n\nconst readPersistedLocalKeygenState = (\n\tservicePath: string,\n): Effect.Effect<PersistedLocalKeygenKeyMaterial | null, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst masterKeyEnvFile = `${servicePath}/${MASTER_KEY_ENVFILE_BASENAME}`;\n\t\tconst masterKeyBody = yield* fs\n\t\t\t.readFileString(masterKeyEnvFile)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed(null)));\n\t\tif (masterKeyBody === null) return null;\n\t\tconst masterKey = parseMasterKeyEnvFile(masterKeyBody);\n\t\tif (masterKey === null) return null;\n\t\tconst metadata = yield* readPersistedLocalKeygenMetadata(servicePath);\n\t\tif (metadata === null) return null;\n\n\t\treturn {\n\t\t\tmasterKey,\n\t\t\tpublicKey: metadata.publicKey,\n\t\t\tmasterKeyEnvFile,\n\t\t};\n\t});\n\n/** BLS12-381 master key — 32 bytes, persisted as 64 hex chars. */\nconst BLS_MASTER_KEY_HEX_LEN = 64;\n/** BLS12-381 G2 uncompressed public key — 96 bytes, persisted as 192 hex chars. */\nconst BLS_PUBLIC_KEY_HEX_LEN = 192;\nconst HEX_ONLY_RE = /^[0-9a-fA-F]+$/;\n\n/** Stricter decode than the JSON-shape Schema alone — verifies the\n * persisted master key + public key are well-formed BLS12-381 hex of\n * the expected widths. The Schema validation alone accepts any\n * string, so a corrupt-on-disk JSON whose `publicKey` was overwritten\n * to a shorter / wrong-width value would otherwise be silently\n * reused on boot — leading to a master-key / KeyServer mismatch\n * (the master-key.env carries one key, the registered KeyServer's\n * on-chain `publicKey` carries a different one). Distilled-doc\n * invariant #6 (keypair B8 verify cascade) covers the on-chain\n * side; this is the on-disk side.\n *\n * We cannot in-process re-derive the G2 public key from the master\n * scalar without bundling a BLS12-381 implementation (the keygen\n * binary owns this in cargo-land). The width invariant is the\n * cheapest defense that catches the realistic corruption modes:\n * truncated writes, hand-edited JSON, stale persistence from an\n * older devstack version. */\nconst validatePersistedKeyMaterialShape = (\n\tmaterial: PersistedLocalKeygenKeyMaterial,\n\tname: string,\n): Effect.Effect<void, SealError> => {\n\tif (\n\t\tmaterial.masterKey.length !== BLS_MASTER_KEY_HEX_LEN ||\n\t\t!HEX_ONLY_RE.test(material.masterKey)\n\t) {\n\t\treturn Effect.fail(\n\t\t\tsealError('config-render', {\n\t\t\t\tname,\n\t\t\t\tmessage:\n\t\t\t\t\t`seal.local-keygen: persisted master key in ${MASTER_KEY_ENVFILE_BASENAME} ` +\n\t\t\t\t\t`is not ${BLS_MASTER_KEY_HEX_LEN}-char hex (got ${material.masterKey.length} chars). ` +\n\t\t\t\t\t'The on-disk state is corrupt; delete the seal service directory and ' +\n\t\t\t\t\t're-boot to regenerate, or restore from a known-good snapshot.',\n\t\t\t}),\n\t\t);\n\t}\n\tif (\n\t\tmaterial.publicKey.length !== BLS_PUBLIC_KEY_HEX_LEN ||\n\t\t!HEX_ONLY_RE.test(material.publicKey)\n\t) {\n\t\treturn Effect.fail(\n\t\t\tsealError('config-render', {\n\t\t\t\tname,\n\t\t\t\tmessage:\n\t\t\t\t\t`seal.local-keygen: persisted publicKey in ${LOCAL_KEYGEN_STATE_BASENAME} ` +\n\t\t\t\t\t`is not ${BLS_PUBLIC_KEY_HEX_LEN}-char hex (got ${material.publicKey.length} chars). ` +\n\t\t\t\t\t'The on-disk state is corrupt; reusing it would publish a KeyServer ' +\n\t\t\t\t\t'whose on-chain publicKey diverges from the local master key. ' +\n\t\t\t\t\t'Delete the seal service directory and re-boot to regenerate.',\n\t\t\t}),\n\t\t);\n\t}\n\treturn Effect.void;\n};\n\nconst sealConfigFingerprint = (parts: {\n\treadonly packageId: string;\n\treadonly keyServerObjectId: string;\n\treadonly nodeUrl: string;\n}): string =>\n\t[\n\t\t`package=${parts.packageId}`,\n\t\t`keyServer=${parts.keyServerObjectId}`,\n\t\t`nodeUrl=${parts.nodeUrl}`,\n\t].join('|');\n\nconst resolveMovePackagePath = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n): Effect.Effect<string, SealError, Scope.Scope | FileSystem.FileSystem | Path.Path> =>\n\topts.movePackagePath\n\t\t? Effect.succeed(opts.movePackagePath)\n\t\t: resolveDefaultSealSource(deps.runtime).pipe(Effect.map((s) => s.path));\n\nconst ensureLocalKeygenArtifacts = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n\tpublicKey: string,\n): Effect.Effect<\n\t{\n\t\treadonly packageId: string;\n\t\treadonly keyServerObjectId: string;\n\t},\n\tSealError | ArtifactPublishError,\n\tScope.Scope | FileSystem.FileSystem | Path.Path\n> =>\n\tEffect.gen(function* () {\n\t\tconst movePackagePath = yield* resolveMovePackagePath(deps, opts);\n\t\tconst { packageId } = yield* publishSealPackage(deps.publisher, {\n\t\t\tname: opts.name,\n\t\t\tchainId: deps.chainId,\n\t\t\tmovePackagePath,\n\t\t\tsigner: deps.signer,\n\t\t\tsdk: deps.sdk,\n\t\t\truntime: deps.runtime,\n\t\t\tchainProbe: deps.chainProbe,\n\t\t\t...(deps.buildImage !== undefined ? { buildImage: deps.buildImage } : {}),\n\t\t});\n\t\tconst { objectId: keyServerObjectId } = yield* registerKeyServer(deps.publisher, {\n\t\t\tname: opts.name,\n\t\t\tchainId: deps.chainId,\n\t\t\tkeyServerUrl: deps.routedUrl,\n\t\t\tsealPackageId: packageId,\n\t\t\tpublicKeyHex: publicKey,\n\t\t\tkeyServerName: opts.keyServerName,\n\t\t\tsigner: deps.signer,\n\t\t\tsdk: deps.sdk,\n\t\t\tchainProbe: deps.chainProbe,\n\t\t});\n\t\treturn { packageId, keyServerObjectId };\n\t});\n\nconst stageResolvedLocalKeygenState = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n\tartifacts: {\n\t\treadonly packageId: string;\n\t\treadonly keyServerObjectId: string;\n\t},\n\tkeypair: Pick<PersistedBlsKeypair, 'masterKey' | 'publicKey'>,\n): Effect.Effect<PersistedLocalKeygenState, SealError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst yaml = renderSealKeyServerConfig({\n\t\t\tsealPackageId: artifacts.packageId,\n\t\t\tnodeUrl: deps.suiRpcUrlInNetwork,\n\t\t\tkeyServerObjectId: artifacts.keyServerObjectId,\n\t\t});\n\t\tconst { masterKeyEnvFile } = yield* stageSealConfig(\n\t\t\tyaml,\n\t\t\tkeypair.masterKey,\n\t\t\tdeps.servicePath,\n\t\t\topts.name,\n\t\t);\n\t\tyield* stagePersistedLocalKeygenMetadata(deps.servicePath, opts.name, {\n\t\t\tversion: LOCAL_KEYGEN_STATE_VERSION,\n\t\t\tpublicKey: keypair.publicKey,\n\t\t});\n\t\treturn {\n\t\t\tpackageId: artifacts.packageId,\n\t\t\tkeyServerObjectId: artifacts.keyServerObjectId,\n\t\t\tmasterKey: keypair.masterKey,\n\t\t\tpublicKey: keypair.publicKey,\n\t\t\tmasterKeyEnvFile,\n\t\t};\n\t});\n\nconst startLocalKeygenContainer = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n\tcargoImage: ImageRef,\n\tstate: PersistedLocalKeygenState,\n): Effect.Effect<SealLocalKeygenResolved, SealError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst spec: KeyServerContainerSpec = buildKeyServerSpec({\n\t\t\tname: opts.name,\n\t\t\timage: cargoImage,\n\t\t\tcontainerName: deps.containerName,\n\t\t\tlabels: deps.labels,\n\t\t\tsuiNetwork: deps.suiNetworkName,\n\t\t\tservicePath: deps.servicePath,\n\t\t\tconfigFingerprint: sealConfigFingerprint({\n\t\t\t\tpackageId: state.packageId,\n\t\t\t\tkeyServerObjectId: state.keyServerObjectId,\n\t\t\t\tnodeUrl: deps.suiRpcUrlInNetwork,\n\t\t\t}),\n\t\t\troutedHostname: deps.routedHostname,\n\t\t\troutedUrl: deps.routedUrl,\n\t\t\treadyTimeoutMs: opts.readyTimeoutMs,\n\t\t});\n\t\tconst { containerName } = yield* startKeyServer(deps.runtime, spec, opts.name);\n\t\tvoid containerName;\n\n\t\tconst serverConfigs: ReadonlyArray<SealKeyServerEntry> = [\n\t\t\t{ objectId: state.keyServerObjectId, weight: 1 },\n\t\t];\n\t\tconst keyManager = makeKeyManager({\n\t\t\tmasterKeyEnvFile: state.masterKeyEnvFile,\n\t\t});\n\t\treturn {\n\t\t\tkeyServer: {\n\t\t\t\tserverConfigs,\n\t\t\t\tkeyServerUrl: deps.routedUrl,\n\t\t\t\tobjectId: state.keyServerObjectId,\n\t\t\t},\n\t\t\tkeyManager,\n\t\t\tpackageId: state.packageId,\n\t\t} satisfies SealLocalKeygenResolved;\n\t});\n\n// ---------------------------------------------------------------------------\n// Boot pipeline\n// ---------------------------------------------------------------------------\n\n/** The plugin's aggregate acquire body. Calls each phase in\n * order; failures surface as typed `SealError` /\n * `ArtifactPublishError` through the Effect's error channel.\n *\n * Distilled-doc invariant #2 — phase order is keygen → publish →\n * register → config-render → container. Any phase failure short-\n * circuits before downstream phases (cheap on warm restart because\n * the ArtifactPublisher cache + verify covers steps 3-4). */\nexport const bootLocalKeygen = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n): Effect.Effect<\n\tSealLocalKeygenResolved,\n\tSealError | ArtifactPublishError,\n\tScope.Scope | FileSystem.FileSystem | Path.Path\n> =>\n\tEffect.gen(function* () {\n\t\t// ---- cargo image (bootstrap asset) -----------------------\n\t\t// The cargo image's resolver honors `SEAL_CARGO_IMAGE_OVERRIDE`\n\t\t// for the pre-baked path; falls back to a documented seam error\n\t\t// pointing at the override hatch.\n\t\tconst cargoImage: ImageRef = yield* resolveDefaultSealCargoImage(deps.runtime, {\n\t\t\tapp: deps.labels.app,\n\t\t\tstack: deps.labels.stack,\n\t\t});\n\n\t\tconst persisted = yield* readPersistedLocalKeygenState(deps.servicePath);\n\t\tif (persisted !== null) {\n\t\t\t// Defensive invariant — if the on-disk publicKey or master key\n\t\t\t// has the wrong shape, the ArtifactPublisher cache might\n\t\t\t// happily reuse a stale on-chain KeyServer whose registered\n\t\t\t// publicKey no longer matches the master key the daemon\n\t\t\t// loads at boot. Refuse to proceed so the operator gets a\n\t\t\t// clear typed error instead of a silently-broken stack.\n\t\t\tyield* validatePersistedKeyMaterialShape(persisted, opts.name);\n\t\t\tconst artifacts = yield* ensureLocalKeygenArtifacts(deps, opts, persisted.publicKey);\n\t\t\tconst refreshed = yield* stageResolvedLocalKeygenState(deps, opts, artifacts, persisted);\n\t\t\treturn yield* startLocalKeygenContainer(deps, opts, cargoImage, refreshed);\n\t\t}\n\n\t\t// ---- keygen (BLS12-381 master + public) -----------------\n\t\t// The keypair flows into publish + register inputs. The\n\t\t// artifact publisher still owns the on-chain artifacts; this\n\t\t// local state only persists key material.\n\t\tconst keypair: PersistedBlsKeypair = yield* runSealKeygen(deps.runtime, opts.name, cargoImage);\n\n\t\t// ---- render config + stage master-key -------------------\n\t\t// Distilled-doc invariant #19 — `network: !Devnet` is hardcoded\n\t\t// by `renderSealKeyServerConfig`. Invariant #4 — `master-key.env`\n\t\t// MUST NOT be unlinked on scope close. `stageSealConfig` does\n\t\t// NOT register a finalizer.\n\t\tconst artifacts = yield* ensureLocalKeygenArtifacts(deps, opts, keypair.publicKey);\n\t\tconst state = yield* stageResolvedLocalKeygenState(deps, opts, artifacts, keypair);\n\t\treturn yield* startLocalKeygenContainer(deps, opts, cargoImage, state);\n\t}).pipe(\n\t\tEffect.catchTag('ArtifactPublishError', (err) =>\n\t\t\tEffect.fail(\n\t\t\t\tsealError('seal', {\n\t\t\t\t\tname: opts.name,\n\t\t\t\t\tmessage: `seal.local-keygen: on-chain artifact failure (${err.reason}): ${err.detail}`,\n\t\t\t\t\tcause: err,\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n"],"mappings":"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"}
1
+ {"version":3,"file":"local-keygen.mjs","names":[],"sources":["../../../../src/plugins/seal/mode/local-keygen.ts"],"sourcesContent":["// Seal local-keygen mode — the full localnet path.\n//\n// Distilled-doc reference (07-seal.md §\"Lifecycle / Startup —\n// `sealLocalKeygen`\"). Eight ordered phases:\n//\n// 0. Yield deps (Sui, Identity, ContainerRuntime, artifact publisher publisher).\n// 1. Image build (bootstrap asset — parallel with sui's boot).\n// 1a. Move source resolve (bootstrap asset, conditional).\n// 2. BLS keygen one-shot (real `runtime.runOneShot` against cargo\n// image; output parsed via regex).\n// 3. Publish seal Move package (via artifact publisher primitive).\n// 4. Register on-chain KeyServer (via artifact publisher primitive).\n// 5. Render config yaml + stage master-key env-file (atomic\n// FileSystem\n// writes).\n// 6. Start long-running key-server container (real\n// `runtime.ensureContainer`\n// + exec-based ready\n// probe).\n// 7. Project the plugin's aggregate value.\n//\n// Distilled-doc invariants pinned by this file:\n//\n// #1 KeyServer.url == routed hostname — single mint point\n// (`routedUrl` passed\n// identically into\n// `registerKeyServer`\n// and `buildKeyServerSpec`).\n// #2 Boot order keygen → publish → register → config → container.\n// #4 env-file for secret loading (not -e MASTER_KEY=…).\n// #5 No host-port publish.\n// #6 Keypair B8 verify cascade (deploy.ts:buildKeypairVerify).\n// #7 Signal-forwarding entrypoint shell (image-side).\n// #8 Fork incompatibility throw — handled at the barrel\n// (`index.ts`), NOT here.\n// #9 Peer-dep structural assignability (compile-time check\n// lives in registry-publish.ts).\n//\n// This is the substrate-name-LEAK boundary. Inside this file we can\n// say \"seal\"; outside `src/plugins/seal/`, the substrate doesn't\n// know seal exists.\n\nimport { Effect, FileSystem, Path, Schema, type Scope } from 'effect';\n\nimport { decodeJsonText } from '../../../substrate/runtime/runtime-decode.ts';\n\nimport type { ChainProbe } from '../../../contracts/chain-probe.ts';\nimport type { ContainerLabelTuple } from '../../../contracts/snapshotable.ts';\nimport type { ContainerRuntime, ImageRef } from '../../../contracts/container-runtime.ts';\nimport type {\n\tArtifactPublishError,\n\tArtifactPublisher,\n} from '../../../primitives/artifact-publisher.ts';\nimport type { AccountValue } from '../../account/index.ts';\nimport {\n\tparseMasterKeyEnvFile,\n\trenderSealKeyServerConfig,\n\tstageSealConfig,\n} from '../config-render.ts';\nimport {\n\tpublishSealPackage,\n\tregisterKeyServer,\n\ttype SealObjectProbeKey,\n\ttype SealSuiSdk,\n} from '../deploy.ts';\nimport { sealError, type SealError } from '../errors.ts';\nimport { MASTER_KEY_ENVFILE_BASENAME, runSealKeygen, type PersistedBlsKeypair } from '../keygen.ts';\nimport { makeKeyManager } from '../key-manager.ts';\nimport { buildKeyServerSpec, startKeyServer, type KeyServerContainerSpec } from '../key-server.ts';\nimport type { SealKeyServerEntry, SealLocalKeygenResolved } from '../registry-publish.ts';\nimport { resolveDefaultSealCargoImage } from '../bootstrap-assets/cargo-image.ts';\nimport { resolveDefaultSealSource } from '../bootstrap-assets/source-fetch.ts';\nimport { atomicWriteFile } from '../../../substrate/runtime/atomic-write.ts';\nimport { versionedDocSchema } from '../../../substrate/versioned-doc-schema.ts';\n\n// ---------------------------------------------------------------------------\n// Options (factory-time)\n// ---------------------------------------------------------------------------\n\n/** Options the local-keygen mode accepts (07-seal.md\n * §\"Configuration\"). The\n * barrel (`index.ts`) folds the typed `SealLocalKeygenOptions`\n * surface into this internal shape after default resolution. */\nexport interface LocalKeygenOptions {\n\treadonly name?: string;\n\treadonly version?: string;\n\treadonly movePackagePath?: string;\n\treadonly readyTimeoutMs?: number;\n\treadonly keyServerName?: string;\n}\n\n/** Resolved options after defaults are applied. */\nexport interface ResolvedLocalKeygenOptions {\n\treadonly name: string;\n\treadonly version: string;\n\treadonly readyTimeoutMs: number;\n\treadonly keyServerName: string;\n\treadonly movePackagePath?: string;\n}\n\nconst DEFAULT_READY_TIMEOUT_MS = 60_000;\nconst DEFAULT_KEY_SERVER_NAME = 'devstack-local';\n\n/** Synchronous factory-time defaults application. Pure — no\n * validation throws because the localnet signer requirement is\n * enforced one layer up in the barrel (the type-narrowed\n * `sealFor(local).localKeygen({signer})` makes signer required). */\nexport const resolveLocalKeygenOptions = (\n\topts: LocalKeygenOptions,\n\tdefaultVersion: string,\n): ResolvedLocalKeygenOptions => ({\n\tname: opts.name ?? 'seal',\n\tversion: opts.version ?? defaultVersion,\n\treadyTimeoutMs: opts.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n\tkeyServerName: opts.keyServerName ?? DEFAULT_KEY_SERVER_NAME,\n\tmovePackagePath: opts.movePackagePath,\n});\n\n// ---------------------------------------------------------------------------\n// Inputs to the boot pipeline (acquire-time)\n// ---------------------------------------------------------------------------\n\n/** Acquire-time deps the boot pipeline consumes. Sourced by the\n * barrel (`index.ts`) from a mix of:\n *\n * - substrate-resolved values (`IdentityContext`,\n * `ContainerRuntimeService`, `ArtifactPublisher`);\n * - substrate-derived identifiers (chainId, servicePath,\n * subnet, etc.);\n * - router-resolved URL (single mint — distilled-doc invariant #1).\n *\n * The barrel composes these from substrate services\n * (ContainerRuntime, IdentityContext, ArtifactPublisher,\n * StackPathsService) at acquire-time. */\nexport interface LocalKeygenDeps {\n\treadonly runtime: ContainerRuntime;\n\treadonly publisher: ArtifactPublisher;\n\treadonly signer: AccountValue;\n\treadonly sdk: SealSuiSdk;\n\treadonly buildImage?: ImageRef;\n\treadonly chainProbe: ChainProbe<SealObjectProbeKey>;\n\treadonly chainId: string;\n\t/** Per-stack on-disk dir under `runtime/seal/`. Host path for the\n\t * config yaml + master-key env-file. */\n\treadonly servicePath: string;\n\t/** Container name composed by the substrate (app + stack + name +\n\t * role). */\n\treadonly containerName: string;\n\t/** Label tuple driving snapshot/inspect by labels. */\n\treadonly labels: ContainerLabelTuple;\n\t/** Seal's per-stack docker network — the key-server daemon attaches\n\t * to this network. */\n\treadonly suiNetworkName: string;\n\t/** In-network sui RPC URL — the daemon's CONFIG_PATH yaml carries\n\t * this. */\n\treadonly suiRpcUrlInNetwork: string;\n\t/** Router-resolved single mint (distilled-doc invariant #1). */\n\treadonly routedHostname: string;\n\treadonly routedUrl: string;\n}\n\ninterface PersistedLocalKeygenKeyMaterial {\n\treadonly masterKey: string;\n\treadonly publicKey: string;\n\treadonly masterKeyEnvFile: string;\n}\n\ninterface PersistedLocalKeygenState extends PersistedLocalKeygenKeyMaterial {\n\treadonly packageId: string;\n\treadonly keyServerObjectId: string;\n}\n\nconst LOCAL_KEYGEN_STATE_VERSION = 1;\nconst LOCAL_KEYGEN_STATE_BASENAME = 'local-keygen-state.v1.json';\n\nconst PersistedLocalKeygenMetadataSchema = versionedDocSchema(LOCAL_KEYGEN_STATE_VERSION, {\n\tpublicKey: Schema.String,\n});\n\ntype PersistedLocalKeygenMetadata = Schema.Schema.Type<typeof PersistedLocalKeygenMetadataSchema>;\n\nconst localKeygenMetadataPath = (servicePath: string): string =>\n\t`${servicePath}/${LOCAL_KEYGEN_STATE_BASENAME}`;\n\nconst readPersistedLocalKeygenMetadata = (\n\tservicePath: string,\n): Effect.Effect<PersistedLocalKeygenMetadata | null, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = localKeygenMetadataPath(servicePath);\n\t\tconst raw = yield* fs.readFileString(path).pipe(Effect.catch(() => Effect.succeed(null)));\n\t\tif (raw === null) return null;\n\t\t// Parse + decode failures here mean \"no usable persisted metadata\"\n\t\t// and the caller treats this as a miss; the typed\n\t\t// `RuntimeDecodeIssue` carries the parse/decode diagnostic for\n\t\t// debugging but is collapsed to `null` so the boot loop falls\n\t\t// through to a fresh keygen.\n\t\treturn yield* decodeJsonText(PersistedLocalKeygenMetadataSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) => issue,\n\t\t}).pipe(Effect.catch(() => Effect.succeed(null)));\n\t});\n\nconst stagePersistedLocalKeygenMetadata = (\n\tservicePath: string,\n\tname: string,\n\tmetadata: PersistedLocalKeygenMetadata,\n): Effect.Effect<void, SealError, FileSystem.FileSystem> =>\n\tatomicWriteFile(\n\t\tlocalKeygenMetadataPath(servicePath),\n\t\tnew TextEncoder().encode(`${JSON.stringify(metadata, null, 2)}\\n`),\n\t\t{\n\t\t\tmode: 0o644,\n\t\t\tparentMode: 0o700,\n\t\t},\n\t).pipe(\n\t\tEffect.catch((cause) =>\n\t\t\tEffect.fail(\n\t\t\t\tsealError('config-render', {\n\t\t\t\t\tname,\n\t\t\t\t\tmessage: `seal.config-render: failed to write ${LOCAL_KEYGEN_STATE_BASENAME} (${cause.stage})`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n\nconst readPersistedLocalKeygenState = (\n\tservicePath: string,\n): Effect.Effect<PersistedLocalKeygenKeyMaterial | null, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst masterKeyEnvFile = `${servicePath}/${MASTER_KEY_ENVFILE_BASENAME}`;\n\t\tconst masterKeyBody = yield* fs\n\t\t\t.readFileString(masterKeyEnvFile)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed(null)));\n\t\tif (masterKeyBody === null) return null;\n\t\tconst masterKey = parseMasterKeyEnvFile(masterKeyBody);\n\t\tif (masterKey === null) return null;\n\t\tconst metadata = yield* readPersistedLocalKeygenMetadata(servicePath);\n\t\tif (metadata === null) return null;\n\n\t\treturn {\n\t\t\tmasterKey,\n\t\t\tpublicKey: metadata.publicKey,\n\t\t\tmasterKeyEnvFile,\n\t\t};\n\t});\n\n/** BLS12-381 master key — 32 bytes, persisted as 64 hex chars. */\nconst BLS_MASTER_KEY_HEX_LEN = 64;\n/** BLS12-381 G2 uncompressed public key — 96 bytes, persisted as 192 hex chars. */\nconst BLS_PUBLIC_KEY_HEX_LEN = 192;\nconst HEX_ONLY_RE = /^[0-9a-fA-F]+$/;\n\n/** Stricter decode than the JSON-shape Schema alone — verifies the\n * persisted master key + public key are well-formed BLS12-381 hex of\n * the expected widths. The Schema validation alone accepts any\n * string, so a corrupt-on-disk JSON whose `publicKey` was overwritten\n * to a shorter / wrong-width value would otherwise be silently\n * reused on boot — leading to a master-key / KeyServer mismatch\n * (the master-key.env carries one key, the registered KeyServer's\n * on-chain `publicKey` carries a different one). Distilled-doc\n * invariant #6 (keypair B8 verify cascade) covers the on-chain\n * side; this is the on-disk side.\n *\n * We cannot in-process re-derive the G2 public key from the master\n * scalar without bundling a BLS12-381 implementation (the keygen\n * binary owns this in cargo-land). The width invariant is the\n * cheapest defense that catches the realistic corruption modes:\n * truncated writes, hand-edited JSON, stale persistence from an\n * older devstack version. */\nconst validatePersistedKeyMaterialShape = (\n\tmaterial: PersistedLocalKeygenKeyMaterial,\n\tname: string,\n): Effect.Effect<void, SealError> => {\n\tif (\n\t\tmaterial.masterKey.length !== BLS_MASTER_KEY_HEX_LEN ||\n\t\t!HEX_ONLY_RE.test(material.masterKey)\n\t) {\n\t\treturn Effect.fail(\n\t\t\tsealError('config-render', {\n\t\t\t\tname,\n\t\t\t\tmessage:\n\t\t\t\t\t`seal.local-keygen: persisted master key in ${MASTER_KEY_ENVFILE_BASENAME} ` +\n\t\t\t\t\t`is not ${BLS_MASTER_KEY_HEX_LEN}-char hex (got ${material.masterKey.length} chars). ` +\n\t\t\t\t\t'The on-disk state is corrupt; delete the seal service directory and ' +\n\t\t\t\t\t're-boot to regenerate, or restore from a known-good snapshot.',\n\t\t\t}),\n\t\t);\n\t}\n\tif (\n\t\tmaterial.publicKey.length !== BLS_PUBLIC_KEY_HEX_LEN ||\n\t\t!HEX_ONLY_RE.test(material.publicKey)\n\t) {\n\t\treturn Effect.fail(\n\t\t\tsealError('config-render', {\n\t\t\t\tname,\n\t\t\t\tmessage:\n\t\t\t\t\t`seal.local-keygen: persisted publicKey in ${LOCAL_KEYGEN_STATE_BASENAME} ` +\n\t\t\t\t\t`is not ${BLS_PUBLIC_KEY_HEX_LEN}-char hex (got ${material.publicKey.length} chars). ` +\n\t\t\t\t\t'The on-disk state is corrupt; reusing it would publish a KeyServer ' +\n\t\t\t\t\t'whose on-chain publicKey diverges from the local master key. ' +\n\t\t\t\t\t'Delete the seal service directory and re-boot to regenerate.',\n\t\t\t}),\n\t\t);\n\t}\n\treturn Effect.void;\n};\n\nconst sealConfigFingerprint = (parts: {\n\treadonly packageId: string;\n\treadonly keyServerObjectId: string;\n\treadonly nodeUrl: string;\n}): string =>\n\t[\n\t\t`package=${parts.packageId}`,\n\t\t`keyServer=${parts.keyServerObjectId}`,\n\t\t`nodeUrl=${parts.nodeUrl}`,\n\t].join('|');\n\nconst resolveMovePackagePath = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n): Effect.Effect<string, SealError, Scope.Scope | FileSystem.FileSystem | Path.Path> =>\n\topts.movePackagePath\n\t\t? Effect.succeed(opts.movePackagePath)\n\t\t: resolveDefaultSealSource(deps.runtime).pipe(Effect.map((s) => s.path));\n\nconst ensureLocalKeygenArtifacts = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n\tpublicKey: string,\n): Effect.Effect<\n\t{\n\t\treadonly packageId: string;\n\t\treadonly keyServerObjectId: string;\n\t},\n\tSealError | ArtifactPublishError,\n\tScope.Scope | FileSystem.FileSystem | Path.Path\n> =>\n\tEffect.gen(function* () {\n\t\tconst movePackagePath = yield* resolveMovePackagePath(deps, opts);\n\t\tconst { packageId } = yield* publishSealPackage(deps.publisher, {\n\t\t\tname: opts.name,\n\t\t\tchainId: deps.chainId,\n\t\t\tmovePackagePath,\n\t\t\tsigner: deps.signer,\n\t\t\tsdk: deps.sdk,\n\t\t\truntime: deps.runtime,\n\t\t\tchainProbe: deps.chainProbe,\n\t\t\t...(deps.buildImage !== undefined ? { buildImage: deps.buildImage } : {}),\n\t\t});\n\t\tconst { objectId: keyServerObjectId } = yield* registerKeyServer(deps.publisher, {\n\t\t\tname: opts.name,\n\t\t\tchainId: deps.chainId,\n\t\t\tkeyServerUrl: deps.routedUrl,\n\t\t\tsealPackageId: packageId,\n\t\t\tpublicKeyHex: publicKey,\n\t\t\tkeyServerName: opts.keyServerName,\n\t\t\tsigner: deps.signer,\n\t\t\tsdk: deps.sdk,\n\t\t\tchainProbe: deps.chainProbe,\n\t\t});\n\t\treturn { packageId, keyServerObjectId };\n\t});\n\nconst stageResolvedLocalKeygenState = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n\tartifacts: {\n\t\treadonly packageId: string;\n\t\treadonly keyServerObjectId: string;\n\t},\n\tkeypair: Pick<PersistedBlsKeypair, 'masterKey' | 'publicKey'>,\n): Effect.Effect<PersistedLocalKeygenState, SealError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst yaml = renderSealKeyServerConfig({\n\t\t\tsealPackageId: artifacts.packageId,\n\t\t\tnodeUrl: deps.suiRpcUrlInNetwork,\n\t\t\tkeyServerObjectId: artifacts.keyServerObjectId,\n\t\t});\n\t\tconst { masterKeyEnvFile } = yield* stageSealConfig(\n\t\t\tyaml,\n\t\t\tkeypair.masterKey,\n\t\t\tdeps.servicePath,\n\t\t\topts.name,\n\t\t);\n\t\tyield* stagePersistedLocalKeygenMetadata(deps.servicePath, opts.name, {\n\t\t\tversion: LOCAL_KEYGEN_STATE_VERSION,\n\t\t\tpublicKey: keypair.publicKey,\n\t\t});\n\t\treturn {\n\t\t\tpackageId: artifacts.packageId,\n\t\t\tkeyServerObjectId: artifacts.keyServerObjectId,\n\t\t\tmasterKey: keypair.masterKey,\n\t\t\tpublicKey: keypair.publicKey,\n\t\t\tmasterKeyEnvFile,\n\t\t};\n\t});\n\nconst startLocalKeygenContainer = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n\tcargoImage: ImageRef,\n\tstate: PersistedLocalKeygenState,\n): Effect.Effect<SealLocalKeygenResolved, SealError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst spec: KeyServerContainerSpec = buildKeyServerSpec({\n\t\t\tname: opts.name,\n\t\t\timage: cargoImage,\n\t\t\tcontainerName: deps.containerName,\n\t\t\tlabels: deps.labels,\n\t\t\tsuiNetwork: deps.suiNetworkName,\n\t\t\tservicePath: deps.servicePath,\n\t\t\tconfigFingerprint: sealConfigFingerprint({\n\t\t\t\tpackageId: state.packageId,\n\t\t\t\tkeyServerObjectId: state.keyServerObjectId,\n\t\t\t\tnodeUrl: deps.suiRpcUrlInNetwork,\n\t\t\t}),\n\t\t\troutedHostname: deps.routedHostname,\n\t\t\troutedUrl: deps.routedUrl,\n\t\t\treadyTimeoutMs: opts.readyTimeoutMs,\n\t\t});\n\t\tconst { containerName } = yield* startKeyServer(deps.runtime, spec, opts.name);\n\t\tvoid containerName;\n\n\t\tconst serverConfigs: ReadonlyArray<SealKeyServerEntry> = [\n\t\t\t{ objectId: state.keyServerObjectId, weight: 1 },\n\t\t];\n\t\tconst keyManager = makeKeyManager({\n\t\t\tmasterKeyEnvFile: state.masterKeyEnvFile,\n\t\t});\n\t\treturn {\n\t\t\tkeyServer: {\n\t\t\t\tserverConfigs,\n\t\t\t\tkeyServerUrl: deps.routedUrl,\n\t\t\t\tobjectId: state.keyServerObjectId,\n\t\t\t\t// Self-signed Open-mode localnet server: the SDK can't verify it\n\t\t\t\t// against its on-chain registration, so verification is off.\n\t\t\t\tverifyKeyServers: false,\n\t\t\t},\n\t\t\tkeyManager,\n\t\t\tpackageId: state.packageId,\n\t\t} satisfies SealLocalKeygenResolved;\n\t});\n\n// ---------------------------------------------------------------------------\n// Boot pipeline\n// ---------------------------------------------------------------------------\n\n/** The plugin's aggregate acquire body. Calls each phase in\n * order; failures surface as typed `SealError` /\n * `ArtifactPublishError` through the Effect's error channel.\n *\n * Distilled-doc invariant #2 — phase order is keygen → publish →\n * register → config-render → container. Any phase failure short-\n * circuits before downstream phases (cheap on warm restart because\n * the ArtifactPublisher cache + verify covers steps 3-4). */\nexport const bootLocalKeygen = (\n\tdeps: LocalKeygenDeps,\n\topts: ResolvedLocalKeygenOptions,\n): Effect.Effect<\n\tSealLocalKeygenResolved,\n\tSealError | ArtifactPublishError,\n\tScope.Scope | FileSystem.FileSystem | Path.Path\n> =>\n\tEffect.gen(function* () {\n\t\t// ---- cargo image (bootstrap asset) -----------------------\n\t\t// The cargo image's resolver honors `SEAL_CARGO_IMAGE_OVERRIDE`\n\t\t// for the pre-baked path; falls back to a documented seam error\n\t\t// pointing at the override hatch.\n\t\tconst cargoImage: ImageRef = yield* resolveDefaultSealCargoImage(deps.runtime, {\n\t\t\tapp: deps.labels.app,\n\t\t\tstack: deps.labels.stack,\n\t\t});\n\n\t\tconst persisted = yield* readPersistedLocalKeygenState(deps.servicePath);\n\t\tif (persisted !== null) {\n\t\t\t// Defensive invariant — if the on-disk publicKey or master key\n\t\t\t// has the wrong shape, the ArtifactPublisher cache might\n\t\t\t// happily reuse a stale on-chain KeyServer whose registered\n\t\t\t// publicKey no longer matches the master key the daemon\n\t\t\t// loads at boot. Refuse to proceed so the operator gets a\n\t\t\t// clear typed error instead of a silently-broken stack.\n\t\t\tyield* validatePersistedKeyMaterialShape(persisted, opts.name);\n\t\t\tconst artifacts = yield* ensureLocalKeygenArtifacts(deps, opts, persisted.publicKey);\n\t\t\tconst refreshed = yield* stageResolvedLocalKeygenState(deps, opts, artifacts, persisted);\n\t\t\treturn yield* startLocalKeygenContainer(deps, opts, cargoImage, refreshed);\n\t\t}\n\n\t\t// ---- keygen (BLS12-381 master + public) -----------------\n\t\t// The keypair flows into publish + register inputs. The\n\t\t// artifact publisher still owns the on-chain artifacts; this\n\t\t// local state only persists key material.\n\t\tconst keypair: PersistedBlsKeypair = yield* runSealKeygen(deps.runtime, opts.name, cargoImage);\n\n\t\t// ---- render config + stage master-key -------------------\n\t\t// Distilled-doc invariant #19 — `network: !Devnet` is hardcoded\n\t\t// by `renderSealKeyServerConfig`. Invariant #4 — `master-key.env`\n\t\t// MUST NOT be unlinked on scope close. `stageSealConfig` does\n\t\t// NOT register a finalizer.\n\t\tconst artifacts = yield* ensureLocalKeygenArtifacts(deps, opts, keypair.publicKey);\n\t\tconst state = yield* stageResolvedLocalKeygenState(deps, opts, artifacts, keypair);\n\t\treturn yield* startLocalKeygenContainer(deps, opts, cargoImage, state);\n\t}).pipe(\n\t\tEffect.catchTag('ArtifactPublishError', (err) =>\n\t\t\tEffect.fail(\n\t\t\t\tsealError('seal', {\n\t\t\t\t\tname: opts.name,\n\t\t\t\t\tmessage: `seal.local-keygen: on-chain artifact failure (${err.reason}): ${err.detail}`,\n\t\t\t\t\tcause: err,\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n"],"mappings":"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"}
@@ -4,12 +4,18 @@ import { SealKeyManager } from "./key-manager.mjs";
4
4
  *
5
5
  * Distilled-doc invariant #9 + #18: peer-dep structural
6
6
  * assignability. `@mysten/seal` is a peer dep — we duplicate the
7
- * shape locally to keep the runtime import off the bundle. A
8
- * compile-time `_SealKeyServerEntryCheck`
9
- * guards against drift. */
7
+ * shape locally to keep the runtime import off the bundle (the
8
+ * `import type` above erases at compile time). The committee path
9
+ * needs `aggregatorUrl` (all fetch-key calls route through the
10
+ * aggregator) and, on mainnet, the API-key pair (`apiKeyName` +
11
+ * `apiKey`); independent servers use just `{objectId, weight}`. The
12
+ * `_drift` assertion below guards against the SDK shape changing
13
+ * underneath us. */
10
14
  interface SealKeyServerEntry {
11
15
  readonly objectId: string;
12
16
  readonly weight: number;
17
+ readonly apiKeyName?: string;
18
+ readonly apiKey?: string;
13
19
  readonly aggregatorUrl?: string;
14
20
  }
15
21
  /** Read-side resolved value. SDK-ready: pass `serverConfigs` to
@@ -18,6 +24,12 @@ interface SealKeyServer {
18
24
  readonly serverConfigs: ReadonlyArray<SealKeyServerEntry>;
19
25
  readonly keyServerUrl: string;
20
26
  readonly objectId: string;
27
+ /** Whether the SDK should verify the key servers against their on-chain
28
+ * registration. `true` on live / fork-known (real Mysten servers);
29
+ * `false` on local-keygen (self-signed Open-mode localnet server whose
30
+ * public key can't be verified). Required so every construction site
31
+ * sets it explicitly. */
32
+ readonly verifyKeyServers: boolean;
21
33
  }
22
34
  /** Public Seal resolved value. The key-server fields stay top-level
23
35
  * so the direct member ref is immediately SDK-ready; local-keygen
@@ -1 +1 @@
1
- {"version":3,"file":"registry-publish.mjs","names":[],"sources":["../../../src/plugins/seal/registry-publish.ts"],"sourcesContent":["// Seal plugin — public resolved value.\n//\n// Resource ids:\n//\n// - `seal:<name>` — read-side key-server fields plus the local-only\n// manager handle when the stack owns the master key.\n//\n// The `<name>` suffix lets multiple seal instances coexist in a stack\n// without colliding on the resource registry (distilled doc §Pain Points\n// #7 — multi-instance currently untested but structurally supported).\n\nimport { resource } from '../../api/define-plugin.ts';\nimport type { SealKeyManager } from './key-manager.ts';\n\n// ---------------------------------------------------------------------------\n// Read-side: SealKeyServer\n// ---------------------------------------------------------------------------\n\n/** Structural mirror of `@mysten/seal`'s `KeyServerConfig`.\n *\n * Distilled-doc invariant #9 + #18: peer-dep structural\n * assignability. `@mysten/seal` is a peer dep — we duplicate the\n * shape locally to keep the runtime import off the bundle. A\n * compile-time `_SealKeyServerEntryCheck`\n * guards against drift. */\nexport interface SealKeyServerEntry {\n\treadonly objectId: string;\n\treadonly weight: number;\n\treadonly aggregatorUrl?: string;\n}\n\n/** Read-side resolved value. SDK-ready: pass `serverConfigs` to\n * `new SealClient({serverConfigs, ...})` directly. */\nexport interface SealKeyServer {\n\treadonly serverConfigs: ReadonlyArray<SealKeyServerEntry>;\n\treadonly keyServerUrl: string;\n\treadonly objectId: string;\n}\n\n/** Public Seal resolved value. The key-server fields stay top-level\n * so the direct member ref is immediately SDK-ready; local-keygen\n * additionally exposes `manager`, while known deployments set it to\n * `null` because this stack does not own the master key. */\nexport interface SealResolved extends SealKeyServer {\n\treadonly mode: 'local-keygen' | 'live' | 'fork-known';\n\treadonly manager: SealKeyManager | null;\n}\n\n/** Resource id constructor for the read-side handle. */\nexport type SealResourceId<Name extends string> = `seal:${Name}`;\nexport const sealResourceId = <Name extends string>(name: Name): SealResourceId<Name> =>\n\t`seal:${name}`;\n\n/** Construct the read-side resource. */\nexport const makeSealResource = <Name extends string>(name: Name) =>\n\tresource<SealResourceId<Name>, SealResolved>(sealResourceId(name));\n\n// ---------------------------------------------------------------------------\n// Projection helpers — narrow the plugin's resolved value\n// ---------------------------------------------------------------------------\n\n/** Aggregate resolved value the plugin's acquire body returns.\n * Distilled-doc §\"SealLocalKeygenInternalShape\" — `{keyServer,\n * keyManager, packageId}`. Modes that don't own a manager omit it. */\nexport interface SealLocalKeygenResolved {\n\treadonly keyServer: SealKeyServer;\n\treadonly keyManager: SealKeyManager;\n\treadonly packageId: string;\n}\n\nexport interface SealKnownResolved {\n\treadonly keyServer: SealKeyServer;\n}\n"],"mappings":";;;AAkDA,MAAa,kBAAuC,SACnD,QAAQ;;AAGT,MAAa,oBAAyC,SACrD,SAA6C,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"registry-publish.mjs","names":[],"sources":["../../../src/plugins/seal/registry-publish.ts"],"sourcesContent":["// Seal plugin — public resolved value.\n//\n// Resource ids:\n//\n// - `seal:<name>` — read-side key-server fields plus the local-only\n// manager handle when the stack owns the master key.\n//\n// The `<name>` suffix lets multiple seal instances coexist in a stack\n// without colliding on the resource registry (distilled doc §Pain Points\n// #7 — multi-instance currently untested but structurally supported).\n\nimport type { KeyServerConfig } from '@mysten/seal';\n\nimport { resource } from '../../api/define-plugin.ts';\nimport type { SealKeyManager } from './key-manager.ts';\n\n// ---------------------------------------------------------------------------\n// Read-side: SealKeyServer\n// ---------------------------------------------------------------------------\n\n/** Structural mirror of `@mysten/seal`'s `KeyServerConfig`.\n *\n * Distilled-doc invariant #9 + #18: peer-dep structural\n * assignability. `@mysten/seal` is a peer dep — we duplicate the\n * shape locally to keep the runtime import off the bundle (the\n * `import type` above erases at compile time). The committee path\n * needs `aggregatorUrl` (all fetch-key calls route through the\n * aggregator) and, on mainnet, the API-key pair (`apiKeyName` +\n * `apiKey`); independent servers use just `{objectId, weight}`. The\n * `_drift` assertion below guards against the SDK shape changing\n * underneath us. */\nexport interface SealKeyServerEntry {\n\treadonly objectId: string;\n\treadonly weight: number;\n\treadonly apiKeyName?: string;\n\treadonly apiKey?: string;\n\treadonly aggregatorUrl?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Compile-time drift guard against `@mysten/seal`'s `KeyServerConfig`\n// ---------------------------------------------------------------------------\n//\n// `import type { KeyServerConfig }` above is erased at compile time, so this\n// guard adds NO runtime import. We mirror `KeyServerConfig` field-for-field;\n// if the SDK adds/removes/renames a field (e.g. makes `apiKey` required), the\n// mutual-extends `_AssertEq` collapses to `never` and this file fails to\n// compile — surfacing the drift at our boundary instead of at the SDK call.\n\n/** Strip `readonly` so our `readonly`-decorated mirror compares structurally\n * equal to the SDK's mutable `KeyServerConfig`. */\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n\n/** `true` iff `A` and `B` mutually extend (structurally equal); `never`\n * otherwise — which makes the `const _drift: … = true` assignment below fail\n * to typecheck, breaking the build on drift. */\ntype _AssertEq<A, B> = [A] extends [B] ? ([B] extends [A] ? true : never) : never;\n\nconst _drift: _AssertEq<Mutable<SealKeyServerEntry>, KeyServerConfig> = true;\nvoid _drift;\n\n/** Read-side resolved value. SDK-ready: pass `serverConfigs` to\n * `new SealClient({serverConfigs, ...})` directly. */\nexport interface SealKeyServer {\n\treadonly serverConfigs: ReadonlyArray<SealKeyServerEntry>;\n\treadonly keyServerUrl: string;\n\treadonly objectId: string;\n\t/** Whether the SDK should verify the key servers against their on-chain\n\t * registration. `true` on live / fork-known (real Mysten servers);\n\t * `false` on local-keygen (self-signed Open-mode localnet server whose\n\t * public key can't be verified). Required so every construction site\n\t * sets it explicitly. */\n\treadonly verifyKeyServers: boolean;\n}\n\n/** Public Seal resolved value. The key-server fields stay top-level\n * so the direct member ref is immediately SDK-ready; local-keygen\n * additionally exposes `manager`, while known deployments set it to\n * `null` because this stack does not own the master key. */\nexport interface SealResolved extends SealKeyServer {\n\treadonly mode: 'local-keygen' | 'live' | 'fork-known';\n\treadonly manager: SealKeyManager | null;\n}\n\n/** Resource id constructor for the read-side handle. */\nexport type SealResourceId<Name extends string> = `seal:${Name}`;\nexport const sealResourceId = <Name extends string>(name: Name): SealResourceId<Name> =>\n\t`seal:${name}`;\n\n/** Construct the read-side resource. */\nexport const makeSealResource = <Name extends string>(name: Name) =>\n\tresource<SealResourceId<Name>, SealResolved>(sealResourceId(name));\n\n// ---------------------------------------------------------------------------\n// Projection helpers — narrow the plugin's resolved value\n// ---------------------------------------------------------------------------\n\n/** Aggregate resolved value the plugin's acquire body returns.\n * Distilled-doc §\"SealLocalKeygenInternalShape\" — `{keyServer,\n * keyManager, packageId}`. Modes that don't own a manager omit it. */\nexport interface SealLocalKeygenResolved {\n\treadonly keyServer: SealKeyServer;\n\treadonly keyManager: SealKeyManager;\n\treadonly packageId: string;\n}\n\nexport interface SealKnownResolved {\n\treadonly keyServer: SealKeyServer;\n}\n"],"mappings":";;;AAsFA,MAAa,kBAAuC,SACnD,QAAQ;;AAGT,MAAa,oBAAyC,SACrD,SAA6C,eAAe,IAAI,CAAC"}
@@ -20,7 +20,7 @@ import { Effect, Scope } from "effect";
20
20
  declare const sui: <const O extends SuiOptions = {
21
21
  mode: "local";
22
22
  }>(opts?: O) => Plugin<"sui", {
23
- mode: "local" | "local-rpc" | "live" | "fork";
23
+ mode: "local" | "live" | "fork" | "local-rpc";
24
24
  fundingFaucetStrategy: FaucetStrategy | null;
25
25
  sdk: SuiSdkShim;
26
26
  rpcUrl: string;
@@ -51,7 +51,7 @@ declare const sui: <const O extends SuiOptions = {
51
51
  declare const suiFor: ModeNamespace<{
52
52
  local: {
53
53
  local: <const O extends Omit<SuiLocalOptions, "mode">>(opts?: O) => Plugin<"sui", {
54
- mode: "local" | "local-rpc" | "live" | "fork";
54
+ mode: "local" | "live" | "fork" | "local-rpc";
55
55
  fundingFaucetStrategy: FaucetStrategy | null;
56
56
  sdk: SuiSdkShim;
57
57
  rpcUrl: string;
@@ -70,7 +70,7 @@ declare const suiFor: ModeNamespace<{
70
70
  buildImage: ImageRef | null;
71
71
  }, readonly []>;
72
72
  localRpc: (opts: Omit<SuiLocalRpcOptions, "mode">) => Plugin<"sui", {
73
- mode: "local" | "local-rpc" | "live" | "fork";
73
+ mode: "local" | "live" | "fork" | "local-rpc";
74
74
  fundingFaucetStrategy: FaucetStrategy | null;
75
75
  sdk: SuiSdkShim;
76
76
  rpcUrl: string;
@@ -91,7 +91,7 @@ declare const suiFor: ModeNamespace<{
91
91
  };
92
92
  live: {
93
93
  testnet: (opts?: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
94
- mode: "local" | "local-rpc" | "live" | "fork";
94
+ mode: "local" | "live" | "fork" | "local-rpc";
95
95
  fundingFaucetStrategy: FaucetStrategy | null;
96
96
  sdk: SuiSdkShim;
97
97
  rpcUrl: string;
@@ -110,7 +110,7 @@ declare const suiFor: ModeNamespace<{
110
110
  buildImage: ImageRef | null;
111
111
  }, readonly []>;
112
112
  mainnet: (opts?: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
113
- mode: "local" | "local-rpc" | "live" | "fork";
113
+ mode: "local" | "live" | "fork" | "local-rpc";
114
114
  fundingFaucetStrategy: FaucetStrategy | null;
115
115
  sdk: SuiSdkShim;
116
116
  rpcUrl: string;
@@ -129,7 +129,7 @@ declare const suiFor: ModeNamespace<{
129
129
  buildImage: ImageRef | null;
130
130
  }, readonly []>;
131
131
  devnet: (opts?: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
132
- mode: "local" | "local-rpc" | "live" | "fork";
132
+ mode: "local" | "live" | "fork" | "local-rpc";
133
133
  fundingFaucetStrategy: FaucetStrategy | null;
134
134
  sdk: SuiSdkShim;
135
135
  rpcUrl: string;
@@ -148,7 +148,7 @@ declare const suiFor: ModeNamespace<{
148
148
  buildImage: ImageRef | null;
149
149
  }, readonly []>;
150
150
  custom: (opts: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
151
- mode: "local" | "local-rpc" | "live" | "fork";
151
+ mode: "local" | "live" | "fork" | "local-rpc";
152
152
  fundingFaucetStrategy: FaucetStrategy | null;
153
153
  sdk: SuiSdkShim;
154
154
  rpcUrl: string;
@@ -169,7 +169,7 @@ declare const suiFor: ModeNamespace<{
169
169
  };
170
170
  fork: {
171
171
  mainnet: (opts?: Omit<SuiForkOptions, "mode" | "upstream">) => Plugin<"sui", {
172
- mode: "local" | "local-rpc" | "live" | "fork";
172
+ mode: "local" | "live" | "fork" | "local-rpc";
173
173
  fundingFaucetStrategy: FaucetStrategy | null;
174
174
  sdk: SuiSdkShim;
175
175
  rpcUrl: string;
@@ -188,7 +188,7 @@ declare const suiFor: ModeNamespace<{
188
188
  buildImage: ImageRef | null;
189
189
  }, readonly []>;
190
190
  testnet: (opts?: Omit<SuiForkOptions, "mode" | "upstream">) => Plugin<"sui", {
191
- mode: "local" | "local-rpc" | "live" | "fork";
191
+ mode: "local" | "live" | "fork" | "local-rpc";
192
192
  fundingFaucetStrategy: FaucetStrategy | null;
193
193
  sdk: SuiSdkShim;
194
194
  rpcUrl: string;
@@ -207,7 +207,7 @@ declare const suiFor: ModeNamespace<{
207
207
  buildImage: ImageRef | null;
208
208
  }, readonly []>;
209
209
  devnet: (opts?: Omit<SuiForkOptions, "mode" | "upstream">) => Plugin<"sui", {
210
- mode: "local" | "local-rpc" | "live" | "fork";
210
+ mode: "local" | "live" | "fork" | "local-rpc";
211
211
  fundingFaucetStrategy: FaucetStrategy | null;
212
212
  sdk: SuiSdkShim;
213
213
  rpcUrl: string;
@@ -0,0 +1 @@
1
+ import { Effect, Scope } from "effect";
@@ -0,0 +1,115 @@
1
+ import { setCurrentPluginPhase } from "../../substrate/runtime/current-plugin.mjs";
2
+ import { ProbeTimeoutError, exitCodeProbeResult, waitForProbe } from "../../substrate/runtime/probes.mjs";
3
+ import { HOST_GATEWAY_EXTRA_HOSTS } from "../../substrate/runtime/host-gateway.mjs";
4
+ import { ensureManagedContainer } from "../../substrate/runtime/managed-container.mjs";
5
+ import { walrusDeployMountPaths } from "./deploy-paths.mjs";
6
+ import { walrusPluginError } from "./errors.mjs";
7
+ import { Effect, Scope } from "effect";
8
+ //#region src/plugins/walrus/client-services.ts
9
+ const DEFAULT_WALRUS_CLIENT_SERVICE_PORT = 31415;
10
+ const WALRUS_CLIENT_SERVICE_STOP_SIGNAL = "SIGINT";
11
+ const WALRUS_CLIENT_CONFIG_FILE = "client_config.yaml";
12
+ const WALRUS_CLIENT_WALLET_FILE = "sui_client.yaml";
13
+ const WALRUS_CLIENT_KEYSTORE_FILE = "sui_client.keystore";
14
+ const walrusClientServiceContainerName = (parts) => `devstack-${parts.app}-${parts.stack}-walrus-${parts.walrusName}-${parts.role}`;
15
+ const walrusClientServiceConfigHash = (parts) => [
16
+ "walrus-client-service-v2",
17
+ `role=${parts.role}`,
18
+ parts.deployConfigHash,
19
+ `client=${WALRUS_CLIENT_CONFIG_FILE},${WALRUS_CLIENT_WALLET_FILE},${WALRUS_CLIENT_KEYSTORE_FILE}`,
20
+ `mount=${parts.deploySourceHostPath}->${parts.deployMountTarget}`,
21
+ `port=${parts.containerPort}`,
22
+ `net=${parts.walrusNetworkName},${parts.suiNetworkName}`,
23
+ `rpc=${parts.suiRpcUrlInNetwork}`
24
+ ].join("|");
25
+ const SERVICE_READY_PROBE_INTERVAL_MS = 500;
26
+ const startWalrusClientServices = (runtime, spec) => Effect.gen(function* () {
27
+ const outerScope = yield* Effect.scope;
28
+ const serviceStopScope = yield* Scope.fork(outerScope, "parallel");
29
+ const deployMount = walrusDeployMountPaths({
30
+ stackRoot: spec.stackRoot,
31
+ deployOutputDirHostPath: spec.deployHostMountPath,
32
+ mountTarget: "/opt/walrus/runtime"
33
+ });
34
+ const readyTimeout = spec.readyTimeoutMs ?? 6e4;
35
+ const stopGraceSeconds = spec.stopGraceSeconds ?? 10;
36
+ const startOne = (role, image, options) => Effect.gen(function* () {
37
+ const containerName = walrusClientServiceContainerName({
38
+ app: spec.app,
39
+ stack: spec.stack,
40
+ walrusName: spec.walrusName,
41
+ role
42
+ });
43
+ yield* setCurrentPluginPhase(`creating Walrus ${role} container ${containerName}`);
44
+ const ensureService = ensureManagedContainer({
45
+ runtime,
46
+ identity: {
47
+ app: spec.app,
48
+ stack: spec.stack
49
+ },
50
+ plugin: "walrus",
51
+ role,
52
+ spec: {
53
+ name: containerName,
54
+ image,
55
+ recreate: "on-config-change",
56
+ configHash: walrusClientServiceConfigHash({
57
+ role,
58
+ deployConfigHash: spec.deployConfigHash,
59
+ deploySourceHostPath: deployMount.sourceHostPath,
60
+ deployMountTarget: deployMount.mountTarget,
61
+ containerPort: options.port,
62
+ walrusNetworkName: spec.walrusNetworkName,
63
+ suiNetworkName: spec.suiNetworkName,
64
+ suiRpcUrlInNetwork: spec.suiRpcUrlInNetwork
65
+ }),
66
+ env: {
67
+ DEPLOY_OUTPUT_DIR: deployMount.outputDirInContainer,
68
+ SUI_RPC_URL: spec.suiRpcUrlInNetwork,
69
+ WALRUS_CLIENT_SERVICE_BIND_ADDRESS: `0.0.0.0:${options.port}`,
70
+ WALRUS_CONTAINER_PORT: String(options.port)
71
+ },
72
+ command: [role],
73
+ networkAttach: [spec.walrusNetworkName, spec.suiNetworkName],
74
+ extraHosts: HOST_GATEWAY_EXTRA_HOSTS,
75
+ mounts: [{
76
+ source: deployMount.sourceHostPath,
77
+ target: deployMount.mountTarget,
78
+ readonly: true
79
+ }],
80
+ stopGraceSeconds,
81
+ stopSignal: WALRUS_CLIENT_SERVICE_STOP_SIGNAL
82
+ },
83
+ mapError: (cause) => walrusPluginError(role, `walrus ${role} ensureContainer failed: ${cause.reason}: ${cause.detail}`, { cause })
84
+ });
85
+ const handle = yield* Scope.provide(ensureService, serviceStopScope);
86
+ yield* setCurrentPluginPhase(`waiting for Walrus ${role} HTTP status on 127.0.0.1:${options.port}`);
87
+ yield* waitForProbe({
88
+ label: `walrus.${role}`,
89
+ timeoutMs: readyTimeout,
90
+ intervalMs: SERVICE_READY_PROBE_INTERVAL_MS,
91
+ probe: () => runtime.exec(handle, [
92
+ "sh",
93
+ "-c",
94
+ `curl -fsS --max-time 2 http://127.0.0.1:${options.port}/status >/dev/null`
95
+ ]).pipe(Effect.map(exitCodeProbeResult))
96
+ }).pipe(Effect.mapError((cause) => {
97
+ if (cause instanceof ProbeTimeoutError) return walrusPluginError(role, `walrus ${role} never became ready within ${readyTimeout}ms (container=${containerName}, probe=http://127.0.0.1:${options.port}/status).`, { cause: cause.lastError ?? cause.lastNotReady ?? cause });
98
+ return walrusPluginError(role, `walrus ${role} ready-probe exec failed: ${cause.reason}: ${cause.detail}`, { cause });
99
+ }));
100
+ return {
101
+ role,
102
+ containerName,
103
+ containerPort: options.port
104
+ };
105
+ });
106
+ const [aggregator, publisher] = yield* Effect.all([spec.options.aggregator === null || spec.images.aggregator === null ? Effect.succeed(null) : startOne("aggregator", spec.images.aggregator, spec.options.aggregator), spec.options.publisher === null || spec.images.publisher === null ? Effect.succeed(null) : startOne("publisher", spec.images.publisher, spec.options.publisher)], { concurrency: "unbounded" });
107
+ return {
108
+ aggregator,
109
+ publisher
110
+ };
111
+ });
112
+ //#endregion
113
+ export { DEFAULT_WALRUS_CLIENT_SERVICE_PORT, startWalrusClientServices };
114
+
115
+ //# sourceMappingURL=client-services.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-services.mjs","names":[],"sources":["../../../src/plugins/walrus/client-services.ts"],"sourcesContent":["// Walrus local client-service lifecycle.\n//\n// These are the release-provided `walrus aggregator` and `walrus publisher`\n// subcommands, wrapped only enough to feed them the local deploy outputs and\n// make them routable through devstack.\n\nimport { Effect, Scope } from 'effect';\n\nimport type {\n\tContainerHandle,\n\tContainerRuntime,\n\tImageRef,\n} from '../../contracts/container-runtime.ts';\nimport { HOST_GATEWAY_EXTRA_HOSTS } from '../../substrate/runtime/host-gateway.ts';\nimport { ensureManagedContainer } from '../../substrate/runtime/managed-container.ts';\nimport {\n\tProbeTimeoutError,\n\texitCodeProbeResult,\n\twaitForProbe,\n} from '../../substrate/runtime/probes.ts';\nimport { setCurrentPluginPhase } from '../../substrate/runtime/current-plugin.ts';\nimport { walrusDeployMountPaths } from './deploy-paths.ts';\nimport { walrusPluginError, type WalrusPluginError } from './errors.ts';\n\nexport const DEFAULT_WALRUS_CLIENT_SERVICE_PORT = 31_415;\nexport const DEFAULT_WALRUS_CLIENT_SERVICE_READY_TIMEOUT_MS = 60_000;\nexport const DEFAULT_WALRUS_CLIENT_SERVICE_STOP_GRACE_SECONDS = 10;\nexport const WALRUS_CLIENT_SERVICE_STOP_SIGNAL = 'SIGINT';\nexport const WALRUS_CLIENT_CONFIG_FILE = 'client_config.yaml';\nexport const WALRUS_CLIENT_WALLET_FILE = 'sui_client.yaml';\nexport const WALRUS_CLIENT_KEYSTORE_FILE = 'sui_client.keystore';\n\nexport type WalrusClientServiceRole = 'aggregator' | 'publisher';\n\nexport interface WalrusClientServiceOptions {\n\treadonly port: number;\n}\n\nexport interface WalrusClientService {\n\treadonly role: WalrusClientServiceRole;\n\treadonly containerName: string;\n\treadonly containerPort: number;\n}\n\nexport interface WalrusClientServices {\n\treadonly aggregator: WalrusClientService | null;\n\treadonly publisher: WalrusClientService | null;\n}\n\nexport interface StartWalrusClientServicesSpec {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly walrusName: string;\n\treadonly images: {\n\t\treadonly aggregator: ImageRef | null;\n\t\treadonly publisher: ImageRef | null;\n\t};\n\treadonly options: {\n\t\treadonly aggregator: WalrusClientServiceOptions | null;\n\t\treadonly publisher: WalrusClientServiceOptions | null;\n\t};\n\treadonly walrusNetworkName: string;\n\treadonly suiNetworkName: string;\n\treadonly deployHostMountPath: string;\n\treadonly stackRoot: string;\n\treadonly deployConfigHash: string;\n\treadonly suiRpcUrlInNetwork: string;\n\treadonly stopGraceSeconds?: number;\n\treadonly readyTimeoutMs?: number;\n}\n\nexport const walrusClientServiceContainerName = (parts: {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly walrusName: string;\n\treadonly role: WalrusClientServiceRole;\n}): string => `devstack-${parts.app}-${parts.stack}-walrus-${parts.walrusName}-${parts.role}`;\n\nexport const walrusClientServiceConfigHash = (parts: {\n\treadonly role: WalrusClientServiceRole;\n\treadonly deployConfigHash: string;\n\treadonly deploySourceHostPath: string;\n\treadonly deployMountTarget: string;\n\treadonly containerPort: number;\n\treadonly walrusNetworkName: string;\n\treadonly suiNetworkName: string;\n\treadonly suiRpcUrlInNetwork: string;\n}): string =>\n\t[\n\t\t'walrus-client-service-v2',\n\t\t`role=${parts.role}`,\n\t\tparts.deployConfigHash,\n\t\t`client=${WALRUS_CLIENT_CONFIG_FILE},${WALRUS_CLIENT_WALLET_FILE},${WALRUS_CLIENT_KEYSTORE_FILE}`,\n\t\t`mount=${parts.deploySourceHostPath}->${parts.deployMountTarget}`,\n\t\t`port=${parts.containerPort}`,\n\t\t`net=${parts.walrusNetworkName},${parts.suiNetworkName}`,\n\t\t`rpc=${parts.suiRpcUrlInNetwork}`,\n\t].join('|');\n\nconst SERVICE_READY_PROBE_INTERVAL_MS = 500;\n\nexport const startWalrusClientServices = (\n\truntime: ContainerRuntime,\n\tspec: StartWalrusClientServicesSpec,\n): Effect.Effect<WalrusClientServices, WalrusPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst outerScope = yield* Effect.scope;\n\t\tconst serviceStopScope = yield* Scope.fork(outerScope, 'parallel');\n\t\tconst deployMount = walrusDeployMountPaths({\n\t\t\tstackRoot: spec.stackRoot,\n\t\t\tdeployOutputDirHostPath: spec.deployHostMountPath,\n\t\t\tmountTarget: '/opt/walrus/runtime',\n\t\t});\n\t\tconst readyTimeout = spec.readyTimeoutMs ?? DEFAULT_WALRUS_CLIENT_SERVICE_READY_TIMEOUT_MS;\n\t\tconst stopGraceSeconds =\n\t\t\tspec.stopGraceSeconds ?? DEFAULT_WALRUS_CLIENT_SERVICE_STOP_GRACE_SECONDS;\n\n\t\tconst startOne = (\n\t\t\trole: WalrusClientServiceRole,\n\t\t\timage: ImageRef,\n\t\t\toptions: WalrusClientServiceOptions,\n\t\t): Effect.Effect<WalrusClientService, WalrusPluginError, Scope.Scope> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst containerName = walrusClientServiceContainerName({\n\t\t\t\t\tapp: spec.app,\n\t\t\t\t\tstack: spec.stack,\n\t\t\t\t\twalrusName: spec.walrusName,\n\t\t\t\t\trole,\n\t\t\t\t});\n\n\t\t\t\tyield* setCurrentPluginPhase(`creating Walrus ${role} container ${containerName}`);\n\t\t\t\tconst ensureService: Effect.Effect<ContainerHandle, WalrusPluginError, Scope.Scope> =\n\t\t\t\t\tensureManagedContainer<WalrusPluginError>({\n\t\t\t\t\t\truntime,\n\t\t\t\t\t\tidentity: { app: spec.app, stack: spec.stack },\n\t\t\t\t\t\tplugin: 'walrus',\n\t\t\t\t\t\trole,\n\t\t\t\t\t\tspec: {\n\t\t\t\t\t\t\tname: containerName,\n\t\t\t\t\t\t\timage,\n\t\t\t\t\t\t\trecreate: 'on-config-change',\n\t\t\t\t\t\t\tconfigHash: walrusClientServiceConfigHash({\n\t\t\t\t\t\t\t\trole,\n\t\t\t\t\t\t\t\tdeployConfigHash: spec.deployConfigHash,\n\t\t\t\t\t\t\t\tdeploySourceHostPath: deployMount.sourceHostPath,\n\t\t\t\t\t\t\t\tdeployMountTarget: deployMount.mountTarget,\n\t\t\t\t\t\t\t\tcontainerPort: options.port,\n\t\t\t\t\t\t\t\twalrusNetworkName: spec.walrusNetworkName,\n\t\t\t\t\t\t\t\tsuiNetworkName: spec.suiNetworkName,\n\t\t\t\t\t\t\t\tsuiRpcUrlInNetwork: spec.suiRpcUrlInNetwork,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tenv: {\n\t\t\t\t\t\t\t\tDEPLOY_OUTPUT_DIR: deployMount.outputDirInContainer,\n\t\t\t\t\t\t\t\tSUI_RPC_URL: spec.suiRpcUrlInNetwork,\n\t\t\t\t\t\t\t\tWALRUS_CLIENT_SERVICE_BIND_ADDRESS: `0.0.0.0:${options.port}`,\n\t\t\t\t\t\t\t\tWALRUS_CONTAINER_PORT: String(options.port),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tcommand: [role],\n\t\t\t\t\t\t\tnetworkAttach: [spec.walrusNetworkName, spec.suiNetworkName],\n\t\t\t\t\t\t\textraHosts: HOST_GATEWAY_EXTRA_HOSTS,\n\t\t\t\t\t\t\tmounts: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tsource: deployMount.sourceHostPath,\n\t\t\t\t\t\t\t\t\ttarget: deployMount.mountTarget,\n\t\t\t\t\t\t\t\t\treadonly: true,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tstopGraceSeconds,\n\t\t\t\t\t\t\tstopSignal: WALRUS_CLIENT_SERVICE_STOP_SIGNAL,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmapError: (cause) =>\n\t\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t\trole,\n\t\t\t\t\t\t\t\t`walrus ${role} ensureContainer 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});\n\t\t\t\tconst handle = yield* Scope.provide(ensureService, serviceStopScope);\n\n\t\t\t\tyield* setCurrentPluginPhase(\n\t\t\t\t\t`waiting for Walrus ${role} HTTP status on 127.0.0.1:${options.port}`,\n\t\t\t\t);\n\t\t\t\tyield* waitForProbe({\n\t\t\t\t\tlabel: `walrus.${role}`,\n\t\t\t\t\ttimeoutMs: readyTimeout,\n\t\t\t\t\tintervalMs: SERVICE_READY_PROBE_INTERVAL_MS,\n\t\t\t\t\tprobe: () =>\n\t\t\t\t\t\truntime\n\t\t\t\t\t\t\t.exec(handle, [\n\t\t\t\t\t\t\t\t'sh',\n\t\t\t\t\t\t\t\t'-c',\n\t\t\t\t\t\t\t\t`curl -fsS --max-time 2 http://127.0.0.1:${options.port}/status >/dev/null`,\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t.pipe(Effect.map(exitCodeProbeResult)),\n\t\t\t\t}).pipe(\n\t\t\t\t\tEffect.mapError((cause) => {\n\t\t\t\t\t\tif (cause instanceof ProbeTimeoutError) {\n\t\t\t\t\t\t\treturn walrusPluginError(\n\t\t\t\t\t\t\t\trole,\n\t\t\t\t\t\t\t\t`walrus ${role} never became ready within ${readyTimeout}ms ` +\n\t\t\t\t\t\t\t\t\t`(container=${containerName}, probe=http://127.0.0.1:${options.port}/status).`,\n\t\t\t\t\t\t\t\t{ cause: cause.lastError ?? cause.lastNotReady ?? cause },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn walrusPluginError(\n\t\t\t\t\t\t\trole,\n\t\t\t\t\t\t\t`walrus ${role} ready-probe exec failed: ${cause.reason}: ${cause.detail}`,\n\t\t\t\t\t\t\t{ cause },\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn { role, containerName, containerPort: options.port };\n\t\t\t});\n\n\t\tconst [aggregator, publisher] = yield* Effect.all(\n\t\t\t[\n\t\t\t\tspec.options.aggregator === null || spec.images.aggregator === null\n\t\t\t\t\t? Effect.succeed(null)\n\t\t\t\t\t: startOne('aggregator', spec.images.aggregator, spec.options.aggregator),\n\t\t\t\tspec.options.publisher === null || spec.images.publisher === null\n\t\t\t\t\t? Effect.succeed(null)\n\t\t\t\t\t: startOne('publisher', spec.images.publisher, spec.options.publisher),\n\t\t\t],\n\t\t\t{ concurrency: 'unbounded' },\n\t\t);\n\n\t\treturn { aggregator, publisher };\n\t});\n"],"mappings":"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"}
@@ -39,11 +39,17 @@ const SuiObjectExistsShape = Schema.Struct({ objectId: Schema.String });
39
39
  * Budget: 5 probes × 3s = 15s. */
40
40
  const WALRUS_DEPLOY_VERIFY_READINESS_RETRIES = 5;
41
41
  const WALRUS_DEPLOY_VERIFY_READINESS_DELAY = "3 seconds";
42
- const requiredDeployOutputFiles = (inputs) => [join(inputs.outputDirHostPath, "deploy"), ...Array.from({ length: inputs.committeeSize }, (_, nodeIndex) => [
43
- join(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.yaml`),
44
- join(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}-sui.yaml`),
45
- join(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.keystore`)
46
- ]).flat()];
42
+ const requiredDeployOutputFiles = (inputs) => [
43
+ join(inputs.outputDirHostPath, "deploy"),
44
+ join(inputs.outputDirHostPath, "client_config.yaml"),
45
+ join(inputs.outputDirHostPath, "sui_client.yaml"),
46
+ join(inputs.outputDirHostPath, "sui_client.keystore"),
47
+ ...Array.from({ length: inputs.committeeSize }, (_, nodeIndex) => [
48
+ join(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.yaml`),
49
+ join(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}-sui.yaml`),
50
+ join(inputs.outputDirHostPath, `dryrun-node-${nodeIndex}.keystore`)
51
+ ]).flat()
52
+ ];
47
53
  const deployOutputFilesComplete = (inputs) => Effect.tryPromise({
48
54
  try: async () => {
49
55
  await Promise.all(requiredDeployOutputFiles(inputs).map((file) => access(file)));
@@ -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":";;;;;;;;;;;;;;AAwEuC,OAAO,OAAO;CACpD,gBAAgB,OAAO;CACvB,iBAAiB,OAAO;AACzB,CAAC;AAGD,MAAM,uBAAuB,OAAO,OAAO,EAC1C,UAAU,OAAO,OAClB,CAAC;;;;;;;;;;;;;;;;;;;;;AAsBD,MAAM,yCAAyC;AAC/C,MAAM,uCAAuC;AAE7C,MAAM,6BAA6B,WAAgD;CAClF,KAAK,OAAO,mBAAmB,QAAQ;CACvC,KAAK,OAAO,mBAAmB,oBAAoB;CACnD,KAAK,OAAO,mBAAmB,iBAAiB;CAChD,KAAK,OAAO,mBAAmB,qBAAqB;CACpD,GAAG,MAAM,KAAK,EAAE,QAAQ,OAAO,cAAc,IAAI,GAAG,cAAc;EACjE,KAAK,OAAO,mBAAmB,eAAe,UAAU,MAAM;EAC9D,KAAK,OAAO,mBAAmB,eAAe,UAAU,UAAU;EAClE,KAAK,OAAO,mBAAmB,eAAe,UAAU,UAAU;CACnE,CAAC,CAAC,CAAC,KAAK;AACT;AAEA,MAAM,6BACL,WAEA,OAAO,WAAW;CACjB,KAAK,YAAY;EAChB,MAAM,QAAQ,IAAI,0BAA0B,MAAM,CAAC,CAAC,KAAK,SAAS,OAAO,IAAI,CAAC,CAAC;EAC/E,OAAO;CACR;CACA,QAAQ,UACP,kBACC,UACA,2DAA2D,OAAO,qBAClE,EAAE,MAAM,CACT;AACF,CAAC,CAAC,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,KAAK,CAAC,CAAC;;;;;AAuClD,MAAM,oBAAoB,IAAI;AAE9B,MAAM,yBAAyB,WAC9B,OAAO,WAAW;CACjB,KAAK,YAAY;EAChB,MAAM,MAAM,OAAO,mBAAmB,EAAE,WAAW,KAAK,CAAC;EAOzD,oBACC,KAAK,OAAO,mBAAmB,uBAAuB,GACtD,iCACA,EAAE,MAAM,IAAM,CACf;CACD;CACA,QAAQ,UACP,kBACC,UACA,oDAAoD,OAAO,qBAC3D,EAAE,MAAM,CACT;AACF,CAAC;AAEF,MAAM,oBACL,QACA,WACY;CACZ,MAAM,qBACL,OAAO,aAAa,MACjB,gGACA;CACJ,OACC,kCAAkC,OAAO,SAAS,KAClD,qBACA,cAAc,OAAO,kBAAkB,aAAa,OAAO,cAAc,UAAU,OAAO,WAC1F,gBAAgB,UAAU,OAAO,MAAM,IACvC,gBAAgB,UAAU,OAAO,MAAM;AAEzC;AAEA,MAAM,uBAAuB,WAI5B,OAAO,aAAa,OACpB,mCAAmC,KAAK,OAAO,MAAM,KACrD,wBAAwB,KAAK,OAAO,MAAM;;;;;;;;;;;;;;;;AAiB3C,MAAa,qBAAqB,WAA6C;CAY9E,MAAM,QAAQ,MAAkC;EAG/C,MAAM,IADI,IADK,OAAO,gBAAgB,EAAE,mBAC7B,CAAC,CAAC,KAAK,MACR,CAAC,GAAG;EACd,IAAI,MAAM,KAAA,KAAa,MAAM,QAAQ,OAAO,KAAA;EAC5C,IAAI,CAAC,oBAAoB,KAAK,CAAC,GAAG,OAAO,KAAA;EACzC,OAAO;CACR;CACA,MAAM,kBAAkB,KAAK,mBAAmB,KAAK,KAAK,YAAY;CACtE,MAAM,eAAe,KAAK,eAAe,KAAK,KAAK,kBAAkB;CACrE,MAAM,gBACL,KAAK,gBAAgB,KAAK,KAAK,iBAAiB,KAAK,KAAK,mBAAmB;CAC9E,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,eAAe,OAAO;CAChE,OAAO;EACN;EACA;EACA;EACA,sBAAsB,KAAK,wBAAwB;EACnD,gBAAgB,KAAK,iBAAiB;EACtC,gBAAgB,KAAK,iBAAiB;CACvC;AACD;;;;;;;;;;AAWA,MAAa,oBACZ,SACA,WAEA,OAAO,IAAI,aAAa;CACvB,IAAI,OAAO,sBAAsB,KAAA,GAChC,OAAO,OAAO,kBAAkB,KAC/B,OAAO,UAAU,UAChB,kBACC,UACA,2DAA2D,mBAAmB,KAAK,KACnF,EAAE,MAAM,CACT,CACD,CACD;CAED,OAAO,sBAAsB,MAAM;CAEnC,MAAM,cAAc,mBAAmB;CACvC,MAAM,cAAc,uBAAuB;EAC1C,WAAW,OAAO;EAClB,yBAAyB,OAAO;EAChC,aAAa;CACd,CAAC;CACD,MAAM,OAA8B;EACnC;EACA;EACA,YAAY;EACZ;EACA,OAAO,OAAO,aAAa;EAC3B;EACA,OAAO,OAAO,MAAM;EACpB;EACA,OAAO;EACP;EACA,OAAO;EACP;EACA,OAAO;EACP;EACA,OAAO;EACP;EACA,OAAO;CACR;CAEA,MAAM,mBACL,QACE,WAAW;EACX,OAAO,OAAO;EACd;EACA,QAAQ,CACP;GACC,QAAQ,YAAY;GACpB,QAAQ,YAAY;EACrB,CACD;EACA,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK,EAAE,uBAAuB,YAAY,EAAE;EACnF,SAAS,OAAO;EAKhB,YAAY;EACZ,eAAe;CAChB,CAAC,CAAC,CACD,KACA,OAAO,OAAO,UACb,OAAO,KACN,kBACC,UACA,kCAAkC,MAAM,OAAO,IAAI,MAAM,UACzD,EAAE,MAAM,CACT,CACD,CACD,CACD;CASF,MAAM,SAAS,OAPC,OAAO,IAAI,aAAa;EACvC,MAAM,SAAS,OAAO,WAAW;EACjC,IAAI,oBAAoB,MAAM,GAC7B,OAAO,sBAAsB,MAAM;EAEpC,OAAO;CACR,CAC4B,CAAC,CAAC,KAC7B,OAAO,OAAO;EACb,UAAU,wBACT,iCAAiC,SACjC,iCAAiC,QAClC;EACA,QAAQ,MAAM,CAAC,oBAAoB,CAAC;CACrC,CAAC,CACF;CAYA,IAAI,oBAAoB,MAAM,GAC7B,OAAO,OAAO,OAAO,KACpB,kBACC,UACA,oEACI,iCAAiC,SAAS,YACzC,iCAAiC,QAAQ,yBAChC,OAAO,kBAAkB,8EAEtC,iBAAiB,QAAQ,MAAM,GAChC;EACC,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,QAAQ,OAAO;CAChB,CACD,CACD;CAGD,IAAI,OAAO,aAAa,GACvB,OAAO,OAAO,OAAO,KACpB,kBAAkB,UAAU,iBAAiB,QAAQ,MAAM,GAAG;EAC7D,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,QAAQ,OAAO;CAChB,CAAC,CACF;CAGD,MAAM,SAAS,kBAAkB,OAAO,MAAM;CAC9C,IAAI,CAAC,QACJ,OAAO,OAAO,OAAO,KACpB,kBACC,UACA,gQAIA;EAAE,QAAQ,OAAO;EAAQ,QAAQ,OAAO;CAAO,CAChD,CACD;CAED,OAAO;AACR,CAAC,CAAC,CAAC,KACF,OAAO,cAAc;CACpB,UAAU,SAAS,OAAO,KAAyB;CACnD,cACC,OAAO,KACN,kBACC,UACA,gCAAgC,kBAAkB,YACnD,CACD;AACF,CAAC,CACF;;;;;;;;;;;AAkBD,MAAa,yBACZ,WACA,OACA,SACA,WAEA,OAAO,IAAI,aAAa;CA0FvB,OAAO,EAAE,OAAA,OAzFY,UAAU,QAAiD;EAC/E,WAAW;EACX,OAAO,OAAO;EACd,aAAa,OAAO;EAKpB,SAAS,WACR,OAAO,IAAI,aAAa;GACvB,IAAI,EAAE,OAAO,0BAA0B,MAAM,IAAI,OAAO;GAWxD,MAAM,YAAY,iBAAiB,CAClC,MAAM,IACL;IAAE,MAAM;IAAU,UAAU,OAAO;GAAa,GAChD,sBACA,SACD,GACA,MAAM,IACL;IAAE,MAAM;IAAU,UAAU,OAAO;GAAc,GACjD,sBACA,SACD,CACD,CAAC;GACD,IAAI,CAAC,QAAQ,WAAW,OAAO;GAC/B,KACC,IAAI,UAAU,IACb,UAAU,QAAQ,WAAW,SAAS,UAAU,wCACjD,WACC;IACD,OAAO,OAAO,MAAM,oCAAoC;IACxD,CAAC,QAAQ,WAAW,OAAO;GAC5B;GACA,IAAI,WAAW,KAAA,KAAa,WAAW,MAAM,OAAO;GACpD,IAAI,YAAY,KAAA,KAAa,YAAY,MAAM,OAAO;GAEtD,OAAO;IACN,gBAAgB,OAAO;IACvB,iBAAiB,QAAQ;GAC1B;EACD,CAAC,CAAC,CAAC,KAcF,OAAO,SAAS,oBAAoB,QAAQ;GAC3C,IAAI,IAAI,WAAW,mBAAmB,IAAI,WAAW,uBACpD,OAAO,OAAO,QAAQ,IAAmC;GAE1D,OAAO,OAAO,WACb,sEACW,IAAI,OAAO,UAAU,IAAI,QAAQ,WAAW,IAAI,OAAO,iBAEnE,CAAC,CAAC,KAAK,OAAO,GAAG,IAAmC,CAAC;EACtD,CAAC,CACF;EAED,SAAS,iBAAiB,SAAS,MAAM,CAAC,CAAC,KAC1C,OAAO,UACL,QACA,qBAAqB,kBAAkB,iBAAiB,IAAI,MAAM,IAAI,IAAI,SAAS,CACrF,CACD;EAMA,gBAAgB,OAAO;CACxB,CAAC,EAEc;AAChB,CAAC"}
@@ -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 {