@mysten-incubation/devstack 0.3.0 → 0.5.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 (154) hide show
  1. package/README.md +10 -11
  2. package/dashboard-ui/assets/{grpc-Clz0oOtc.js → grpc-CpkDu4SA.js} +1 -1
  3. package/dashboard-ui/assets/{index-Cmqv9kiU.js → index-jLPRmjst.js} +2 -2
  4. package/dashboard-ui/index.html +1 -1
  5. package/dist/api/inference-network.d.mts +5 -5
  6. package/dist/api/inference-network.mjs +5 -5
  7. package/dist/api/inference-network.mjs.map +1 -1
  8. package/dist/api/run-stack-internal.mjs +1 -3
  9. package/dist/api/run-stack-internal.mjs.map +1 -1
  10. package/dist/build-integrations/dapp-kit/index.d.mts +12 -0
  11. package/dist/build-integrations/dapp-kit/index.mjs +14 -1
  12. package/dist/build-integrations/dapp-kit/index.mjs.map +1 -1
  13. package/dist/build-integrations/playwright/errors.d.mts +1 -1
  14. package/dist/build-integrations/playwright/errors.mjs.map +1 -1
  15. package/dist/build-integrations/playwright/index.d.mts +2 -2
  16. package/dist/build-integrations/playwright/index.mjs +2 -2
  17. package/dist/build-integrations/playwright/wallet-context.d.mts +2 -1
  18. package/dist/build-integrations/playwright/wallet-context.mjs +141 -41
  19. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  20. package/dist/build-integrations/runtime/dapp-kit-slot.mjs +1 -1
  21. package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
  22. package/dist/build-integrations/vite/index.d.mts +54 -42
  23. package/dist/build-integrations/vite/index.mjs +239 -106
  24. package/dist/build-integrations/vite/index.mjs.map +1 -1
  25. package/dist/build-integrations/vitest/global-setup.mjs +8 -8
  26. package/dist/build-integrations/vitest/global-setup.mjs.map +1 -1
  27. package/dist/cli/main.mjs +3 -2
  28. package/dist/cli/main.mjs.map +1 -1
  29. package/dist/cli/wirings/apply.mjs +0 -1
  30. package/dist/cli/wirings/apply.mjs.map +1 -1
  31. package/dist/cli/wirings/codegen.mjs +2 -3
  32. package/dist/cli/wirings/codegen.mjs.map +1 -1
  33. package/dist/cli/wirings/dump-deployment.mjs +141 -0
  34. package/dist/cli/wirings/dump-deployment.mjs.map +1 -0
  35. package/dist/cli/wirings/snapshot.mjs +0 -1
  36. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  37. package/dist/contracts/codegenable.d.mts +22 -25
  38. package/dist/contracts/codegenable.mjs +35 -4
  39. package/dist/contracts/codegenable.mjs.map +1 -1
  40. package/dist/contracts/config-bindings.mjs +43 -18
  41. package/dist/contracts/config-bindings.mjs.map +1 -1
  42. package/dist/index.d.mts +1 -1
  43. package/dist/index.mjs +1 -1
  44. package/dist/orchestrators/boot.d.mts +0 -10
  45. package/dist/orchestrators/boot.mjs +19 -59
  46. package/dist/orchestrators/boot.mjs.map +1 -1
  47. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  48. package/dist/orchestrators/codegen/config-runtime.mjs +137 -112
  49. package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -1
  50. package/dist/orchestrators/codegen/deployment-network-file.mjs +130 -0
  51. package/dist/orchestrators/codegen/deployment-network-file.mjs.map +1 -0
  52. package/dist/orchestrators/codegen/deployment-strict.mjs +77 -0
  53. package/dist/orchestrators/codegen/deployment-strict.mjs.map +1 -0
  54. package/dist/orchestrators/codegen/deployment.d.mts +63 -0
  55. package/dist/orchestrators/codegen/deployment.mjs +153 -0
  56. package/dist/orchestrators/codegen/deployment.mjs.map +1 -0
  57. package/dist/orchestrators/codegen/errors.mjs +7 -9
  58. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  59. package/dist/orchestrators/codegen/format.mjs +20 -1
  60. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  61. package/dist/orchestrators/codegen/paths.mjs +1 -8
  62. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  63. package/dist/orchestrators/codegen/service.mjs +354 -141
  64. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  65. package/dist/orchestrators/layers.mjs +0 -1
  66. package/dist/orchestrators/layers.mjs.map +1 -1
  67. package/dist/orchestrators/network-options.d.mts +37 -35
  68. package/dist/orchestrators/network-options.mjs +13 -10
  69. package/dist/orchestrators/network-options.mjs.map +1 -1
  70. package/dist/plugins/account/codegen.d.mts +3 -4
  71. package/dist/plugins/account/codegen.mjs +5 -7
  72. package/dist/plugins/account/codegen.mjs.map +1 -1
  73. package/dist/plugins/account/index.mjs +1 -3
  74. package/dist/plugins/account/index.mjs.map +1 -1
  75. package/dist/plugins/coin/codegen.mjs +5 -5
  76. package/dist/plugins/coin/codegen.mjs.map +1 -1
  77. package/dist/plugins/coin/index.mjs.map +1 -1
  78. package/dist/plugins/dashboard/schema/root.mjs +3 -1
  79. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  80. package/dist/plugins/deepbook/codegen.mjs +4 -4
  81. package/dist/plugins/deepbook/codegen.mjs.map +1 -1
  82. package/dist/plugins/deepbook/index.d.mts +36 -6
  83. package/dist/plugins/deepbook/index.mjs +52 -16
  84. package/dist/plugins/deepbook/index.mjs.map +1 -1
  85. package/dist/plugins/internal/codegen-helpers.mjs +0 -1
  86. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
  87. package/dist/plugins/package/codegen.d.mts +1 -12
  88. package/dist/plugins/package/codegen.mjs +43 -25
  89. package/dist/plugins/package/codegen.mjs.map +1 -1
  90. package/dist/plugins/package/dep-resolution.mjs +1 -1
  91. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  92. package/dist/plugins/package/index.d.mts +14 -11
  93. package/dist/plugins/package/index.mjs +6 -7
  94. package/dist/plugins/package/index.mjs.map +1 -1
  95. package/dist/plugins/seal/codegen.d.mts +5 -0
  96. package/dist/plugins/seal/codegen.mjs +55 -12
  97. package/dist/plugins/seal/codegen.mjs.map +1 -1
  98. package/dist/plugins/seal/index.d.mts +64 -19
  99. package/dist/plugins/seal/index.mjs +26 -36
  100. package/dist/plugins/seal/index.mjs.map +1 -1
  101. package/dist/plugins/seal/mode/fork-known.d.mts +0 -2
  102. package/dist/plugins/seal/mode/fork-known.mjs +11 -22
  103. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  104. package/dist/plugins/seal/mode/live.d.mts +36 -13
  105. package/dist/plugins/seal/mode/live.mjs +104 -43
  106. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  107. package/dist/plugins/seal/mode/local-keygen.mjs +2 -1
  108. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  109. package/dist/plugins/seal/registry-publish.d.mts +15 -3
  110. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  111. package/dist/plugins/sui/codegen.mjs +5 -4
  112. package/dist/plugins/sui/codegen.mjs.map +1 -1
  113. package/dist/plugins/sui/index.mjs +1 -2
  114. package/dist/plugins/sui/index.mjs.map +1 -1
  115. package/dist/plugins/wallet/codegen.d.mts +8 -12
  116. package/dist/plugins/wallet/codegen.mjs +22 -15
  117. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  118. package/dist/plugins/wallet/index.d.mts +4 -3
  119. package/dist/plugins/wallet/index.mjs +1 -1
  120. package/dist/plugins/wallet/index.mjs.map +1 -1
  121. package/dist/plugins/wallet/service.d.mts +2 -2
  122. package/dist/plugins/wallet/service.mjs +2 -3
  123. package/dist/plugins/wallet/service.mjs.map +1 -1
  124. package/dist/plugins/walrus/codegen.mjs +4 -4
  125. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  126. package/dist/plugins/walrus/index.d.mts +137 -8
  127. package/dist/plugins/walrus/index.mjs +46 -9
  128. package/dist/plugins/walrus/index.mjs.map +1 -1
  129. package/dist/plugins/walrus/mode/known-deploy.d.mts +5 -1
  130. package/dist/plugins/walrus/mode/known-deploy.mjs +13 -10
  131. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  132. package/dist/substrate/manifest.d.mts +17 -22
  133. package/dist/substrate/manifest.mjs +4 -6
  134. package/dist/substrate/manifest.mjs.map +1 -1
  135. package/dist/substrate/plugin-ctx.d.mts +6 -6
  136. package/dist/substrate/plugin-ctx.mjs.map +1 -1
  137. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  138. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  139. package/dist/substrate/runtime/paths.mjs +1 -1
  140. package/dist/surfaces/cli/command-tree.mjs +6 -6
  141. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  142. package/dist/surfaces/cli/index.mjs +6 -4
  143. package/dist/surfaces/cli/index.mjs.map +1 -1
  144. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  145. package/package.json +2 -2
  146. package/dist/cli/wirings/dump-ids.mjs +0 -86
  147. package/dist/cli/wirings/dump-ids.mjs.map +0 -1
  148. package/dist/orchestrators/codegen/id-config.d.mts +0 -1
  149. package/dist/orchestrators/codegen/id-config.mjs +0 -107
  150. package/dist/orchestrators/codegen/id-config.mjs.map +0 -1
  151. package/dist/orchestrators/codegen/output-location.mjs +0 -32
  152. package/dist/orchestrators/codegen/output-location.mjs.map +0 -1
  153. package/dist/plugins/seal/service.mjs +0 -17
  154. package/dist/plugins/seal/service.mjs.map +0 -1
@@ -1,20 +1,43 @@
1
- import { Effect } from "effect";
2
-
3
1
  //#region src/plugins/seal/mode/live.d.ts
4
- /** Closed set of known deployments. `mainnet` + `devnet` intentionally
5
- * null (no public default key server). `testnet` carries the public
6
- * key-server URL but `keyServerObjectId` is `null` until a real id is
7
- * sourced `validateLiveInputs` forces the caller to supply
8
- * `objectId` explicitly in that case. */
9
- declare const KNOWN_DEPLOYMENTS: {
10
- readonly testnet: {
11
- readonly keyServerObjectId: string | null;
12
- readonly keyServerUrl: string;
2
+ /** Which class of known key-server backing to resolve. `independent`
3
+ * fans out to all of the network's standalone Open-mode servers
4
+ * (weight 1 each); `committee` resolves the single threshold-committee
5
+ * object reached through an aggregator. Plugin-INTERNAL not exported
6
+ * from the barrel. */
7
+ type SealServerKind = 'independent' | 'committee';
8
+ /** A single independent (Open-mode) key server. */
9
+ interface IndependentServerSpec {
10
+ readonly objectId: string;
11
+ readonly url: string;
12
+ }
13
+ /** The committee (threshold) deployment for a network. */
14
+ interface CommitteeSpec {
15
+ readonly objectId: string;
16
+ readonly aggregatorUrl: string;
17
+ /** Threshold `m-of-n`, carried for diagnostics / display only. */
18
+ readonly threshold: {
19
+ readonly m: number;
20
+ readonly n: number;
13
21
  };
14
- readonly mainnet: null;
22
+ /** When `true`, the committee aggregator demands an API key and the
23
+ * factory throws `SealConfigError` if the caller omits `apiKeyName`. */
24
+ readonly requiresApiKey: boolean;
25
+ }
26
+ /** Per-network known deployment. `independent` is `null` when the
27
+ * network ships no public standalone server (mainnet); `committee` is
28
+ * `null` when none ships (devnet). */
29
+ interface KnownDeployment {
30
+ readonly independent: ReadonlyArray<IndependentServerSpec> | null;
31
+ readonly committee: CommitteeSpec | null;
32
+ /** The default server kind for zero-config use of this network. */
33
+ readonly defaultServer: SealServerKind;
34
+ }
35
+ declare const KNOWN_DEPLOYMENTS: {
36
+ readonly testnet: KnownDeployment;
37
+ readonly mainnet: KnownDeployment;
15
38
  readonly devnet: null;
16
39
  };
17
40
  type KnownNetwork = keyof typeof KNOWN_DEPLOYMENTS;
18
41
  //#endregion
19
- export { KnownNetwork };
42
+ export { KnownNetwork, SealServerKind };
20
43
  //# sourceMappingURL=live.d.mts.map
@@ -1,57 +1,118 @@
1
- import { expectNonEmptyString } from "../../../substrate/runtime/config-validation.mjs";
2
1
  import { sealConfigError } from "../errors.mjs";
3
- import { Effect } from "effect";
4
2
  //#region src/plugins/seal/mode/live.ts
5
- /** Closed set of known deployments. `mainnet` + `devnet` intentionally
6
- * null (no public default key server). `testnet` carries the public
7
- * key-server URL but `keyServerObjectId` is `null` until a real id is
8
- * sourced — `validateLiveInputs` forces the caller to supply
9
- * `objectId` explicitly in that case. */
10
3
  const KNOWN_DEPLOYMENTS = {
11
4
  testnet: {
12
- keyServerObjectId: null,
13
- keyServerUrl: "https://seal-keyserver.testnet.mystenlabs.com"
5
+ independent: [{
6
+ objectId: "0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75",
7
+ url: "https://seal-key-server-testnet-1.mystenlabs.com"
8
+ }, {
9
+ objectId: "0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8",
10
+ url: "https://seal-key-server-testnet-2.mystenlabs.com"
11
+ }],
12
+ committee: {
13
+ objectId: "0xb012378c9f3799fb5b1a7083da74a4069e3c3f1c93de0b27212a5799ce1e1e98",
14
+ aggregatorUrl: "https://seal-aggregator-testnet.mystenlabs.com",
15
+ threshold: {
16
+ m: 3,
17
+ n: 5
18
+ },
19
+ requiresApiKey: false
20
+ },
21
+ defaultServer: "independent"
22
+ },
23
+ mainnet: {
24
+ independent: null,
25
+ committee: {
26
+ objectId: "0x686098f1439237fff9f36b99c7329683c22979d2005c2465cb891acb012a7595",
27
+ aggregatorUrl: "https://seal-aggregator-mainnet.mystenlabs.com",
28
+ threshold: {
29
+ m: 5,
30
+ n: 8
31
+ },
32
+ requiresApiKey: true
33
+ },
34
+ defaultServer: "committee"
14
35
  },
15
- mainnet: null,
16
36
  devnet: null
17
37
  };
18
- /** Validate the inputs at the factory boundary. Pure synchronous
19
- * function. The plugin `start` body is reserved for Effect-flavored
20
- * work; this validation throws synchronously (distilled-doc
21
- * §Failure modes). */
38
+ /** Validate + resolve the inputs at the factory boundary. Pure
39
+ * synchronous function the plugin `start` body is reserved for
40
+ * Effect-flavored work; this resolution throws `SealConfigError`
41
+ * synchronously (distilled-doc §Failure modes).
42
+ *
43
+ * Resolution rules (locked decisions #5/#6):
44
+ * - `serverConfigs` override wins verbatim.
45
+ * - else pick the server kind: explicit `server`, else the network's
46
+ * `defaultServer`.
47
+ * - `independent` → map every standalone server to `{objectId, weight:1}`.
48
+ * - `committee` → a single entry `{objectId, weight:1, aggregatorUrl,
49
+ * …apiKeyName}` (NO secret apiKey value); throw if the committee
50
+ * `requiresApiKey` and no `apiKeyName` is declared.
51
+ * - mainnet has no independent → `mainnet({server:'independent'})` throws. */
22
52
  const validateLiveInputs = (inputs) => {
23
- const fromNetwork = inputs.network && KNOWN_DEPLOYMENTS[inputs.network] ? KNOWN_DEPLOYMENTS[inputs.network] : null;
24
- const objectId = inputs.objectId ?? fromNetwork?.keyServerObjectId;
25
- const keyServerUrl = inputs.keyServerUrl ?? fromNetwork?.keyServerUrl;
26
- const message = `seal.live: missing required fields. Pass network ('testnet') or set objectId + keyServerUrl explicitly (got network=${String(inputs.network)}, objectId=${String(inputs.objectId)}, keyServerUrl=${String(inputs.keyServerUrl)}).`;
53
+ const context = `seal.live: network=${String(inputs.network)}, server=${String(inputs.server)}`;
54
+ const verifyKeyServers = inputs.verifyKeyServers ?? true;
55
+ if (inputs.serverConfigs !== void 0) {
56
+ const configs = inputs.serverConfigs;
57
+ if (configs.length === 0) throw sealConfigError({
58
+ field: "serverConfigs",
59
+ message: `${context}: serverConfigs override must carry at least one entry.`
60
+ });
61
+ if (configs.some((c) => c.apiKey !== void 0)) throw sealConfigError({
62
+ field: "serverConfigs",
63
+ message: `${context}: serverConfigs override embeds an apiKey, but devstack never carries the secret apiKey value (the committed config + deployment.json are world-readable). Drop apiKey here (keep apiKeyName) and inject the apiKey at runtime when you build SealClient.`
64
+ });
65
+ const first = configs[0];
66
+ return {
67
+ serverConfigs: configs,
68
+ objectId: first.objectId,
69
+ keyServerUrl: first.aggregatorUrl ?? "",
70
+ verifyKeyServers
71
+ };
72
+ }
73
+ const deployment = inputs.network ? KNOWN_DEPLOYMENTS[inputs.network] : null;
74
+ if (deployment === null || deployment === void 0) throw sealConfigError({
75
+ field: "network",
76
+ message: `${context}: no known seal deployment. Pass a supported network ('testnet'|'mainnet') with an optional { server } selector, or supply serverConfigs explicitly.`
77
+ });
78
+ if ((inputs.server ?? deployment.defaultServer) === "independent") {
79
+ const servers = deployment.independent;
80
+ if (servers === null) throw sealConfigError({
81
+ field: "server",
82
+ message: `${context}: no independent (standalone) key server ships for this network — use { server: 'committee' } (declare { apiKeyName } where required; inject the apiKey at runtime).`
83
+ });
84
+ return {
85
+ serverConfigs: servers.map((s) => ({
86
+ objectId: s.objectId,
87
+ weight: 1
88
+ })),
89
+ objectId: servers[0].objectId,
90
+ keyServerUrl: servers[0].url,
91
+ verifyKeyServers
92
+ };
93
+ }
94
+ const committee = deployment.committee;
95
+ if (committee === null) throw sealConfigError({
96
+ field: "server",
97
+ message: `${context}: no committee key server ships for this network.`
98
+ });
99
+ if (committee.requiresApiKey && (inputs.apiKeyName === void 0 || inputs.apiKeyName.length === 0)) throw sealConfigError({
100
+ field: "apiKeyName",
101
+ message: `${context}: the ${committee.threshold.m}-of-${committee.threshold.n} committee requires credentials. Pass { apiKeyName } (the non-secret header name); inject the secret apiKey value at runtime when you build SealClient — devstack does not carry it.`
102
+ });
27
103
  return {
28
- objectId: expectNonEmptyString(objectId, {
29
- field: "objectId",
30
- message,
31
- mkError: sealConfigError
32
- }),
33
- keyServerUrl: expectNonEmptyString(keyServerUrl, {
34
- field: "keyServerUrl",
35
- message,
36
- mkError: sealConfigError
37
- })
38
- };
39
- };
40
- /** Acquire body for the live mode. Validation already ran at the
41
- * factory boundary (see `index.ts:buildLivePlugin`); this projects
42
- * the validated bundle into the resolved shape. Returns the
43
- * read-side handle ONLY (no manager tag — distilled-doc invariant #15). */
44
- const acquireLive = (inputs) => Effect.sync(() => {
45
- return { keyServer: {
46
104
  serverConfigs: [{
47
- objectId: inputs.resolved.objectId,
48
- weight: 1
105
+ objectId: committee.objectId,
106
+ weight: 1,
107
+ aggregatorUrl: committee.aggregatorUrl,
108
+ ...inputs.apiKeyName !== void 0 ? { apiKeyName: inputs.apiKeyName } : {}
49
109
  }],
50
- keyServerUrl: inputs.resolved.keyServerUrl,
51
- objectId: inputs.resolved.objectId
52
- } };
53
- });
110
+ objectId: committee.objectId,
111
+ keyServerUrl: committee.aggregatorUrl,
112
+ verifyKeyServers
113
+ };
114
+ };
54
115
  //#endregion
55
- export { acquireLive, validateLiveInputs };
116
+ export { validateLiveInputs };
56
117
 
57
118
  //# sourceMappingURL=live.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"live.mjs","names":[],"sources":["../../../../src/plugins/seal/mode/live.ts"],"sourcesContent":["// Seal live mode — testnet / mainnet known key server.\n//\n// Distilled-doc §\"Startup — `sealKnownKeyServer`\": far simpler than\n// the local-keygen path.\n//\n// 1. Look up the known deployment (or accept explicit overrides).\n// 2. Publish the resolved key-server endpoint as the plugin's\n// resource (the codegen + manifest emitters consume it).\n// 3. Return the read-side handle.\n//\n// NO chain interactions, NO docker, NO on-disk persistence, NO\n// keygen. Distilled-doc invariant #15 — the manager tag is NOT\n// produced (we don't own the master key for a remote deployment).\n//\n// Known-deployment lookup: distilled-doc §\"Adjacent Seal references\"\n// → `engine/known-deployments.ts:153-157` carries the\n// `mysten-testnet-1` Open-mode independent server. `mainnet`\n// intentionally empty (Mysten doesn't ship a public default key\n// server on mainnet — production is via Enoki). `devnet`\n// intentionally empty.\n\nimport { Effect } from 'effect';\n\nimport { expectNonEmptyString } from '../../../substrate/runtime/config-validation.ts';\nimport { sealConfigError } from '../errors.ts';\nimport type { SealKeyServerEntry, SealKnownResolved } from '../registry-publish.ts';\n\n// ---------------------------------------------------------------------------\n// Known-deployment table — distilled-doc reference\n// ---------------------------------------------------------------------------\n\n/** Closed set of known deployments. `mainnet` + `devnet` intentionally\n * null (no public default key server). `testnet` carries the public\n * key-server URL but `keyServerObjectId` is `null` until a real id is\n * sourced — `validateLiveInputs` forces the caller to supply\n * `objectId` explicitly in that case. */\nexport const KNOWN_DEPLOYMENTS: {\n\treadonly testnet: {\n\t\treadonly keyServerObjectId: string | null;\n\t\treadonly keyServerUrl: string;\n\t};\n\treadonly mainnet: null;\n\treadonly devnet: null;\n} = {\n\ttestnet: {\n\t\tkeyServerObjectId: null,\n\t\tkeyServerUrl: 'https://seal-keyserver.testnet.mystenlabs.com',\n\t},\n\tmainnet: null,\n\tdevnet: null,\n};\n\nexport type KnownNetwork = keyof typeof KNOWN_DEPLOYMENTS;\n\n// ---------------------------------------------------------------------------\n// Inputs\n// ---------------------------------------------------------------------------\n\n/** Live-mode inputs. Either `network` resolves to a known deployment\n * OR the user supplies explicit `objectId` + `keyServerUrl`\n * overrides. Distilled-doc §\"Configuration\" — the factory throws\n * synchronously if neither path produces a usable tuple. */\nexport interface LiveModeInputs {\n\treadonly name: string;\n\treadonly network?: KnownNetwork;\n\treadonly objectId?: string;\n\treadonly keyServerUrl?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Resolved\n// ---------------------------------------------------------------------------\n\n/** Validate the inputs at the factory boundary. Pure synchronous\n * function. The plugin `start` body is reserved for Effect-flavored\n * work; this validation throws synchronously (distilled-doc\n * §Failure modes). */\nexport const validateLiveInputs = (\n\tinputs: LiveModeInputs,\n): {\n\treadonly objectId: string;\n\treadonly keyServerUrl: string;\n} => {\n\tconst fromNetwork =\n\t\tinputs.network && KNOWN_DEPLOYMENTS[inputs.network] ? KNOWN_DEPLOYMENTS[inputs.network] : null;\n\tconst objectId = inputs.objectId ?? fromNetwork?.keyServerObjectId;\n\tconst keyServerUrl = inputs.keyServerUrl ?? fromNetwork?.keyServerUrl;\n\tconst message = `seal.live: missing required fields. Pass network ('testnet') or set objectId + keyServerUrl explicitly (got network=${String(\n\t\tinputs.network,\n\t)}, objectId=${String(inputs.objectId)}, keyServerUrl=${String(inputs.keyServerUrl)}).`;\n\treturn {\n\t\tobjectId: expectNonEmptyString(objectId, {\n\t\t\tfield: 'objectId',\n\t\t\tmessage,\n\t\t\tmkError: sealConfigError,\n\t\t}),\n\t\tkeyServerUrl: expectNonEmptyString(keyServerUrl, {\n\t\t\tfield: 'keyServerUrl',\n\t\t\tmessage,\n\t\t\tmkError: sealConfigError,\n\t\t}),\n\t};\n};\n\n// ---------------------------------------------------------------------------\n// Mode acquire\n// ---------------------------------------------------------------------------\n\n/** Acquire body for the live mode. Validation already ran at the\n * factory boundary (see `index.ts:buildLivePlugin`); this projects\n * the validated bundle into the resolved shape. Returns the\n * read-side handle ONLY (no manager tag — distilled-doc invariant #15). */\nexport const acquireLive = (inputs: {\n\treadonly name: string;\n\treadonly resolved: { readonly objectId: string; readonly keyServerUrl: string };\n}): Effect.Effect<SealKnownResolved> =>\n\tEffect.sync(() => {\n\t\tconst serverConfigs: ReadonlyArray<SealKeyServerEntry> = [\n\t\t\t{ objectId: inputs.resolved.objectId, weight: 1 },\n\t\t];\n\t\treturn {\n\t\t\tkeyServer: {\n\t\t\t\tserverConfigs,\n\t\t\t\tkeyServerUrl: inputs.resolved.keyServerUrl,\n\t\t\t\tobjectId: inputs.resolved.objectId,\n\t\t\t},\n\t\t} satisfies SealKnownResolved;\n\t});\n"],"mappings":";;;;;;;;;AAoCA,MAAa,oBAOT;CACH,SAAS;EACR,mBAAmB;EACnB,cAAc;CACf;CACA,SAAS;CACT,QAAQ;AACT;;;;;AA2BA,MAAa,sBACZ,WAII;CACJ,MAAM,cACL,OAAO,WAAW,kBAAkB,OAAO,WAAW,kBAAkB,OAAO,WAAW;CAC3F,MAAM,WAAW,OAAO,YAAY,aAAa;CACjD,MAAM,eAAe,OAAO,gBAAgB,aAAa;CACzD,MAAM,UAAU,uHAAuH,OACtI,OAAO,OACR,EAAE,aAAa,OAAO,OAAO,QAAQ,EAAE,iBAAiB,OAAO,OAAO,YAAY,EAAE;CACpF,OAAO;EACN,UAAU,qBAAqB,UAAU;GACxC,OAAO;GACP;GACA,SAAS;EACV,CAAC;EACD,cAAc,qBAAqB,cAAc;GAChD,OAAO;GACP;GACA,SAAS;EACV,CAAC;CACF;AACD;;;;;AAUA,MAAa,eAAe,WAI3B,OAAO,WAAW;CAIjB,OAAO,EACN,WAAW;EACV,eAAA,CAJD;GAAE,UAAU,OAAO,SAAS;GAAU,QAAQ;EAAE,CAInC;EACZ,cAAc,OAAO,SAAS;EAC9B,UAAU,OAAO,SAAS;CAC3B,EACD;AACD,CAAC"}
1
+ {"version":3,"file":"live.mjs","names":[],"sources":["../../../../src/plugins/seal/mode/live.ts"],"sourcesContent":["// Seal live mode — testnet / mainnet known key server.\n//\n// Distilled-doc §\"Startup — `sealKnownKeyServer`\": far simpler than\n// the local-keygen path.\n//\n// 1. Resolve the requested deployment (independent servers vs the\n// committee aggregator) into a `serverConfigs` array.\n// 2. Publish the resolved key-server endpoint as the plugin's\n// resource (the codegen + manifest emitters consume it).\n// 3. Return the read-side handle.\n//\n// NO chain interactions, NO docker, NO on-disk persistence, NO\n// keygen. Distilled-doc invariant #15 — the manager tag is NOT\n// produced (we don't own the master key for a remote deployment).\n//\n// Known-deployment table (real Mysten ids):\n// - testnet INDEPENDENT: two Open-mode servers, weight 1 each. This\n// is the zero-config default (`sealFor(net).testnet()`).\n// - testnet COMMITTEE: a single 3-of-5 committee object reached\n// through the testnet aggregator. Opt in via `{ server: 'committee' }`;\n// no API key required.\n// - mainnet COMMITTEE: a single 5-of-8 committee object reached\n// through the mainnet aggregator. This is the mainnet DEFAULT and\n// requires credentials — declare the non-secret `apiKeyName`; the\n// secret apiKey value is injected by the app at runtime, never by\n// devstack. Mainnet ships NO independent default — so\n// `mainnet({ server: 'independent' })` throws.\n\nimport { sealConfigError } from '../errors.ts';\nimport type { SealKeyServerEntry } from '../registry-publish.ts';\n\n// ---------------------------------------------------------------------------\n// Server-kind selector\n// ---------------------------------------------------------------------------\n\n/** Which class of known key-server backing to resolve. `independent`\n * fans out to all of the network's standalone Open-mode servers\n * (weight 1 each); `committee` resolves the single threshold-committee\n * object reached through an aggregator. Plugin-INTERNAL — not exported\n * from the barrel. */\nexport type SealServerKind = 'independent' | 'committee';\n\n// ---------------------------------------------------------------------------\n// Known-deployment table — real Mysten object ids\n// ---------------------------------------------------------------------------\n\n/** A single independent (Open-mode) key server. */\ninterface IndependentServerSpec {\n\treadonly objectId: string;\n\treadonly url: string;\n}\n\n/** The committee (threshold) deployment for a network. */\ninterface CommitteeSpec {\n\treadonly objectId: string;\n\treadonly aggregatorUrl: string;\n\t/** Threshold `m-of-n`, carried for diagnostics / display only. */\n\treadonly threshold: { readonly m: number; readonly n: number };\n\t/** When `true`, the committee aggregator demands an API key and the\n\t * factory throws `SealConfigError` if the caller omits `apiKeyName`. */\n\treadonly requiresApiKey: boolean;\n}\n\n/** Per-network known deployment. `independent` is `null` when the\n * network ships no public standalone server (mainnet); `committee` is\n * `null` when none ships (devnet). */\ninterface KnownDeployment {\n\treadonly independent: ReadonlyArray<IndependentServerSpec> | null;\n\treadonly committee: CommitteeSpec | null;\n\t/** The default server kind for zero-config use of this network. */\n\treadonly defaultServer: SealServerKind;\n}\n\nexport const KNOWN_DEPLOYMENTS: {\n\treadonly testnet: KnownDeployment;\n\treadonly mainnet: KnownDeployment;\n\treadonly devnet: null;\n} = {\n\ttestnet: {\n\t\tindependent: [\n\t\t\t{\n\t\t\t\tobjectId: '0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75',\n\t\t\t\turl: 'https://seal-key-server-testnet-1.mystenlabs.com',\n\t\t\t},\n\t\t\t{\n\t\t\t\tobjectId: '0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8',\n\t\t\t\turl: 'https://seal-key-server-testnet-2.mystenlabs.com',\n\t\t\t},\n\t\t],\n\t\tcommittee: {\n\t\t\tobjectId: '0xb012378c9f3799fb5b1a7083da74a4069e3c3f1c93de0b27212a5799ce1e1e98',\n\t\t\taggregatorUrl: 'https://seal-aggregator-testnet.mystenlabs.com',\n\t\t\tthreshold: { m: 3, n: 5 },\n\t\t\trequiresApiKey: false,\n\t\t},\n\t\t// Zero-config testnet = BOTH independent servers (locked decision #6).\n\t\tdefaultServer: 'independent',\n\t},\n\tmainnet: {\n\t\t// Mainnet ships no public independent default — committee only.\n\t\tindependent: null,\n\t\tcommittee: {\n\t\t\tobjectId: '0x686098f1439237fff9f36b99c7329683c22979d2005c2465cb891acb012a7595',\n\t\t\taggregatorUrl: 'https://seal-aggregator-mainnet.mystenlabs.com',\n\t\t\tthreshold: { m: 5, n: 8 },\n\t\t\trequiresApiKey: true,\n\t\t},\n\t\tdefaultServer: 'committee',\n\t},\n\tdevnet: null,\n};\n\nexport type KnownNetwork = keyof typeof KNOWN_DEPLOYMENTS;\n\n// ---------------------------------------------------------------------------\n// Inputs\n// ---------------------------------------------------------------------------\n\n/** Live-mode inputs. Either `network` resolves to a known deployment\n * (optionally narrowed by `server`), OR the user supplies explicit\n * `serverConfigs` verbatim. Distilled-doc §\"Configuration\" — the\n * factory throws synchronously if neither path produces a usable\n * `serverConfigs` array. */\nexport interface LiveModeInputs {\n\treadonly name: string;\n\treadonly network?: KnownNetwork;\n\t/** Which known server kind to resolve. Defaults to the network's\n\t * `defaultServer` (testnet→independent, mainnet→committee). */\n\treadonly server?: SealServerKind;\n\t/** The NON-secret header name for a credentialed committee (mainnet).\n\t * devstack emits this so the generated config tells the app which header\n\t * to set. The secret apiKey VALUE is NEVER taken by devstack — the app\n\t * injects it at runtime when constructing `SealClient` (a committed\n\t * config + the world-readable `deployment.json` must not carry secrets). */\n\treadonly apiKeyName?: string;\n\t/** Whether the SDK should verify the key servers against their on-chain\n\t * registration. Defaults to `true` for live / fork-known (real Mysten\n\t * servers). */\n\treadonly verifyKeyServers?: boolean;\n\t/** Raw override — bypasses the known table entirely. */\n\treadonly serverConfigs?: ReadonlyArray<SealKeyServerEntry>;\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/** The validated/resolved live-mode bundle. `serverConfigs` is the\n * SDK-ready array; the legacy single `objectId` / `keyServerUrl`\n * fields are derived from the first entry / chosen url for the\n * read-side handle + codegen literals that still expect them. */\nexport interface ResolvedLiveInputs {\n\treadonly serverConfigs: ReadonlyArray<SealKeyServerEntry>;\n\treadonly objectId: string;\n\treadonly keyServerUrl: string;\n\t/** SDK key-server verification flag. `true` by default for live /\n\t * fork-known (real Mysten servers); the caller may override. */\n\treadonly verifyKeyServers: boolean;\n}\n\n/** Validate + resolve the inputs at the factory boundary. Pure\n * synchronous function — the plugin `start` body is reserved for\n * Effect-flavored work; this resolution throws `SealConfigError`\n * synchronously (distilled-doc §Failure modes).\n *\n * Resolution rules (locked decisions #5/#6):\n * - `serverConfigs` override wins verbatim.\n * - else pick the server kind: explicit `server`, else the network's\n * `defaultServer`.\n * - `independent` → map every standalone server to `{objectId, weight:1}`.\n * - `committee` → a single entry `{objectId, weight:1, aggregatorUrl,\n * …apiKeyName}` (NO secret apiKey value); throw if the committee\n * `requiresApiKey` and no `apiKeyName` is declared.\n * - mainnet has no independent → `mainnet({server:'independent'})` throws. */\nexport const validateLiveInputs = (inputs: LiveModeInputs): ResolvedLiveInputs => {\n\tconst context = `seal.live: network=${String(inputs.network)}, server=${String(inputs.server)}`;\n\n\t// SDK default for live / fork-known is to verify the key servers against\n\t// their on-chain registration (real Mysten servers). The caller may opt out.\n\tconst verifyKeyServers = inputs.verifyKeyServers ?? true;\n\n\t// 1. Verbatim override.\n\tif (inputs.serverConfigs !== undefined) {\n\t\tconst configs = inputs.serverConfigs;\n\t\tif (configs.length === 0) {\n\t\t\tthrow sealConfigError({\n\t\t\t\tfield: 'serverConfigs',\n\t\t\t\tmessage: `${context}: serverConfigs override must carry at least one entry.`,\n\t\t\t});\n\t\t}\n\t\t// Secrets never ride a committed/world-readable config. devstack does\n\t\t// NOT carry the committee `apiKey` value (the committed `seal.ts` and the\n\t\t// browser-injected `deployment.json` are both world-readable). Reject an\n\t\t// override that embeds one — pass `apiKeyName` here and inject the secret\n\t\t// apiKey VALUE into `serverConfigs` at runtime when constructing SealClient.\n\t\tif (configs.some((c) => c.apiKey !== undefined)) {\n\t\t\tthrow sealConfigError({\n\t\t\t\tfield: 'serverConfigs',\n\t\t\t\tmessage: `${context}: serverConfigs override embeds an apiKey, but devstack never carries the secret apiKey value (the committed config + deployment.json are world-readable). Drop apiKey here (keep apiKeyName) and inject the apiKey at runtime when you build SealClient.`,\n\t\t\t});\n\t\t}\n\t\tconst first = configs[0]!;\n\t\treturn {\n\t\t\tserverConfigs: configs,\n\t\t\tobjectId: first.objectId,\n\t\t\t// `keyServerUrl` is best-effort / legacy for raw overrides: a\n\t\t\t// KeyServerConfig has no `url` field (the URL lives on the on-chain\n\t\t\t// object), so we derive it from `aggregatorUrl` when present and\n\t\t\t// fall back to empty. Consumers should read `serverConfigs`, not this.\n\t\t\tkeyServerUrl: first.aggregatorUrl ?? '',\n\t\t\tverifyKeyServers,\n\t\t};\n\t}\n\n\t// 2. Known-table resolution.\n\tconst deployment = inputs.network ? KNOWN_DEPLOYMENTS[inputs.network] : null;\n\tif (deployment === null || deployment === undefined) {\n\t\tthrow sealConfigError({\n\t\t\tfield: 'network',\n\t\t\tmessage: `${context}: no known seal deployment. Pass a supported network ('testnet'|'mainnet') with an optional { server } selector, or supply serverConfigs explicitly.`,\n\t\t});\n\t}\n\n\tconst kind: SealServerKind = inputs.server ?? deployment.defaultServer;\n\n\tif (kind === 'independent') {\n\t\tconst servers = deployment.independent;\n\t\tif (servers === null) {\n\t\t\tthrow sealConfigError({\n\t\t\t\tfield: 'server',\n\t\t\t\tmessage: `${context}: no independent (standalone) key server ships for this network — use { server: 'committee' } (declare { apiKeyName } where required; inject the apiKey at runtime).`,\n\t\t\t});\n\t\t}\n\t\tconst serverConfigs: ReadonlyArray<SealKeyServerEntry> = servers.map((s) => ({\n\t\t\tobjectId: s.objectId,\n\t\t\tweight: 1,\n\t\t}));\n\t\treturn {\n\t\t\tserverConfigs,\n\t\t\tobjectId: servers[0]!.objectId,\n\t\t\tkeyServerUrl: servers[0]!.url,\n\t\t\tverifyKeyServers,\n\t\t};\n\t}\n\n\t// kind === 'committee'\n\tconst committee = deployment.committee;\n\tif (committee === null) {\n\t\tthrow sealConfigError({\n\t\t\tfield: 'server',\n\t\t\tmessage: `${context}: no committee key server ships for this network.`,\n\t\t});\n\t}\n\t// A committee that requires credentials needs the NON-secret `apiKeyName`\n\t// declared so the generated config tells the app which header to set. The\n\t// secret apiKey VALUE is NEVER taken or emitted by devstack — the app\n\t// injects it at runtime when constructing SealClient (committed config +\n\t// deployment.json are world-readable).\n\tif (\n\t\tcommittee.requiresApiKey &&\n\t\t(inputs.apiKeyName === undefined || inputs.apiKeyName.length === 0)\n\t) {\n\t\tthrow sealConfigError({\n\t\t\tfield: 'apiKeyName',\n\t\t\tmessage: `${context}: the ${committee.threshold.m}-of-${committee.threshold.n} committee requires credentials. Pass { apiKeyName } (the non-secret header name); inject the secret apiKey value at runtime when you build SealClient — devstack does not carry it.`,\n\t\t});\n\t}\n\tconst serverConfigs: ReadonlyArray<SealKeyServerEntry> = [\n\t\t{\n\t\t\tobjectId: committee.objectId,\n\t\t\tweight: 1,\n\t\t\taggregatorUrl: committee.aggregatorUrl,\n\t\t\t...(inputs.apiKeyName !== undefined ? { apiKeyName: inputs.apiKeyName } : {}),\n\t\t},\n\t];\n\treturn {\n\t\tserverConfigs,\n\t\tobjectId: committee.objectId,\n\t\tkeyServerUrl: committee.aggregatorUrl,\n\t\tverifyKeyServers,\n\t};\n};\n"],"mappings":";;AAyEA,MAAa,oBAIT;CACH,SAAS;EACR,aAAa,CACZ;GACC,UAAU;GACV,KAAK;EACN,GACA;GACC,UAAU;GACV,KAAK;EACN,CACD;EACA,WAAW;GACV,UAAU;GACV,eAAe;GACf,WAAW;IAAE,GAAG;IAAG,GAAG;GAAE;GACxB,gBAAgB;EACjB;EAEA,eAAe;CAChB;CACA,SAAS;EAER,aAAa;EACb,WAAW;GACV,UAAU;GACV,eAAe;GACf,WAAW;IAAE,GAAG;IAAG,GAAG;GAAE;GACxB,gBAAgB;EACjB;EACA,eAAe;CAChB;CACA,QAAQ;AACT;;;;;;;;;;;;;;;AAgEA,MAAa,sBAAsB,WAA+C;CACjF,MAAM,UAAU,sBAAsB,OAAO,OAAO,OAAO,EAAE,WAAW,OAAO,OAAO,MAAM;CAI5F,MAAM,mBAAmB,OAAO,oBAAoB;CAGpD,IAAI,OAAO,kBAAkB,KAAA,GAAW;EACvC,MAAM,UAAU,OAAO;EACvB,IAAI,QAAQ,WAAW,GACtB,MAAM,gBAAgB;GACrB,OAAO;GACP,SAAS,GAAG,QAAQ;EACrB,CAAC;EAOF,IAAI,QAAQ,MAAM,MAAM,EAAE,WAAW,KAAA,CAAS,GAC7C,MAAM,gBAAgB;GACrB,OAAO;GACP,SAAS,GAAG,QAAQ;EACrB,CAAC;EAEF,MAAM,QAAQ,QAAQ;EACtB,OAAO;GACN,eAAe;GACf,UAAU,MAAM;GAKhB,cAAc,MAAM,iBAAiB;GACrC;EACD;CACD;CAGA,MAAM,aAAa,OAAO,UAAU,kBAAkB,OAAO,WAAW;CACxE,IAAI,eAAe,QAAQ,eAAe,KAAA,GACzC,MAAM,gBAAgB;EACrB,OAAO;EACP,SAAS,GAAG,QAAQ;CACrB,CAAC;CAKF,KAF6B,OAAO,UAAU,WAAW,mBAE5C,eAAe;EAC3B,MAAM,UAAU,WAAW;EAC3B,IAAI,YAAY,MACf,MAAM,gBAAgB;GACrB,OAAO;GACP,SAAS,GAAG,QAAQ;EACrB,CAAC;EAMF,OAAO;GACN,eALwD,QAAQ,KAAK,OAAO;IAC5E,UAAU,EAAE;IACZ,QAAQ;GACT,EAEa;GACZ,UAAU,QAAQ,EAAE,CAAE;GACtB,cAAc,QAAQ,EAAE,CAAE;GAC1B;EACD;CACD;CAGA,MAAM,YAAY,WAAW;CAC7B,IAAI,cAAc,MACjB,MAAM,gBAAgB;EACrB,OAAO;EACP,SAAS,GAAG,QAAQ;CACrB,CAAC;CAOF,IACC,UAAU,mBACT,OAAO,eAAe,KAAA,KAAa,OAAO,WAAW,WAAW,IAEjE,MAAM,gBAAgB;EACrB,OAAO;EACP,SAAS,GAAG,QAAQ,QAAQ,UAAU,UAAU,EAAE,MAAM,UAAU,UAAU,EAAE;CAC/E,CAAC;CAUF,OAAO;EACN,eAAA,CARA;GACC,UAAU,UAAU;GACpB,QAAQ;GACR,eAAe,UAAU;GACzB,GAAI,OAAO,eAAe,KAAA,IAAY,EAAE,YAAY,OAAO,WAAW,IAAI,CAAC;EAC5E,CAGY;EACZ,UAAU,UAAU;EACpB,cAAc,UAAU;EACxB;CACD;AACD"}
@@ -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":";;;;;;;;;;;;;AAoGA,MAAM,2BAA2B;AACjC,MAAM,0BAA0B;;;;;AAMhC,MAAa,6BACZ,MACA,oBACiC;CACjC,MAAM,KAAK,QAAQ;CACnB,SAAS,KAAK,WAAW;CACzB,gBAAgB,KAAK,kBAAkB;CACvC,eAAe,KAAK,iBAAiB;CACrC,iBAAiB,KAAK;AACvB;AAwDA,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AAEpC,MAAM,qCAAqC,mBAAmB,4BAA4B,EACzF,WAAW,OAAO,OACnB,CAAC;AAID,MAAM,2BAA2B,gBAChC,GAAG,YAAY,GAAG;AAEnB,MAAM,oCACL,gBAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,wBAAwB,WAAW;CAChD,MAAM,MAAM,OAAO,GAAG,eAAe,IAAI,CAAC,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC;CACxF,IAAI,QAAQ,MAAM,OAAO;CAMzB,OAAO,OAAO,eAAe,oCAAoC,KAAK;EACrE,QAAQ;EACR,UAAU,UAAU;CACrB,CAAC,CAAC,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC;AACjD,CAAC;AAEF,MAAM,qCACL,aACA,MACA,aAEA,gBACC,wBAAwB,WAAW,GACnC,IAAI,YAAY,CAAC,CAAC,OAAO,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,GAAG,GACjE;CACC,MAAM;CACN,YAAY;AACb,CACD,CAAC,CAAC,KACD,OAAO,OAAO,UACb,OAAO,KACN,UAAU,iBAAiB;CAC1B;CACA,SAAS,uCAAuC,4BAA4B,IAAI,MAAM,MAAM;CAC5F;AACD,CAAC,CACF,CACD,CACD;AAED,MAAM,iCACL,gBAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,mBAAmB,GAAG,YAAY,GAAG;CAC3C,MAAM,gBAAgB,OAAO,GAC3B,eAAe,gBAAgB,CAAC,CAChC,KAAK,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC;CAC/C,IAAI,kBAAkB,MAAM,OAAO;CACnC,MAAM,YAAY,sBAAsB,aAAa;CACrD,IAAI,cAAc,MAAM,OAAO;CAC/B,MAAM,WAAW,OAAO,iCAAiC,WAAW;CACpE,IAAI,aAAa,MAAM,OAAO;CAE9B,OAAO;EACN;EACA,WAAW,SAAS;EACpB;CACD;AACD,CAAC;;AAGF,MAAM,yBAAyB;;AAE/B,MAAM,yBAAyB;AAC/B,MAAM,cAAc;;;;;;;;;;;;;;;;;;AAmBpB,MAAM,qCACL,UACA,SACoC;CACpC,IACC,SAAS,UAAU,WAAW,0BAC9B,CAAC,YAAY,KAAK,SAAS,SAAS,GAEpC,OAAO,OAAO,KACb,UAAU,iBAAiB;EAC1B;EACA,SACC,8CAA8C,4BAA4B,UAChE,uBAAuB,iBAAiB,SAAS,UAAU,OAAO;CAG9E,CAAC,CACF;CAED,IACC,SAAS,UAAU,WAAW,0BAC9B,CAAC,YAAY,KAAK,SAAS,SAAS,GAEpC,OAAO,OAAO,KACb,UAAU,iBAAiB;EAC1B;EACA,SACC,6CAA6C,4BAA4B,UAC/D,uBAAuB,iBAAiB,SAAS,UAAU,OAAO;CAI9E,CAAC,CACF;CAED,OAAO,OAAO;AACf;AAEA,MAAM,yBAAyB,UAK9B;CACC,WAAW,MAAM;CACjB,aAAa,MAAM;CACnB,WAAW,MAAM;AAClB,CAAC,CAAC,KAAK,GAAG;AAEX,MAAM,0BACL,MACA,SAEA,KAAK,kBACF,OAAO,QAAQ,KAAK,eAAe,IACnC,yBAAyB,KAAK,OAAO,CAAC,CAAC,KAAK,OAAO,KAAK,MAAM,EAAE,IAAI,CAAC;AAEzE,MAAM,8BACL,MACA,MACA,cASA,OAAO,IAAI,aAAa;CACvB,MAAM,kBAAkB,OAAO,uBAAuB,MAAM,IAAI;CAChE,MAAM,EAAE,cAAc,OAAO,mBAAmB,KAAK,WAAW;EAC/D,MAAM,KAAK;EACX,SAAS,KAAK;EACd;EACA,QAAQ,KAAK;EACb,KAAK,KAAK;EACV,SAAS,KAAK;EACd,YAAY,KAAK;EACjB,GAAI,KAAK,eAAe,KAAA,IAAY,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;CACxE,CAAC;CACD,MAAM,EAAE,UAAU,sBAAsB,OAAO,kBAAkB,KAAK,WAAW;EAChF,MAAM,KAAK;EACX,SAAS,KAAK;EACd,cAAc,KAAK;EACnB,eAAe;EACf,cAAc;EACd,eAAe,KAAK;EACpB,QAAQ,KAAK;EACb,KAAK,KAAK;EACV,YAAY,KAAK;CAClB,CAAC;CACD,OAAO;EAAE;EAAW;CAAkB;AACvC,CAAC;AAEF,MAAM,iCACL,MACA,MACA,WAIA,YAEA,OAAO,IAAI,aAAa;CAMvB,MAAM,EAAE,qBAAqB,OAAO,gBALvB,0BAA0B;EACtC,eAAe,UAAU;EACzB,SAAS,KAAK;EACd,mBAAmB,UAAU;CAC9B,CAEI,GACH,QAAQ,WACR,KAAK,aACL,KAAK,IACN;CACA,OAAO,kCAAkC,KAAK,aAAa,KAAK,MAAM;EACrE,SAAS;EACT,WAAW,QAAQ;CACpB,CAAC;CACD,OAAO;EACN,WAAW,UAAU;EACrB,mBAAmB,UAAU;EAC7B,WAAW,QAAQ;EACnB,WAAW,QAAQ;EACnB;CACD;AACD,CAAC;AAEF,MAAM,6BACL,MACA,MACA,YACA,UAEA,OAAO,IAAI,aAAa;CACvB,MAAM,OAA+B,mBAAmB;EACvD,MAAM,KAAK;EACX,OAAO;EACP,eAAe,KAAK;EACpB,QAAQ,KAAK;EACb,YAAY,KAAK;EACjB,aAAa,KAAK;EAClB,mBAAmB,sBAAsB;GACxC,WAAW,MAAM;GACjB,mBAAmB,MAAM;GACzB,SAAS,KAAK;EACf,CAAC;EACD,gBAAgB,KAAK;EACrB,WAAW,KAAK;EAChB,gBAAgB,KAAK;CACtB,CAAC;CACD,MAAM,EAAE,kBAAkB,OAAO,eAAe,KAAK,SAAS,MAAM,KAAK,IAAI;CAG7E,MAAM,gBAAmD,CACxD;EAAE,UAAU,MAAM;EAAmB,QAAQ;CAAE,CAChD;CACA,MAAM,aAAa,eAAe,EACjC,kBAAkB,MAAM,iBACzB,CAAC;CACD,OAAO;EACN,WAAW;GACV;GACA,cAAc,KAAK;GACnB,UAAU,MAAM;EACjB;EACA;EACA,WAAW,MAAM;CAClB;AACD,CAAC;;;;;;;;;AAcF,MAAa,mBACZ,MACA,SAMA,OAAO,IAAI,aAAa;CAKvB,MAAM,aAAuB,OAAO,6BAA6B,KAAK,SAAS;EAC9E,KAAK,KAAK,OAAO;EACjB,OAAO,KAAK,OAAO;CACpB,CAAC;CAED,MAAM,YAAY,OAAO,8BAA8B,KAAK,WAAW;CACvE,IAAI,cAAc,MAAM;EAOvB,OAAO,kCAAkC,WAAW,KAAK,IAAI;EAG7D,OAAO,OAAO,0BAA0B,MAAM,MAAM,YAAY,OADvC,8BAA8B,MAAM,MAAM,OAD1C,2BAA2B,MAAM,MAAM,UAAU,SAAS,GACL,SAAS,CACd;CAC1E;CAMA,MAAM,UAA+B,OAAO,cAAc,KAAK,SAAS,KAAK,MAAM,UAAU;CAS7F,OAAO,OAAO,0BAA0B,MAAM,MAAM,YAAY,OAD3C,8BAA8B,MAAM,MAAM,OADtC,2BAA2B,MAAM,MAAM,QAAQ,SAAS,GACP,OAAO,CACZ;AACtE,CAAC,CAAC,CAAC,KACF,OAAO,SAAS,yBAAyB,QACxC,OAAO,KACN,UAAU,QAAQ;CACjB,MAAM,KAAK;CACX,SAAS,iDAAiD,IAAI,OAAO,KAAK,IAAI;CAC9E,OAAO;AACR,CAAC,CACF,CACD,CACD"}
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"}
@@ -3,8 +3,8 @@ import { configCodegenable } from "../../contracts/config-bindings.mjs";
3
3
  //#region src/plugins/sui/codegen.ts
4
4
  /** The sui-plugin's config bindings, declared ONCE. Both the live boot decl
5
5
  * and the static committed-tree decl are derived from this set:
6
- * - `network` — sugar `resolveNetwork()` / live = `"localnet"`.
7
- * - `networks` — sugar `resolveNetworks()` / live = `{ localnet: entry }`.
6
+ * - `network` — sugar `dep.network` / live = `"localnet"`.
7
+ * - `networks` — sugar `Object.fromEntries(networkNames.map(forNetwork))` / live = `{ localnet: entry }`.
8
8
  * The network NAME + connection map are environment/live data, so both are
9
9
  * RESOLVED bindings (never literals). */
10
10
  const suiConfigBindings = () => {
@@ -39,13 +39,14 @@ const suiConfigBindings = () => {
39
39
  };
40
40
  /** The LIVE Codegenable contribution. Bakes the resolved network entry into
41
41
  * the combined `config.ts` aggregate (chainId/rpc/faucet/graphql) — boot's
42
- * `assembleIdConfig` slices it back into the loadable id-config. */
42
+ * `assembleDeployment` slices it back into the loadable deployment. */
43
43
  const makeCodegenable = (resolved) => configCodegenable(suiConfigBindings(), {
44
44
  mode: "live",
45
45
  state: resolved
46
46
  });
47
47
  /** The STATIC (stack-free) Codegenable contribution for the `codegen` verb.
48
- * Emits `resolveNetwork()` / `resolveNetworks()` raw expressions — the
48
+ * Emits `dep.network` / `Object.fromEntries(networkNames.map(forNetwork))`
49
+ * raw expressions off the loaded deployment — the
49
50
  * committed `config.ts` carries no network name and no literal rpc URL. No
50
51
  * id-resolver input needed (the values are injected, not config-derived). */
51
52
  const makeStaticCodegen = () => () => [configCodegenable(suiConfigBindings(), "static")];
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/sui/codegen.ts"],"sourcesContent":["// Sui plugin — Codegenable contribution, via the UNIFIED config-binding\n// declaration.\n//\n// Architecture §6: plugins emit typed `CodegenableDecl`s; the codegen\n// orchestrator stages files into the user's source tree WITHOUT naming the\n// plugin. Sui's contribution is the active network: the active key\n// (`network: \"localnet\"`) plus the `networks.localnet` entry (chainId, mode,\n// rpc, faucet, graphql, forkUpstream).\n//\n// ONE declaration, TWO derivations. Sui declares its `config.ts`\n// contributions ONCE as a `ConfigBindingSet`; the framework derives:\n// - the LIVE (boot) decl — bakes the resolved network entry into the\n// loadable id-config (so `assembleIdConfig` reads it back), AND\n// - the STATIC (committed-tree) decl — emits `resolveNetwork()` /\n// `resolveNetworks()` raw expressions so the committed `config.ts`\n// carries NO network name and NO literal rpc URL (both are\n// environment/live data: a dynamic local rpc port; a real deployment\n// names a different network — resolved at app build/dev time via the\n// injected `__DEVSTACK_IDS__` global).\n//\n// The decl is `aggregateOnly` — it projects directly into the combined\n// `generated/config.ts` (`config.network` + `config.networks.localnet`) and\n// emits NO standalone `sui/network.ts`.\n\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport { configCodegenable, type ConfigBindingSet } from '../../contracts/config-bindings.ts';\nimport type { JsonValue } from '../../orchestrators/codegen/id-config.ts';\nimport type { ResolvedSuiNetwork } from './network-resolver.ts';\n\n/** The typed shape one `networks.<key>` entry in `config.ts` exports. */\nexport interface SuiNetworkConfigEntry {\n\t/** Genesis-digest chain identifier of the running node (not the network\n\t * name, which is the `networks.<key>` key itself). */\n\treadonly chainId: string;\n\treadonly mode: 'local' | 'local-rpc' | 'live' | 'fork';\n\treadonly rpc: string;\n\treadonly faucet: string | null;\n\treadonly graphql: string | null;\n\t/** Fork-only — upstream identity for known-package lookups. */\n\treadonly forkUpstream: string | null;\n}\n\n/** The sui-plugin's config bindings, declared ONCE. Both the live boot decl\n * and the static committed-tree decl are derived from this set:\n * - `network` — sugar `resolveNetwork()` / live = `\"localnet\"`.\n * - `networks` — sugar `resolveNetworks()` / live = `{ localnet: entry }`.\n * The network NAME + connection map are environment/live data, so both are\n * RESOLVED bindings (never literals). */\nconst suiConfigBindings = (): ConfigBindingSet<ResolvedSuiNetwork> => {\n\tconst entryOf = (r: ResolvedSuiNetwork): JsonValue =>\n\t\t({\n\t\t\tchainId: r.chainId,\n\t\t\tmode: r.mode,\n\t\t\trpc: r.rpc,\n\t\t\tfaucet: r.faucet ?? null,\n\t\t\tgraphql: r.graphql ?? null,\n\t\t\tforkUpstream: r.forkUpstream ?? null,\n\t\t}) satisfies SuiNetworkConfigEntry;\n\n\treturn {\n\t\tbucket: 'config.ts',\n\t\tkind: 'sui-network',\n\t\temitterName: 'sui-network',\n\t\tbindings: [\n\t\t\t{\n\t\t\t\tvariant: 'resolved',\n\t\t\t\tconfigPath: ['network'],\n\t\t\t\tnamespace: 'sui',\n\t\t\t\tkey: 'network',\n\t\t\t\tsugar: { kind: 'network' },\n\t\t\t\tlive: () => LOCAL_NETWORK_NAME,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvariant: 'resolved',\n\t\t\t\tconfigPath: ['networks'],\n\t\t\t\tnamespace: 'sui',\n\t\t\t\tkey: 'networks',\n\t\t\t\tsugar: { kind: 'networks' },\n\t\t\t\tlive: (r) => ({ [LOCAL_NETWORK_NAME]: entryOf(r) }),\n\t\t\t},\n\t\t],\n\t} satisfies ConfigBindingSet<ResolvedSuiNetwork>;\n};\n\n/** The LIVE Codegenable contribution. Bakes the resolved network entry into\n * the combined `config.ts` aggregate (chainId/rpc/faucet/graphql) — boot's\n * `assembleIdConfig` slices it back into the loadable id-config. */\nexport const makeCodegenable = (resolved: ResolvedSuiNetwork): CodegenableDecl =>\n\tconfigCodegenable(suiConfigBindings(), { mode: 'live', state: resolved });\n\n/** The STATIC (stack-free) Codegenable contribution for the `codegen` verb.\n * Emits `resolveNetwork()` / `resolveNetworks()` raw expressions — the\n * committed `config.ts` carries no network name and no literal rpc URL. No\n * id-resolver input needed (the values are injected, not config-derived). */\nexport const makeStaticCodegen = (): (() => ReadonlyArray<CodegenableDecl>) => () => [\n\tconfigCodegenable(suiConfigBindings(), 'static'),\n];\n"],"mappings":";;;;;;;;;AAiDA,MAAM,0BAAgE;CACrE,MAAM,WAAW,OACf;EACA,SAAS,EAAE;EACX,MAAM,EAAE;EACR,KAAK,EAAE;EACP,QAAQ,EAAE,UAAU;EACpB,SAAS,EAAE,WAAW;EACtB,cAAc,EAAE,gBAAgB;CACjC;CAED,OAAO;EACN,QAAQ;EACR,MAAM;EACN,aAAa;EACb,UAAU,CACT;GACC,SAAS;GACT,YAAY,CAAC,SAAS;GACtB,WAAW;GACX,KAAK;GACL,OAAO,EAAE,MAAM,UAAU;GACzB,YAAY;EACb,GACA;GACC,SAAS;GACT,YAAY,CAAC,UAAU;GACvB,WAAW;GACX,KAAK;GACL,OAAO,EAAE,MAAM,WAAW;GAC1B,OAAO,OAAO,GAAG,qBAAqB,QAAQ,CAAC,EAAE;EAClD,CACD;CACD;AACD;;;;AAKA,MAAa,mBAAmB,aAC/B,kBAAkB,kBAAkB,GAAG;CAAE,MAAM;CAAQ,OAAO;AAAS,CAAC;;;;;AAMzE,MAAa,gCAAwE,CACpF,kBAAkB,kBAAkB,GAAG,QAAQ,CAChD"}
1
+ {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/sui/codegen.ts"],"sourcesContent":["// Sui plugin — Codegenable contribution, via the UNIFIED config-binding\n// declaration.\n//\n// Architecture §6: plugins emit typed `CodegenableDecl`s; the codegen\n// orchestrator stages files into the user's source tree WITHOUT naming the\n// plugin. Sui's contribution is the active network: the active key\n// (`network: \"localnet\"`) plus the `networks.localnet` entry (chainId, mode,\n// rpc, faucet, graphql, forkUpstream).\n//\n// ONE declaration, TWO derivations. Sui declares its `config.ts`\n// contributions ONCE as a `ConfigBindingSet`; the framework derives:\n// - the LIVE (boot) decl — bakes the resolved network entry into the\n// loadable deployment (so `assembleDeployment` reads it back), AND\n// - the STATIC (committed-tree) decl — emits `dep.network` /\n// `Object.fromEntries(networkNames.map(forNetwork))` raw expressions off\n// the loaded deployment so the committed `config.ts`\n// carries NO network name and NO literal rpc URL (both are\n// environment/live data: a dynamic local rpc port; a real deployment\n// names a different network — resolved at app build/dev time via the\n// injected `__DEVSTACK_DEPLOYMENT__` global).\n//\n// The decl is `aggregateOnly` — it projects directly into the combined\n// `generated/config.ts` (`config.network` + `config.networks.localnet`) and\n// emits NO standalone `sui/network.ts`.\n\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport { configCodegenable, type ConfigBindingSet } from '../../contracts/config-bindings.ts';\nimport type { JsonValue } from '../../orchestrators/codegen/deployment.ts';\nimport type { ResolvedSuiNetwork } from './network-resolver.ts';\n\n/** The typed shape one `networks.<key>` entry in `config.ts` exports. */\nexport interface SuiNetworkConfigEntry {\n\t/** Genesis-digest chain identifier of the running node (not the network\n\t * name, which is the `networks.<key>` key itself). */\n\treadonly chainId: string;\n\treadonly mode: 'local' | 'local-rpc' | 'live' | 'fork';\n\treadonly rpc: string;\n\treadonly faucet: string | null;\n\treadonly graphql: string | null;\n\t/** Fork-only — upstream identity for known-package lookups. */\n\treadonly forkUpstream: string | null;\n}\n\n/** The sui-plugin's config bindings, declared ONCE. Both the live boot decl\n * and the static committed-tree decl are derived from this set:\n * - `network` — sugar `dep.network` / live = `\"localnet\"`.\n * - `networks` — sugar `Object.fromEntries(networkNames.map(forNetwork))` / live = `{ localnet: entry }`.\n * The network NAME + connection map are environment/live data, so both are\n * RESOLVED bindings (never literals). */\nconst suiConfigBindings = (): ConfigBindingSet<ResolvedSuiNetwork> => {\n\tconst entryOf = (r: ResolvedSuiNetwork): JsonValue =>\n\t\t({\n\t\t\tchainId: r.chainId,\n\t\t\tmode: r.mode,\n\t\t\trpc: r.rpc,\n\t\t\tfaucet: r.faucet ?? null,\n\t\t\tgraphql: r.graphql ?? null,\n\t\t\tforkUpstream: r.forkUpstream ?? null,\n\t\t}) satisfies SuiNetworkConfigEntry;\n\n\treturn {\n\t\tbucket: 'config.ts',\n\t\tkind: 'sui-network',\n\t\temitterName: 'sui-network',\n\t\tbindings: [\n\t\t\t{\n\t\t\t\tvariant: 'resolved',\n\t\t\t\tconfigPath: ['network'],\n\t\t\t\tnamespace: 'sui',\n\t\t\t\tkey: 'network',\n\t\t\t\tsugar: { kind: 'network' },\n\t\t\t\tlive: () => LOCAL_NETWORK_NAME,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvariant: 'resolved',\n\t\t\t\tconfigPath: ['networks'],\n\t\t\t\tnamespace: 'sui',\n\t\t\t\tkey: 'networks',\n\t\t\t\tsugar: { kind: 'networks' },\n\t\t\t\tlive: (r) => ({ [LOCAL_NETWORK_NAME]: entryOf(r) }),\n\t\t\t},\n\t\t],\n\t} satisfies ConfigBindingSet<ResolvedSuiNetwork>;\n};\n\n/** The LIVE Codegenable contribution. Bakes the resolved network entry into\n * the combined `config.ts` aggregate (chainId/rpc/faucet/graphql) — boot's\n * `assembleDeployment` slices it back into the loadable deployment. */\nexport const makeCodegenable = (resolved: ResolvedSuiNetwork): CodegenableDecl =>\n\tconfigCodegenable(suiConfigBindings(), { mode: 'live', state: resolved });\n\n/** The STATIC (stack-free) Codegenable contribution for the `codegen` verb.\n * Emits `dep.network` / `Object.fromEntries(networkNames.map(forNetwork))`\n * raw expressions off the loaded deployment — the\n * committed `config.ts` carries no network name and no literal rpc URL. No\n * id-resolver input needed (the values are injected, not config-derived). */\nexport const makeStaticCodegen = (): (() => ReadonlyArray<CodegenableDecl>) => () => [\n\tconfigCodegenable(suiConfigBindings(), 'static'),\n];\n"],"mappings":";;;;;;;;;AAkDA,MAAM,0BAAgE;CACrE,MAAM,WAAW,OACf;EACA,SAAS,EAAE;EACX,MAAM,EAAE;EACR,KAAK,EAAE;EACP,QAAQ,EAAE,UAAU;EACpB,SAAS,EAAE,WAAW;EACtB,cAAc,EAAE,gBAAgB;CACjC;CAED,OAAO;EACN,QAAQ;EACR,MAAM;EACN,aAAa;EACb,UAAU,CACT;GACC,SAAS;GACT,YAAY,CAAC,SAAS;GACtB,WAAW;GACX,KAAK;GACL,OAAO,EAAE,MAAM,UAAU;GACzB,YAAY;EACb,GACA;GACC,SAAS;GACT,YAAY,CAAC,UAAU;GACvB,WAAW;GACX,KAAK;GACL,OAAO,EAAE,MAAM,WAAW;GAC1B,OAAO,OAAO,GAAG,qBAAqB,QAAQ,CAAC,EAAE;EAClD,CACD;CACD;AACD;;;;AAKA,MAAa,mBAAmB,aAC/B,kBAAkB,kBAAkB,GAAG;CAAE,MAAM;CAAQ,OAAO;AAAS,CAAC;;;;;;AAOzE,MAAa,gCAAwE,CACpF,kBAAkB,kBAAkB,GAAG,QAAQ,CAChD"}
@@ -11,7 +11,6 @@ import { chainProbeCapabilityKey } from "../../contracts/chain-probe.mjs";
11
11
  import "./move/index.mjs";
12
12
  import { makeSuiForkRoutables, makeSuiLocalRoutables } from "./routable.mjs";
13
13
  import { sanitizeAlias } from "../../substrate/runtime/managed-container.mjs";
14
- import { resolveNetworkOptions } from "../../orchestrators/network-options.mjs";
15
14
  import { defineModeNamespace } from "../../api/mode-narrowed-factory.mjs";
16
15
  import { credentialedUrl, withDatabase } from "../internal/postgres-sidecar/connection.mjs";
17
16
  import { bootPostgresSidecar } from "../internal/postgres-sidecar/service.mjs";
@@ -208,7 +207,7 @@ const bootAndEmit = (opts, indexer, prebuiltImage) => Effect.gen(function* () {
208
207
  fundingFaucetStrategy
209
208
  };
210
209
  const realChainId = value.chainId;
211
- const faucetContribution = !resolveNetworkOptions(identity.network).faucet || value.fundingFaucetStrategy === null ? [] : [{
210
+ const faucetContribution = !(identity.network !== "mainnet") || value.fundingFaucetStrategy === null ? [] : [{
212
211
  kind: "strategy-contributor",
213
212
  capabilityKey: faucetCapabilityKey(realChainId),
214
213
  strategy: value.fundingFaucetStrategy,