@mysten-incubation/devstack 0.7.0 → 0.7.1

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 (44) hide show
  1. package/dist/build-integrations/vite/index.mjs +1 -0
  2. package/dist/build-integrations/vite/index.mjs.map +1 -1
  3. package/dist/cli/wirings/apply.mjs +24 -6
  4. package/dist/cli/wirings/apply.mjs.map +1 -1
  5. package/dist/cli/wirings/dump-deployment.mjs +2 -1
  6. package/dist/cli/wirings/dump-deployment.mjs.map +1 -1
  7. package/dist/cli/wirings/identity.mjs +2 -1
  8. package/dist/cli/wirings/identity.mjs.map +1 -1
  9. package/dist/cli/wirings/snapshot.mjs +2 -1
  10. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  11. package/dist/cli/wirings/up.mjs +25 -29
  12. package/dist/cli/wirings/up.mjs.map +1 -1
  13. package/dist/plugins/package/codegen.mjs +4 -4
  14. package/dist/plugins/package/codegen.mjs.map +1 -1
  15. package/dist/plugins/package/dep-resolution.mjs +19 -9
  16. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  17. package/dist/plugins/package/index.d.mts +3 -3
  18. package/dist/plugins/package/index.mjs +9 -5
  19. package/dist/plugins/package/index.mjs.map +1 -1
  20. package/dist/plugins/package/mode-known.mjs +2 -2
  21. package/dist/plugins/package/mode-known.mjs.map +1 -1
  22. package/dist/plugins/package/mode-local.mjs +4 -4
  23. package/dist/plugins/package/mode-local.mjs.map +1 -1
  24. package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
  25. package/dist/plugins/sui/index.d.mts +10 -10
  26. package/dist/plugins/sui/index.mjs.map +1 -1
  27. package/dist/plugins/sui/move/index.mjs +10 -6
  28. package/dist/plugins/sui/move/index.mjs.map +1 -1
  29. package/dist/plugins/wallet/codegen.d.mts +14 -10
  30. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  31. package/dist/plugins/wallet/origin-policy.mjs +43 -3
  32. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  33. package/dist/plugins/wallet/service.mjs +9 -7
  34. package/dist/plugins/wallet/service.mjs.map +1 -1
  35. package/dist/substrate/cross-process.mjs +2 -1
  36. package/dist/substrate/cross-process.mjs.map +1 -1
  37. package/dist/substrate/runtime/cross-process/roster.mjs +5 -2
  38. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  39. package/dist/surfaces/cli/commands/supervisor-presence.mjs +4 -2
  40. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  41. package/dist/surfaces/cli/errors.mjs +12 -2
  42. package/dist/surfaces/cli/errors.mjs.map +1 -1
  43. package/dist/surfaces/cli/index.mjs.map +1 -1
  44. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../src/cli/wirings/snapshot.ts"],"sourcesContent":["// `devstack snapshot save / restore / delete` verb wirings.\n//\n// `save` is live-aware: when a supervisor owns the stack, publish a\n// `snapshot.capture` command and read the structured result from the\n// command-channel reply payload (no tail-fiber). `restore` and\n// `delete` are direct/offline only; they refuse to run when a\n// supervisor is live.\n\nimport { dirname } from 'node:path';\n\nimport { Effect, Exit, FileSystem, Logger } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\nimport {\n\tcommandChannelPaths,\n\tmakeCommandChannelPublisher,\n\tmakeProjectionRef,\n\ttype SupervisedStack,\n} from '../../substrate/runtime/index.ts';\nimport { superviseStackWithProductionBoot } from '../../orchestrators/boot.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\tSnapshotOrchestratorService,\n\ttype SnapshotMetadata,\n} from '../../orchestrators/snapshot/index.ts';\nimport { CliInternalError, CliUnavailableError } from '../../surfaces/cli/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { makeConfigLoader, resolveConfigPath } from './config-loader.ts';\nimport {\n\tensureNoLiveSupervisor,\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { buildDirectSnapshotLayers, buildVerbLayers } from '../../orchestrators/layers.ts';\nimport { provideFileSystem } from './provide-file-system.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS = 60 * 60 * 1000;\n\nconst mintCliSnapshotId = (): string => `snap-${Date.now()}-${mintRandomSuffix(8)}`;\n\n/** Structured payload that the supervisor's command-channel bridge\n * attaches to the ack/error reply for `snapshot.capture`. Mirrors the\n * shape produced by `cli/wirings/up.ts:snapshotCaptureAckFromEvent`. */\ninterface SnapshotCaptureAckPayload {\n\treadonly kind: 'captured' | 'failed' | 'skipped';\n\treadonly snapshotId?: string;\n\treadonly name?: string;\n\treadonly summary?: string;\n\treadonly reason?: string;\n}\n\nconst isCapturePayload = (value: unknown): value is SnapshotCaptureAckPayload => {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst kind = (value as { readonly kind?: unknown }).kind;\n\treturn kind === 'captured' || kind === 'failed' || kind === 'skipped';\n};\n\n/** Publish `snapshot.capture` to a live supervisor and surface its\n * structured ack/error payload. The reply payload carries the\n * captured metadata (success) or failure summary (failure) — no\n * side-channel event tail required. Returns `null` if no supervisor\n * is live so the caller can fall through to the direct path. */\nexport const runSnapshotCaptureAgainstLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string } | null, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst presence = yield* probeSupervisorPresence(identity.rosterFile).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (!presence.live) return null;\n\n\t\tconst snapshotId = args.snapshotId ?? mintCliSnapshotId();\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst publisher = yield* makeCommandChannelPublisher(\n\t\t\t\t\tcommandChannelPaths(identity.stackRoot),\n\t\t\t\t);\n\t\t\t\tconst published = yield* publisher.publish({\n\t\t\t\t\ttag: 'snapshot.capture',\n\t\t\t\t\tsnapshotId,\n\t\t\t\t\t...(args.name === undefined ? {} : { name: args.name }),\n\t\t\t\t});\n\t\t\t\tconst reply = yield* publisher.awaitCompletion(published, {\n\t\t\t\t\ttimeoutMillis: LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS,\n\t\t\t\t});\n\n\t\t\t\tif (reply.ok) {\n\t\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\t\tif (payload?.kind === 'captured' && typeof payload.snapshotId === 'string') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsnapshotId: payload.snapshotId,\n\t\t\t\t\t\t\tname: payload.name ?? args.name ?? payload.snapshotId,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Older supervisor without structured payload (or a peer mock\n\t\t\t\t\t// in tests): synthesize from the request — the supervisor\n\t\t\t\t\t// already accepted our snapshotId, so it's authoritative.\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\tname: args.name ?? snapshotId,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\tif (payload?.kind === 'skipped') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (payload?.kind === 'failed') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\tmessage: `snapshot capture failed: ${payload.summary ?? reply.message}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// No structured payload — surface the raw reply.message.\n\t\t\t\tif (/already running/i.test(reply.message)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\tservice: 'devstack supervisor',\n\t\t\t\t\t\tmessage: reply.message,\n\t\t\t\t\t\thint: 'check the attached `devstack up` session and try again',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}),\n\t\t);\n\t});\n\nexport const runSnapshotRestoreDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t// Offline restore: there is NO live supervisor (ensured below), so no\n\t\t// plugin contributes a live identity slice. Call `restore` with no\n\t\t// `participants` — `runRestore` reads this as \"no live stack\" and\n\t\t// skips ONLY the cross-plugin contribution guard (the snapshot's own\n\t\t// recorded identity has nothing live to disagree with). The runtime\n\t\t// guard (app/stack/network) and the snapshot-side emptiness refusal\n\t\t// still fire. This is the same empty-participants startup restore\n\t\t// contract interrupted-restore recovery uses.\n\t\tyield* provideFileSystem(fs, snapshot.restore({ id: snapshotId }));\n\t});\n\tconst restored = program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n\treturn ensureNoLiveSupervisor(\n\t\tidentity,\n\t\t'shut down the attached `devstack up` session before restoring a snapshot',\n\t).pipe(Effect.andThen(restored));\n};\n\nexport const runSnapshotDeleteDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* provideFileSystem(fs, snapshot.delete(snapshotId));\n\t});\n\treturn program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n};\n\nconst runSnapshotCaptureDirect = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst loaded = yield* loader.load(args.configPath);\n\t\treturn yield* runSnapshotCaptureDirectLoaded(identity, loaded, args);\n\t});\n};\n\nexport const runSnapshotCaptureDirectLoaded = (\n\tidentity: ResolvedIdentity,\n\tloaded: LoadedConfig,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\t\tconst substrateLayers = buildVerbLayers({\n\t\t\tidentity: identityValue,\n\t\t\tstack,\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t});\n\n\t\tconst program = Effect.gen(function* () {\n\t\t\tconst state = yield* makeProjectionRef();\n\t\t\tlet captureExit: Exit.Exit<void, unknown> = Exit.succeed(undefined);\n\t\t\tconst capturedMeta: { current: SnapshotMetadata | null } = { current: null };\n\t\t\t// The contribution dispatcher + post-acquire hook + built-in\n\t\t\t// plugin-context extension are assembled in ONE place\n\t\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`); this\n\t\t\t// one-shot verb supplies only the per-caller inputs (`extras`,\n\t\t\t// `lifetime: 'one-shot'`, and the capture `withinScope` hook).\n\t\t\t// Result semantics are unchanged: the hook still tees the capture\n\t\t\t// `Exit` + metadata into the closure cells read below.\n\t\t\tyield* superviseStackWithProductionBoot(\n\t\t\t\t{ _tag: 'Stack', members: stack.members, options: stack.options },\n\t\t\t\tidentityValue,\n\t\t\t\tstate,\n\t\t\t\t{\n\t\t\t\t\textras: stack.options.extras,\n\t\t\t\t\tlifetime: 'one-shot',\n\t\t\t\t\t// Offline one-shot capture: the bounce gather → stop → commit →\n\t\t\t\t\t// retag → hard-rm runs here; NO `resume` is injected because\n\t\t\t\t\t// this supervise scope is `one-shot` (it closes right after) —\n\t\t\t\t\t// the NEXT boot is the resume. Direct call to the orchestrator\n\t\t\t\t\t// (the former `captureSnapshot` command-primitive is gone).\n\t\t\t\t\twithinScope: () =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\t\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\t\t\t\tconst graphInput = yield* computeSnapshotGraphInputFromStack({\n\t\t\t\t\t\t\t\tstack,\n\t\t\t\t\t\t\t\tdevstackVersion: readDevstackVersion({ fallback: '0.0.0' }),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn yield* snapshot\n\t\t\t\t\t\t\t\t.capture({\n\t\t\t\t\t\t\t\t\t...(args.snapshotId === undefined ? {} : { id: args.snapshotId }),\n\t\t\t\t\t\t\t\t\t...(args.name === undefined ? {} : { label: args.name }),\n\t\t\t\t\t\t\t\t\tgraphInput,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.pipe(Effect.provideService(FileSystem.FileSystem, fs));\n\t\t\t\t\t\t}).pipe(\n\t\t\t\t\t\t\tEffect.tap((meta) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcapturedMeta.current = meta;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t\tEffect.exit,\n\t\t\t\t\t\t\tEffect.tap((exit) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcaptureExit = exit;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t);\n\t\t\tif (Exit.isFailure(captureExit)) {\n\t\t\t\tyield* Effect.failCause(captureExit.cause);\n\t\t\t}\n\t\t\tif (capturedMeta.current === null) {\n\t\t\t\treturn yield* Effect.die('snapshot capture completed without metadata');\n\t\t\t}\n\t\t\tconst meta = capturedMeta.current;\n\t\t\treturn { snapshotId: meta.id, name: meta.label ?? meta.id };\n\t\t});\n\n\t\treturn yield* ensureNoLiveSupervisor(\n\t\t\teffectiveIdentity,\n\t\t\t'shut down the attached `devstack up` session before saving a snapshot',\n\t\t).pipe(\n\t\t\tEffect.andThen(\n\t\t\t\tprogram.pipe(\n\t\t\t\t\tEffect.provide(substrateLayers),\n\t\t\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t});\n\nexport const runSnapshotCaptureLiveAware = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tif (args.configPath === undefined && resolveConfigPath(undefined) === null) {\n\t\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(identity, args);\n\t\t\tif (live !== null) return live;\n\t\t\treturn yield* runSnapshotCaptureDirect(identity, args);\n\t\t}\n\t\tconst loaded = yield* makeConfigLoader().load(args.configPath);\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(effectiveIdentity, args);\n\t\tif (live !== null) return live;\n\t\treturn yield* runSnapshotCaptureDirectLoaded(effectiveIdentity, loaded, args);\n\t});\n"],"mappings":"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"}
1
+ {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../src/cli/wirings/snapshot.ts"],"sourcesContent":["// `devstack snapshot save / restore / delete` verb wirings.\n//\n// `save` is live-aware: when a supervisor owns the stack, publish a\n// `snapshot.capture` command and read the structured result from the\n// command-channel reply payload (no tail-fiber). `restore` and\n// `delete` are direct/offline only; they refuse to run when a\n// supervisor is live.\n\nimport { dirname } from 'node:path';\n\nimport { Effect, Exit, FileSystem, Logger } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\nimport {\n\tcommandChannelPaths,\n\tmakeCommandChannelPublisher,\n\tmakeProjectionRef,\n\ttype SupervisedStack,\n} from '../../substrate/runtime/index.ts';\nimport { superviseStackWithProductionBoot } from '../../orchestrators/boot.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\tSnapshotOrchestratorService,\n\ttype SnapshotMetadata,\n} from '../../orchestrators/snapshot/index.ts';\nimport { CliInternalError, CliUnavailableError } from '../../surfaces/cli/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { makeConfigLoader, resolveConfigPath } from './config-loader.ts';\nimport {\n\tensureNoLiveSupervisor,\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { buildDirectSnapshotLayers, buildVerbLayers } from '../../orchestrators/layers.ts';\nimport { provideFileSystem } from './provide-file-system.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS = 60 * 60 * 1000;\n\nconst mintCliSnapshotId = (): string => `snap-${Date.now()}-${mintRandomSuffix(8)}`;\n\n/** Structured payload that the supervisor's command-channel bridge\n * attaches to the ack/error reply for `snapshot.capture`. Mirrors the\n * shape produced by `cli/wirings/up.ts:snapshotCaptureAckFromEvent`. */\ninterface SnapshotCaptureAckPayload {\n\treadonly kind: 'captured' | 'failed' | 'skipped';\n\treadonly snapshotId?: string;\n\treadonly name?: string;\n\treadonly summary?: string;\n\treadonly reason?: string;\n}\n\nconst isCapturePayload = (value: unknown): value is SnapshotCaptureAckPayload => {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst kind = (value as { readonly kind?: unknown }).kind;\n\treturn kind === 'captured' || kind === 'failed' || kind === 'skipped';\n};\n\n/** Publish `snapshot.capture` to a live supervisor and surface its\n * structured ack/error payload. The reply payload carries the\n * captured metadata (success) or failure summary (failure) — no\n * side-channel event tail required. Returns `null` if no supervisor\n * is live so the caller can fall through to the direct path. */\nexport const runSnapshotCaptureAgainstLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string } | null, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst presence = yield* probeSupervisorPresence(identity.rosterFile).pipe(\n\t\t\tEffect.catch(() =>\n\t\t\t\tEffect.succeed({ live: false, pid: null, hostname: null, graphInputId: null }),\n\t\t\t),\n\t\t);\n\t\tif (!presence.live) return null;\n\n\t\tconst snapshotId = args.snapshotId ?? mintCliSnapshotId();\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst publisher = yield* makeCommandChannelPublisher(\n\t\t\t\t\tcommandChannelPaths(identity.stackRoot),\n\t\t\t\t);\n\t\t\t\tconst published = yield* publisher.publish({\n\t\t\t\t\ttag: 'snapshot.capture',\n\t\t\t\t\tsnapshotId,\n\t\t\t\t\t...(args.name === undefined ? {} : { name: args.name }),\n\t\t\t\t});\n\t\t\t\tconst reply = yield* publisher.awaitCompletion(published, {\n\t\t\t\t\ttimeoutMillis: LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS,\n\t\t\t\t});\n\n\t\t\t\tif (reply.ok) {\n\t\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\t\tif (payload?.kind === 'captured' && typeof payload.snapshotId === 'string') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsnapshotId: payload.snapshotId,\n\t\t\t\t\t\t\tname: payload.name ?? args.name ?? payload.snapshotId,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Older supervisor without structured payload (or a peer mock\n\t\t\t\t\t// in tests): synthesize from the request — the supervisor\n\t\t\t\t\t// already accepted our snapshotId, so it's authoritative.\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\tname: args.name ?? snapshotId,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\tif (payload?.kind === 'skipped') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (payload?.kind === 'failed') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\tmessage: `snapshot capture failed: ${payload.summary ?? reply.message}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// No structured payload — surface the raw reply.message.\n\t\t\t\tif (/already running/i.test(reply.message)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\tservice: 'devstack supervisor',\n\t\t\t\t\t\tmessage: reply.message,\n\t\t\t\t\t\thint: 'check the attached `devstack up` session and try again',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}),\n\t\t);\n\t});\n\nexport const runSnapshotRestoreDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t// Offline restore: there is NO live supervisor (ensured below), so no\n\t\t// plugin contributes a live identity slice. Call `restore` with no\n\t\t// `participants` — `runRestore` reads this as \"no live stack\" and\n\t\t// skips ONLY the cross-plugin contribution guard (the snapshot's own\n\t\t// recorded identity has nothing live to disagree with). The runtime\n\t\t// guard (app/stack/network) and the snapshot-side emptiness refusal\n\t\t// still fire. This is the same empty-participants startup restore\n\t\t// contract interrupted-restore recovery uses.\n\t\tyield* provideFileSystem(fs, snapshot.restore({ id: snapshotId }));\n\t});\n\tconst restored = program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n\treturn ensureNoLiveSupervisor(\n\t\tidentity,\n\t\t'shut down the attached `devstack up` session before restoring a snapshot',\n\t).pipe(Effect.andThen(restored));\n};\n\nexport const runSnapshotDeleteDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* provideFileSystem(fs, snapshot.delete(snapshotId));\n\t});\n\treturn program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n};\n\nconst runSnapshotCaptureDirect = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst loaded = yield* loader.load(args.configPath);\n\t\treturn yield* runSnapshotCaptureDirectLoaded(identity, loaded, args);\n\t});\n};\n\nexport const runSnapshotCaptureDirectLoaded = (\n\tidentity: ResolvedIdentity,\n\tloaded: LoadedConfig,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\t\tconst substrateLayers = buildVerbLayers({\n\t\t\tidentity: identityValue,\n\t\t\tstack,\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t});\n\n\t\tconst program = Effect.gen(function* () {\n\t\t\tconst state = yield* makeProjectionRef();\n\t\t\tlet captureExit: Exit.Exit<void, unknown> = Exit.succeed(undefined);\n\t\t\tconst capturedMeta: { current: SnapshotMetadata | null } = { current: null };\n\t\t\t// The contribution dispatcher + post-acquire hook + built-in\n\t\t\t// plugin-context extension are assembled in ONE place\n\t\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`); this\n\t\t\t// one-shot verb supplies only the per-caller inputs (`extras`,\n\t\t\t// `lifetime: 'one-shot'`, and the capture `withinScope` hook).\n\t\t\t// Result semantics are unchanged: the hook still tees the capture\n\t\t\t// `Exit` + metadata into the closure cells read below.\n\t\t\tyield* superviseStackWithProductionBoot(\n\t\t\t\t{ _tag: 'Stack', members: stack.members, options: stack.options },\n\t\t\t\tidentityValue,\n\t\t\t\tstate,\n\t\t\t\t{\n\t\t\t\t\textras: stack.options.extras,\n\t\t\t\t\tlifetime: 'one-shot',\n\t\t\t\t\t// Offline one-shot capture: the bounce gather → stop → commit →\n\t\t\t\t\t// retag → hard-rm runs here; NO `resume` is injected because\n\t\t\t\t\t// this supervise scope is `one-shot` (it closes right after) —\n\t\t\t\t\t// the NEXT boot is the resume. Direct call to the orchestrator\n\t\t\t\t\t// (the former `captureSnapshot` command-primitive is gone).\n\t\t\t\t\twithinScope: () =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\t\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\t\t\t\tconst graphInput = yield* computeSnapshotGraphInputFromStack({\n\t\t\t\t\t\t\t\tstack,\n\t\t\t\t\t\t\t\tdevstackVersion: readDevstackVersion({ fallback: '0.0.0' }),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn yield* snapshot\n\t\t\t\t\t\t\t\t.capture({\n\t\t\t\t\t\t\t\t\t...(args.snapshotId === undefined ? {} : { id: args.snapshotId }),\n\t\t\t\t\t\t\t\t\t...(args.name === undefined ? {} : { label: args.name }),\n\t\t\t\t\t\t\t\t\tgraphInput,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.pipe(Effect.provideService(FileSystem.FileSystem, fs));\n\t\t\t\t\t\t}).pipe(\n\t\t\t\t\t\t\tEffect.tap((meta) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcapturedMeta.current = meta;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t\tEffect.exit,\n\t\t\t\t\t\t\tEffect.tap((exit) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcaptureExit = exit;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t);\n\t\t\tif (Exit.isFailure(captureExit)) {\n\t\t\t\tyield* Effect.failCause(captureExit.cause);\n\t\t\t}\n\t\t\tif (capturedMeta.current === null) {\n\t\t\t\treturn yield* Effect.die('snapshot capture completed without metadata');\n\t\t\t}\n\t\t\tconst meta = capturedMeta.current;\n\t\t\treturn { snapshotId: meta.id, name: meta.label ?? meta.id };\n\t\t});\n\n\t\treturn yield* ensureNoLiveSupervisor(\n\t\t\teffectiveIdentity,\n\t\t\t'shut down the attached `devstack up` session before saving a snapshot',\n\t\t).pipe(\n\t\t\tEffect.andThen(\n\t\t\t\tprogram.pipe(\n\t\t\t\t\tEffect.provide(substrateLayers),\n\t\t\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t});\n\nexport const runSnapshotCaptureLiveAware = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tif (args.configPath === undefined && resolveConfigPath(undefined) === null) {\n\t\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(identity, args);\n\t\t\tif (live !== null) return live;\n\t\t\treturn yield* runSnapshotCaptureDirect(identity, args);\n\t\t}\n\t\tconst loaded = yield* makeConfigLoader().load(args.configPath);\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(effectiveIdentity, args);\n\t\tif (live !== null) return live;\n\t\treturn yield* runSnapshotCaptureDirectLoaded(effectiveIdentity, loaded, args);\n\t});\n"],"mappings":"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"}
@@ -81,7 +81,7 @@ const makeSnapshotCommandHandler = (params) => Effect.gen(function* () {
81
81
  });
82
82
  const installLiveSupervisorRoster = (params) => Effect.gen(function* () {
83
83
  const paths = rosterPathsFor(params.stackRoot);
84
- if (!(yield* claim(paths)).soleHolder) {
84
+ if (!(yield* claim(paths, "normal", void 0, { graphInputId: params.graphInputId })).soleHolder) {
85
85
  yield* release(paths).pipe(Effect.catch(() => Effect.void));
86
86
  return yield* Effect.fail(new CliSupervisorLiveError({
87
87
  app: params.app,
@@ -122,43 +122,38 @@ const buildUpBootBundle = (input) => {
122
122
  const snapshot = yield* SnapshotOrchestratorService;
123
123
  const fs = yield* FileSystem.FileSystem;
124
124
  const stackPaths = yield* StackPathsService;
125
+ const graphInput = yield* computeGraphInput;
125
126
  yield* recoverInterruptedRestore({
126
127
  liveRoot: stackPaths.stackRoot,
127
- restoreSnapshot: (id) => computeGraphInput.pipe(Effect.flatMap((currentGraphInput) => snapshot.restore({
128
+ restoreSnapshot: (id) => snapshot.restore({
128
129
  id,
129
- currentGraphInput,
130
+ currentGraphInput: graphInput,
130
131
  graphInputMismatchPolicy: "warn"
131
- })))
132
+ })
132
133
  });
133
134
  if (snapshotCache !== void 0) if (snapshotCache.existingSnapshotId === void 0) yield* Effect.logWarning(`snapshot cache ${snapshotCache.name} was not found; boot will refresh it`);
134
- else {
135
- const graphInput = yield* computeGraphInput;
136
- if (((yield* provideFileSystem(fs, snapshot.list)).find((snapshotEntry) => snapshotEntry.id === snapshotCache.existingSnapshotId)?.metadata?.graphInput.graphInputId ?? null) === graphInput.graphInputId) {
137
- refreshSnapshotCache = false;
138
- yield* provideFileSystem(fs, snapshot.restore({
139
- id: snapshotCache.existingSnapshotId,
140
- currentGraphInput: graphInput,
141
- graphInputMismatchPolicy: "block"
142
- }));
143
- } else {
144
- refreshSnapshotCache = true;
145
- yield* Effect.logWarning(`snapshot cache ${snapshotCache.name} graph input is stale or unreadable; boot will refresh it`);
146
- }
147
- }
148
- else if (startFromSnapshot !== void 0) {
149
- const graphInput = yield* computeGraphInput;
150
- if (startFromSnapshot.stalePolicy === "clean-start") if (((yield* provideFileSystem(fs, snapshot.list)).find((snapshotEntry) => snapshotEntry.id === startFromSnapshot.id)?.metadata?.graphInput.graphInputId ?? null) !== graphInput.graphInputId) yield* Effect.logWarning(`snapshot ${startFromSnapshot.id} graph input is stale; clean-start policy skipped restore`);
151
- else yield* provideFileSystem(fs, snapshot.restore({
152
- id: startFromSnapshot.id,
135
+ else if (((yield* provideFileSystem(fs, snapshot.list)).find((snapshotEntry) => snapshotEntry.id === snapshotCache.existingSnapshotId)?.metadata?.graphInput.graphInputId ?? null) === graphInput.graphInputId) {
136
+ refreshSnapshotCache = false;
137
+ yield* provideFileSystem(fs, snapshot.restore({
138
+ id: snapshotCache.existingSnapshotId,
153
139
  currentGraphInput: graphInput,
154
140
  graphInputMismatchPolicy: "block"
155
141
  }));
156
- else yield* provideFileSystem(fs, snapshot.restore({
157
- id: startFromSnapshot.id,
158
- currentGraphInput: graphInput,
159
- graphInputMismatchPolicy: startFromSnapshot.stalePolicy
160
- }));
142
+ } else {
143
+ refreshSnapshotCache = true;
144
+ yield* Effect.logWarning(`snapshot cache ${snapshotCache.name} graph input is stale or unreadable; boot will refresh it`);
161
145
  }
146
+ else if (startFromSnapshot !== void 0) if (startFromSnapshot.stalePolicy === "clean-start") if (((yield* provideFileSystem(fs, snapshot.list)).find((snapshotEntry) => snapshotEntry.id === startFromSnapshot.id)?.metadata?.graphInput.graphInputId ?? null) !== graphInput.graphInputId) yield* Effect.logWarning(`snapshot ${startFromSnapshot.id} graph input is stale; clean-start policy skipped restore`);
147
+ else yield* provideFileSystem(fs, snapshot.restore({
148
+ id: startFromSnapshot.id,
149
+ currentGraphInput: graphInput,
150
+ graphInputMismatchPolicy: "block"
151
+ }));
152
+ else yield* provideFileSystem(fs, snapshot.restore({
153
+ id: startFromSnapshot.id,
154
+ currentGraphInput: graphInput,
155
+ graphInputMismatchPolicy: startFromSnapshot.stalePolicy
156
+ }));
162
157
  const commandChannel = yield* installCommandChannelBridge({
163
158
  stackRoot: stackPaths.stackRoot,
164
159
  handle: h.supervisor
@@ -166,7 +161,8 @@ const buildUpBootBundle = (input) => {
166
161
  yield* installLiveSupervisorRoster({
167
162
  stackRoot: stackPaths.stackRoot,
168
163
  app: String(identityValue.app),
169
- stack: String(identityValue.stack)
164
+ stack: String(identityValue.stack),
165
+ graphInputId: graphInput.graphInputId
170
166
  });
171
167
  const rendererEvents = yield* Queue.unbounded();
172
168
  const renderer = makeTuiSurface({
@@ -1 +1 @@
1
- {"version":3,"file":"up.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/up.ts"],"sourcesContent":["// `devstack up` verb wiring.\n//\n// Wires the substrate Layer stack, supervisor, attached renderer, and\n// in-process TUI command queue. The Effect runs as the outer Node fiber\n// so SIGINT reaches scope finalizers (see surfaces/cli/index.ts\n// architecture invariant).\n//\n// Logger layer: `Logger.layer([])` (silent). `up` owns the TUI, which is\n// the operator-facing output channel; routing structured `Effect.log*`\n// records to stderr would scribble on top of the live ink renderer in\n// human mode and would duplicate the structured event stream the plain\n// renderer already emits in non-TTY mode. The sibling `apply` verb (no\n// TUI) uses `Logger.consolePretty()` because its consumer is CI.\n\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect, Exit, FileSystem, Queue, Scope, Stream } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport type { EngineEvent } from '../../substrate/events.ts';\nimport { StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tclaim,\n\theartbeatFiber,\n\trelease,\n\tstartFileWatcher,\n\ttype SupervisedStack,\n\ttype SupervisorCommandHandler,\n} from '../../substrate/runtime/index.ts';\nimport {\n\tcollectLifecyclePruneInventory,\n\tdefaultLifecyclePruneSelection,\n\tDEFAULT_LIFECYCLE_PRUNE_RESOURCES,\n\trunLifecyclePrune,\n} from '../../orchestrators/lifecycle-prune/index.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\trecoverInterruptedRestore,\n\tSnapshotOrchestratorService,\n} from '../../orchestrators/snapshot/index.ts';\nimport type { Stack } from '../../api/define-devstack.ts';\nimport {\n\trunStackWithBoot,\n\ttype CommandHandlerFactory,\n\ttype RunStackBootBag,\n} from '../../api/run-stack-internal.ts';\nimport type { BootError } from '../../api/run-stack.ts';\nimport {\n\ttype CliError,\n\tCliInternalError,\n\tCliSnapshotNotFoundError,\n\tCliSupervisorLiveError,\n\tCliUsageError,\n\ttype GlobalFlags,\n} from '../../surfaces/cli/index.ts';\nimport type { CommandResult } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport { makeTuiSurface } from '../../surfaces/tui/index.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeQueueCommandPublisher, resolveUpRendererMode } from '../up-lifecycle.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport { makeSnapshotReader } from '../snapshot-reader.ts';\nimport {\n\tfindCliSupervisorLiveError,\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { installCommandChannelBridge } from './up-ipc.ts';\nimport { provideFileSystem } from './provide-file-system.ts';\nimport { deriveContributions } from './codegen.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst rosterPathsFor = (stackRoot: string) => ({\n\tstackLockFile: resolvePath(stackRoot, 'stack.lock'),\n\trosterFile: resolvePath(stackRoot, 'roster.json'),\n});\n\n// -----------------------------------------------------------------------------\n// Supervisor command handler — snapshot/wipe/prune injected dispatch\n// -----------------------------------------------------------------------------\n\n/** Build the snapshot/wipe/prune `commandHandler` as a FACTORY the seam\n * resolves against its LIVE substrate (see `CommandHandlerFactory`). The\n * factory yields the seam's `SnapshotOrchestratorService` + `FileSystem` —\n * the SAME orchestrator instance the supervisor's contribution dispatcher\n * registers live participants on, so an operator `snapshot save` captures\n * real chain/blob/db state, not an empty set — and returns a handler\n * (R = `never`, as the command loop requires) that closes over them\n * directly. No Deferred hand-off: the seam runs this factory before\n * `startSupervisor` consumes the handler. */\nconst makeSnapshotCommandHandler = (params: {\n\treadonly runtimeRoot: string;\n\treadonly stack: SupervisedStack;\n\treadonly devstackVersion: string;\n}): CommandHandlerFactory =>\n\tEffect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst computeGraphInput = computeSnapshotGraphInputFromStack({\n\t\t\tstack: params.stack,\n\t\t\tdevstackVersion: params.devstackVersion,\n\t\t});\n\t\tconst handler: SupervisorCommandHandler = (cmd) => {\n\t\t\tswitch (cmd.tag) {\n\t\t\t\tcase 'snapshot.capture':\n\t\t\t\t\t// The L3 capture is the bounce's gather → stop → commit → retag\n\t\t\t\t\t// → hard-rm half; the RESUME (recreate + wait-write-ready) is the\n\t\t\t\t\t// command-loop's converge after this handler succeeds (it owns the\n\t\t\t\t\t// graph), mirroring restore. So no `resume` is injected here.\n\t\t\t\t\treturn provideFileSystem(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tcomputeGraphInput.pipe(\n\t\t\t\t\t\t\tEffect.flatMap((graphInput) =>\n\t\t\t\t\t\t\t\tsnapshot.capture({\n\t\t\t\t\t\t\t\t\tid: cmd.snapshotId,\n\t\t\t\t\t\t\t\t\t...(cmd.name === undefined ? {} : { label: cmd.name }),\n\t\t\t\t\t\t\t\t\tgraphInput,\n\t\t\t\t\t\t\t\t\t...(cmd.replaceExisting === true ? { replaceExistingLabel: true } : {}),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.map((meta) => [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttag: 'snapshot.captured',\n\t\t\t\t\t\t\t\tsnapshotId: meta.id,\n\t\t\t\t\t\t\t\t...(meta.label === null ? {} : { name: meta.label }),\n\t\t\t\t\t\t\t\tat: Date.now(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\tcase 'snapshot.restore':\n\t\t\t\t\treturn provideFileSystem(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tcomputeGraphInput.pipe(\n\t\t\t\t\t\t\tEffect.flatMap((currentGraphInput) =>\n\t\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\t\tid: cmd.snapshotId,\n\t\t\t\t\t\t\t\t\tcurrentGraphInput,\n\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'warn',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.map((meta) => [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttag: 'snapshot.restored',\n\t\t\t\t\t\t\t\tsnapshotId: meta.id,\n\t\t\t\t\t\t\t\tat: Date.now(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\tcase 'snapshot.list':\n\t\t\t\t\treturn provideFileSystem(fs, snapshot.list).pipe(Effect.as([]));\n\t\t\t\tcase 'snapshot.delete':\n\t\t\t\t\treturn provideFileSystem(fs, snapshot.delete(cmd.snapshotId)).pipe(Effect.as([]));\n\t\t\t\tcase 'wipe.requested':\n\t\t\t\t\treturn provideFileSystem(fs, snapshot.wipe({})).pipe(Effect.as([]));\n\t\t\t\tcase 'prune.requested':\n\t\t\t\t\t// Route to the same orchestrator the offline `devstack prune`\n\t\t\t\t\t// verb uses (`runLifecyclePrune`), NOT the snapshot-orchestrator\n\t\t\t\t\t// prune (which only cleans the snapshot catalog and would leave\n\t\t\t\t\t// stale containers/networks/volumes/images behind for an attached\n\t\t\t\t\t// supervisor — silent under-prune is the bug we're closing).\n\t\t\t\t\t// Live supervisor implies the current stack's group is live and\n\t\t\t\t\t// therefore excluded from the default selection — exactly the\n\t\t\t\t\t// invariant the live-attached `prune` needs (operator can prune\n\t\t\t\t\t// sibling stacks under the same Docker daemon without touching\n\t\t\t\t\t// the running one).\n\t\t\t\t\treturn collectLifecyclePruneInventory({ runtimeRoot: params.runtimeRoot }).pipe(\n\t\t\t\t\t\tEffect.flatMap((inventory) =>\n\t\t\t\t\t\t\trunLifecyclePrune(\n\t\t\t\t\t\t\t\t{ runtimeRoot: params.runtimeRoot },\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tgroupKeys: defaultLifecyclePruneSelection(\n\t\t\t\t\t\t\t\t\t\tinventory,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_LIFECYCLE_PRUNE_RESOURCES,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tresources: DEFAULT_LIFECYCLE_PRUNE_RESOURCES,\n\t\t\t\t\t\t\t\t\tdryRun: false,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tEffect.as([]),\n\t\t\t\t\t);\n\t\t\t\tdefault:\n\t\t\t\t\treturn Effect.succeed([]);\n\t\t\t}\n\t\t};\n\t\treturn handler;\n\t});\n\nconst installLiveSupervisorRoster = (params: {\n\treadonly stackRoot: string;\n\treadonly app: string;\n\treadonly stack: string;\n}): Effect.Effect<void, unknown, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst paths = rosterPathsFor(params.stackRoot);\n\t\tconst claimed = yield* claim(paths);\n\t\tif (!claimed.soleHolder) {\n\t\t\tyield* release(paths).pipe(Effect.catch(() => Effect.void));\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliSupervisorLiveError({\n\t\t\t\t\tapp: params.app,\n\t\t\t\t\tstack: params.stack,\n\t\t\t\t\thint: 'use `devstack apply` from another shell, or choose a different --stack name',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tyield* Effect.addFinalizer(() => release(paths).pipe(Effect.catch(() => Effect.void)));\n\t\tyield* Effect.forkScoped(heartbeatFiber(paths));\n\t});\n\n// -----------------------------------------------------------------------------\n// CLI boot bundle — the `commandHandler` + `boot` hooks the seam consumes\n// -----------------------------------------------------------------------------\n\nexport interface UpBootBundleInput {\n\treadonly stack: SupervisedStack;\n\treadonly identityValue: Identity;\n\treadonly runtimeRoot: string;\n\treadonly devstackVersion: string;\n\treadonly rendererMode: import('../../surfaces/tui/mode-detect.ts').RendererMode;\n\treadonly startFromSnapshot?: {\n\t\treadonly id: string;\n\t\treadonly stalePolicy: 'warn' | 'block' | 'clean-start';\n\t};\n\treadonly snapshotCache?: {\n\t\treadonly name: string;\n\t\treadonly existingSnapshotId?: string;\n\t};\n}\n\nexport interface UpBootBundle {\n\treadonly commandHandler: CommandHandlerFactory;\n\treadonly boot: RunStackBootBag;\n}\n\ntype StartFromSnapshot = NonNullable<UpBootBundleInput['startFromSnapshot']>;\ntype SnapshotCache = NonNullable<UpBootBundleInput['snapshotCache']>;\n\n/**\n * Build the CLI's boot concerns as a VALUE bundle the seam consumes — the\n * snapshot/wipe/prune `commandHandler` plus the `beforeInitialAcquire`\n * (recover → IPC bridge → roster claim → TUI mount + event tee). Pure\n * value construction: no substrate is touched here; the hooks pull the\n * SEAM's substrate services at boot (they run inside the supervised scope).\n *\n * Extracted from `runUpLive` so the Docker-free CLI-boot smoke test drives\n * the EXACT bundle the production path feeds `runStackWithBoot` — the only\n * non-e2e gate on the cutover (`main.test.ts` only runs `up --help`).\n */\nexport const buildUpBootBundle = (input: UpBootBundleInput): UpBootBundle => {\n\tconst {\n\t\tstack,\n\t\tidentityValue,\n\t\truntimeRoot,\n\t\tdevstackVersion,\n\t\trendererMode,\n\t\tstartFromSnapshot,\n\t\tsnapshotCache,\n\t} = input;\n\n\tconst computeGraphInput = computeSnapshotGraphInputFromStack({ stack, devstackVersion });\n\tlet refreshSnapshotCache =\n\t\tsnapshotCache !== undefined && snapshotCache.existingSnapshotId === undefined;\n\n\t// The snapshot/wipe/prune `commandHandler` — a FACTORY the seam resolves\n\t// against its LIVE substrate (the seam runs it before `startSupervisor`\n\t// consumes the handler). The factory closes over the seam's ONE\n\t// `SnapshotOrchestratorService` instance — the same one the supervisor's\n\t// contribution dispatcher registers live participants on — so an operator\n\t// `snapshot save` captures the LIVE chain/blob/db state, not an empty set\n\t// off a sibling orchestrator. No Deferred hand-off.\n\tconst commandHandler = makeSnapshotCommandHandler({ runtimeRoot, stack, devstackVersion });\n\n\tconst boot: RunStackBootBag = {\n\t\tdevstackVersion,\n\t\tbeforeInitialAcquire: (h) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// The seam's substrate services — the SAME instances the\n\t\t\t\t// `commandHandler` factory closed over and the supervisor uses,\n\t\t\t\t// driven here for the recover/IPC/roster/TUI work.\n\t\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\t\t// Resume any restore interrupted by a hard kill / power-loss\n\t\t\t\t// between the atomic swap and the end of the image-promotion\n\t\t\t\t// handoff (the interrupted-restore sentinel rode the swap into\n\t\t\t\t// the live root and was never cleared). Runs BEFORE any plugin\n\t\t\t\t// acquire so a half-promoted image set is reconciled before any\n\t\t\t\t// L2 lookup observes the runtime root. No-op when the sentinel\n\t\t\t\t// is absent (the clean-boot case); idempotent re-run of restore\n\t\t\t\t// when present.\n\t\t\t\t//\n\t\t\t\t// `restore({ id })` passes NO participants on purpose: this\n\t\t\t\t// hook fires BEFORE the initial acquire registers any snapshot\n\t\t\t\t// participant, so there is no live stack to contribute identity.\n\t\t\t\t// `runRestore` reads the empty participant set as \"no live\n\t\t\t\t// stack\" and skips ONLY the cross-plugin contribution guard\n\t\t\t\t// (the runtime + snapshot-emptiness guards still fire). With a\n\t\t\t\t// participants-required guard this recovery could never clear\n\t\t\t\t// the sentinel — it failed `IdentityMissingLive` every boot.\n\t\t\t\tyield* recoverInterruptedRestore({\n\t\t\t\t\tliveRoot: stackPaths.stackRoot,\n\t\t\t\t\trestoreSnapshot: (id) =>\n\t\t\t\t\t\tcomputeGraphInput.pipe(\n\t\t\t\t\t\t\tEffect.flatMap((currentGraphInput) =>\n\t\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\t\tcurrentGraphInput,\n\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'warn',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t\tif (snapshotCache !== undefined) {\n\t\t\t\t\tif (snapshotCache.existingSnapshotId === undefined) {\n\t\t\t\t\t\tyield* Effect.logWarning(\n\t\t\t\t\t\t\t`snapshot cache ${snapshotCache.name} was not found; boot will refresh it`,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst graphInput = yield* computeGraphInput;\n\t\t\t\t\t\tconst catalog = yield* provideFileSystem(fs, snapshot.list);\n\t\t\t\t\t\tconst entry = catalog.find(\n\t\t\t\t\t\t\t(snapshotEntry) => snapshotEntry.id === snapshotCache.existingSnapshotId,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst snapshotGraphInputId = entry?.metadata?.graphInput.graphInputId ?? null;\n\t\t\t\t\t\tif (snapshotGraphInputId === graphInput.graphInputId) {\n\t\t\t\t\t\t\trefreshSnapshotCache = false;\n\t\t\t\t\t\t\tyield* provideFileSystem(\n\t\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\t\tid: snapshotCache.existingSnapshotId,\n\t\t\t\t\t\t\t\t\tcurrentGraphInput: graphInput,\n\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'block',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trefreshSnapshotCache = true;\n\t\t\t\t\t\t\tyield* Effect.logWarning(\n\t\t\t\t\t\t\t\t`snapshot cache ${snapshotCache.name} graph input is stale or unreadable; boot will refresh it`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (startFromSnapshot !== undefined) {\n\t\t\t\t\tconst graphInput = yield* computeGraphInput;\n\t\t\t\t\tif (startFromSnapshot.stalePolicy === 'clean-start') {\n\t\t\t\t\t\tconst catalog = yield* provideFileSystem(fs, snapshot.list);\n\t\t\t\t\t\tconst entry = catalog.find(\n\t\t\t\t\t\t\t(snapshotEntry) => snapshotEntry.id === startFromSnapshot.id,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst snapshotGraphInputId = entry?.metadata?.graphInput.graphInputId ?? null;\n\t\t\t\t\t\tif (snapshotGraphInputId !== graphInput.graphInputId) {\n\t\t\t\t\t\t\tyield* Effect.logWarning(\n\t\t\t\t\t\t\t\t`snapshot ${startFromSnapshot.id} graph input is stale; clean-start policy skipped restore`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tyield* provideFileSystem(\n\t\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\t\tid: startFromSnapshot.id,\n\t\t\t\t\t\t\t\t\tcurrentGraphInput: graphInput,\n\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'block',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tyield* provideFileSystem(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\tid: startFromSnapshot.id,\n\t\t\t\t\t\t\t\tcurrentGraphInput: graphInput,\n\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: startFromSnapshot.stalePolicy,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst commandChannel = yield* installCommandChannelBridge({\n\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\thandle: h.supervisor,\n\t\t\t\t});\n\t\t\t\tyield* installLiveSupervisorRoster({\n\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\tapp: String(identityValue.app),\n\t\t\t\t\tstack: String(identityValue.stack),\n\t\t\t\t});\n\t\t\t\tconst rendererEvents = yield* Queue.unbounded<EngineEvent>();\n\t\t\t\tconst renderer = makeTuiSurface({\n\t\t\t\t\tmode: rendererMode,\n\t\t\t\t\tpublishCommand: makeQueueCommandPublisher(h.commands),\n\t\t\t\t\t// Opt-in quiet plain stream (readiness/endpoint/codegen\n\t\t\t\t\t// milestones + warns/errors only). Set by embedded consumers\n\t\t\t\t\t// that pipe `devstack up` output through another tool — the\n\t\t\t\t\t// Playwright `webServer` (see `devstackPlaywrightWebServer`)\n\t\t\t\t\t// — where the full per-event firehose reads as `[WebServer]`\n\t\t\t\t\t// noise. No effect on a TTY (`ink`) or normal CI `plain` run.\n\t\t\t\t\tquiet: process.env.DEVSTACK_PLAIN_QUIET === '1',\n\t\t\t\t});\n\t\t\t\tyield* Effect.addFinalizer(() => renderer.flush.pipe(Effect.catch(() => Effect.void)));\n\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\trenderer.mount(h.state, Stream.fromQueue(rendererEvents)).pipe(\n\t\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\tprocess.stderr.write(`renderer failed: ${cause.detail ?? String(cause)}\\n`);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\th.events.pipe(\n\t\t\t\t\t\tStream.runForEach((event) =>\n\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\tyield* Queue.offer(rendererEvents, event);\n\t\t\t\t\t\t\t\tyield* commandChannel.publishEvent(event);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\t// L0 file watcher — dev-only. Drives the supervisor's\n\t\t\t\t// `notifyWatchFire` on edits to any plugin's declared `watch`\n\t\t\t\t// paths (today: the package plugin's Move sources), which\n\t\t\t\t// issues a selective restart → re-produce. Forked into the\n\t\t\t\t// supervised scope so it tears down on shutdown.\n\t\t\t\tyield* startFileWatcher({\n\t\t\t\t\twatchIndex: h.supervisor.watchIndex,\n\t\t\t\t\tnotifyWatchFire: h.supervisor.notifyWatchFire,\n\t\t\t\t});\n\t\t\t}),\n\t\twithinScope: (h) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tif (snapshotCache === undefined || !refreshSnapshotCache) return;\n\t\t\t\tyield* h.supervisor.runCommand({\n\t\t\t\t\ttag: 'snapshot.capture',\n\t\t\t\t\tname: snapshotCache.name,\n\t\t\t\t\treplaceExisting: true,\n\t\t\t\t});\n\t\t\t}),\n\t};\n\n\treturn { commandHandler, boot };\n};\n\n/**\n * Run `devstack up`. Wires the substrate Layer stack, supervisor,\n * attached renderer, and in-process TUI command queue. The Effect runs\n * as the outer Node fiber so SIGINT reaches scope finalizers.\n *\n * Returns a typed `CommandResult` / fails with a typed `CliError` so\n * the dispatcher's `emitFailure` renders the envelope (JSON mode) or\n * human-mode stderr line. Wirings MUST NOT write raw bytes to\n * `process.stderr` or mutate `process.exitCode` for terminal failures —\n * the dispatcher owns the projection.\n */\nexport const runUpLive = (\n\tconfigPath: string | undefined,\n\tidentity: ResolvedIdentity,\n\toptions: {\n\t\treadonly renderer: GlobalFlags['renderer'];\n\t\treadonly stdoutIsTty: boolean;\n\t\treadonly fromSnapshot?: string;\n\t\treadonly snapshotCache?: string;\n\t\treadonly snapshotStalePolicy?: GlobalFlags['snapshotStalePolicy'];\n\t},\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tif (options.fromSnapshot !== undefined && options.snapshotCache !== undefined) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: '--snapshot-cache cannot be combined with --from-snapshot',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (options.snapshotCache !== undefined && options.snapshotStalePolicy !== undefined) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: '--snapshot-stale is only valid with --from-snapshot',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst loadExit = yield* Effect.exit(loader.load(configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst loaded = loadExit.value;\n\t\tconst publicStack = (\n\t\t\tloaded as LoadedConfig & {\n\t\t\t\treadonly stack: Stack<SupervisedStack['members']>;\n\t\t\t}\n\t\t).stack;\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\n\t\t// Re-derive the identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/`$DEVSTACK_STACK` > `config.stackName` > inferred) so\n\t\t// the roster lock, command channel, and container/router naming all\n\t\t// target the same stack the operator selected — matching what\n\t\t// `snapshot.ts` already does. Without this, an explicit `--stack`\n\t\t// would boot the supervisor under the config's `stackName` and a\n\t\t// concurrent default-stack `up` would falsely collide on the live\n\t\t// supervisor (`error: supervisor live for <app>/<stack>`, exit 40).\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\n\t\tconst devstackVersion = readDevstackVersion({ fallback: '0.0.0' });\n\t\tconst rendererMode = resolveUpRendererMode({\n\t\t\tcliRenderer: options.renderer,\n\t\t\tstackRenderer: stack.options.renderer,\n\t\t\tstdoutIsTty: options.stdoutIsTty,\n\t\t});\n\t\tlet startFromSnapshot: StartFromSnapshot | undefined;\n\t\tlet snapshotCache: SnapshotCache | undefined;\n\t\tif (options.fromSnapshot !== undefined) {\n\t\t\tconst resolved = yield* makeSnapshotReader({ stackRoot: effectiveIdentity.stackRoot })\n\t\t\t\t.resolve(options.fromSnapshot)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\tmessage: 'snapshot lookup failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\tswitch (resolved.tag) {\n\t\t\t\tcase 'found':\n\t\t\t\t\tstartFromSnapshot = {\n\t\t\t\t\t\tid: resolved.entry.snapshotId,\n\t\t\t\t\t\tstalePolicy: options.snapshotStalePolicy ?? 'warn',\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'not-found':\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliSnapshotNotFoundError({ snapshotRef: options.fromSnapshot }),\n\t\t\t\t\t);\n\t\t\t\tcase 'ambiguous':\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUsageError({\n\t\t\t\t\t\t\tmessage: `snapshot reference is ambiguous: ${resolved.snapshotRef}`,\n\t\t\t\t\t\t\thint: `matches: ${resolved.matches.map((entry) => entry.snapshotId).join(', ')}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tif (options.snapshotCache !== undefined) {\n\t\t\tconst resolved = yield* makeSnapshotReader({ stackRoot: effectiveIdentity.stackRoot })\n\t\t\t\t.resolve(options.snapshotCache)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\tmessage: 'snapshot lookup failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\tswitch (resolved.tag) {\n\t\t\t\tcase 'found':\n\t\t\t\t\tsnapshotCache = {\n\t\t\t\t\t\tname: options.snapshotCache,\n\t\t\t\t\t\texistingSnapshotId: resolved.entry.snapshotId,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'not-found':\n\t\t\t\t\tsnapshotCache = { name: options.snapshotCache };\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'ambiguous':\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUsageError({\n\t\t\t\t\t\t\tmessage: `snapshot reference is ambiguous: ${resolved.snapshotRef}`,\n\t\t\t\t\t\t\thint: `matches: ${resolved.matches.map((entry) => entry.snapshotId).join(', ')}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// The CLI concerns (snapshot/wipe/prune `commandHandler` + the\n\t\t// recover/roster/IPC/TUI boot hooks) as a value bundle — the\n\t\t// EXACT thing the seam consumes, extracted so the Docker-free CLI-boot\n\t\t// smoke test can drive the real bundle (see `test/cli/up-boot-smoke`).\n\t\tconst { commandHandler, boot } = buildUpBootBundle({\n\t\t\tstack,\n\t\t\tidentityValue,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t\tdevstackVersion,\n\t\t\trendererMode,\n\t\t\t...(startFromSnapshot === undefined ? {} : { startFromSnapshot }),\n\t\t\t...(snapshotCache === undefined ? {} : { snapshotCache }),\n\t\t});\n\n\t\t// The ONE boot seam. `runStackWithBoot` owns the substrate Layer\n\t\t// composition, the contribution dispatcher + post-acquire hook +\n\t\t// `extendContext` assembly, the projection ref, and the\n\t\t// forkDetach/Deferred boot lifecycle — `up` no longer forks its own\n\t\t// parallel orchestration. The CLI concerns (recover/roster/IPC/TUI\n\t\t// + snapshot handler) are passed as injected hooks + `commandHandler`.\n\t\tconst handle = runStackWithBoot(publicStack, {\n\t\t\tidentity: {\n\t\t\t\tapp: String(identityValue.app),\n\t\t\t\tstack: String(identityValue.stack),\n\t\t\t\tnetwork: String(identityValue.network),\n\t\t\t},\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t\tcodegen: stack.options.codegen,\n\t\t\t// Dev loop: a Move-source edit fires the watcher → reacquires the\n\t\t\t// package (new id) → this regenerates the committed bindings so the\n\t\t\t// app sees the new shapes. Pass the STATIC (id-free) contributions —\n\t\t\t// the same the stack-free `codegen` verb uses — so the regen NEVER\n\t\t\t// bakes the live on-chain id into the committed `config.ts`. `apply` /\n\t\t\t// `runStack` leave it off.\n\t\t\temitBindings: deriveContributions(stack.members),\n\t\t\tcommandHandler,\n\t\t\tboot,\n\t\t});\n\n\t\t// The outer fiber BLOCKS on `awaitShutdown` so SIGINT reaches the\n\t\t// supervisor's in-scope signal handler (forked inside `startSupervisor`)\n\t\t// and drives teardown through the command-channel/latch — the same path\n\t\t// the CLI used as the outer fiber before the cutover. `Effect.scoped`\n\t\t// owns the supervised scope so the TUI flush finalizer + roster release\n\t\t// run on scope close.\n\t\t//\n\t\t// Errors project through the seam's DISCRIMINATED channels — no\n\t\t// `matchCauseEffect` re-discrimination:\n\t\t// - `handle.start` fails ⇒ BOOT-time `BootError`. Extract a\n\t\t// `CliSupervisorLiveError` (roster-claim loss) for exit 40; else\n\t\t// wrap as `CliInternalError`.\n\t\t// - `handle.awaitShutdown` fails ⇒ MID-RUN cause. Wrap as\n\t\t// `CliInternalError` (boot already succeeded by then).\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* handle.start.pipe(\n\t\t\t\t\tEffect.catch((bootError: BootError) => {\n\t\t\t\t\t\tconst live = findCliSupervisorLiveError(bootError.cause);\n\t\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\t\tlive ??\n\t\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\t\tmessage: 'stack failed',\n\t\t\t\t\t\t\t\t\tcause: Cause.pretty(bootError.cause),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tyield* handle.awaitShutdown.pipe(\n\t\t\t\t\tEffect.catchCause(\n\t\t\t\t\t\t(cause): Effect.Effect<never, CliError> =>\n\t\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\t\tmessage: 'stack failed',\n\t\t\t\t\t\t\t\t\tcause: Cause.pretty(cause as Cause.Cause<unknown>),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn { exitCode: ExitCode.OK } satisfies CommandResult;\n\t\t\t}),\n\t\t);\n\t});\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"up.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/up.ts"],"sourcesContent":["// `devstack up` verb wiring.\n//\n// Wires the substrate Layer stack, supervisor, attached renderer, and\n// in-process TUI command queue. The Effect runs as the outer Node fiber\n// so SIGINT reaches scope finalizers (see surfaces/cli/index.ts\n// architecture invariant).\n//\n// Logger layer: `Logger.layer([])` (silent). `up` owns the TUI, which is\n// the operator-facing output channel; routing structured `Effect.log*`\n// records to stderr would scribble on top of the live ink renderer in\n// human mode and would duplicate the structured event stream the plain\n// renderer already emits in non-TTY mode. The sibling `apply` verb (no\n// TUI) uses `Logger.consolePretty()` because its consumer is CI.\n\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect, Exit, FileSystem, Queue, Scope, Stream } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport type { EngineEvent } from '../../substrate/events.ts';\nimport { StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tclaim,\n\theartbeatFiber,\n\trelease,\n\tstartFileWatcher,\n\ttype SupervisedStack,\n\ttype SupervisorCommandHandler,\n} from '../../substrate/runtime/index.ts';\nimport {\n\tcollectLifecyclePruneInventory,\n\tdefaultLifecyclePruneSelection,\n\tDEFAULT_LIFECYCLE_PRUNE_RESOURCES,\n\trunLifecyclePrune,\n} from '../../orchestrators/lifecycle-prune/index.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\trecoverInterruptedRestore,\n\tSnapshotOrchestratorService,\n} from '../../orchestrators/snapshot/index.ts';\nimport type { Stack } from '../../api/define-devstack.ts';\nimport {\n\trunStackWithBoot,\n\ttype CommandHandlerFactory,\n\ttype RunStackBootBag,\n} from '../../api/run-stack-internal.ts';\nimport type { BootError } from '../../api/run-stack.ts';\nimport {\n\ttype CliError,\n\tCliInternalError,\n\tCliSnapshotNotFoundError,\n\tCliSupervisorLiveError,\n\tCliUsageError,\n\ttype GlobalFlags,\n} from '../../surfaces/cli/index.ts';\nimport type { CommandResult } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport { makeTuiSurface } from '../../surfaces/tui/index.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeQueueCommandPublisher, resolveUpRendererMode } from '../up-lifecycle.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport { makeSnapshotReader } from '../snapshot-reader.ts';\nimport {\n\tfindCliSupervisorLiveError,\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { installCommandChannelBridge } from './up-ipc.ts';\nimport { provideFileSystem } from './provide-file-system.ts';\nimport { deriveContributions } from './codegen.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst rosterPathsFor = (stackRoot: string) => ({\n\tstackLockFile: resolvePath(stackRoot, 'stack.lock'),\n\trosterFile: resolvePath(stackRoot, 'roster.json'),\n});\n\n// -----------------------------------------------------------------------------\n// Supervisor command handler — snapshot/wipe/prune injected dispatch\n// -----------------------------------------------------------------------------\n\n/** Build the snapshot/wipe/prune `commandHandler` as a FACTORY the seam\n * resolves against its LIVE substrate (see `CommandHandlerFactory`). The\n * factory yields the seam's `SnapshotOrchestratorService` + `FileSystem` —\n * the SAME orchestrator instance the supervisor's contribution dispatcher\n * registers live participants on, so an operator `snapshot save` captures\n * real chain/blob/db state, not an empty set — and returns a handler\n * (R = `never`, as the command loop requires) that closes over them\n * directly. No Deferred hand-off: the seam runs this factory before\n * `startSupervisor` consumes the handler. */\nconst makeSnapshotCommandHandler = (params: {\n\treadonly runtimeRoot: string;\n\treadonly stack: SupervisedStack;\n\treadonly devstackVersion: string;\n}): CommandHandlerFactory =>\n\tEffect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst computeGraphInput = computeSnapshotGraphInputFromStack({\n\t\t\tstack: params.stack,\n\t\t\tdevstackVersion: params.devstackVersion,\n\t\t});\n\t\tconst handler: SupervisorCommandHandler = (cmd) => {\n\t\t\tswitch (cmd.tag) {\n\t\t\t\tcase 'snapshot.capture':\n\t\t\t\t\t// The L3 capture is the bounce's gather → stop → commit → retag\n\t\t\t\t\t// → hard-rm half; the RESUME (recreate + wait-write-ready) is the\n\t\t\t\t\t// command-loop's converge after this handler succeeds (it owns the\n\t\t\t\t\t// graph), mirroring restore. So no `resume` is injected here.\n\t\t\t\t\treturn provideFileSystem(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tcomputeGraphInput.pipe(\n\t\t\t\t\t\t\tEffect.flatMap((graphInput) =>\n\t\t\t\t\t\t\t\tsnapshot.capture({\n\t\t\t\t\t\t\t\t\tid: cmd.snapshotId,\n\t\t\t\t\t\t\t\t\t...(cmd.name === undefined ? {} : { label: cmd.name }),\n\t\t\t\t\t\t\t\t\tgraphInput,\n\t\t\t\t\t\t\t\t\t...(cmd.replaceExisting === true ? { replaceExistingLabel: true } : {}),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.map((meta) => [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttag: 'snapshot.captured',\n\t\t\t\t\t\t\t\tsnapshotId: meta.id,\n\t\t\t\t\t\t\t\t...(meta.label === null ? {} : { name: meta.label }),\n\t\t\t\t\t\t\t\tat: Date.now(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\tcase 'snapshot.restore':\n\t\t\t\t\treturn provideFileSystem(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tcomputeGraphInput.pipe(\n\t\t\t\t\t\t\tEffect.flatMap((currentGraphInput) =>\n\t\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\t\tid: cmd.snapshotId,\n\t\t\t\t\t\t\t\t\tcurrentGraphInput,\n\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'warn',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.map((meta) => [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttag: 'snapshot.restored',\n\t\t\t\t\t\t\t\tsnapshotId: meta.id,\n\t\t\t\t\t\t\t\tat: Date.now(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\tcase 'snapshot.list':\n\t\t\t\t\treturn provideFileSystem(fs, snapshot.list).pipe(Effect.as([]));\n\t\t\t\tcase 'snapshot.delete':\n\t\t\t\t\treturn provideFileSystem(fs, snapshot.delete(cmd.snapshotId)).pipe(Effect.as([]));\n\t\t\t\tcase 'wipe.requested':\n\t\t\t\t\treturn provideFileSystem(fs, snapshot.wipe({})).pipe(Effect.as([]));\n\t\t\t\tcase 'prune.requested':\n\t\t\t\t\t// Route to the same orchestrator the offline `devstack prune`\n\t\t\t\t\t// verb uses (`runLifecyclePrune`), NOT the snapshot-orchestrator\n\t\t\t\t\t// prune (which only cleans the snapshot catalog and would leave\n\t\t\t\t\t// stale containers/networks/volumes/images behind for an attached\n\t\t\t\t\t// supervisor — silent under-prune is the bug we're closing).\n\t\t\t\t\t// Live supervisor implies the current stack's group is live and\n\t\t\t\t\t// therefore excluded from the default selection — exactly the\n\t\t\t\t\t// invariant the live-attached `prune` needs (operator can prune\n\t\t\t\t\t// sibling stacks under the same Docker daemon without touching\n\t\t\t\t\t// the running one).\n\t\t\t\t\treturn collectLifecyclePruneInventory({ runtimeRoot: params.runtimeRoot }).pipe(\n\t\t\t\t\t\tEffect.flatMap((inventory) =>\n\t\t\t\t\t\t\trunLifecyclePrune(\n\t\t\t\t\t\t\t\t{ runtimeRoot: params.runtimeRoot },\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tgroupKeys: defaultLifecyclePruneSelection(\n\t\t\t\t\t\t\t\t\t\tinventory,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_LIFECYCLE_PRUNE_RESOURCES,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tresources: DEFAULT_LIFECYCLE_PRUNE_RESOURCES,\n\t\t\t\t\t\t\t\t\tdryRun: false,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tEffect.as([]),\n\t\t\t\t\t);\n\t\t\t\tdefault:\n\t\t\t\t\treturn Effect.succeed([]);\n\t\t\t}\n\t\t};\n\t\treturn handler;\n\t});\n\nconst installLiveSupervisorRoster = (params: {\n\treadonly stackRoot: string;\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly graphInputId: string;\n}): Effect.Effect<void, unknown, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst paths = rosterPathsFor(params.stackRoot);\n\t\tconst claimed = yield* claim(paths, 'normal', undefined, {\n\t\t\tgraphInputId: params.graphInputId,\n\t\t});\n\t\tif (!claimed.soleHolder) {\n\t\t\tyield* release(paths).pipe(Effect.catch(() => Effect.void));\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliSupervisorLiveError({\n\t\t\t\t\tapp: params.app,\n\t\t\t\t\tstack: params.stack,\n\t\t\t\t\thint: 'use `devstack apply` from another shell, or choose a different --stack name',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tyield* Effect.addFinalizer(() => release(paths).pipe(Effect.catch(() => Effect.void)));\n\t\tyield* Effect.forkScoped(heartbeatFiber(paths));\n\t});\n\n// -----------------------------------------------------------------------------\n// CLI boot bundle — the `commandHandler` + `boot` hooks the seam consumes\n// -----------------------------------------------------------------------------\n\nexport interface UpBootBundleInput {\n\treadonly stack: SupervisedStack;\n\treadonly identityValue: Identity;\n\treadonly runtimeRoot: string;\n\treadonly devstackVersion: string;\n\treadonly rendererMode: import('../../surfaces/tui/mode-detect.ts').RendererMode;\n\treadonly startFromSnapshot?: {\n\t\treadonly id: string;\n\t\treadonly stalePolicy: 'warn' | 'block' | 'clean-start';\n\t};\n\treadonly snapshotCache?: {\n\t\treadonly name: string;\n\t\treadonly existingSnapshotId?: string;\n\t};\n}\n\nexport interface UpBootBundle {\n\treadonly commandHandler: CommandHandlerFactory;\n\treadonly boot: RunStackBootBag;\n}\n\ntype StartFromSnapshot = NonNullable<UpBootBundleInput['startFromSnapshot']>;\ntype SnapshotCache = NonNullable<UpBootBundleInput['snapshotCache']>;\n\n/**\n * Build the CLI's boot concerns as a VALUE bundle the seam consumes — the\n * snapshot/wipe/prune `commandHandler` plus the `beforeInitialAcquire`\n * (recover → IPC bridge → roster claim → TUI mount + event tee). Pure\n * value construction: no substrate is touched here; the hooks pull the\n * SEAM's substrate services at boot (they run inside the supervised scope).\n *\n * Extracted from `runUpLive` so the Docker-free CLI-boot smoke test drives\n * the EXACT bundle the production path feeds `runStackWithBoot` — the only\n * non-e2e gate on the cutover (`main.test.ts` only runs `up --help`).\n */\nexport const buildUpBootBundle = (input: UpBootBundleInput): UpBootBundle => {\n\tconst {\n\t\tstack,\n\t\tidentityValue,\n\t\truntimeRoot,\n\t\tdevstackVersion,\n\t\trendererMode,\n\t\tstartFromSnapshot,\n\t\tsnapshotCache,\n\t} = input;\n\n\tconst computeGraphInput = computeSnapshotGraphInputFromStack({ stack, devstackVersion });\n\tlet refreshSnapshotCache =\n\t\tsnapshotCache !== undefined && snapshotCache.existingSnapshotId === undefined;\n\n\t// The snapshot/wipe/prune `commandHandler` — a FACTORY the seam resolves\n\t// against its LIVE substrate (the seam runs it before `startSupervisor`\n\t// consumes the handler). The factory closes over the seam's ONE\n\t// `SnapshotOrchestratorService` instance — the same one the supervisor's\n\t// contribution dispatcher registers live participants on — so an operator\n\t// `snapshot save` captures the LIVE chain/blob/db state, not an empty set\n\t// off a sibling orchestrator. No Deferred hand-off.\n\tconst commandHandler = makeSnapshotCommandHandler({ runtimeRoot, stack, devstackVersion });\n\n\tconst boot: RunStackBootBag = {\n\t\tdevstackVersion,\n\t\tbeforeInitialAcquire: (h) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// The seam's substrate services — the SAME instances the\n\t\t\t\t// `commandHandler` factory closed over and the supervisor uses,\n\t\t\t\t// driven here for the recover/IPC/roster/TUI work.\n\t\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\t\tconst graphInput = yield* computeGraphInput;\n\t\t\t\t// Resume any restore interrupted by a hard kill / power-loss\n\t\t\t\t// between the atomic swap and the end of the image-promotion\n\t\t\t\t// handoff (the interrupted-restore sentinel rode the swap into\n\t\t\t\t// the live root and was never cleared). Runs BEFORE any plugin\n\t\t\t\t// acquire so a half-promoted image set is reconciled before any\n\t\t\t\t// L2 lookup observes the runtime root. No-op when the sentinel\n\t\t\t\t// is absent (the clean-boot case); idempotent re-run of restore\n\t\t\t\t// when present.\n\t\t\t\t//\n\t\t\t\t// `restore({ id })` passes NO participants on purpose: this\n\t\t\t\t// hook fires BEFORE the initial acquire registers any snapshot\n\t\t\t\t// participant, so there is no live stack to contribute identity.\n\t\t\t\t// `runRestore` reads the empty participant set as \"no live\n\t\t\t\t// stack\" and skips ONLY the cross-plugin contribution guard\n\t\t\t\t// (the runtime + snapshot-emptiness guards still fire). With a\n\t\t\t\t// participants-required guard this recovery could never clear\n\t\t\t\t// the sentinel — it failed `IdentityMissingLive` every boot.\n\t\t\t\tyield* recoverInterruptedRestore({\n\t\t\t\t\tliveRoot: stackPaths.stackRoot,\n\t\t\t\t\trestoreSnapshot: (id) =>\n\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\tcurrentGraphInput: graphInput,\n\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'warn',\n\t\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t\tif (snapshotCache !== undefined) {\n\t\t\t\t\tif (snapshotCache.existingSnapshotId === undefined) {\n\t\t\t\t\t\tyield* Effect.logWarning(\n\t\t\t\t\t\t\t`snapshot cache ${snapshotCache.name} was not found; boot will refresh it`,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst catalog = yield* provideFileSystem(fs, snapshot.list);\n\t\t\t\t\t\tconst entry = catalog.find(\n\t\t\t\t\t\t\t(snapshotEntry) => snapshotEntry.id === snapshotCache.existingSnapshotId,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst snapshotGraphInputId = entry?.metadata?.graphInput.graphInputId ?? null;\n\t\t\t\t\t\tif (snapshotGraphInputId === graphInput.graphInputId) {\n\t\t\t\t\t\t\trefreshSnapshotCache = false;\n\t\t\t\t\t\t\tyield* provideFileSystem(\n\t\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\t\tid: snapshotCache.existingSnapshotId,\n\t\t\t\t\t\t\t\t\tcurrentGraphInput: graphInput,\n\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'block',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trefreshSnapshotCache = true;\n\t\t\t\t\t\t\tyield* Effect.logWarning(\n\t\t\t\t\t\t\t\t`snapshot cache ${snapshotCache.name} graph input is stale or unreadable; boot will refresh it`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (startFromSnapshot !== undefined) {\n\t\t\t\t\tif (startFromSnapshot.stalePolicy === 'clean-start') {\n\t\t\t\t\t\tconst catalog = yield* provideFileSystem(fs, snapshot.list);\n\t\t\t\t\t\tconst entry = catalog.find(\n\t\t\t\t\t\t\t(snapshotEntry) => snapshotEntry.id === startFromSnapshot.id,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst snapshotGraphInputId = entry?.metadata?.graphInput.graphInputId ?? null;\n\t\t\t\t\t\tif (snapshotGraphInputId !== graphInput.graphInputId) {\n\t\t\t\t\t\t\tyield* Effect.logWarning(\n\t\t\t\t\t\t\t\t`snapshot ${startFromSnapshot.id} graph input is stale; clean-start policy skipped restore`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tyield* provideFileSystem(\n\t\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\t\tid: startFromSnapshot.id,\n\t\t\t\t\t\t\t\t\tcurrentGraphInput: graphInput,\n\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'block',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tyield* provideFileSystem(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\tid: startFromSnapshot.id,\n\t\t\t\t\t\t\t\tcurrentGraphInput: graphInput,\n\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: startFromSnapshot.stalePolicy,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst commandChannel = yield* installCommandChannelBridge({\n\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\thandle: h.supervisor,\n\t\t\t\t});\n\t\t\t\tyield* installLiveSupervisorRoster({\n\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\tapp: String(identityValue.app),\n\t\t\t\t\tstack: String(identityValue.stack),\n\t\t\t\t\tgraphInputId: graphInput.graphInputId,\n\t\t\t\t});\n\t\t\t\tconst rendererEvents = yield* Queue.unbounded<EngineEvent>();\n\t\t\t\tconst renderer = makeTuiSurface({\n\t\t\t\t\tmode: rendererMode,\n\t\t\t\t\tpublishCommand: makeQueueCommandPublisher(h.commands),\n\t\t\t\t\t// Opt-in quiet plain stream (readiness/endpoint/codegen\n\t\t\t\t\t// milestones + warns/errors only). Set by embedded consumers\n\t\t\t\t\t// that pipe `devstack up` output through another tool — the\n\t\t\t\t\t// Playwright `webServer` (see `devstackPlaywrightWebServer`)\n\t\t\t\t\t// — where the full per-event firehose reads as `[WebServer]`\n\t\t\t\t\t// noise. No effect on a TTY (`ink`) or normal CI `plain` run.\n\t\t\t\t\tquiet: process.env.DEVSTACK_PLAIN_QUIET === '1',\n\t\t\t\t});\n\t\t\t\tyield* Effect.addFinalizer(() => renderer.flush.pipe(Effect.catch(() => Effect.void)));\n\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\trenderer.mount(h.state, Stream.fromQueue(rendererEvents)).pipe(\n\t\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\tprocess.stderr.write(`renderer failed: ${cause.detail ?? String(cause)}\\n`);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\th.events.pipe(\n\t\t\t\t\t\tStream.runForEach((event) =>\n\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\tyield* Queue.offer(rendererEvents, event);\n\t\t\t\t\t\t\t\tyield* commandChannel.publishEvent(event);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\t// L0 file watcher — dev-only. Drives the supervisor's\n\t\t\t\t// `notifyWatchFire` on edits to any plugin's declared `watch`\n\t\t\t\t// paths (today: the package plugin's Move sources), which\n\t\t\t\t// issues a selective restart → re-produce. Forked into the\n\t\t\t\t// supervised scope so it tears down on shutdown.\n\t\t\t\tyield* startFileWatcher({\n\t\t\t\t\twatchIndex: h.supervisor.watchIndex,\n\t\t\t\t\tnotifyWatchFire: h.supervisor.notifyWatchFire,\n\t\t\t\t});\n\t\t\t}),\n\t\twithinScope: (h) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tif (snapshotCache === undefined || !refreshSnapshotCache) return;\n\t\t\t\tyield* h.supervisor.runCommand({\n\t\t\t\t\ttag: 'snapshot.capture',\n\t\t\t\t\tname: snapshotCache.name,\n\t\t\t\t\treplaceExisting: true,\n\t\t\t\t});\n\t\t\t}),\n\t};\n\n\treturn { commandHandler, boot };\n};\n\n/**\n * Run `devstack up`. Wires the substrate Layer stack, supervisor,\n * attached renderer, and in-process TUI command queue. The Effect runs\n * as the outer Node fiber so SIGINT reaches scope finalizers.\n *\n * Returns a typed `CommandResult` / fails with a typed `CliError` so\n * the dispatcher's `emitFailure` renders the envelope (JSON mode) or\n * human-mode stderr line. Wirings MUST NOT write raw bytes to\n * `process.stderr` or mutate `process.exitCode` for terminal failures —\n * the dispatcher owns the projection.\n */\nexport const runUpLive = (\n\tconfigPath: string | undefined,\n\tidentity: ResolvedIdentity,\n\toptions: {\n\t\treadonly renderer: GlobalFlags['renderer'];\n\t\treadonly stdoutIsTty: boolean;\n\t\treadonly fromSnapshot?: string;\n\t\treadonly snapshotCache?: string;\n\t\treadonly snapshotStalePolicy?: GlobalFlags['snapshotStalePolicy'];\n\t},\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tif (options.fromSnapshot !== undefined && options.snapshotCache !== undefined) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: '--snapshot-cache cannot be combined with --from-snapshot',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (options.snapshotCache !== undefined && options.snapshotStalePolicy !== undefined) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: '--snapshot-stale is only valid with --from-snapshot',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst loadExit = yield* Effect.exit(loader.load(configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst loaded = loadExit.value;\n\t\tconst publicStack = (\n\t\t\tloaded as LoadedConfig & {\n\t\t\t\treadonly stack: Stack<SupervisedStack['members']>;\n\t\t\t}\n\t\t).stack;\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\n\t\t// Re-derive the identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/`$DEVSTACK_STACK` > `config.stackName` > inferred) so\n\t\t// the roster lock, command channel, and container/router naming all\n\t\t// target the same stack the operator selected — matching what\n\t\t// `snapshot.ts` already does. Without this, an explicit `--stack`\n\t\t// would boot the supervisor under the config's `stackName` and a\n\t\t// concurrent default-stack `up` would falsely collide on the live\n\t\t// supervisor (`error: supervisor live for <app>/<stack>`, exit 40).\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\n\t\tconst devstackVersion = readDevstackVersion({ fallback: '0.0.0' });\n\t\tconst rendererMode = resolveUpRendererMode({\n\t\t\tcliRenderer: options.renderer,\n\t\t\tstackRenderer: stack.options.renderer,\n\t\t\tstdoutIsTty: options.stdoutIsTty,\n\t\t});\n\t\tlet startFromSnapshot: StartFromSnapshot | undefined;\n\t\tlet snapshotCache: SnapshotCache | undefined;\n\t\tif (options.fromSnapshot !== undefined) {\n\t\t\tconst resolved = yield* makeSnapshotReader({ stackRoot: effectiveIdentity.stackRoot })\n\t\t\t\t.resolve(options.fromSnapshot)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\tmessage: 'snapshot lookup failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\tswitch (resolved.tag) {\n\t\t\t\tcase 'found':\n\t\t\t\t\tstartFromSnapshot = {\n\t\t\t\t\t\tid: resolved.entry.snapshotId,\n\t\t\t\t\t\tstalePolicy: options.snapshotStalePolicy ?? 'warn',\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'not-found':\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliSnapshotNotFoundError({ snapshotRef: options.fromSnapshot }),\n\t\t\t\t\t);\n\t\t\t\tcase 'ambiguous':\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUsageError({\n\t\t\t\t\t\t\tmessage: `snapshot reference is ambiguous: ${resolved.snapshotRef}`,\n\t\t\t\t\t\t\thint: `matches: ${resolved.matches.map((entry) => entry.snapshotId).join(', ')}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tif (options.snapshotCache !== undefined) {\n\t\t\tconst resolved = yield* makeSnapshotReader({ stackRoot: effectiveIdentity.stackRoot })\n\t\t\t\t.resolve(options.snapshotCache)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\tmessage: 'snapshot lookup failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\tswitch (resolved.tag) {\n\t\t\t\tcase 'found':\n\t\t\t\t\tsnapshotCache = {\n\t\t\t\t\t\tname: options.snapshotCache,\n\t\t\t\t\t\texistingSnapshotId: resolved.entry.snapshotId,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'not-found':\n\t\t\t\t\tsnapshotCache = { name: options.snapshotCache };\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'ambiguous':\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUsageError({\n\t\t\t\t\t\t\tmessage: `snapshot reference is ambiguous: ${resolved.snapshotRef}`,\n\t\t\t\t\t\t\thint: `matches: ${resolved.matches.map((entry) => entry.snapshotId).join(', ')}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// The CLI concerns (snapshot/wipe/prune `commandHandler` + the\n\t\t// recover/roster/IPC/TUI boot hooks) as a value bundle — the\n\t\t// EXACT thing the seam consumes, extracted so the Docker-free CLI-boot\n\t\t// smoke test can drive the real bundle (see `test/cli/up-boot-smoke`).\n\t\tconst { commandHandler, boot } = buildUpBootBundle({\n\t\t\tstack,\n\t\t\tidentityValue,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t\tdevstackVersion,\n\t\t\trendererMode,\n\t\t\t...(startFromSnapshot === undefined ? {} : { startFromSnapshot }),\n\t\t\t...(snapshotCache === undefined ? {} : { snapshotCache }),\n\t\t});\n\n\t\t// The ONE boot seam. `runStackWithBoot` owns the substrate Layer\n\t\t// composition, the contribution dispatcher + post-acquire hook +\n\t\t// `extendContext` assembly, the projection ref, and the\n\t\t// forkDetach/Deferred boot lifecycle — `up` no longer forks its own\n\t\t// parallel orchestration. The CLI concerns (recover/roster/IPC/TUI\n\t\t// + snapshot handler) are passed as injected hooks + `commandHandler`.\n\t\tconst handle = runStackWithBoot(publicStack, {\n\t\t\tidentity: {\n\t\t\t\tapp: String(identityValue.app),\n\t\t\t\tstack: String(identityValue.stack),\n\t\t\t\tnetwork: String(identityValue.network),\n\t\t\t},\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t\tcodegen: stack.options.codegen,\n\t\t\t// Dev loop: a Move-source edit fires the watcher → reacquires the\n\t\t\t// package (new id) → this regenerates the committed bindings so the\n\t\t\t// app sees the new shapes. Pass the STATIC (id-free) contributions —\n\t\t\t// the same the stack-free `codegen` verb uses — so the regen NEVER\n\t\t\t// bakes the live on-chain id into the committed `config.ts`. `apply` /\n\t\t\t// `runStack` leave it off.\n\t\t\temitBindings: deriveContributions(stack.members),\n\t\t\tcommandHandler,\n\t\t\tboot,\n\t\t});\n\n\t\t// The outer fiber BLOCKS on `awaitShutdown` so SIGINT reaches the\n\t\t// supervisor's in-scope signal handler (forked inside `startSupervisor`)\n\t\t// and drives teardown through the command-channel/latch — the same path\n\t\t// the CLI used as the outer fiber before the cutover. `Effect.scoped`\n\t\t// owns the supervised scope so the TUI flush finalizer + roster release\n\t\t// run on scope close.\n\t\t//\n\t\t// Errors project through the seam's DISCRIMINATED channels — no\n\t\t// `matchCauseEffect` re-discrimination:\n\t\t// - `handle.start` fails ⇒ BOOT-time `BootError`. Extract a\n\t\t// `CliSupervisorLiveError` (roster-claim loss) for exit 40; else\n\t\t// wrap as `CliInternalError`.\n\t\t// - `handle.awaitShutdown` fails ⇒ MID-RUN cause. Wrap as\n\t\t// `CliInternalError` (boot already succeeded by then).\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* handle.start.pipe(\n\t\t\t\t\tEffect.catch((bootError: BootError) => {\n\t\t\t\t\t\tconst live = findCliSupervisorLiveError(bootError.cause);\n\t\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\t\tlive ??\n\t\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\t\tmessage: 'stack failed',\n\t\t\t\t\t\t\t\t\tcause: Cause.pretty(bootError.cause),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tyield* handle.awaitShutdown.pipe(\n\t\t\t\t\tEffect.catchCause(\n\t\t\t\t\t\t(cause): Effect.Effect<never, CliError> =>\n\t\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\t\t\tmessage: 'stack failed',\n\t\t\t\t\t\t\t\t\tcause: Cause.pretty(cause as Cause.Cause<unknown>),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn { exitCode: ExitCode.OK } satisfies CommandResult;\n\t\t\t}),\n\t\t);\n\t});\n};\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  import { configCodegenable } from "../../contracts/config-bindings.mjs";
2
- import { mvrNamedForm, mvrNamedFormFrom } from "./dep-resolution.mjs";
2
+ import { mvrNamedFormFrom, normalizeMvrPlaceholder } from "./dep-resolution.mjs";
3
3
  //#region src/plugins/package/codegen.ts
4
4
  /** Validate + normalize a declared MVR-type suffix. Entries are
5
5
  * `'<module>::<Name>'` relative to the package; we reject anything that does
6
6
  * not parse as two `::`-joined Move identifiers so a typo fails at config time
7
7
  * rather than emitting an `isValidNamedType`-rejected key into the override
8
- * map. The `@local/<slug>::` prefix is implied (and stripped if the developer
8
+ * map. The package MVR prefix is implied (and stripped if the developer
9
9
  * redundantly included it). */
10
10
  const normalizeMvrTypeSuffix = (packageName, mvr, entry) => {
11
11
  const suffix = entry.startsWith(`${mvr}::`) ? entry.slice(mvr.length + 2) : entry;
@@ -169,7 +169,7 @@ const makeKnownCodegenable = (resolved, options = {}) => {
169
169
  * compiles (a local `sourcePath`; a git source whose tree has not been
170
170
  * materialized contributes `null`, so the bindings step skips it). */
171
171
  const makeLocalStaticCodegen = (config) => {
172
- const mvrPlaceholder = mvrNamedForm(config.mvrPlaceholder ?? config.name);
172
+ const mvrPlaceholder = normalizeMvrPlaceholder(config.name, config.mvrPlaceholder);
173
173
  return () => {
174
174
  return [packageDecl(packageConfigBindings({
175
175
  name: config.name,
@@ -190,7 +190,7 @@ const makeLocalStaticCodegen = (config) => {
190
190
  * packages already carry a literal `packageId` in config; the committed stub
191
191
  * emits it verbatim. */
192
192
  const makeKnownStaticCodegen = (config) => {
193
- const mvrPlaceholder = mvrNamedForm(config.mvrPlaceholder ?? config.name);
193
+ const mvrPlaceholder = normalizeMvrPlaceholder(config.name, config.mvrPlaceholder);
194
194
  return () => {
195
195
  return [packageDecl(packageConfigBindings({
196
196
  name: config.name,
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/package/codegen.ts"],"sourcesContent":["// Package plugin — Codegenable contribution, via the UNIFIED config-binding\n// declaration.\n//\n// Distilled doc §Outputs: \"Bindings input — the source path of every local\n// Package is read by the bindings emitter (KnownPackages filtered out).\"\n//\n// The contribution emitted here is the LIGHTWEIGHT one: package id + MVR\n// placeholder + (for local packages) the source path the bindings emitter\n// consumes. The HEAVY codegen — `@mysten/codegen` emitting typed function\n// shims — happens in the codegen ORCHESTRATOR (NOT this plugin). This file\n// declares the SEAM, not the binding bytes.\n//\n// ONE declaration, TWO derivations. A package declares its `config.ts`\n// contributions ONCE as a `ConfigBindingSet`; the framework's\n// `projectLiveConfig` / `projectStaticConfig` derive both behaviors:\n// - LIVE (boot): bakes the resolved package id literal — boot's\n// `assembleDeployment` reads it into the loadable deployment.\n// - STATIC (committed-tree): emits `requireId(dep, '<mvr>')` so the committed\n// `config.ts` carries NO on-chain id (resolved at app build/dev time).\n//\n// The package contribution is `aggregateOnly`: it projects into the combined\n// `generated/config.ts` (`config.packages.<name>` + top-level\n// `config.objects.<name>`) and emits NO standalone `package/<name>.ts`. The\n// `packageBindings` value is still exported on the emit context so the\n// orchestrator's `isPackageBindings` seam forwards it to the Move-bindings\n// emitter (bindings stay in `generated/bindings/`).\n\nimport type { CodegenableDecl, StaticCodegenSource } from '../../contracts/codegenable.ts';\nimport {\n\tconfigCodegenable,\n\ttype ConfigBinding,\n\ttype ConfigBindingSet,\n} from '../../contracts/config-bindings.ts';\nimport type { PackageBindings } from '../../orchestrators/codegen/bindings.ts';\nimport type { JsonValue } from '../../orchestrators/codegen/deployment.ts';\nimport { mvrNamedForm, mvrNamedFormFrom } from './dep-resolution.ts';\nimport type { ResolvedLocalPackage, ResolvedKnownPackage } from './registry.ts';\n\n/** Codegenable shape — what each Package contributes to the codegen\n * orchestrator. Defined once on the orchestrator's `emitBindings` consumer\n * contract (`orchestrators/codegen/bindings.ts`); re-exported here as the\n * package plugin's public surface. */\nexport type { PackageBindings };\n\n/** The typed shape one `config.packages.<name>` entry exports. Per-network\n * package ids now live in the injected deployment envelope (live local +\n * committed `deployments/<net>.ts`), resolved via\n * `config.forNetwork(net).packages.<name>.id` — so this entry carries only\n * the MVR placeholder + the default-network convenience id. */\nexport interface PackageConfigEntry {\n\treadonly mvr: string;\n\t/** The default (local) network's resolved id. */\n\treadonly packageId: string;\n\t/** Resolved (local) object ids for the default network. Present only\n\t * when at least one object is known. */\n\treadonly objects?: Readonly<Record<string, string>>;\n}\n\n/** The state the LIVE binding derivation reads — the resolved package's\n * active-network id + captured object ids. */\ninterface PackageLiveState {\n\treadonly packageId: string;\n\treadonly captured: Readonly<Record<string, string>>;\n}\n\ninterface PackageBindingInput {\n\treadonly name: string;\n\treadonly mvrPlaceholder: string;\n\t/** Pinned literal id (KNOWN package with a declared id). When set, the\n\t * active-network id is a LITERAL binding (identical in both paths). When\n\t * absent (a LOCAL package, or a KNOWN-shaped local stub), the id is a\n\t * RESOLVED binding: static emits `requireId(dep, '<mvr>')`, live computes the\n\t * real resolved id from acquired state. */\n\treadonly pinnedId?: string | undefined;\n\t/** Resolved local object captures (keyed by user `capture` name).\n\t * Surfaced into `config.objects.<name>` + `packages.<name>.objects`\n\t * for the active (local) network. Empty on the committed-stub path —\n\t * the captured IDS are loaded config data (resolved at app build/dev\n\t * time), so the static path emits `requireValue` from `objectKeys`. */\n\treadonly captured: Readonly<Record<string, string>>;\n\t/** The capture KEYS this package declares (the user `capture` option's\n\t * key set, known at config time). Drives the object-id bindings on BOTH\n\t * paths so the committed stub carries `requireValue(dep, 'package:<name>:\n\t * objects', '<key>')` references rather than a live-only `objects` field.\n\t * When omitted, falls back to the keys present in `captured` (live path).\n\t */\n\treadonly objectKeys?: ReadonlyArray<string> | undefined;\n\t/** OPT-IN Move datatypes to expose as MVR `types` overrides. Each entry is a\n\t * `'<module>::<Name>'` suffix relative to this package (the `@local/<slug>`\n\t * prefix is implied). Each emits one\n\t * `mvrOverrides.types['@local/<slug>::<module>::<Name>']` whose static value\n\t * resolves per-network as `` `${requireId(dep, \"<mvr>\")}::<module>::<Name>` ``.\n\t * Absent / empty ⇒ no `types` entries (the orchestrator emits `types: {}`).\n\t * Identical on the live and static paths (config-known, resolution-\n\t * independent); the live deployment slice ignores the value (keeps\n\t * `types: {}`). */\n\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n}\n\n/** Validate + normalize a declared MVR-type suffix. Entries are\n * `'<module>::<Name>'` relative to the package; we reject anything that does\n * not parse as two `::`-joined Move identifiers so a typo fails at config time\n * rather than emitting an `isValidNamedType`-rejected key into the override\n * map. The `@local/<slug>::` prefix is implied (and stripped if the developer\n * redundantly included it). */\nconst normalizeMvrTypeSuffix = (packageName: string, mvr: string, entry: string): string => {\n\t// Tolerate a redundant `@local/<slug>::` prefix the developer may have\n\t// pasted from a fully-qualified tag.\n\tconst suffix = entry.startsWith(`${mvr}::`) ? entry.slice(mvr.length + 2) : entry;\n\tif (!/^[A-Za-z_][\\w]*::[A-Za-z_][\\w]*$/.test(suffix)) {\n\t\tthrow new Error(\n\t\t\t`localPackage('${packageName}') mvrTypes entry '${entry}' must be '<module>::<Name>' ` +\n\t\t\t\t`(two Move identifiers joined by '::'); the '${mvr}' package prefix is implied.`,\n\t\t);\n\t}\n\treturn suffix;\n};\n\n/**\n * Build the package's config-binding set, declared ONCE. The `name` keys the\n * `config.packages.<name>` entry; `mvrPlaceholder` is a literal in both paths;\n * the active-network id is a RESOLVED binding (sugar `requireId(dep, '<mvr>')` when\n * `resolveViaRuntime`, otherwise a literal already-known id). Declared\n * per-network literals (testnet/mainnet) and captured objects are literals.\n *\n * The same set drives `projectStaticConfig` (committed tree) and\n * `projectLiveConfig` (boot deployment) — no parallel projectors.\n */\nconst packageConfigBindings = (input: PackageBindingInput): ConfigBindingSet<PackageLiveState> => {\n\tconst { name, mvrPlaceholder } = input;\n\n\tconst bindings: Array<ConfigBinding<PackageLiveState>> = [];\n\n\t// `packages.<name>.mvr` — pure literal in both paths.\n\tbindings.push({\n\t\tvariant: 'literal',\n\t\tconfigPath: ['packages', name, 'mvr'],\n\t\tvalue: mvrPlaceholder,\n\t});\n\n\t// The active-network id binding. Two cases:\n\t// - no pinned id (LOCAL, or KNOWN-shaped local stub) → RESOLVED (sugar\n\t// `requireId(dep, '<mvr>')` static; live = the concrete resolved id from\n\t// `state.packageId`).\n\t// - pinned literal (KNOWN with a declared id) → LITERAL (the id stands\n\t// identically in both paths).\n\t// Reused for `packages.<name>.packageId` and the `mvrOverrides` entry so\n\t// both agree.\n\tconst pinned = input.pinnedId;\n\tconst idBinding = (configPath: ReadonlyArray<string>): ConfigBinding<PackageLiveState> =>\n\t\tpinned === undefined\n\t\t\t? {\n\t\t\t\t\tvariant: 'resolved',\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tnamespace: 'package',\n\t\t\t\t\tkey: `${name}:packageId`,\n\t\t\t\t\tsugar: { kind: 'id', mvrPlaceholder },\n\t\t\t\t\tlive: (state) => state.packageId,\n\t\t\t\t}\n\t\t\t: { variant: 'literal', configPath, value: pinned };\n\n\tbindings.push(idBinding(['packages', name, 'packageId']));\n\n\t// Active-network objects — captured ids (local). The captured ids are\n\t// LOADED CONFIG DATA, so each is a RESOLVED binding on the generic\n\t// `requireValue(dep, 'package:<name>:objects', '<key>')` channel: the static\n\t// committed stub emits the resolver expr, the live path bakes the real\n\t// captured id AND feeds the deployment `values` channel. The key set comes\n\t// from `objectKeys` (config-known) so BOTH paths emit identical paths —\n\t// no live-only `objects` field. Falls back to the live capture keys when\n\t// `objectKeys` is absent (the live emit path).\n\tconst objectKeys = input.objectKeys ?? Object.keys(input.captured);\n\tif (objectKeys.length > 0) {\n\t\tconst objectsNamespace = `package:${name}:objects`;\n\t\tfor (const objectKey of objectKeys) {\n\t\t\tconst objectBinding = (\n\t\t\t\tconfigPath: ReadonlyArray<string>,\n\t\t\t): ConfigBinding<PackageLiveState> => ({\n\t\t\t\tvariant: 'resolved',\n\t\t\t\tconfigPath,\n\t\t\t\tnamespace: objectsNamespace,\n\t\t\t\tkey: objectKey,\n\t\t\t\tlive: (state) => (state.captured[objectKey] ?? null) as JsonValue,\n\t\t\t});\n\t\t\tbindings.push(objectBinding(['packages', name, 'objects', objectKey]));\n\t\t\t// Top-level `objects.<name>.<key>` mirror.\n\t\t\tbindings.push(objectBinding(['objects', name, objectKey]));\n\t\t}\n\t}\n\n\t// Active-network MVR override entry — `mvrOverrides.packages.<mvr> = <active\n\t// id>` (the @mysten MVR override shape's `packages` map). The sibling `types`\n\t// map is OPT-IN — populated below ONLY from developer-declared `mvrTypes`\n\t// (the change from auto-enumerating every package type). A resolved binding\n\t// always emits (the resolver expr / live id); a pinned literal emits only\n\t// when non-empty.\n\tif (pinned === undefined || pinned.length > 0) {\n\t\tbindings.push(idBinding(['mvrOverrides', 'packages', mvrPlaceholder]));\n\t}\n\n\t// OPT-IN MVR `types` overrides — `mvrOverrides.types['<mvr>::<module>::<Name>']`\n\t// for each developer-declared `<module>::<Name>`. The static path emits the\n\t// per-network type tag `` `${requireId(dep, \"<mvr>\")}::<module>::<Name>` ``\n\t// (the `mvrType` sugar); the live path's value is ignored by the live\n\t// deployment slice (`deploymentFromBucket` keeps `types: {}`). Sugar bindings\n\t// never touch the generic `values` channel. No declared types ⇒ no entries\n\t// here (the orchestrator emits `types: {}`).\n\tfor (const entry of input.mvrTypes ?? []) {\n\t\tconst typeSuffix = normalizeMvrTypeSuffix(name, mvrPlaceholder, entry);\n\t\tconst tag = `${mvrPlaceholder}::${typeSuffix}`;\n\t\tbindings.push({\n\t\t\tvariant: 'resolved',\n\t\t\tconfigPath: ['mvrOverrides', 'types', tag],\n\t\t\tnamespace: 'package',\n\t\t\tkey: `${name}:mvrType:${tag}`,\n\t\t\tsugar: { kind: 'mvrType', mvrPlaceholder, typeSuffix },\n\t\t\t// Ignored by the live deployment slice; a value is required by the\n\t\t\t// binding shape. The resolved per-network tag is what the static\n\t\t\t// committed `config.ts` emits via the sugar.\n\t\t\tlive: () => tag,\n\t\t});\n\t}\n\n\treturn {\n\t\tbucket: 'config.ts',\n\t\tkind: 'package',\n\t\temitterName: 'package',\n\t\t// One Package contribution per published package — shared `'package'`\n\t\t// emitter name is by-design (the orchestrator skips its uniqueness\n\t\t// check for this flag).\n\t\tallowEmitterNameRepetition: true,\n\t\tbindings,\n\t};\n};\n\n/**\n * Build the package's `CodegenableDecl` from its binding set via the unified\n * `configCodegenable` derivation. Mode `'live'` bakes concrete values + feeds\n * the deployment; `'static'` emits resolver expressions. The decl ALSO exports\n * `packageBindings` (the `extraExports` hook) so the orchestrator's\n * `isPackageBindings` seam forwards it to the Move-bindings emitter (bindings\n * stay in `generated/bindings/`).\n */\nconst packageDecl = (\n\tset: ConfigBindingSet<PackageLiveState>,\n\tbindings: PackageBindings,\n\thow: 'static' | { readonly mode: 'live'; readonly state: PackageLiveState },\n): CodegenableDecl<'package'> =>\n\tconfigCodegenable<PackageLiveState, 'package'>(set, how, {\n\t\textraExports: { packageBindings: bindings },\n\t});\n\n/** Build the Codegenable contribution for a local package (LIVE path).\n * Bakes the resolved active-network id + captured objects. */\nexport const makeLocalCodegenable = (\n\tresolved: ResolvedLocalPackage,\n\toptions: {\n\t\treadonly excluded: boolean;\n\t\t/** OPT-IN MVR `types` to expose — `'<module>::<Name>'` suffixes. */\n\t\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n\t},\n): CodegenableDecl<'package'> => {\n\t// Coerce the resolved placeholder into the current `@local/<slug>` named\n\t// form at the emit seam rather than trusting it verbatim — a cache-served\n\t// `mvrPlaceholder` can be a STALE BARE slug. `mvrNamedFormFrom` is pure +\n\t// deterministic; computed ONCE so the binding default and `config.mvr`\n\t// stay equal. A LOCAL package has NO pinned id — the active-network id is\n\t// always a RESOLVED binding (live bakes the real id; static emits\n\t// `requireId`).\n\tconst mvrPlaceholder = mvrNamedFormFrom(resolved.mvrPlaceholder);\n\tconst set = packageConfigBindings({\n\t\tname: resolved.name,\n\t\tmvrPlaceholder,\n\t\tcaptured: resolved.captured,\n\t\t...(options.mvrTypes !== undefined ? { mvrTypes: options.mvrTypes } : {}),\n\t});\n\tconst bindings: PackageBindings = {\n\t\tname: resolved.name,\n\t\tpackageId: resolved.packageId,\n\t\tmvrPlaceholder,\n\t\tsourcePath: resolved.sourcePath,\n\t\texcluded: options.excluded,\n\t};\n\treturn packageDecl(set, bindings, {\n\t\tmode: 'live',\n\t\tstate: { packageId: resolved.packageId, captured: resolved.captured },\n\t});\n};\n\n/** Build the Codegenable contribution for a known package — `sourcePath:\n * null`, no captured object ids. A KNOWN package's declared id is a PINNED\n * LITERAL (not loaded runtime data), so it renders identically in both the\n * live and static paths. */\nexport const makeKnownCodegenable = (\n\tresolved: ResolvedKnownPackage,\n\toptions: { readonly mvrTypes?: ReadonlyArray<string> | undefined } = {},\n): CodegenableDecl<'package'> => {\n\t// Defensive parity with the local emit seam: coerce to the current\n\t// `@local/<slug>` named form (preserving an already-named override).\n\tconst mvrPlaceholder = mvrNamedFormFrom(resolved.mvrPlaceholder);\n\tconst set = packageConfigBindings({\n\t\tname: resolved.name,\n\t\tmvrPlaceholder,\n\t\tcaptured: {},\n\t\tpinnedId: resolved.packageId,\n\t\t...(options.mvrTypes !== undefined ? { mvrTypes: options.mvrTypes } : {}),\n\t});\n\tconst bindings: PackageBindings = {\n\t\tname: resolved.name,\n\t\tpackageId: resolved.packageId,\n\t\tmvrPlaceholder,\n\t\tsourcePath: null,\n\t\texcluded: true, // implicit — KnownPackages never emit bindings.\n\t};\n\t// A pinned literal renders identically in both paths; derive as `static`\n\t// (the literal bindings ignore live state).\n\treturn packageDecl(set, bindings, 'static');\n};\n\n// ---------------------------------------------------------------------------\n// Stack-free codegen derivation (the `codegen` verb)\n//\n// The live `start` body resolves a package (publish/verify) and feeds the\n// resolved value into `makeLocalCodegenable` / `makeKnownCodegenable`. The\n// `staticCodegen` sources below reconstruct the SAME decls from CONFIG ALONE\n// — no chain, no publish — drawing the `packageId` from the projection\n// id-resolver (sentinel for `'placeholder'`, declared id for `'known'`). The\n// decl SHAPE is identical to the live path.\n// ---------------------------------------------------------------------------\n\n/** Build the static (stack-free) codegen source for a LOCAL package.\n * `sourcePath` is the resolved on-disk Move tree the bindings emitter\n * compiles (a local `sourcePath`; a git source whose tree has not been\n * materialized contributes `null`, so the bindings step skips it). */\nexport const makeLocalStaticCodegen = (config: {\n\treadonly name: string;\n\treadonly sourcePath: string | null;\n\treadonly mvrPlaceholder?: string | undefined;\n\treadonly excluded: boolean;\n\t/** Capture KEYS declared by the user `capture` option (config-known).\n\t * The static stub emits `requireValue(dep, 'package:<name>:objects', '<key>')`\n\t * for each so the committed tree carries object-id references with NO\n\t * baked id and NO live-only `objects` field. */\n\treadonly objectKeys?: ReadonlyArray<string> | undefined;\n\t/** OPT-IN MVR `types` to expose — `'<module>::<Name>'` suffixes. */\n\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n}): StaticCodegenSource => {\n\tconst mvrPlaceholder = mvrNamedForm(config.mvrPlaceholder ?? config.name);\n\treturn () => {\n\t\t// A LOCAL package has no pinned id — resolve the active id at app\n\t\t// build/dev time, never embed it in the committed tree. A git source\n\t\t// whose tree has NOT been materialized has no local path, so the\n\t\t// bindings emitter can't compile it; the `sourcePath` carries through\n\t\t// (null → bindings step skips it) but the `config.ts` entry is the same.\n\t\tconst set = packageConfigBindings({\n\t\t\tname: config.name,\n\t\t\tmvrPlaceholder,\n\t\t\tcaptured: {},\n\t\t\t...(config.objectKeys !== undefined ? { objectKeys: config.objectKeys } : {}),\n\t\t\t...(config.mvrTypes !== undefined ? { mvrTypes: config.mvrTypes } : {}),\n\t\t\t// No pinned id — the active id resolves at app build/dev time.\n\t\t});\n\t\tconst bindings: PackageBindings = {\n\t\t\tname: config.name,\n\t\t\t// No live publish → the committed stub carries no id (the resolver\n\t\t\t// fills it at app build/dev time). Carry the sentinel-free name.\n\t\t\tpackageId: '',\n\t\t\tmvrPlaceholder,\n\t\t\tsourcePath: config.sourcePath,\n\t\t\texcluded: config.sourcePath === null ? true : config.excluded,\n\t\t};\n\t\treturn [packageDecl(set, bindings, 'static')];\n\t};\n};\n\n/** Build the static (stack-free) codegen source for a KNOWN package. Known\n * packages already carry a literal `packageId` in config; the committed stub\n * emits it verbatim. */\nexport const makeKnownStaticCodegen = (config: {\n\treadonly name: string;\n\treadonly packageId: string;\n\treadonly upgradeCapId?: string | undefined;\n\treadonly mvrPlaceholder?: string | undefined;\n\t/** OPT-IN MVR `types` to expose — `'<module>::<Name>'` suffixes. */\n\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n}): StaticCodegenSource => {\n\tconst mvrPlaceholder = mvrNamedForm(config.mvrPlaceholder ?? config.name);\n\treturn () => {\n\t\tconst set = packageConfigBindings({\n\t\t\tname: config.name,\n\t\t\tmvrPlaceholder,\n\t\t\tcaptured: {},\n\t\t\tpinnedId: config.packageId,\n\t\t\t...(config.mvrTypes !== undefined ? { mvrTypes: config.mvrTypes } : {}),\n\t\t});\n\t\tconst bindings: PackageBindings = {\n\t\t\tname: config.name,\n\t\t\tpackageId: config.packageId,\n\t\t\tmvrPlaceholder,\n\t\t\tsourcePath: null,\n\t\t\texcluded: true,\n\t\t};\n\t\t// A known package's literal id renders the same in both paths — derive\n\t\t// the decl as `static` (the literal bindings ignore live state).\n\t\treturn [packageDecl(set, bindings, 'static')];\n\t};\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/package/codegen.ts"],"sourcesContent":["// Package plugin — Codegenable contribution, via the UNIFIED config-binding\n// declaration.\n//\n// Distilled doc §Outputs: \"Bindings input — the source path of every local\n// Package is read by the bindings emitter (KnownPackages filtered out).\"\n//\n// The contribution emitted here is the LIGHTWEIGHT one: package id + MVR\n// placeholder + (for local packages) the source path the bindings emitter\n// consumes. The HEAVY codegen — `@mysten/codegen` emitting typed function\n// shims — happens in the codegen ORCHESTRATOR (NOT this plugin). This file\n// declares the SEAM, not the binding bytes.\n//\n// ONE declaration, TWO derivations. A package declares its `config.ts`\n// contributions ONCE as a `ConfigBindingSet`; the framework's\n// `projectLiveConfig` / `projectStaticConfig` derive both behaviors:\n// - LIVE (boot): bakes the resolved package id literal — boot's\n// `assembleDeployment` reads it into the loadable deployment.\n// - STATIC (committed-tree): emits `requireId(dep, '<mvr>')` so the committed\n// `config.ts` carries NO on-chain id (resolved at app build/dev time).\n//\n// The package contribution is `aggregateOnly`: it projects into the combined\n// `generated/config.ts` (`config.packages.<name>` + top-level\n// `config.objects.<name>`) and emits NO standalone `package/<name>.ts`. The\n// `packageBindings` value is still exported on the emit context so the\n// orchestrator's `isPackageBindings` seam forwards it to the Move-bindings\n// emitter (bindings stay in `generated/bindings/`).\n\nimport type { CodegenableDecl, StaticCodegenSource } from '../../contracts/codegenable.ts';\nimport {\n\tconfigCodegenable,\n\ttype ConfigBinding,\n\ttype ConfigBindingSet,\n} from '../../contracts/config-bindings.ts';\nimport type { PackageBindings } from '../../orchestrators/codegen/bindings.ts';\nimport type { JsonValue } from '../../orchestrators/codegen/deployment.ts';\nimport { mvrNamedFormFrom, normalizeMvrPlaceholder } from './dep-resolution.ts';\nimport type { ResolvedLocalPackage, ResolvedKnownPackage } from './registry.ts';\n\n/** Codegenable shape — what each Package contributes to the codegen\n * orchestrator. Defined once on the orchestrator's `emitBindings` consumer\n * contract (`orchestrators/codegen/bindings.ts`); re-exported here as the\n * package plugin's public surface. */\nexport type { PackageBindings };\n\n/** The typed shape one `config.packages.<name>` entry exports. Per-network\n * package ids now live in the injected deployment envelope (live local +\n * committed `deployments/<net>.ts`), resolved via\n * `config.forNetwork(net).packages.<name>.id` — so this entry carries only\n * the MVR placeholder + the default-network convenience id. */\nexport interface PackageConfigEntry {\n\treadonly mvr: string;\n\t/** The default (local) network's resolved id. */\n\treadonly packageId: string;\n\t/** Resolved (local) object ids for the default network. Present only\n\t * when at least one object is known. */\n\treadonly objects?: Readonly<Record<string, string>>;\n}\n\n/** The state the LIVE binding derivation reads — the resolved package's\n * active-network id + captured object ids. */\ninterface PackageLiveState {\n\treadonly packageId: string;\n\treadonly captured: Readonly<Record<string, string>>;\n}\n\ninterface PackageBindingInput {\n\treadonly name: string;\n\treadonly mvrPlaceholder: string;\n\t/** Pinned literal id (KNOWN package with a declared id). When set, the\n\t * active-network id is a LITERAL binding (identical in both paths). When\n\t * absent (a LOCAL package, or a KNOWN-shaped local stub), the id is a\n\t * RESOLVED binding: static emits `requireId(dep, '<mvr>')`, live computes the\n\t * real resolved id from acquired state. */\n\treadonly pinnedId?: string | undefined;\n\t/** Resolved local object captures (keyed by user `capture` name).\n\t * Surfaced into `config.objects.<name>` + `packages.<name>.objects`\n\t * for the active (local) network. Empty on the committed-stub path —\n\t * the captured IDS are loaded config data (resolved at app build/dev\n\t * time), so the static path emits `requireValue` from `objectKeys`. */\n\treadonly captured: Readonly<Record<string, string>>;\n\t/** The capture KEYS this package declares (the user `capture` option's\n\t * key set, known at config time). Drives the object-id bindings on BOTH\n\t * paths so the committed stub carries `requireValue(dep, 'package:<name>:\n\t * objects', '<key>')` references rather than a live-only `objects` field.\n\t * When omitted, falls back to the keys present in `captured` (live path).\n\t */\n\treadonly objectKeys?: ReadonlyArray<string> | undefined;\n\t/** OPT-IN Move datatypes to expose as MVR `types` overrides. Each entry is a\n\t * `'<module>::<Name>'` suffix relative to this package (the package MVR\n\t * prefix is implied). Each emits one\n\t * `mvrOverrides.types['<mvr>::<module>::<Name>']` whose static value\n\t * resolves per-network as `` `${requireId(dep, \"<mvr>\")}::<module>::<Name>` ``.\n\t * Absent / empty ⇒ no `types` entries (the orchestrator emits `types: {}`).\n\t * Identical on the live and static paths (config-known, resolution-\n\t * independent); the live deployment slice ignores the value (keeps\n\t * `types: {}`). */\n\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n}\n\n/** Validate + normalize a declared MVR-type suffix. Entries are\n * `'<module>::<Name>'` relative to the package; we reject anything that does\n * not parse as two `::`-joined Move identifiers so a typo fails at config time\n * rather than emitting an `isValidNamedType`-rejected key into the override\n * map. The package MVR prefix is implied (and stripped if the developer\n * redundantly included it). */\nconst normalizeMvrTypeSuffix = (packageName: string, mvr: string, entry: string): string => {\n\t// Tolerate a redundant package prefix the developer may have pasted from a\n\t// fully-qualified tag.\n\tconst suffix = entry.startsWith(`${mvr}::`) ? entry.slice(mvr.length + 2) : entry;\n\tif (!/^[A-Za-z_][\\w]*::[A-Za-z_][\\w]*$/.test(suffix)) {\n\t\tthrow new Error(\n\t\t\t`localPackage('${packageName}') mvrTypes entry '${entry}' must be '<module>::<Name>' ` +\n\t\t\t\t`(two Move identifiers joined by '::'); the '${mvr}' package prefix is implied.`,\n\t\t);\n\t}\n\treturn suffix;\n};\n\n/**\n * Build the package's config-binding set, declared ONCE. The `name` keys the\n * `config.packages.<name>` entry; `mvrPlaceholder` is a literal in both paths;\n * the active-network id is a RESOLVED binding (sugar `requireId(dep, '<mvr>')` when\n * `resolveViaRuntime`, otherwise a literal already-known id). Declared\n * per-network literals (testnet/mainnet) and captured objects are literals.\n *\n * The same set drives `projectStaticConfig` (committed tree) and\n * `projectLiveConfig` (boot deployment) — no parallel projectors.\n */\nconst packageConfigBindings = (input: PackageBindingInput): ConfigBindingSet<PackageLiveState> => {\n\tconst { name, mvrPlaceholder } = input;\n\n\tconst bindings: Array<ConfigBinding<PackageLiveState>> = [];\n\n\t// `packages.<name>.mvr` — pure literal in both paths.\n\tbindings.push({\n\t\tvariant: 'literal',\n\t\tconfigPath: ['packages', name, 'mvr'],\n\t\tvalue: mvrPlaceholder,\n\t});\n\n\t// The active-network id binding. Two cases:\n\t// - no pinned id (LOCAL, or KNOWN-shaped local stub) → RESOLVED (sugar\n\t// `requireId(dep, '<mvr>')` static; live = the concrete resolved id from\n\t// `state.packageId`).\n\t// - pinned literal (KNOWN with a declared id) → LITERAL (the id stands\n\t// identically in both paths).\n\t// Reused for `packages.<name>.packageId` and the `mvrOverrides` entry so\n\t// both agree.\n\tconst pinned = input.pinnedId;\n\tconst idBinding = (configPath: ReadonlyArray<string>): ConfigBinding<PackageLiveState> =>\n\t\tpinned === undefined\n\t\t\t? {\n\t\t\t\t\tvariant: 'resolved',\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tnamespace: 'package',\n\t\t\t\t\tkey: `${name}:packageId`,\n\t\t\t\t\tsugar: { kind: 'id', mvrPlaceholder },\n\t\t\t\t\tlive: (state) => state.packageId,\n\t\t\t\t}\n\t\t\t: { variant: 'literal', configPath, value: pinned };\n\n\tbindings.push(idBinding(['packages', name, 'packageId']));\n\n\t// Active-network objects — captured ids (local). The captured ids are\n\t// LOADED CONFIG DATA, so each is a RESOLVED binding on the generic\n\t// `requireValue(dep, 'package:<name>:objects', '<key>')` channel: the static\n\t// committed stub emits the resolver expr, the live path bakes the real\n\t// captured id AND feeds the deployment `values` channel. The key set comes\n\t// from `objectKeys` (config-known) so BOTH paths emit identical paths —\n\t// no live-only `objects` field. Falls back to the live capture keys when\n\t// `objectKeys` is absent (the live emit path).\n\tconst objectKeys = input.objectKeys ?? Object.keys(input.captured);\n\tif (objectKeys.length > 0) {\n\t\tconst objectsNamespace = `package:${name}:objects`;\n\t\tfor (const objectKey of objectKeys) {\n\t\t\tconst objectBinding = (\n\t\t\t\tconfigPath: ReadonlyArray<string>,\n\t\t\t): ConfigBinding<PackageLiveState> => ({\n\t\t\t\tvariant: 'resolved',\n\t\t\t\tconfigPath,\n\t\t\t\tnamespace: objectsNamespace,\n\t\t\t\tkey: objectKey,\n\t\t\t\tlive: (state) => (state.captured[objectKey] ?? null) as JsonValue,\n\t\t\t});\n\t\t\tbindings.push(objectBinding(['packages', name, 'objects', objectKey]));\n\t\t\t// Top-level `objects.<name>.<key>` mirror.\n\t\t\tbindings.push(objectBinding(['objects', name, objectKey]));\n\t\t}\n\t}\n\n\t// Active-network MVR override entry — `mvrOverrides.packages.<mvr> = <active\n\t// id>` (the @mysten MVR override shape's `packages` map). The sibling `types`\n\t// map is OPT-IN — populated below ONLY from developer-declared `mvrTypes`\n\t// (the change from auto-enumerating every package type). A resolved binding\n\t// always emits (the resolver expr / live id); a pinned literal emits only\n\t// when non-empty.\n\tif (pinned === undefined || pinned.length > 0) {\n\t\tbindings.push(idBinding(['mvrOverrides', 'packages', mvrPlaceholder]));\n\t}\n\n\t// OPT-IN MVR `types` overrides — `mvrOverrides.types['<mvr>::<module>::<Name>']`\n\t// for each developer-declared `<module>::<Name>`. The static path emits the\n\t// per-network type tag `` `${requireId(dep, \"<mvr>\")}::<module>::<Name>` ``\n\t// (the `mvrType` sugar); the live path's value is ignored by the live\n\t// deployment slice (`deploymentFromBucket` keeps `types: {}`). Sugar bindings\n\t// never touch the generic `values` channel. No declared types ⇒ no entries\n\t// here (the orchestrator emits `types: {}`).\n\tfor (const entry of input.mvrTypes ?? []) {\n\t\tconst typeSuffix = normalizeMvrTypeSuffix(name, mvrPlaceholder, entry);\n\t\tconst tag = `${mvrPlaceholder}::${typeSuffix}`;\n\t\tbindings.push({\n\t\t\tvariant: 'resolved',\n\t\t\tconfigPath: ['mvrOverrides', 'types', tag],\n\t\t\tnamespace: 'package',\n\t\t\tkey: `${name}:mvrType:${tag}`,\n\t\t\tsugar: { kind: 'mvrType', mvrPlaceholder, typeSuffix },\n\t\t\t// Ignored by the live deployment slice; a value is required by the\n\t\t\t// binding shape. The resolved per-network tag is what the static\n\t\t\t// committed `config.ts` emits via the sugar.\n\t\t\tlive: () => tag,\n\t\t});\n\t}\n\n\treturn {\n\t\tbucket: 'config.ts',\n\t\tkind: 'package',\n\t\temitterName: 'package',\n\t\t// One Package contribution per published package — shared `'package'`\n\t\t// emitter name is by-design (the orchestrator skips its uniqueness\n\t\t// check for this flag).\n\t\tallowEmitterNameRepetition: true,\n\t\tbindings,\n\t};\n};\n\n/**\n * Build the package's `CodegenableDecl` from its binding set via the unified\n * `configCodegenable` derivation. Mode `'live'` bakes concrete values + feeds\n * the deployment; `'static'` emits resolver expressions. The decl ALSO exports\n * `packageBindings` (the `extraExports` hook) so the orchestrator's\n * `isPackageBindings` seam forwards it to the Move-bindings emitter (bindings\n * stay in `generated/bindings/`).\n */\nconst packageDecl = (\n\tset: ConfigBindingSet<PackageLiveState>,\n\tbindings: PackageBindings,\n\thow: 'static' | { readonly mode: 'live'; readonly state: PackageLiveState },\n): CodegenableDecl<'package'> =>\n\tconfigCodegenable<PackageLiveState, 'package'>(set, how, {\n\t\textraExports: { packageBindings: bindings },\n\t});\n\n/** Build the Codegenable contribution for a local package (LIVE path).\n * Bakes the resolved active-network id + captured objects. */\nexport const makeLocalCodegenable = (\n\tresolved: ResolvedLocalPackage,\n\toptions: {\n\t\treadonly excluded: boolean;\n\t\t/** OPT-IN MVR `types` to expose — `'<module>::<Name>'` suffixes. */\n\t\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n\t},\n): CodegenableDecl<'package'> => {\n\t// Coerce the resolved placeholder into the current `@local/<slug>` named\n\t// form at the emit seam rather than trusting it verbatim — a cache-served\n\t// `mvrPlaceholder` can be a STALE BARE slug. `mvrNamedFormFrom` is pure +\n\t// deterministic; computed ONCE so the binding default and `config.mvr`\n\t// stay equal. A LOCAL package has NO pinned id — the active-network id is\n\t// always a RESOLVED binding (live bakes the real id; static emits\n\t// `requireId`).\n\tconst mvrPlaceholder = mvrNamedFormFrom(resolved.mvrPlaceholder);\n\tconst set = packageConfigBindings({\n\t\tname: resolved.name,\n\t\tmvrPlaceholder,\n\t\tcaptured: resolved.captured,\n\t\t...(options.mvrTypes !== undefined ? { mvrTypes: options.mvrTypes } : {}),\n\t});\n\tconst bindings: PackageBindings = {\n\t\tname: resolved.name,\n\t\tpackageId: resolved.packageId,\n\t\tmvrPlaceholder,\n\t\tsourcePath: resolved.sourcePath,\n\t\texcluded: options.excluded,\n\t};\n\treturn packageDecl(set, bindings, {\n\t\tmode: 'live',\n\t\tstate: { packageId: resolved.packageId, captured: resolved.captured },\n\t});\n};\n\n/** Build the Codegenable contribution for a known package — `sourcePath:\n * null`, no captured object ids. A KNOWN package's declared id is a PINNED\n * LITERAL (not loaded runtime data), so it renders identically in both the\n * live and static paths. */\nexport const makeKnownCodegenable = (\n\tresolved: ResolvedKnownPackage,\n\toptions: { readonly mvrTypes?: ReadonlyArray<string> | undefined } = {},\n): CodegenableDecl<'package'> => {\n\t// Defensive parity with the local emit seam: coerce to the current\n\t// `@local/<slug>` named form (preserving an already-named override).\n\tconst mvrPlaceholder = mvrNamedFormFrom(resolved.mvrPlaceholder);\n\tconst set = packageConfigBindings({\n\t\tname: resolved.name,\n\t\tmvrPlaceholder,\n\t\tcaptured: {},\n\t\tpinnedId: resolved.packageId,\n\t\t...(options.mvrTypes !== undefined ? { mvrTypes: options.mvrTypes } : {}),\n\t});\n\tconst bindings: PackageBindings = {\n\t\tname: resolved.name,\n\t\tpackageId: resolved.packageId,\n\t\tmvrPlaceholder,\n\t\tsourcePath: null,\n\t\texcluded: true, // implicit — KnownPackages never emit bindings.\n\t};\n\t// A pinned literal renders identically in both paths; derive as `static`\n\t// (the literal bindings ignore live state).\n\treturn packageDecl(set, bindings, 'static');\n};\n\n// ---------------------------------------------------------------------------\n// Stack-free codegen derivation (the `codegen` verb)\n//\n// The live `start` body resolves a package (publish/verify) and feeds the\n// resolved value into `makeLocalCodegenable` / `makeKnownCodegenable`. The\n// `staticCodegen` sources below reconstruct the SAME decls from CONFIG ALONE\n// — no chain, no publish — drawing the `packageId` from the projection\n// id-resolver (sentinel for `'placeholder'`, declared id for `'known'`). The\n// decl SHAPE is identical to the live path.\n// ---------------------------------------------------------------------------\n\n/** Build the static (stack-free) codegen source for a LOCAL package.\n * `sourcePath` is the resolved on-disk Move tree the bindings emitter\n * compiles (a local `sourcePath`; a git source whose tree has not been\n * materialized contributes `null`, so the bindings step skips it). */\nexport const makeLocalStaticCodegen = (config: {\n\treadonly name: string;\n\treadonly sourcePath: string | null;\n\treadonly mvrPlaceholder?: string | undefined;\n\treadonly excluded: boolean;\n\t/** Capture KEYS declared by the user `capture` option (config-known).\n\t * The static stub emits `requireValue(dep, 'package:<name>:objects', '<key>')`\n\t * for each so the committed tree carries object-id references with NO\n\t * baked id and NO live-only `objects` field. */\n\treadonly objectKeys?: ReadonlyArray<string> | undefined;\n\t/** OPT-IN MVR `types` to expose — `'<module>::<Name>'` suffixes. */\n\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n}): StaticCodegenSource => {\n\tconst mvrPlaceholder = normalizeMvrPlaceholder(config.name, config.mvrPlaceholder);\n\treturn () => {\n\t\t// A LOCAL package has no pinned id — resolve the active id at app\n\t\t// build/dev time, never embed it in the committed tree. A git source\n\t\t// whose tree has NOT been materialized has no local path, so the\n\t\t// bindings emitter can't compile it; the `sourcePath` carries through\n\t\t// (null → bindings step skips it) but the `config.ts` entry is the same.\n\t\tconst set = packageConfigBindings({\n\t\t\tname: config.name,\n\t\t\tmvrPlaceholder,\n\t\t\tcaptured: {},\n\t\t\t...(config.objectKeys !== undefined ? { objectKeys: config.objectKeys } : {}),\n\t\t\t...(config.mvrTypes !== undefined ? { mvrTypes: config.mvrTypes } : {}),\n\t\t\t// No pinned id — the active id resolves at app build/dev time.\n\t\t});\n\t\tconst bindings: PackageBindings = {\n\t\t\tname: config.name,\n\t\t\t// No live publish → the committed stub carries no id (the resolver\n\t\t\t// fills it at app build/dev time). Carry the sentinel-free name.\n\t\t\tpackageId: '',\n\t\t\tmvrPlaceholder,\n\t\t\tsourcePath: config.sourcePath,\n\t\t\texcluded: config.sourcePath === null ? true : config.excluded,\n\t\t};\n\t\treturn [packageDecl(set, bindings, 'static')];\n\t};\n};\n\n/** Build the static (stack-free) codegen source for a KNOWN package. Known\n * packages already carry a literal `packageId` in config; the committed stub\n * emits it verbatim. */\nexport const makeKnownStaticCodegen = (config: {\n\treadonly name: string;\n\treadonly packageId: string;\n\treadonly upgradeCapId?: string | undefined;\n\treadonly mvrPlaceholder?: string | undefined;\n\t/** OPT-IN MVR `types` to expose — `'<module>::<Name>'` suffixes. */\n\treadonly mvrTypes?: ReadonlyArray<string> | undefined;\n}): StaticCodegenSource => {\n\tconst mvrPlaceholder = normalizeMvrPlaceholder(config.name, config.mvrPlaceholder);\n\treturn () => {\n\t\tconst set = packageConfigBindings({\n\t\t\tname: config.name,\n\t\t\tmvrPlaceholder,\n\t\t\tcaptured: {},\n\t\t\tpinnedId: config.packageId,\n\t\t\t...(config.mvrTypes !== undefined ? { mvrTypes: config.mvrTypes } : {}),\n\t\t});\n\t\tconst bindings: PackageBindings = {\n\t\t\tname: config.name,\n\t\t\tpackageId: config.packageId,\n\t\t\tmvrPlaceholder,\n\t\t\tsourcePath: null,\n\t\t\texcluded: true,\n\t\t};\n\t\t// A known package's literal id renders the same in both paths — derive\n\t\t// the decl as `static` (the literal bindings ignore live state).\n\t\treturn [packageDecl(set, bindings, 'static')];\n\t};\n};\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { isValidNamedPackage } from "@mysten/sui/utils";
1
2
  //#region src/plugins/package/dep-resolution.ts
2
3
  /**
3
4
  * Slugify a package name into the bare slug shape (`[a-z0-9-]+`).
@@ -28,9 +29,21 @@ const MVR_LOCAL_ORG = "@local";
28
29
  * `hasMvrName` (see `test/plugins/package/mvr-named-form.test.ts`).
29
30
  */
30
31
  const mvrNamedForm = (name) => `${MVR_LOCAL_ORG}/${mvrSlugify(name)}`;
32
+ /** Normalize the user-facing `mvrPlaceholder` option.
33
+ *
34
+ * Devstack's default remains `@local/<slug>`, derived from the package's
35
+ * symbolic name. An explicit override is different: it is already a full
36
+ * MVR named package (`@org/package`) and must be preserved verbatim so it
37
+ * matches `@mysten/codegen`'s documented `@local-pkg/counter` style.
38
+ */
39
+ const normalizeMvrPlaceholder = (packageName, placeholder) => {
40
+ if (placeholder === void 0) return mvrNamedForm(packageName);
41
+ if (isValidNamedPackage(placeholder)) return placeholder;
42
+ throw new Error(`package '${packageName}' mvrPlaceholder '${placeholder}' is not a valid MVR named package; use a full name like '@local/${mvrSlugify(packageName)}' or '@local-pkg/${mvrSlugify(packageName)}'.`);
43
+ };
31
44
  /**
32
- * Coerce a (possibly STALE) persisted MVR placeholder into the current
33
- * `@local/<slug>` named form.
45
+ * Coerce a (possibly STALE) persisted MVR placeholder into a valid named
46
+ * package form.
34
47
  *
35
48
  * The package-publish cache (`projection.v4.json`'s `mvrPlaceholder`)
36
49
  * persists whatever placeholder shape was current at publish time. A
@@ -42,17 +55,14 @@ const mvrNamedForm = (name) => `${MVR_LOCAL_ORG}/${mvrSlugify(name)}`;
42
55
  *
43
56
  * `mvrNamedForm` is not idempotent over an already-named string
44
57
  * (`mvrSlugify('@local/vault')` → `'local-vault'`), so we cannot blindly
45
- * re-wrap. Instead:
46
- * - if the placeholder is ALREADY in `@<org>/...` named form, keep it
47
- * verbatim (this preserves a user-supplied `mvrPlaceholder` override);
48
- * - otherwise treat it as a bare slug/name and wrap it via
49
- * `mvrNamedForm`.
58
+ * re-wrap. Instead, preserve anything the SDK accepts as a full MVR named
59
+ * package and wrap only legacy bare names.
50
60
  *
51
61
  * Pure + deterministic over its input, so recomputing at the emit seam is
52
62
  * always safe and always current regardless of cached projection state.
53
63
  */
54
- const mvrNamedFormFrom = (placeholder) => placeholder.startsWith(`${MVR_LOCAL_ORG}/`) ? placeholder : mvrNamedForm(placeholder);
64
+ const mvrNamedFormFrom = (placeholder) => isValidNamedPackage(placeholder) ? placeholder : mvrNamedForm(placeholder);
55
65
  //#endregion
56
- export { mvrNamedForm, mvrNamedFormFrom };
66
+ export { mvrNamedFormFrom, normalizeMvrPlaceholder };
57
67
 
58
68
  //# sourceMappingURL=dep-resolution.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dep-resolution.mjs","names":[],"sources":["../../../src/plugins/package/dep-resolution.ts"],"sourcesContent":["// Move-dep helpers.\n//\n// `mvrSlugify` normalises a package name into the slug shape that\n// codegen emitters write into Move source as MVR placeholders.\n// Distilled doc Invariant 13: the result MUST satisfy `[a-z0-9-]+`\n// (downstream validators reject underscores).\n\n/**\n * Slugify a package name into the bare slug shape (`[a-z0-9-]+`).\n *\n * This bare slug is what codegen writes into Move-source file stems /\n * the dead `package/<slug>.ts` output path, and is the `{app}`\n * component of the MVR named form (`mvrNamedForm` below).\n */\nexport const mvrSlugify = (name: string): string =>\n\tname\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9-]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '')\n\t\t.replace(/-{2,}/g, '-');\n\n/**\n * Local-org used for the MVR named form devstack emits as the binding\n * package default + `config.packages.<name>.mvr`. It is syntactically\n * valid per `@mysten/sui`'s `isValidSuiNSName` (it contains `@`), so\n * the resulting `@local/<slug>` passes BOTH `hasMvrName` and\n * `isValidNamedPackage`. Resolution never hits a real registry — the\n * dapp-kit/`MvrClient` override map keys on this exact string and\n * short-circuits to the active network's resolved package id.\n */\nconst MVR_LOCAL_ORG = '@local';\n\n/**\n * Build the MVR NAMED form (`@local/<slug>`) from a package name.\n *\n * This is the value emitted as BOTH the generated-binding package\n * default (`options.package ?? '@local/<slug>'`) AND\n * `config.packages.<name>.mvr` — the two MUST match so a dapp can key\n * an `MvrClient` override on `config.mvr` and have generated functions\n * resolve by name alone. Verified to satisfy `isValidNamedPackage` and\n * `hasMvrName` (see `test/plugins/package/mvr-named-form.test.ts`).\n */\nexport const mvrNamedForm = (name: string): string => `${MVR_LOCAL_ORG}/${mvrSlugify(name)}`;\n\n/**\n * Coerce a (possibly STALE) persisted MVR placeholder into the current\n * `@local/<slug>` named form.\n *\n * The package-publish cache (`projection.v4.json`'s `mvrPlaceholder`)\n * persists whatever placeholder shape was current at publish time. A\n * stack created BEFORE the `mvrSlugify`→`mvrNamedForm` change carries a\n * BARE slug (e.g. `'vault'`), which an INCREMENTAL re-apply would\n * otherwise emit verbatim — and a bare slug fails `hasMvrName`, so the\n * generated binding's `package:` default never resolves through the\n * `MvrClient` override map. We MUST always emit the named form.\n *\n * `mvrNamedForm` is not idempotent over an already-named string\n * (`mvrSlugify('@local/vault')` → `'local-vault'`), so we cannot blindly\n * re-wrap. Instead:\n * - if the placeholder is ALREADY in `@<org>/...` named form, keep it\n * verbatim (this preserves a user-supplied `mvrPlaceholder` override);\n * - otherwise treat it as a bare slug/name and wrap it via\n * `mvrNamedForm`.\n *\n * Pure + deterministic over its input, so recomputing at the emit seam is\n * always safe and always current regardless of cached projection state.\n */\nexport const mvrNamedFormFrom = (placeholder: string): string =>\n\tplaceholder.startsWith(`${MVR_LOCAL_ORG}/`) ? placeholder : mvrNamedForm(placeholder);\n"],"mappings":";;;;;;;;AAcA,MAAa,cAAc,SAC1B,KACE,YAAY,CAAC,CACb,QAAQ,gBAAgB,GAAG,CAAC,CAC5B,QAAQ,YAAY,EAAE,CAAC,CACvB,QAAQ,UAAU,GAAG;;;;;;;;;;AAWxB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,MAAa,gBAAgB,SAAyB,GAAG,cAAc,GAAG,WAAW,IAAI;;;;;;;;;;;;;;;;;;;;;;;;AAyBzF,MAAa,oBAAoB,gBAChC,YAAY,WAAW,GAAG,cAAc,EAAE,IAAI,cAAc,aAAa,WAAW"}
1
+ {"version":3,"file":"dep-resolution.mjs","names":[],"sources":["../../../src/plugins/package/dep-resolution.ts"],"sourcesContent":["// Move-dep helpers.\n//\n// `mvrSlugify` normalises a package name into the slug shape that\n// devstack uses for its default local MVR placeholders.\n// Distilled doc Invariant 13: the result MUST satisfy `[a-z0-9-]+`\n// (downstream validators reject underscores).\n\nimport { isValidNamedPackage } from '@mysten/sui/utils';\n\n/**\n * Slugify a package name into the bare slug shape (`[a-z0-9-]+`).\n *\n * This bare slug is what codegen writes into Move-source file stems /\n * the dead `package/<slug>.ts` output path, and is the `{app}`\n * component of the MVR named form (`mvrNamedForm` below).\n */\nexport const mvrSlugify = (name: string): string =>\n\tname\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9-]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '')\n\t\t.replace(/-{2,}/g, '-');\n\n/**\n * Local-org used for the MVR named form devstack emits as the binding\n * package default + `config.packages.<name>.mvr`. It is syntactically\n * valid per `@mysten/sui`'s `isValidSuiNSName` (it contains `@`), so\n * the resulting `@local/<slug>` passes BOTH `hasMvrName` and\n * `isValidNamedPackage`. Resolution never hits a real registry — the\n * dapp-kit/`MvrClient` override map keys on this exact string and\n * short-circuits to the active network's resolved package id.\n */\nconst MVR_LOCAL_ORG = '@local';\n\n/**\n * Build the MVR NAMED form (`@local/<slug>`) from a package name.\n *\n * This is the value emitted as BOTH the generated-binding package\n * default (`options.package ?? '@local/<slug>'`) AND\n * `config.packages.<name>.mvr` — the two MUST match so a dapp can key\n * an `MvrClient` override on `config.mvr` and have generated functions\n * resolve by name alone. Verified to satisfy `isValidNamedPackage` and\n * `hasMvrName` (see `test/plugins/package/mvr-named-form.test.ts`).\n */\nexport const mvrNamedForm = (name: string): string => `${MVR_LOCAL_ORG}/${mvrSlugify(name)}`;\n\n/** Normalize the user-facing `mvrPlaceholder` option.\n *\n * Devstack's default remains `@local/<slug>`, derived from the package's\n * symbolic name. An explicit override is different: it is already a full\n * MVR named package (`@org/package`) and must be preserved verbatim so it\n * matches `@mysten/codegen`'s documented `@local-pkg/counter` style.\n */\nexport const normalizeMvrPlaceholder = (\n\tpackageName: string,\n\tplaceholder: string | undefined,\n): string => {\n\tif (placeholder === undefined) return mvrNamedForm(packageName);\n\tif (isValidNamedPackage(placeholder)) return placeholder;\n\tthrow new Error(\n\t\t`package '${packageName}' mvrPlaceholder '${placeholder}' is not a valid MVR named package; ` +\n\t\t\t`use a full name like '@local/${mvrSlugify(packageName)}' or '@local-pkg/${mvrSlugify(packageName)}'.`,\n\t);\n};\n\n/**\n * Coerce a (possibly STALE) persisted MVR placeholder into a valid named\n * package form.\n *\n * The package-publish cache (`projection.v4.json`'s `mvrPlaceholder`)\n * persists whatever placeholder shape was current at publish time. A\n * stack created BEFORE the `mvrSlugify`→`mvrNamedForm` change carries a\n * BARE slug (e.g. `'vault'`), which an INCREMENTAL re-apply would\n * otherwise emit verbatim — and a bare slug fails `hasMvrName`, so the\n * generated binding's `package:` default never resolves through the\n * `MvrClient` override map. We MUST always emit the named form.\n *\n * `mvrNamedForm` is not idempotent over an already-named string\n * (`mvrSlugify('@local/vault')` → `'local-vault'`), so we cannot blindly\n * re-wrap. Instead, preserve anything the SDK accepts as a full MVR named\n * package and wrap only legacy bare names.\n *\n * Pure + deterministic over its input, so recomputing at the emit seam is\n * always safe and always current regardless of cached projection state.\n */\nexport const mvrNamedFormFrom = (placeholder: string): string =>\n\tisValidNamedPackage(placeholder) ? placeholder : mvrNamedForm(placeholder);\n"],"mappings":";;;;;;;;;AAgBA,MAAa,cAAc,SAC1B,KACE,YAAY,CAAC,CACb,QAAQ,gBAAgB,GAAG,CAAC,CAC5B,QAAQ,YAAY,EAAE,CAAC,CACvB,QAAQ,UAAU,GAAG;;;;;;;;;;AAWxB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,MAAa,gBAAgB,SAAyB,GAAG,cAAc,GAAG,WAAW,IAAI;;;;;;;;AASzF,MAAa,2BACZ,aACA,gBACY;CACZ,IAAI,gBAAgB,KAAA,GAAW,OAAO,aAAa,WAAW;CAC9D,IAAI,oBAAoB,WAAW,GAAG,OAAO;CAC7C,MAAM,IAAI,MACT,YAAY,YAAY,oBAAoB,YAAY,mEACvB,WAAW,WAAW,EAAE,mBAAmB,WAAW,WAAW,EAAE,GACrG;AACD;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,oBAAoB,gBAChC,oBAAoB,WAAW,IAAI,cAAc,aAAa,WAAW"}
@@ -54,10 +54,10 @@ interface LocalPackageOptions<Publisher extends PublisherAccountMember = Publish
54
54
  readonly excludeFromCodegen?: boolean;
55
55
  /** Move datatypes (struct / enum) to expose as MVR `types` overrides.
56
56
  * OPT-IN: each entry is a `'<module>::<Name>'` relative to THIS package
57
- * (the `@local/<slug>` prefix is implied), e.g. `['game::Lobby',
57
+ * (the package MVR prefix is implied), e.g. `['game::Lobby',
58
58
  * 'game::Game']`. Each declared type emits one
59
- * `mvrOverrides.types['@local/<slug>::<module>::<Name>']` entry whose value
60
- * resolves per-network to `` `${requireId(dep, "@local/<slug>")}::<module>::<Name>` ``
59
+ * `mvrOverrides.types['<mvr>::<module>::<Name>']` entry whose value
60
+ * resolves per-network to `` `${requireId(dep, "<mvr>")}::<module>::<Name>` ``
61
61
  * (the resolved package id, never baked). Absent / empty ⇒ `types: {}` — we
62
62
  * do NOT auto-enumerate every package type (that balloons the config + every
63
63
  * committed `deployments/<net>.ts`). */