@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.
- package/dist/build-integrations/vite/index.mjs +1 -0
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +24 -6
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/dump-deployment.mjs +2 -1
- package/dist/cli/wirings/dump-deployment.mjs.map +1 -1
- package/dist/cli/wirings/identity.mjs +2 -1
- package/dist/cli/wirings/identity.mjs.map +1 -1
- package/dist/cli/wirings/snapshot.mjs +2 -1
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/cli/wirings/up.mjs +25 -29
- package/dist/cli/wirings/up.mjs.map +1 -1
- package/dist/plugins/package/codegen.mjs +4 -4
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +19 -9
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +3 -3
- package/dist/plugins/package/index.mjs +9 -5
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +2 -2
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +4 -4
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +10 -10
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/sui/move/index.mjs +10 -6
- package/dist/plugins/sui/move/index.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +14 -10
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +43 -3
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/service.mjs +9 -7
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +2 -1
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/roster.mjs +5 -2
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs +4 -2
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs +12 -2
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import { suiResource } from "../sui/index.mjs";
|
|
|
13
13
|
import { pickCreatedByType } from "./publish-output.mjs";
|
|
14
14
|
import { discoverCoinsFromPublish } from "../coin/discovery.mjs";
|
|
15
15
|
import "../coin/index.mjs";
|
|
16
|
+
import { normalizeMvrPlaceholder } from "./dep-resolution.mjs";
|
|
16
17
|
import { makeKnownCodegenable, makeKnownStaticCodegen, makeLocalCodegenable, makeLocalStaticCodegen } from "./codegen.mjs";
|
|
17
18
|
import { hashMoveSources } from "./build.mjs";
|
|
18
19
|
import { materializeGitSource } from "./git-source.mjs";
|
|
@@ -63,6 +64,7 @@ const buildLocalPlugin = (name, opts) => {
|
|
|
63
64
|
const capture = normalizeCapture(name, opts.capture);
|
|
64
65
|
if (opts.sourcePath === void 0 && opts.git === void 0) throw new Error(`localPackage('${name}'): provide either 'sourcePath' or 'git'.`);
|
|
65
66
|
if (opts.sourcePath !== void 0 && opts.git !== void 0) throw new Error(`localPackage('${name}'): 'sourcePath' and 'git' are mutually exclusive.`);
|
|
67
|
+
const mvrPlaceholder = normalizeMvrPlaceholder(name, opts.mvrPlaceholder);
|
|
66
68
|
const resolveSourcePath = () => opts.git !== void 0 ? materializeGitSource(opts.git, name) : Effect.succeed(opts.sourcePath);
|
|
67
69
|
return definePlugin({
|
|
68
70
|
id: packageRef.id,
|
|
@@ -96,7 +98,7 @@ const buildLocalPlugin = (name, opts) => {
|
|
|
96
98
|
staticCodegen: makeLocalStaticCodegen({
|
|
97
99
|
name,
|
|
98
100
|
sourcePath: opts.sourcePath ?? null,
|
|
99
|
-
mvrPlaceholder
|
|
101
|
+
mvrPlaceholder,
|
|
100
102
|
excluded: opts.excludeFromCodegen ?? false,
|
|
101
103
|
...opts.mvrTypes !== void 0 ? { mvrTypes: opts.mvrTypes } : {},
|
|
102
104
|
...opts.capture !== void 0 ? { objectKeys: Object.keys(opts.capture) } : {}
|
|
@@ -122,7 +124,7 @@ const buildLocalPlugin = (name, opts) => {
|
|
|
122
124
|
sourcePath,
|
|
123
125
|
chainId: sui.chainId,
|
|
124
126
|
publisherAddress: publisherAccount.address,
|
|
125
|
-
mvrOverride:
|
|
127
|
+
mvrOverride: mvrPlaceholder,
|
|
126
128
|
...capture !== void 0 ? { capture } : {},
|
|
127
129
|
executor
|
|
128
130
|
});
|
|
@@ -139,8 +141,10 @@ const buildLocalPlugin = (name, opts) => {
|
|
|
139
141
|
});
|
|
140
142
|
};
|
|
141
143
|
const buildKnownPlugin = (name, opts) => {
|
|
144
|
+
const packageRef = resource(packageResourceId(name));
|
|
145
|
+
const mvrPlaceholder = normalizeMvrPlaceholder(name, opts.mvrPlaceholder);
|
|
142
146
|
return definePlugin({
|
|
143
|
-
id:
|
|
147
|
+
id: packageRef.id,
|
|
144
148
|
dependsOn: { sui: suiResource },
|
|
145
149
|
role: "task",
|
|
146
150
|
section: "package",
|
|
@@ -155,7 +159,7 @@ const buildKnownPlugin = (name, opts) => {
|
|
|
155
159
|
staticCodegen: makeKnownStaticCodegen({
|
|
156
160
|
name,
|
|
157
161
|
packageId: opts.packageId,
|
|
158
|
-
mvrPlaceholder
|
|
162
|
+
mvrPlaceholder,
|
|
159
163
|
...opts.upgradeCapId !== void 0 ? { upgradeCapId: opts.upgradeCapId } : {},
|
|
160
164
|
...opts.mvrTypes !== void 0 ? { mvrTypes: opts.mvrTypes } : {}
|
|
161
165
|
}),
|
|
@@ -166,7 +170,7 @@ const buildKnownPlugin = (name, opts) => {
|
|
|
166
170
|
packageName: name,
|
|
167
171
|
packageId: opts.packageId,
|
|
168
172
|
upgradeCapId: opts.upgradeCapId,
|
|
169
|
-
mvrOverride:
|
|
173
|
+
mvrOverride: mvrPlaceholder
|
|
170
174
|
});
|
|
171
175
|
emitContributions(ctx, makeKnownCapabilities(name, opts, resolved));
|
|
172
176
|
return resolved;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/package/index.ts"],"sourcesContent":["// Package plugin — barrel + factories.\n//\n// Architecture: Package is the canonical implementation of the\n// `ArtifactPublisher` substrate primitive. Many service\n// plugins depend on its publish output (Coin types, Walrus/Seal/\n// Deepbook contracts, Action outputs).\n//\n// Public surface:\n//\n// - `localPackage(name, opts)` — build + publish a Move source tree.\n// - `knownPackage(name, opts)` — verify-only against a fixed id.\n//\n// Type split (distilled doc Invariant 9): `localPackage` resolves to\n// `LocalPackage`; `knownPackage` resolves to `KnownPackage`. The\n// bindings emitter (in the codegen orchestrator) types-out KnownPackage\n// at compose time, so misuse fails at compile time rather than at\n// emit time.\n//\n// Resource id: `'package:<name>'` — one tag per user-declared package\n// (the symbolic name is part of the identity so two `localPackage`\n// calls in the same stack don't collide on the substrate's tag\n// registry). Substrate-side plugin key is the same string.\n\nimport { Effect } from 'effect';\n\nimport { projection } from '../../api/define-capabilities.ts';\nimport {\n\tcomputedInputIdentity,\n\tdefinePlugin,\n\tresource,\n\tstaticInputIdentity,\n\ttype ResourceRef,\n} from '../../api/define-plugin.ts';\nimport type { Contribution } from '../../substrate/plugin-ctx.ts';\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport type { ProjectionDecl } from '../../contracts/projection.ts';\nimport { pickCreatedByType, type LocalPackagePublishOutput } from './publish-output.ts';\nimport type { SnapshotableDecl } from '../../contracts/snapshotable.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport {\n\tCoinRegistryService,\n\tdiscoverCoinsFromPublish,\n\ttype CoinRecord,\n\ttype CoinRegistry,\n} from '../coin/index.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { chainProbeFor } from '../../substrate/runtime/strategy-registry/index.ts';\nimport { suiResource, type SuiProbeKey } from '../sui/index.ts';\nimport type { AccountResourceId, AccountValue } from '../account/index.ts';\nimport {\n\tmakeKnownCodegenable,\n\tmakeKnownStaticCodegen,\n\tmakeLocalCodegenable,\n\tmakeLocalStaticCodegen,\n} from './codegen.ts';\nimport { hashMoveSources } from './build.ts';\nimport type { PublishError } from './errors.ts';\nimport { materializeGitSource, type GitSource } from './git-source.ts';\nimport { makePublishExecutor } from './publish-executor.ts';\nimport { bootPackageService, type PackageMode } from './service.ts';\nimport {\n\tPACKAGE_REGISTRY_CAPABILITY_KEY,\n\tPackageRegistryService,\n\ttype ResolvedKnownPackage,\n\ttype ResolvedLocalPackage,\n} from './registry.ts';\nimport { makeSnapshotable } from './snapshot.ts';\n\n// ---------------------------------------------------------------------------\n// Publisher account ref — explicit upstream\n// ---------------------------------------------------------------------------\n\n/** A user-supplied publisher account ref. The user passes the result\n * of `account('alice')` — NOT a bare string. Generic over the\n * literal account name so the package dependency preserves the\n * per-account resource id. */\nexport type PublisherAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\n// ---------------------------------------------------------------------------\n// Resource — one per declared package, keyed by symbolic name\n// ---------------------------------------------------------------------------\n\n/** Resource id constructor. The symbolic package name is part of the tag\n * identity so the substrate's compose-time dedup detects collisions\n * cleanly (two `localPackage('foo', ...)` calls in one stack → typed\n * error at compose time). */\nexport const packageResourceId = <Name extends string>(name: Name): `package:${Name}` =>\n\t`package:${name}`;\n\n/** The literal-template resource id for a package by symbolic name. */\nexport type PackageResourceId<Name extends string> = `package:${Name}`;\n\n/** Public resolved value shapes — re-exported from `registry.ts` to\n * give consumers one stable import path. */\nexport type { ResolvedLocalPackage, ResolvedKnownPackage, ResolvedPackage } from './registry.ts';\nexport type {\n\tPickCreatedByTypeOptions,\n\tLocalPackagePublishOutput,\n\tPackagePublishObjectChange,\n} from './publish-output.ts';\nexport { pickCreatedByType } from './publish-output.ts';\nexport type { PublishError } from './errors.ts';\nexport type { PackageBindings } from './codegen.ts';\nexport type { PublishExecutor } from './mode-local.ts';\n\n/** Resolved value carried by the package resource. Local packages also\n * expose the publish output so manifest emitters and capture-spec\n * callers can read it. Sibling plugin folds consume the package-owned\n * extension contribution instead of importing package internals. */\nexport type PackageCaptureMap = Readonly<Record<string, string>>;\n\ntype PackageCaptureCallback = (\n\toutput: LocalPackagePublishOutput,\n) => Readonly<Record<string, string>>;\n\nexport type PackageCapture = PackageCaptureMap;\n\nexport type CapturedPackageValues<Capture> = Capture extends PackageCaptureCallback\n\t? Readonly<Record<string, string>>\n\t: Capture extends PackageCaptureMap\n\t\t? { readonly [K in keyof Capture]: string }\n\t\t: Readonly<Record<string, string>>;\n\nexport interface LocalPackageResolved<Capture = undefined> extends Omit<\n\tResolvedLocalPackage,\n\t'captured'\n> {\n\t/** Captured object ids keyed by the user's `capture` option. */\n\treadonly captured: CapturedPackageValues<Capture>;\n\t/** Publisher account that signed this package. Kept on the live\n\t * resource value so downstream local coin plugins can mint from\n\t * publisher-owned TreasuryCaps through the centralized funding\n\t * strategy path. It is intentionally not written to generated\n\t * bindings or the package registry. */\n\treadonly publisher: AccountValue;\n\t/** Publish output — present after a fresh publish, null on\n\t * cache hit (verify-only path). Consumers that need the\n\t * output MUST tolerate null and fall back to chain reads via\n\t * the ChainProbe. */\n\treadonly publishResult: LocalPackagePublishOutput | null;\n}\n\nexport type KnownPackageResolved = ResolvedKnownPackage;\n\nexport type PackageResolved = LocalPackageResolved | KnownPackageResolved;\n\n// ---------------------------------------------------------------------------\n// Factory options\n// ---------------------------------------------------------------------------\n\nexport interface LocalPackageOptions<\n\tPublisher extends PublisherAccountMember = PublisherAccountMember,\n\tCapture extends PackageCapture | undefined = undefined,\n> {\n\t/** Local on-disk path to the Move package. Provide EXACTLY ONE of\n\t * `sourcePath` or `git`. */\n\treadonly sourcePath?: string;\n\t/** Remote git source — clone a repo and build a sub-path of it instead of\n\t * vendoring the Move tree. Cloned once into a host cache keyed by\n\t * `url + rev` (see `git-source.ts`); pin `rev` to a commit SHA or tag for\n\t * reproducibility. Mutually exclusive with `sourcePath`. */\n\treadonly git?: GitSource;\n\treadonly mvrPlaceholder?: string;\n\treadonly excludeFromCodegen?: boolean;\n\t/** Move datatypes (struct / enum) to expose as MVR `types` overrides.\n\t * OPT-IN: each entry is a `'<module>::<Name>'` relative to THIS package\n\t * (the `@local/<slug>` prefix is implied), e.g. `['game::Lobby',\n\t * 'game::Game']`. Each declared type emits one\n\t * `mvrOverrides.types['@local/<slug>::<module>::<Name>']` entry whose value\n\t * resolves per-network to `` `${requireId(dep, \"@local/<slug>\")}::<module>::<Name>` ``\n\t * (the resolved package id, never baked). Absent / empty ⇒ `types: {}` — we\n\t * do NOT auto-enumerate every package type (that balloons the config + every\n\t * committed `deployments/<net>.ts`). */\n\treadonly mvrTypes?: readonly string[];\n\t/** Capture created objects from the publish output. The record\n\t * form maps output keys to object-type suffixes, e.g.\n\t * `{ boardId: '::board::Board' }`. */\n\treadonly capture?: Capture;\n\t/** Publisher account — the signer for the publish tx. Pass the\n\t * result of `account('alice')` (the same plugin/resource ref used\n\t * in the rest of the stack — NOT a duplicate factory call).\n\t *\n\t * Required for local packages: a publish tx must be signed by\n\t * SOMEONE; we make the choice explicit so two packages in the\n\t * same stack can publish under different accounts (no implicit\n\t * \"first account\" convention to memorise). Distilled doc Invariant\n\t * 4 — \"Signer MUST be an explicit upstream\". */\n\treadonly publisher: Publisher;\n}\n\nexport interface KnownPackageOptions {\n\treadonly packageId: string;\n\treadonly upgradeCapId?: string;\n\treadonly mvrPlaceholder?: string;\n\t/** Move datatypes (struct / enum) to expose as MVR `types` overrides —\n\t * see {@link LocalPackageOptions.mvrTypes}. Each entry is a\n\t * `'<module>::<Name>'` relative to this package; OPT-IN, defaults to none. */\n\treadonly mvrTypes?: readonly string[];\n}\n\ninterface PackageRegistryProjectionContribution {\n\treadonly kind: 'local' | 'known';\n\treadonly name: string;\n\treadonly packageId: string;\n\treadonly upgradeCapId: string | null;\n\treadonly mvrPlaceholder: string;\n\treadonly sourcePath: string | null;\n}\n\nconst makePackageProjectionContribution = (\n\tcontribution: PackageRegistryProjectionContribution,\n): ProjectionDecl => {\n\tconst updatedAt = Date.now();\n\tconst key = `package/${contribution.name}` as `package/${string}`;\n\treturn projection({\n\t\tkind: 'package',\n\t\tkey,\n\t\tpayload: {\n\t\t\tkey,\n\t\t\trowKey: null,\n\t\t\tname: contribution.name,\n\t\t\tkind: contribution.kind,\n\t\t\tpackageId: contribution.packageId,\n\t\t\tupgradeCapId: contribution.upgradeCapId,\n\t\t\tmvrPlaceholder: contribution.mvrPlaceholder,\n\t\t\tsourcePath: contribution.sourcePath,\n\t\t\tupdatedAt,\n\t\t},\n\t\tat: updatedAt,\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// Internal builders\n// ---------------------------------------------------------------------------\n\nconst normalizeCapture = <Capture extends PackageCapture | undefined>(\n\tpackageName: string,\n\tcapture: Capture,\n): PackageCaptureCallback | undefined => {\n\tif (capture === undefined) return undefined;\n\treturn (output) => {\n\t\tconst captured: Record<string, string> = {};\n\t\tfor (const [key, suffix] of Object.entries(capture)) {\n\t\t\tconst objectId = pickCreatedByType(output.objectChanges, { suffix });\n\t\t\tif (objectId === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`localPackage('${packageName}') capture '${key}' matched no created object with suffix '${suffix}'.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcaptured[key] = objectId;\n\t\t}\n\t\treturn captured;\n\t};\n};\n\nconst buildLocalPlugin = <\n\tName extends string,\n\tconst Publisher extends PublisherAccountMember,\n\tCapture extends PackageCapture | undefined,\n>(\n\tname: Name,\n\topts: LocalPackageOptions<Publisher, Capture>,\n) => {\n\tconst packageRef = resource<PackageResourceId<Name>, LocalPackageResolved<Capture>>(\n\t\tpackageResourceId(name),\n\t);\n\tconst capture = normalizeCapture(name, opts.capture);\n\n\t// Exactly one source is required. Validated at config-evaluation time (like\n\t// `normalizeCapture`) so a typo surfaces immediately, not mid-boot.\n\tif (opts.sourcePath === undefined && opts.git === undefined) {\n\t\tthrow new Error(`localPackage('${name}'): provide either 'sourcePath' or 'git'.`);\n\t}\n\tif (opts.sourcePath !== undefined && opts.git !== undefined) {\n\t\tthrow new Error(`localPackage('${name}'): 'sourcePath' and 'git' are mutually exclusive.`);\n\t}\n\n\t// Resolve the on-disk path to build from: a local `sourcePath` is used\n\t// verbatim; a `git` source is cloned into the host cache (idempotent —\n\t// see `materializeGitSource`) and the cached path returned. Called from\n\t// both `inputIdentity` and `start`; the second call is a cache hit.\n\tconst resolveSourcePath = (): Effect.Effect<string, PublishError> =>\n\t\topts.git !== undefined\n\t\t\t? materializeGitSource(opts.git, name)\n\t\t\t: Effect.succeed(opts.sourcePath as string);\n\n\treturn definePlugin({\n\t\tid: packageRef.id,\n\t\tdependsOn: { sui: suiResource, publisher: opts.publisher },\n\t\trole: 'task',\n\t\tsection: 'package',\n\t\tinputIdentity: computedInputIdentity(() =>\n\t\t\tresolveSourcePath().pipe(\n\t\t\t\tEffect.flatMap((sourcePath) =>\n\t\t\t\t\thashMoveSources(sourcePath).pipe(\n\t\t\t\t\t\tEffect.map((sourceHash) => ({\n\t\t\t\t\t\t\tplugin: 'package',\n\t\t\t\t\t\t\tkind: 'local',\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t// The MATERIALIZED path: for a git source this is the\n\t\t\t\t\t\t\t// cache dir, so the hash + identity reflect what is built.\n\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\tsourceHash,\n\t\t\t\t\t\t\tpublisher: opts.publisher.id,\n\t\t\t\t\t\t\tmvrPlaceholder: opts.mvrPlaceholder ?? null,\n\t\t\t\t\t\t\tmvrTypes: opts.mvrTypes ?? null,\n\t\t\t\t\t\t\texcludeFromCodegen: opts.excludeFromCodegen === true,\n\t\t\t\t\t\t\tcapture: opts.capture ?? null,\n\t\t\t\t\t\t\t// url+rev change → new identity → re-clone + rebuild.\n\t\t\t\t\t\t\tgit: opts.git ?? null,\n\t\t\t\t\t\t})),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tEffect.orDie,\n\t\t\t),\n\t\t),\n\t\twatch: {\n\t\t\t// File-watcher contribution — restart on Move source edits.\n\t\t\t// Distilled doc §Outputs: literal-path Packages contribute\n\t\t\t// watch roots. Effect-resolved paths do NOT auto-attach. A git\n\t\t\t// source lives in the read-only host cache, not the dev tree, so\n\t\t\t// it contributes no watch roots (nothing local to edit).\n\t\t\tpaths:\n\t\t\t\topts.sourcePath !== undefined\n\t\t\t\t\t? [\n\t\t\t\t\t\t\t`${opts.sourcePath}/**/*.move`,\n\t\t\t\t\t\t\t`${opts.sourcePath}/Move.toml`,\n\t\t\t\t\t\t\t`${opts.sourcePath}/Move.lock`,\n\t\t\t\t\t\t]\n\t\t\t\t\t: [],\n\t\t\tcascade: true,\n\t\t},\n\t\t// Stack-free codegen: the `codegen` verb derives this package's\n\t\t// committed-projection decls from config alone, drawing the\n\t\t// `packageId` from the projection resolver (sentinel / pinned). The\n\t\t// bindings emitter compiles a local `sourcePath` verbatim; a git\n\t\t// source's tree is only materialized at acquire, so it contributes no\n\t\t// stub bindings until the source is on disk (the live `.devstack`\n\t\t// overlay carries them when a stack is running).\n\t\tstaticCodegen: makeLocalStaticCodegen({\n\t\t\tname,\n\t\t\tsourcePath: opts.sourcePath ?? null,\n\t\t\tmvrPlaceholder: opts.mvrPlaceholder,\n\t\t\texcluded: opts.excludeFromCodegen ?? false,\n\t\t\t...(opts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {}),\n\t\t\t// Capture KEYS (config-known) so the committed stub carries\n\t\t\t// `requireValue(dep, 'package:<name>:objects', '<key>')` references —\n\t\t\t// no live-only `objects` field, no baked object id.\n\t\t\t...(opts.capture !== undefined ? { objectKeys: Object.keys(opts.capture) } : {}),\n\t\t}),\n\t\t// `deps` auto-infers the resolved `{ sui, publisher }` dependency\n\t\t// object; `ctx` arrives via the `PluginContext` service.\n\t\tstart: ({ sui, publisher: publisherAccount }) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Resolve the build path first: clone a git source into the host\n\t\t\t\t// cache (cache hit after `inputIdentity` already materialized it),\n\t\t\t\t// or use the local `sourcePath` verbatim. Everything below — and\n\t\t\t\t// the contributions derived from `resolved.sourcePath` — then sees\n\t\t\t\t// a plain local directory.\n\t\t\t\tconst sourcePath = yield* resolveSourcePath();\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\t// Substrate-context primitives: ArtifactPublisher\n\t\t\t\t// is provided by the supervisor's pluginContext;\n\t\t\t\t// ChainProbe is looked up via the StrategyRegistry\n\t\t\t\t// (Sui registered itself there at acquire). The\n\t\t\t\t// PackageRegistry is a per-stack plugin-owned service\n\t\t\t\t// (a self-contained last-write-wins map — see\n\t\t\t\t// `registry.ts`) — every\n\t\t\t\t// package plugin in the stack yields the SAME instance\n\t\t\t\t// via `PackageRegistryService`, so cross-plugin lookups\n\t\t\t\t// stay consistent and warm-restart verify can use the\n\t\t\t\t// previous packageId as a hint.\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\t\t\t\tconst registry = yield* PackageRegistryService;\n\t\t\t\t// The per-stack CoinRegistry — same instance every plugin in\n\t\t\t\t// the stack yields via `CoinRegistryService` (the boot wiring\n\t\t\t\t// adds it to the plugin context via\n\t\t\t\t// `extendBuiltInPluginContext`). Local publish folds its\n\t\t\t\t// discovered coins into it directly (was the orchestrator's\n\t\t\t\t// `publishResultSink`).\n\t\t\t\tconst coinRegistry = yield* CoinRegistryService;\n\t\t\t\t// ContainerRuntime + the Sui plugin's resolved image feed\n\t\t\t\t// `runMoveBuild`'s path-(b) (`docker run --rm`) build path.\n\t\t\t\t// Sui surfaces `buildImage` on its resolved client; modes\n\t\t\t\t// without an in-stack image (external, live) surface null\n\t\t\t\t// — runMoveBuild then surfaces a typed error from path (c)\n\t\t\t\t// (host CLI not routed).\n\t\t\t\tconst containerRuntime = yield* ContainerRuntimeService;\n\n\t\t\t\t// Build the concrete `PublishExecutor` once per acquire.\n\t\t\t\t// Hands the resolved SuiSdkShim (for `Transaction.build`,\n\t\t\t\t// `executeTransaction`, `waitForTransaction`) and the\n\t\t\t\t// publisher account (for `signAndExecute`) to the executor\n\t\t\t\t// methods; mode-local's produce body drives them.\n\t\t\t\tconst executor = makePublishExecutor({\n\t\t\t\t\tsdk: sui.sdk,\n\t\t\t\t\taccount: publisherAccount,\n\t\t\t\t\truntime: containerRuntime,\n\t\t\t\t\tforkMode: sui.fork !== null,\n\t\t\t\t\t...(sui.buildImage !== null ? { buildImage: sui.buildImage } : {}),\n\t\t\t\t});\n\n\t\t\t\tconst mode = {\n\t\t\t\t\tmode: 'local',\n\t\t\t\t\tpackageName: name,\n\t\t\t\t\tsourcePath,\n\t\t\t\t\tchainId: sui.chainId,\n\t\t\t\t\tpublisherAddress: publisherAccount.address,\n\t\t\t\t\tmvrOverride: opts.mvrPlaceholder,\n\t\t\t\t\t...(capture !== undefined ? { capture } : {}),\n\t\t\t\t\texecutor,\n\t\t\t\t} satisfies PackageMode;\n\n\t\t\t\tconst { resolved, output } = yield* bootPackageService(publisher, probe, registry, mode);\n\n\t\t\t\tconst projected: LocalPackageResolved<Capture> = {\n\t\t\t\t\t...resolved,\n\t\t\t\t\tcaptured: resolved.captured as CapturedPackageValues<Capture>,\n\t\t\t\t\tpublisher: publisherAccount,\n\t\t\t\t\tpublishResult: output,\n\t\t\t\t};\n\t\t\t\t// Emit the resolved package's contributions inline via the\n\t\t\t\t// shared `emitContributions` router. `projected` is the\n\t\t\t\t// just-resolved local value; `makeLocalCapabilities` builds the\n\t\t\t\t// ordered decl list (snapshot, codegen, registry, projection).\n\t\t\t\temitContributions(ctx, makeLocalCapabilities(name, opts, projected));\n\t\t\t\t// Part 2 (custom-kind re-home): on a fresh publish, fold the\n\t\t\t\t// output's coins into the per-stack CoinRegistry DIRECTLY (was\n\t\t\t\t// the orchestrator's `publishResultSink` consuming the now-\n\t\t\t\t// dead `LOCAL_PACKAGE_PUBLISHED` decl). `output` is null on a\n\t\t\t\t// cache hit (verify path), so discovery is skipped then — the\n\t\t\t\t// registry was already populated on the fresh-publish run that\n\t\t\t\t// seeded the cache.\n\t\t\t\tif (output !== null) {\n\t\t\t\t\tyield* discoverPublishedCoins(coinRegistry, name, projected.packageId, output);\n\t\t\t\t}\n\t\t\t\treturn projected;\n\t\t\t}),\n\t});\n};\n\nconst buildKnownPlugin = <Name extends string>(name: Name, opts: KnownPackageOptions) => {\n\tconst packageRef = resource<PackageResourceId<Name>, KnownPackageResolved>(\n\t\tpackageResourceId(name),\n\t);\n\treturn definePlugin({\n\t\tid: packageRef.id,\n\t\tdependsOn: { sui: suiResource },\n\t\trole: 'task',\n\t\tsection: 'package',\n\t\tinputIdentity: staticInputIdentity({\n\t\t\tplugin: 'package',\n\t\t\tkind: 'known',\n\t\t\tname,\n\t\t\tpackageId: opts.packageId,\n\t\t\tupgradeCapId: opts.upgradeCapId ?? null,\n\t\t\tmvrPlaceholder: opts.mvrPlaceholder ?? null,\n\t\t}),\n\t\t// Stack-free codegen: a known package's `packageId` is already a\n\t\t// config literal, so the committed stub emits it verbatim (no chain\n\t\t// contact, no sentinel substitution).\n\t\tstaticCodegen: makeKnownStaticCodegen({\n\t\t\tname,\n\t\t\tpackageId: opts.packageId,\n\t\t\tmvrPlaceholder: opts.mvrPlaceholder,\n\t\t\t...(opts.upgradeCapId !== undefined ? { upgradeCapId: opts.upgradeCapId } : {}),\n\t\t\t...(opts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {}),\n\t\t}),\n\t\tstart: ({ sui }) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\t\t\t\tconst registry = yield* PackageRegistryService;\n\t\t\t\tconst mode = {\n\t\t\t\t\tmode: 'known',\n\t\t\t\t\tpackageName: name,\n\t\t\t\t\tpackageId: opts.packageId,\n\t\t\t\t\tupgradeCapId: opts.upgradeCapId,\n\t\t\t\t\tmvrOverride: opts.mvrPlaceholder,\n\t\t\t\t} satisfies PackageMode;\n\t\t\t\tconst { resolved } = yield* bootPackageService(publisher, probe, registry, mode);\n\t\t\t\t// Emit the resolved package's contributions inline via the\n\t\t\t\t// shared `emitContributions` router. `makeKnownCapabilities`\n\t\t\t\t// builds the ordered decl list (snapshot, codegen, registry,\n\t\t\t\t// projection).\n\t\t\t\temitContributions(ctx, makeKnownCapabilities(name, opts, resolved));\n\t\t\t\t// Known mode never publishes — no output to walk, so\n\t\t\t\t// the coin-discovery hook is skipped here. Users who\n\t\t\t\t// want coin records for a knownPackage point a\n\t\t\t\t// `coin.known('0xPKG::module::Witness')` at the\n\t\t\t\t// fully-qualified type directly; the bare-type path\n\t\t\t\t// hits the live RPC for metadata rather than the\n\t\t\t\t// output-walker.\n\t\t\t\treturn resolved;\n\t\t\t}),\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// Capability builders — pure helpers returning the ordered decl list.\n//\n// The package `start` bodies feed these into the shared `emitContributions`\n// router after resolving the value. Decl shapes + emit ORDER are load-bearing.\n//\n// NOTE: the LOCAL builder does NOT append a custom published-coin decl.\n// Coin discovery runs DIRECTLY in the local `start` body (see\n// `discoverPublishedCoins`); appending a decl here would double-discover.\n// ---------------------------------------------------------------------------\n\nconst makeLocalCapabilities = (\n\tname: string,\n\topts: { readonly excludeFromCodegen?: boolean; readonly mvrTypes?: readonly string[] },\n\tresolved: LocalPackageResolved,\n): ReadonlyArray<Contribution> => {\n\t// Snapshot + codegen lift their typed fields off the resolved\n\t// publish (real packageId + captured object ids). The static-form\n\t// placeholders are gone.\n\tconst snap: SnapshotableDecl = makeSnapshotable(\n\t\tname,\n\t\tresolved.publishResult?.packageId ?? resolved.packageId,\n\t);\n\tconst codegen: CodegenableDecl<'package'> = makeLocalCodegenable(\n\t\t{\n\t\t\tkind: 'local',\n\t\t\tname,\n\t\t\tpackageId: resolved.packageId,\n\t\t\tupgradeCapId: resolved.upgradeCapId,\n\t\t\tsourcePath: resolved.sourcePath,\n\t\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\t\tcaptured: resolved.captured,\n\t\t},\n\t\t{\n\t\t\texcluded: opts.excludeFromCodegen ?? false,\n\t\t\t...(opts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {}),\n\t\t},\n\t);\n\t// The plugin contributes to the package-registry strategy under a\n\t// fixed key — the substrate orchestrator wires all packages'\n\t// contributions into the same per-stack registry.\n\tconst projection: PackageRegistryProjectionContribution = {\n\t\tkind: 'local',\n\t\tname,\n\t\tpackageId: resolved.packageId,\n\t\tupgradeCapId: resolved.upgradeCapId ?? null,\n\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\tsourcePath: resolved.sourcePath,\n\t};\n\tconst registryContribution: StrategyContributorDecl<\n\t\ttypeof PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tPackageRegistryProjectionContribution\n\t> = {\n\t\tkind: 'strategy-contributor',\n\t\tcapabilityKey: PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tstrategy: projection,\n\t\tautoMounted: true,\n\t};\n\treturn [snap, codegen, registryContribution, makePackageProjectionContribution(projection)];\n};\n\nconst makeKnownCapabilities = (\n\tname: string,\n\topts: KnownPackageOptions,\n\tresolved: KnownPackageResolved,\n): ReadonlyArray<Contribution> => {\n\tconst snap: SnapshotableDecl = makeSnapshotable(name, `known:${resolved.packageId}`);\n\tconst codegen: CodegenableDecl<'package'> = makeKnownCodegenable(\n\t\t{\n\t\t\tkind: 'known',\n\t\t\tname,\n\t\t\tpackageId: resolved.packageId,\n\t\t\tupgradeCapId: resolved.upgradeCapId ?? opts.upgradeCapId,\n\t\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\t},\n\t\topts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {},\n\t);\n\tconst projection: PackageRegistryProjectionContribution = {\n\t\tkind: 'known',\n\t\tname,\n\t\tpackageId: resolved.packageId,\n\t\tupgradeCapId: resolved.upgradeCapId ?? opts.upgradeCapId ?? null,\n\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\tsourcePath: null,\n\t};\n\tconst registryContribution: StrategyContributorDecl<\n\t\ttypeof PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tPackageRegistryProjectionContribution\n\t> = {\n\t\tkind: 'strategy-contributor',\n\t\tcapabilityKey: PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tstrategy: projection,\n\t\tautoMounted: true,\n\t};\n\treturn [snap, codegen, registryContribution, makePackageProjectionContribution(projection)];\n};\n\n/** Fold a fresh local-package publish output into the per-stack\n * `CoinRegistry`. Lifted VERBATIM from the orchestrator's former\n * `publishResultSink` (orchestrators/boot.ts): the same\n * `discoverCoinsFromPublish` walk + the same `CoinRecord` projection\n * (including the publisher-owns-cap gate on `treasuryCapId`).\n *\n * Re-homing this into the local `start` body — AFTER the publish output\n * is known, BEFORE `start` returns — preserves discovery timing: the\n * coin plugin `dependsOn` package, so it acquires only after package\n * readies, by which point the registry is already populated. The\n * previous decl-driven sink ran at the same point in the lifecycle\n * (post-`start` dispatch), so consumers see no ordering change. */\nexport const discoverPublishedCoins = (\n\tcoinRegistry: CoinRegistry,\n\tpackageName: string,\n\tpackageId: string,\n\toutput: LocalPackagePublishOutput,\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tfor (const discovered of discoverCoinsFromPublish(output)) {\n\t\t\tconst record: CoinRecord = {\n\t\t\t\tkey: (discovered.symbol ?? discovered.witness).toLowerCase(),\n\t\t\t\ttype: discovered.fullCoinType,\n\t\t\t\twitness: discovered.witness,\n\t\t\t\tmoduleName: discovered.moduleName,\n\t\t\t\tdecimals: discovered.decimals ?? 0,\n\t\t\t\t...(discovered.symbol === undefined ? {} : { symbol: discovered.symbol }),\n\t\t\t\t...(discovered.displayName === undefined ? {} : { displayName: discovered.displayName }),\n\t\t\t\t...(discovered.iconUrl === undefined ? {} : { iconUrl: discovered.iconUrl }),\n\t\t\t\t...(!discovered.publisherOwnsCap || discovered.treasuryCapId === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: { treasuryCapId: discovered.treasuryCapId }),\n\t\t\t\t...(discovered.metadataId === undefined ? {} : { metadataId: discovered.metadataId }),\n\t\t\t\tpackageId,\n\t\t\t\tpublishingPackageName: packageName,\n\t\t\t};\n\t\t\tyield* coinRegistry.register(record);\n\t\t}\n\t});\n\n// ---------------------------------------------------------------------------\n// Public factories\n// ---------------------------------------------------------------------------\n\n/** Build + publish a local Move package. The resolved value is\n * `LocalPackageResolved` so consumers that need bindings / source\n * path are typed correctly.\n *\n * Required `opts.publisher`: the account that signs the publish tx.\n * Pass the same `account('alice')` reference used elsewhere in the\n * stack — the package.s `dependsOn` includes `account/<publisher>`\n * so the substrate orders the publisher's keypair + funding strictly\n * before publish. */\nexport const localPackage = <\n\tName extends string,\n\tconst Publisher extends PublisherAccountMember,\n\tconst Capture extends PackageCapture | undefined = undefined,\n>(\n\tname: Name,\n\topts: LocalPackageOptions<Publisher, Capture>,\n) => buildLocalPlugin(name, opts);\n\n/** Verify-only against a fixed on-chain package id. The resolved\n * value is `KnownPackageResolved` — narrower than local, so the\n * bindings emitter rejects this at compose time. */\nexport const knownPackage = <Name extends string>(name: Name, opts: KnownPackageOptions) =>\n\tbuildKnownPlugin(name, opts);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/package/index.ts"],"sourcesContent":["// Package plugin — barrel + factories.\n//\n// Architecture: Package is the canonical implementation of the\n// `ArtifactPublisher` substrate primitive. Many service\n// plugins depend on its publish output (Coin types, Walrus/Seal/\n// Deepbook contracts, Action outputs).\n//\n// Public surface:\n//\n// - `localPackage(name, opts)` — build + publish a Move source tree.\n// - `knownPackage(name, opts)` — verify-only against a fixed id.\n//\n// Type split (distilled doc Invariant 9): `localPackage` resolves to\n// `LocalPackage`; `knownPackage` resolves to `KnownPackage`. The\n// bindings emitter (in the codegen orchestrator) types-out KnownPackage\n// at compose time, so misuse fails at compile time rather than at\n// emit time.\n//\n// Resource id: `'package:<name>'` — one tag per user-declared package\n// (the symbolic name is part of the identity so two `localPackage`\n// calls in the same stack don't collide on the substrate's tag\n// registry). Substrate-side plugin key is the same string.\n\nimport { Effect } from 'effect';\n\nimport { projection } from '../../api/define-capabilities.ts';\nimport {\n\tcomputedInputIdentity,\n\tdefinePlugin,\n\tresource,\n\tstaticInputIdentity,\n\ttype ResourceRef,\n} from '../../api/define-plugin.ts';\nimport type { Contribution } from '../../substrate/plugin-ctx.ts';\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport type { ProjectionDecl } from '../../contracts/projection.ts';\nimport { pickCreatedByType, type LocalPackagePublishOutput } from './publish-output.ts';\nimport type { SnapshotableDecl } from '../../contracts/snapshotable.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport {\n\tCoinRegistryService,\n\tdiscoverCoinsFromPublish,\n\ttype CoinRecord,\n\ttype CoinRegistry,\n} from '../coin/index.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { chainProbeFor } from '../../substrate/runtime/strategy-registry/index.ts';\nimport { suiResource, type SuiProbeKey } from '../sui/index.ts';\nimport type { AccountResourceId, AccountValue } from '../account/index.ts';\nimport {\n\tmakeKnownCodegenable,\n\tmakeKnownStaticCodegen,\n\tmakeLocalCodegenable,\n\tmakeLocalStaticCodegen,\n} from './codegen.ts';\nimport { hashMoveSources } from './build.ts';\nimport type { PublishError } from './errors.ts';\nimport { materializeGitSource, type GitSource } from './git-source.ts';\nimport { makePublishExecutor } from './publish-executor.ts';\nimport { bootPackageService, type PackageMode } from './service.ts';\nimport {\n\tPACKAGE_REGISTRY_CAPABILITY_KEY,\n\tPackageRegistryService,\n\ttype ResolvedKnownPackage,\n\ttype ResolvedLocalPackage,\n} from './registry.ts';\nimport { makeSnapshotable } from './snapshot.ts';\nimport { normalizeMvrPlaceholder } from './dep-resolution.ts';\n\n// ---------------------------------------------------------------------------\n// Publisher account ref — explicit upstream\n// ---------------------------------------------------------------------------\n\n/** A user-supplied publisher account ref. The user passes the result\n * of `account('alice')` — NOT a bare string. Generic over the\n * literal account name so the package dependency preserves the\n * per-account resource id. */\nexport type PublisherAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\n// ---------------------------------------------------------------------------\n// Resource — one per declared package, keyed by symbolic name\n// ---------------------------------------------------------------------------\n\n/** Resource id constructor. The symbolic package name is part of the tag\n * identity so the substrate's compose-time dedup detects collisions\n * cleanly (two `localPackage('foo', ...)` calls in one stack → typed\n * error at compose time). */\nexport const packageResourceId = <Name extends string>(name: Name): `package:${Name}` =>\n\t`package:${name}`;\n\n/** The literal-template resource id for a package by symbolic name. */\nexport type PackageResourceId<Name extends string> = `package:${Name}`;\n\n/** Public resolved value shapes — re-exported from `registry.ts` to\n * give consumers one stable import path. */\nexport type { ResolvedLocalPackage, ResolvedKnownPackage, ResolvedPackage } from './registry.ts';\nexport type {\n\tPickCreatedByTypeOptions,\n\tLocalPackagePublishOutput,\n\tPackagePublishObjectChange,\n} from './publish-output.ts';\nexport { pickCreatedByType } from './publish-output.ts';\nexport type { PublishError } from './errors.ts';\nexport type { PackageBindings } from './codegen.ts';\nexport type { PublishExecutor } from './mode-local.ts';\n\n/** Resolved value carried by the package resource. Local packages also\n * expose the publish output so manifest emitters and capture-spec\n * callers can read it. Sibling plugin folds consume the package-owned\n * extension contribution instead of importing package internals. */\nexport type PackageCaptureMap = Readonly<Record<string, string>>;\n\ntype PackageCaptureCallback = (\n\toutput: LocalPackagePublishOutput,\n) => Readonly<Record<string, string>>;\n\nexport type PackageCapture = PackageCaptureMap;\n\nexport type CapturedPackageValues<Capture> = Capture extends PackageCaptureCallback\n\t? Readonly<Record<string, string>>\n\t: Capture extends PackageCaptureMap\n\t\t? { readonly [K in keyof Capture]: string }\n\t\t: Readonly<Record<string, string>>;\n\nexport interface LocalPackageResolved<Capture = undefined> extends Omit<\n\tResolvedLocalPackage,\n\t'captured'\n> {\n\t/** Captured object ids keyed by the user's `capture` option. */\n\treadonly captured: CapturedPackageValues<Capture>;\n\t/** Publisher account that signed this package. Kept on the live\n\t * resource value so downstream local coin plugins can mint from\n\t * publisher-owned TreasuryCaps through the centralized funding\n\t * strategy path. It is intentionally not written to generated\n\t * bindings or the package registry. */\n\treadonly publisher: AccountValue;\n\t/** Publish output — present after a fresh publish, null on\n\t * cache hit (verify-only path). Consumers that need the\n\t * output MUST tolerate null and fall back to chain reads via\n\t * the ChainProbe. */\n\treadonly publishResult: LocalPackagePublishOutput | null;\n}\n\nexport type KnownPackageResolved = ResolvedKnownPackage;\n\nexport type PackageResolved = LocalPackageResolved | KnownPackageResolved;\n\n// ---------------------------------------------------------------------------\n// Factory options\n// ---------------------------------------------------------------------------\n\nexport interface LocalPackageOptions<\n\tPublisher extends PublisherAccountMember = PublisherAccountMember,\n\tCapture extends PackageCapture | undefined = undefined,\n> {\n\t/** Local on-disk path to the Move package. Provide EXACTLY ONE of\n\t * `sourcePath` or `git`. */\n\treadonly sourcePath?: string;\n\t/** Remote git source — clone a repo and build a sub-path of it instead of\n\t * vendoring the Move tree. Cloned once into a host cache keyed by\n\t * `url + rev` (see `git-source.ts`); pin `rev` to a commit SHA or tag for\n\t * reproducibility. Mutually exclusive with `sourcePath`. */\n\treadonly git?: GitSource;\n\treadonly mvrPlaceholder?: string;\n\treadonly excludeFromCodegen?: boolean;\n\t/** Move datatypes (struct / enum) to expose as MVR `types` overrides.\n\t * OPT-IN: each entry is a `'<module>::<Name>'` relative to THIS package\n\t * (the package MVR prefix is implied), e.g. `['game::Lobby',\n\t * 'game::Game']`. Each declared type emits one\n\t * `mvrOverrides.types['<mvr>::<module>::<Name>']` entry whose value\n\t * resolves per-network to `` `${requireId(dep, \"<mvr>\")}::<module>::<Name>` ``\n\t * (the resolved package id, never baked). Absent / empty ⇒ `types: {}` — we\n\t * do NOT auto-enumerate every package type (that balloons the config + every\n\t * committed `deployments/<net>.ts`). */\n\treadonly mvrTypes?: readonly string[];\n\t/** Capture created objects from the publish output. The record\n\t * form maps output keys to object-type suffixes, e.g.\n\t * `{ boardId: '::board::Board' }`. */\n\treadonly capture?: Capture;\n\t/** Publisher account — the signer for the publish tx. Pass the\n\t * result of `account('alice')` (the same plugin/resource ref used\n\t * in the rest of the stack — NOT a duplicate factory call).\n\t *\n\t * Required for local packages: a publish tx must be signed by\n\t * SOMEONE; we make the choice explicit so two packages in the\n\t * same stack can publish under different accounts (no implicit\n\t * \"first account\" convention to memorise). Distilled doc Invariant\n\t * 4 — \"Signer MUST be an explicit upstream\". */\n\treadonly publisher: Publisher;\n}\n\nexport interface KnownPackageOptions {\n\treadonly packageId: string;\n\treadonly upgradeCapId?: string;\n\treadonly mvrPlaceholder?: string;\n\t/** Move datatypes (struct / enum) to expose as MVR `types` overrides —\n\t * see {@link LocalPackageOptions.mvrTypes}. Each entry is a\n\t * `'<module>::<Name>'` relative to this package; OPT-IN, defaults to none. */\n\treadonly mvrTypes?: readonly string[];\n}\n\ninterface PackageRegistryProjectionContribution {\n\treadonly kind: 'local' | 'known';\n\treadonly name: string;\n\treadonly packageId: string;\n\treadonly upgradeCapId: string | null;\n\treadonly mvrPlaceholder: string;\n\treadonly sourcePath: string | null;\n}\n\nconst makePackageProjectionContribution = (\n\tcontribution: PackageRegistryProjectionContribution,\n): ProjectionDecl => {\n\tconst updatedAt = Date.now();\n\tconst key = `package/${contribution.name}` as `package/${string}`;\n\treturn projection({\n\t\tkind: 'package',\n\t\tkey,\n\t\tpayload: {\n\t\t\tkey,\n\t\t\trowKey: null,\n\t\t\tname: contribution.name,\n\t\t\tkind: contribution.kind,\n\t\t\tpackageId: contribution.packageId,\n\t\t\tupgradeCapId: contribution.upgradeCapId,\n\t\t\tmvrPlaceholder: contribution.mvrPlaceholder,\n\t\t\tsourcePath: contribution.sourcePath,\n\t\t\tupdatedAt,\n\t\t},\n\t\tat: updatedAt,\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// Internal builders\n// ---------------------------------------------------------------------------\n\nconst normalizeCapture = <Capture extends PackageCapture | undefined>(\n\tpackageName: string,\n\tcapture: Capture,\n): PackageCaptureCallback | undefined => {\n\tif (capture === undefined) return undefined;\n\treturn (output) => {\n\t\tconst captured: Record<string, string> = {};\n\t\tfor (const [key, suffix] of Object.entries(capture)) {\n\t\t\tconst objectId = pickCreatedByType(output.objectChanges, { suffix });\n\t\t\tif (objectId === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`localPackage('${packageName}') capture '${key}' matched no created object with suffix '${suffix}'.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcaptured[key] = objectId;\n\t\t}\n\t\treturn captured;\n\t};\n};\n\nconst buildLocalPlugin = <\n\tName extends string,\n\tconst Publisher extends PublisherAccountMember,\n\tCapture extends PackageCapture | undefined,\n>(\n\tname: Name,\n\topts: LocalPackageOptions<Publisher, Capture>,\n) => {\n\tconst packageRef = resource<PackageResourceId<Name>, LocalPackageResolved<Capture>>(\n\t\tpackageResourceId(name),\n\t);\n\tconst capture = normalizeCapture(name, opts.capture);\n\n\t// Exactly one source is required. Validated at config-evaluation time (like\n\t// `normalizeCapture`) so a typo surfaces immediately, not mid-boot.\n\tif (opts.sourcePath === undefined && opts.git === undefined) {\n\t\tthrow new Error(`localPackage('${name}'): provide either 'sourcePath' or 'git'.`);\n\t}\n\tif (opts.sourcePath !== undefined && opts.git !== undefined) {\n\t\tthrow new Error(`localPackage('${name}'): 'sourcePath' and 'git' are mutually exclusive.`);\n\t}\n\tconst mvrPlaceholder = normalizeMvrPlaceholder(name, opts.mvrPlaceholder);\n\n\t// Resolve the on-disk path to build from: a local `sourcePath` is used\n\t// verbatim; a `git` source is cloned into the host cache (idempotent —\n\t// see `materializeGitSource`) and the cached path returned. Called from\n\t// both `inputIdentity` and `start`; the second call is a cache hit.\n\tconst resolveSourcePath = (): Effect.Effect<string, PublishError> =>\n\t\topts.git !== undefined\n\t\t\t? materializeGitSource(opts.git, name)\n\t\t\t: Effect.succeed(opts.sourcePath as string);\n\n\treturn definePlugin({\n\t\tid: packageRef.id,\n\t\tdependsOn: { sui: suiResource, publisher: opts.publisher },\n\t\trole: 'task',\n\t\tsection: 'package',\n\t\tinputIdentity: computedInputIdentity(() =>\n\t\t\tresolveSourcePath().pipe(\n\t\t\t\tEffect.flatMap((sourcePath) =>\n\t\t\t\t\thashMoveSources(sourcePath).pipe(\n\t\t\t\t\t\tEffect.map((sourceHash) => ({\n\t\t\t\t\t\t\tplugin: 'package',\n\t\t\t\t\t\t\tkind: 'local',\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t// The MATERIALIZED path: for a git source this is the\n\t\t\t\t\t\t\t// cache dir, so the hash + identity reflect what is built.\n\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\tsourceHash,\n\t\t\t\t\t\t\tpublisher: opts.publisher.id,\n\t\t\t\t\t\t\tmvrPlaceholder: opts.mvrPlaceholder ?? null,\n\t\t\t\t\t\t\tmvrTypes: opts.mvrTypes ?? null,\n\t\t\t\t\t\t\texcludeFromCodegen: opts.excludeFromCodegen === true,\n\t\t\t\t\t\t\tcapture: opts.capture ?? null,\n\t\t\t\t\t\t\t// url+rev change → new identity → re-clone + rebuild.\n\t\t\t\t\t\t\tgit: opts.git ?? null,\n\t\t\t\t\t\t})),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tEffect.orDie,\n\t\t\t),\n\t\t),\n\t\twatch: {\n\t\t\t// File-watcher contribution — restart on Move source edits.\n\t\t\t// Distilled doc §Outputs: literal-path Packages contribute\n\t\t\t// watch roots. Effect-resolved paths do NOT auto-attach. A git\n\t\t\t// source lives in the read-only host cache, not the dev tree, so\n\t\t\t// it contributes no watch roots (nothing local to edit).\n\t\t\tpaths:\n\t\t\t\topts.sourcePath !== undefined\n\t\t\t\t\t? [\n\t\t\t\t\t\t\t`${opts.sourcePath}/**/*.move`,\n\t\t\t\t\t\t\t`${opts.sourcePath}/Move.toml`,\n\t\t\t\t\t\t\t`${opts.sourcePath}/Move.lock`,\n\t\t\t\t\t\t]\n\t\t\t\t\t: [],\n\t\t\tcascade: true,\n\t\t},\n\t\t// Stack-free codegen: the `codegen` verb derives this package's\n\t\t// committed-projection decls from config alone, drawing the\n\t\t// `packageId` from the projection resolver (sentinel / pinned). The\n\t\t// bindings emitter compiles a local `sourcePath` verbatim; a git\n\t\t// source's tree is only materialized at acquire, so it contributes no\n\t\t// stub bindings until the source is on disk (the live `.devstack`\n\t\t// overlay carries them when a stack is running).\n\t\tstaticCodegen: makeLocalStaticCodegen({\n\t\t\tname,\n\t\t\tsourcePath: opts.sourcePath ?? null,\n\t\t\tmvrPlaceholder,\n\t\t\texcluded: opts.excludeFromCodegen ?? false,\n\t\t\t...(opts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {}),\n\t\t\t// Capture KEYS (config-known) so the committed stub carries\n\t\t\t// `requireValue(dep, 'package:<name>:objects', '<key>')` references —\n\t\t\t// no live-only `objects` field, no baked object id.\n\t\t\t...(opts.capture !== undefined ? { objectKeys: Object.keys(opts.capture) } : {}),\n\t\t}),\n\t\t// `deps` auto-infers the resolved `{ sui, publisher }` dependency\n\t\t// object; `ctx` arrives via the `PluginContext` service.\n\t\tstart: ({ sui, publisher: publisherAccount }) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Resolve the build path first: clone a git source into the host\n\t\t\t\t// cache (cache hit after `inputIdentity` already materialized it),\n\t\t\t\t// or use the local `sourcePath` verbatim. Everything below — and\n\t\t\t\t// the contributions derived from `resolved.sourcePath` — then sees\n\t\t\t\t// a plain local directory.\n\t\t\t\tconst sourcePath = yield* resolveSourcePath();\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\t// Substrate-context primitives: ArtifactPublisher\n\t\t\t\t// is provided by the supervisor's pluginContext;\n\t\t\t\t// ChainProbe is looked up via the StrategyRegistry\n\t\t\t\t// (Sui registered itself there at acquire). The\n\t\t\t\t// PackageRegistry is a per-stack plugin-owned service\n\t\t\t\t// (a self-contained last-write-wins map — see\n\t\t\t\t// `registry.ts`) — every\n\t\t\t\t// package plugin in the stack yields the SAME instance\n\t\t\t\t// via `PackageRegistryService`, so cross-plugin lookups\n\t\t\t\t// stay consistent and warm-restart verify can use the\n\t\t\t\t// previous packageId as a hint.\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\t\t\t\tconst registry = yield* PackageRegistryService;\n\t\t\t\t// The per-stack CoinRegistry — same instance every plugin in\n\t\t\t\t// the stack yields via `CoinRegistryService` (the boot wiring\n\t\t\t\t// adds it to the plugin context via\n\t\t\t\t// `extendBuiltInPluginContext`). Local publish folds its\n\t\t\t\t// discovered coins into it directly (was the orchestrator's\n\t\t\t\t// `publishResultSink`).\n\t\t\t\tconst coinRegistry = yield* CoinRegistryService;\n\t\t\t\t// ContainerRuntime + the Sui plugin's resolved image feed\n\t\t\t\t// `runMoveBuild`'s path-(b) (`docker run --rm`) build path.\n\t\t\t\t// Sui surfaces `buildImage` on its resolved client; modes\n\t\t\t\t// without an in-stack image (external, live) surface null\n\t\t\t\t// — runMoveBuild then surfaces a typed error from path (c)\n\t\t\t\t// (host CLI not routed).\n\t\t\t\tconst containerRuntime = yield* ContainerRuntimeService;\n\n\t\t\t\t// Build the concrete `PublishExecutor` once per acquire.\n\t\t\t\t// Hands the resolved SuiSdkShim (for `Transaction.build`,\n\t\t\t\t// `executeTransaction`, `waitForTransaction`) and the\n\t\t\t\t// publisher account (for `signAndExecute`) to the executor\n\t\t\t\t// methods; mode-local's produce body drives them.\n\t\t\t\tconst executor = makePublishExecutor({\n\t\t\t\t\tsdk: sui.sdk,\n\t\t\t\t\taccount: publisherAccount,\n\t\t\t\t\truntime: containerRuntime,\n\t\t\t\t\tforkMode: sui.fork !== null,\n\t\t\t\t\t...(sui.buildImage !== null ? { buildImage: sui.buildImage } : {}),\n\t\t\t\t});\n\n\t\t\t\tconst mode = {\n\t\t\t\t\tmode: 'local',\n\t\t\t\t\tpackageName: name,\n\t\t\t\t\tsourcePath,\n\t\t\t\t\tchainId: sui.chainId,\n\t\t\t\t\tpublisherAddress: publisherAccount.address,\n\t\t\t\t\tmvrOverride: mvrPlaceholder,\n\t\t\t\t\t...(capture !== undefined ? { capture } : {}),\n\t\t\t\t\texecutor,\n\t\t\t\t} satisfies PackageMode;\n\n\t\t\t\tconst { resolved, output } = yield* bootPackageService(publisher, probe, registry, mode);\n\n\t\t\t\tconst projected: LocalPackageResolved<Capture> = {\n\t\t\t\t\t...resolved,\n\t\t\t\t\tcaptured: resolved.captured as CapturedPackageValues<Capture>,\n\t\t\t\t\tpublisher: publisherAccount,\n\t\t\t\t\tpublishResult: output,\n\t\t\t\t};\n\t\t\t\t// Emit the resolved package's contributions inline via the\n\t\t\t\t// shared `emitContributions` router. `projected` is the\n\t\t\t\t// just-resolved local value; `makeLocalCapabilities` builds the\n\t\t\t\t// ordered decl list (snapshot, codegen, registry, projection).\n\t\t\t\temitContributions(ctx, makeLocalCapabilities(name, opts, projected));\n\t\t\t\t// Part 2 (custom-kind re-home): on a fresh publish, fold the\n\t\t\t\t// output's coins into the per-stack CoinRegistry DIRECTLY (was\n\t\t\t\t// the orchestrator's `publishResultSink` consuming the now-\n\t\t\t\t// dead `LOCAL_PACKAGE_PUBLISHED` decl). `output` is null on a\n\t\t\t\t// cache hit (verify path), so discovery is skipped then — the\n\t\t\t\t// registry was already populated on the fresh-publish run that\n\t\t\t\t// seeded the cache.\n\t\t\t\tif (output !== null) {\n\t\t\t\t\tyield* discoverPublishedCoins(coinRegistry, name, projected.packageId, output);\n\t\t\t\t}\n\t\t\t\treturn projected;\n\t\t\t}),\n\t});\n};\n\nconst buildKnownPlugin = <Name extends string>(name: Name, opts: KnownPackageOptions) => {\n\tconst packageRef = resource<PackageResourceId<Name>, KnownPackageResolved>(\n\t\tpackageResourceId(name),\n\t);\n\tconst mvrPlaceholder = normalizeMvrPlaceholder(name, opts.mvrPlaceholder);\n\treturn definePlugin({\n\t\tid: packageRef.id,\n\t\tdependsOn: { sui: suiResource },\n\t\trole: 'task',\n\t\tsection: 'package',\n\t\tinputIdentity: staticInputIdentity({\n\t\t\tplugin: 'package',\n\t\t\tkind: 'known',\n\t\t\tname,\n\t\t\tpackageId: opts.packageId,\n\t\t\tupgradeCapId: opts.upgradeCapId ?? null,\n\t\t\tmvrPlaceholder: opts.mvrPlaceholder ?? null,\n\t\t}),\n\t\t// Stack-free codegen: a known package's `packageId` is already a\n\t\t// config literal, so the committed stub emits it verbatim (no chain\n\t\t// contact, no sentinel substitution).\n\t\tstaticCodegen: makeKnownStaticCodegen({\n\t\t\tname,\n\t\t\tpackageId: opts.packageId,\n\t\t\tmvrPlaceholder,\n\t\t\t...(opts.upgradeCapId !== undefined ? { upgradeCapId: opts.upgradeCapId } : {}),\n\t\t\t...(opts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {}),\n\t\t}),\n\t\tstart: ({ sui }) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\t\t\t\tconst registry = yield* PackageRegistryService;\n\t\t\t\tconst mode = {\n\t\t\t\t\tmode: 'known',\n\t\t\t\t\tpackageName: name,\n\t\t\t\t\tpackageId: opts.packageId,\n\t\t\t\t\tupgradeCapId: opts.upgradeCapId,\n\t\t\t\t\tmvrOverride: mvrPlaceholder,\n\t\t\t\t} satisfies PackageMode;\n\t\t\t\tconst { resolved } = yield* bootPackageService(publisher, probe, registry, mode);\n\t\t\t\t// Emit the resolved package's contributions inline via the\n\t\t\t\t// shared `emitContributions` router. `makeKnownCapabilities`\n\t\t\t\t// builds the ordered decl list (snapshot, codegen, registry,\n\t\t\t\t// projection).\n\t\t\t\temitContributions(ctx, makeKnownCapabilities(name, opts, resolved));\n\t\t\t\t// Known mode never publishes — no output to walk, so\n\t\t\t\t// the coin-discovery hook is skipped here. Users who\n\t\t\t\t// want coin records for a knownPackage point a\n\t\t\t\t// `coin.known('0xPKG::module::Witness')` at the\n\t\t\t\t// fully-qualified type directly; the bare-type path\n\t\t\t\t// hits the live RPC for metadata rather than the\n\t\t\t\t// output-walker.\n\t\t\t\treturn resolved;\n\t\t\t}),\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// Capability builders — pure helpers returning the ordered decl list.\n//\n// The package `start` bodies feed these into the shared `emitContributions`\n// router after resolving the value. Decl shapes + emit ORDER are load-bearing.\n//\n// NOTE: the LOCAL builder does NOT append a custom published-coin decl.\n// Coin discovery runs DIRECTLY in the local `start` body (see\n// `discoverPublishedCoins`); appending a decl here would double-discover.\n// ---------------------------------------------------------------------------\n\nconst makeLocalCapabilities = (\n\tname: string,\n\topts: { readonly excludeFromCodegen?: boolean; readonly mvrTypes?: readonly string[] },\n\tresolved: LocalPackageResolved,\n): ReadonlyArray<Contribution> => {\n\t// Snapshot + codegen lift their typed fields off the resolved\n\t// publish (real packageId + captured object ids). The static-form\n\t// placeholders are gone.\n\tconst snap: SnapshotableDecl = makeSnapshotable(\n\t\tname,\n\t\tresolved.publishResult?.packageId ?? resolved.packageId,\n\t);\n\tconst codegen: CodegenableDecl<'package'> = makeLocalCodegenable(\n\t\t{\n\t\t\tkind: 'local',\n\t\t\tname,\n\t\t\tpackageId: resolved.packageId,\n\t\t\tupgradeCapId: resolved.upgradeCapId,\n\t\t\tsourcePath: resolved.sourcePath,\n\t\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\t\tcaptured: resolved.captured,\n\t\t},\n\t\t{\n\t\t\texcluded: opts.excludeFromCodegen ?? false,\n\t\t\t...(opts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {}),\n\t\t},\n\t);\n\t// The plugin contributes to the package-registry strategy under a\n\t// fixed key — the substrate orchestrator wires all packages'\n\t// contributions into the same per-stack registry.\n\tconst projection: PackageRegistryProjectionContribution = {\n\t\tkind: 'local',\n\t\tname,\n\t\tpackageId: resolved.packageId,\n\t\tupgradeCapId: resolved.upgradeCapId ?? null,\n\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\tsourcePath: resolved.sourcePath,\n\t};\n\tconst registryContribution: StrategyContributorDecl<\n\t\ttypeof PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tPackageRegistryProjectionContribution\n\t> = {\n\t\tkind: 'strategy-contributor',\n\t\tcapabilityKey: PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tstrategy: projection,\n\t\tautoMounted: true,\n\t};\n\treturn [snap, codegen, registryContribution, makePackageProjectionContribution(projection)];\n};\n\nconst makeKnownCapabilities = (\n\tname: string,\n\topts: KnownPackageOptions,\n\tresolved: KnownPackageResolved,\n): ReadonlyArray<Contribution> => {\n\tconst snap: SnapshotableDecl = makeSnapshotable(name, `known:${resolved.packageId}`);\n\tconst codegen: CodegenableDecl<'package'> = makeKnownCodegenable(\n\t\t{\n\t\t\tkind: 'known',\n\t\t\tname,\n\t\t\tpackageId: resolved.packageId,\n\t\t\tupgradeCapId: resolved.upgradeCapId ?? opts.upgradeCapId,\n\t\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\t},\n\t\topts.mvrTypes !== undefined ? { mvrTypes: opts.mvrTypes } : {},\n\t);\n\tconst projection: PackageRegistryProjectionContribution = {\n\t\tkind: 'known',\n\t\tname,\n\t\tpackageId: resolved.packageId,\n\t\tupgradeCapId: resolved.upgradeCapId ?? opts.upgradeCapId ?? null,\n\t\tmvrPlaceholder: resolved.mvrPlaceholder,\n\t\tsourcePath: null,\n\t};\n\tconst registryContribution: StrategyContributorDecl<\n\t\ttypeof PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tPackageRegistryProjectionContribution\n\t> = {\n\t\tkind: 'strategy-contributor',\n\t\tcapabilityKey: PACKAGE_REGISTRY_CAPABILITY_KEY,\n\t\tstrategy: projection,\n\t\tautoMounted: true,\n\t};\n\treturn [snap, codegen, registryContribution, makePackageProjectionContribution(projection)];\n};\n\n/** Fold a fresh local-package publish output into the per-stack\n * `CoinRegistry`. Lifted VERBATIM from the orchestrator's former\n * `publishResultSink` (orchestrators/boot.ts): the same\n * `discoverCoinsFromPublish` walk + the same `CoinRecord` projection\n * (including the publisher-owns-cap gate on `treasuryCapId`).\n *\n * Re-homing this into the local `start` body — AFTER the publish output\n * is known, BEFORE `start` returns — preserves discovery timing: the\n * coin plugin `dependsOn` package, so it acquires only after package\n * readies, by which point the registry is already populated. The\n * previous decl-driven sink ran at the same point in the lifecycle\n * (post-`start` dispatch), so consumers see no ordering change. */\nexport const discoverPublishedCoins = (\n\tcoinRegistry: CoinRegistry,\n\tpackageName: string,\n\tpackageId: string,\n\toutput: LocalPackagePublishOutput,\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tfor (const discovered of discoverCoinsFromPublish(output)) {\n\t\t\tconst record: CoinRecord = {\n\t\t\t\tkey: (discovered.symbol ?? discovered.witness).toLowerCase(),\n\t\t\t\ttype: discovered.fullCoinType,\n\t\t\t\twitness: discovered.witness,\n\t\t\t\tmoduleName: discovered.moduleName,\n\t\t\t\tdecimals: discovered.decimals ?? 0,\n\t\t\t\t...(discovered.symbol === undefined ? {} : { symbol: discovered.symbol }),\n\t\t\t\t...(discovered.displayName === undefined ? {} : { displayName: discovered.displayName }),\n\t\t\t\t...(discovered.iconUrl === undefined ? {} : { iconUrl: discovered.iconUrl }),\n\t\t\t\t...(!discovered.publisherOwnsCap || discovered.treasuryCapId === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: { treasuryCapId: discovered.treasuryCapId }),\n\t\t\t\t...(discovered.metadataId === undefined ? {} : { metadataId: discovered.metadataId }),\n\t\t\t\tpackageId,\n\t\t\t\tpublishingPackageName: packageName,\n\t\t\t};\n\t\t\tyield* coinRegistry.register(record);\n\t\t}\n\t});\n\n// ---------------------------------------------------------------------------\n// Public factories\n// ---------------------------------------------------------------------------\n\n/** Build + publish a local Move package. The resolved value is\n * `LocalPackageResolved` so consumers that need bindings / source\n * path are typed correctly.\n *\n * Required `opts.publisher`: the account that signs the publish tx.\n * Pass the same `account('alice')` reference used elsewhere in the\n * stack — the package.s `dependsOn` includes `account/<publisher>`\n * so the substrate orders the publisher's keypair + funding strictly\n * before publish. */\nexport const localPackage = <\n\tName extends string,\n\tconst Publisher extends PublisherAccountMember,\n\tconst Capture extends PackageCapture | undefined = undefined,\n>(\n\tname: Name,\n\topts: LocalPackageOptions<Publisher, Capture>,\n) => buildLocalPlugin(name, opts);\n\n/** Verify-only against a fixed on-chain package id. The resolved\n * value is `KnownPackageResolved` — narrower than local, so the\n * bindings emitter rejects this at compose time. */\nexport const knownPackage = <Name extends string>(name: Name, opts: KnownPackageOptions) =>\n\tbuildKnownPlugin(name, opts);\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./registry.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeMvrPlaceholder } from "./dep-resolution.mjs";
|
|
3
3
|
import { publishError } from "./errors.mjs";
|
|
4
4
|
import { Effect, Schema } from "effect";
|
|
5
5
|
//#region src/plugins/package/mode-known.ts
|
|
@@ -23,7 +23,7 @@ const KnownObjectShape = Schema.Struct({ objectId: Schema.String });
|
|
|
23
23
|
* verify failure.
|
|
24
24
|
*/
|
|
25
25
|
const acquireKnown = (probe, registry, inputs) => Effect.gen(function* () {
|
|
26
|
-
const mvrPlaceholder =
|
|
26
|
+
const mvrPlaceholder = normalizeMvrPlaceholder(inputs.packageName, inputs.mvrOverride);
|
|
27
27
|
const verifyError = (message) => publishError("verify", {
|
|
28
28
|
sourcePath: `known:${inputs.packageId}`,
|
|
29
29
|
packageName: inputs.packageName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mode-known.mjs","names":[],"sources":["../../../src/plugins/package/mode-known.ts"],"sourcesContent":["// Known mode — skip publish; load existing package id from on-chain.\n//\n// Distilled doc §Responsibilities + §Inputs/dependencies §KnownPackage-\n// only: a `KnownPackage` declares a fixed `packageId` (optionally\n// `upgradeCapId`, `mvrPlaceholder`) and threads through the same\n// registries as a freshly published one. No build, no publish-tx, no\n// cache key folding by content-hash — the id IS the identity.\n//\n// What we still do:\n//\n// 1. Strict verify probe (chain reachable + object exists). Strict\n// mode lets the ChainProbe distinguish an authoritative miss from a\n// transient RPC failure. Two miss shapes surface a\n// PublishError(phase='verify') so the user catches the typo /\n// wrong-network mistake at boot:\n// - `reason: 'not-found'` — no object at the id.\n// - `reason: 'decode-failed'` — an object exists at the id but\n// its on-chain shape does not satisfy `KnownObjectShape`\n// (a wrong-kind object — the typical wrong-network / copy-paste\n// mistake). This is a THROWN `ChainProbeError`, not a `null`\n// result, because the real Sui probe fails Schema decode on a\n// wrong-shape id.\n// Only genuinely-transient reasons (`'transient'`,\n// `'no-probe-registered'`) are masked (no abort) — the id re-verifies\n// on the next cycle rather than failing boot on a flaky RPC. A `null`\n// strict result (object decodes against the minimal shape but the\n// probe still reports absence) is also treated as a verify failure.\n// 2. Register the resolved value on EVERY cycle.\n//\n// Bindings: KnownPackage cannot be bound by `@mysten/codegen` (no\n// source tree). The codegen contribution emitted for known packages\n// has `sourcePath: null`; the codegen orchestrator filters those\n// out before invoking the bindings emitter. The user-facing type\n// split (`localPackage` vs `knownPackage` factories) enforces this\n// at compose time per distilled doc Invariant 9.\n\nimport { Effect, Schema, type Scope } from 'effect';\n\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { SuiProbeKey } from '../sui/index.ts';\nimport {
|
|
1
|
+
{"version":3,"file":"mode-known.mjs","names":[],"sources":["../../../src/plugins/package/mode-known.ts"],"sourcesContent":["// Known mode — skip publish; load existing package id from on-chain.\n//\n// Distilled doc §Responsibilities + §Inputs/dependencies §KnownPackage-\n// only: a `KnownPackage` declares a fixed `packageId` (optionally\n// `upgradeCapId`, `mvrPlaceholder`) and threads through the same\n// registries as a freshly published one. No build, no publish-tx, no\n// cache key folding by content-hash — the id IS the identity.\n//\n// What we still do:\n//\n// 1. Strict verify probe (chain reachable + object exists). Strict\n// mode lets the ChainProbe distinguish an authoritative miss from a\n// transient RPC failure. Two miss shapes surface a\n// PublishError(phase='verify') so the user catches the typo /\n// wrong-network mistake at boot:\n// - `reason: 'not-found'` — no object at the id.\n// - `reason: 'decode-failed'` — an object exists at the id but\n// its on-chain shape does not satisfy `KnownObjectShape`\n// (a wrong-kind object — the typical wrong-network / copy-paste\n// mistake). This is a THROWN `ChainProbeError`, not a `null`\n// result, because the real Sui probe fails Schema decode on a\n// wrong-shape id.\n// Only genuinely-transient reasons (`'transient'`,\n// `'no-probe-registered'`) are masked (no abort) — the id re-verifies\n// on the next cycle rather than failing boot on a flaky RPC. A `null`\n// strict result (object decodes against the minimal shape but the\n// probe still reports absence) is also treated as a verify failure.\n// 2. Register the resolved value on EVERY cycle.\n//\n// Bindings: KnownPackage cannot be bound by `@mysten/codegen` (no\n// source tree). The codegen contribution emitted for known packages\n// has `sourcePath: null`; the codegen orchestrator filters those\n// out before invoking the bindings emitter. The user-facing type\n// split (`localPackage` vs `knownPackage` factories) enforces this\n// at compose time per distilled doc Invariant 9.\n\nimport { Effect, Schema, type Scope } from 'effect';\n\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { SuiProbeKey } from '../sui/index.ts';\nimport { normalizeMvrPlaceholder } from './dep-resolution.ts';\nimport { type PackageRegistry, type ResolvedKnownPackage } from './registry.ts';\nimport { publishError, type PublishError } from './errors.ts';\n\n/** Verify-schema for known mode — minimum signal: object exists at\n * the given id. */\nconst KnownObjectShape = Schema.Struct({\n\tobjectId: Schema.String,\n});\n\nexport interface KnownModeInputs {\n\treadonly packageName: string;\n\treadonly packageId: string;\n\treadonly upgradeCapId?: string;\n\treadonly mvrOverride?: string;\n}\n\nexport interface KnownModeOutputs {\n\treadonly resolved: ResolvedKnownPackage;\n}\n\n/**\n * Acquire body for known mode.\n *\n * Runs a strict ChainProbe verify and registers the resolved value.\n * Strict mode surfaces a typed `ChainProbeError` whose `reason`\n * discriminates an authoritative miss from a transient RPC failure, so we\n * can fail boot on a real typo / wrong-network mistake while masking\n * transient failures so a flaky RPC does not abort boot:\n * - `reason: 'not-found'` → verify failure (no object at the id).\n * - `reason: 'decode-failed'` → verify failure (an object exists but is\n * the wrong object kind — its on-chain shape fails `KnownObjectShape`).\n * - `reason: 'transient' | 'no-probe-registered'` → masked; the verify\n * re-runs on the next cycle.\n * A `null` strict payload (probe reports absence after decode) is also a\n * verify failure.\n */\nexport const acquireKnown = (\n\tprobe: ChainProbe<SuiProbeKey>,\n\tregistry: PackageRegistry,\n\tinputs: KnownModeInputs,\n): Effect.Effect<KnownModeOutputs, PublishError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\t// Defaults use devstack's `@local/<slug>` form; explicit config values\n\t\t// are validated full MVR names and preserved verbatim.\n\t\tconst mvrPlaceholder = normalizeMvrPlaceholder(inputs.packageName, inputs.mvrOverride);\n\n\t\tconst verifyError = (message: string): PublishError =>\n\t\t\tpublishError('verify', {\n\t\t\t\tsourcePath: `known:${inputs.packageId}`,\n\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\tmessage,\n\t\t\t});\n\t\t// `'transient'` is a sentinel for \"masked transient RPC failure —\n\t\t// skip the verify aborts and re-derive next cycle\".\n\t\tconst found: { readonly objectId: string } | null | 'transient' = yield* probe\n\t\t\t.get({ kind: 'object', objectId: inputs.packageId }, KnownObjectShape, 'strict')\n\t\t\t.pipe(\n\t\t\t\t// Classify the strict-probe failure. An authoritative miss\n\t\t\t\t// (`not-found`) and a wrong-kind object (`decode-failed` — an\n\t\t\t\t// object exists at the id but its shape does not satisfy\n\t\t\t\t// `KnownObjectShape`) BOTH surface a verify error: they are the\n\t\t\t\t// typo / wrong-network mistakes strict mode exists to catch. Only\n\t\t\t\t// genuinely-transient reasons (`transient`, `no-probe-registered`)\n\t\t\t\t// are masked so the id re-verifies next cycle rather than aborting\n\t\t\t\t// boot on a flaky RPC.\n\t\t\t\tEffect.catch((err) => {\n\t\t\t\t\tswitch (err.reason) {\n\t\t\t\t\t\tcase 'not-found':\n\t\t\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\t\t\tverifyError(\n\t\t\t\t\t\t\t\t\t`known package id ${inputs.packageId} does not exist on this chain — check for a typo or wrong network.`,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tcase 'decode-failed':\n\t\t\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\t\t\tverifyError(\n\t\t\t\t\t\t\t\t\t`known package id ${inputs.packageId} resolved to an unexpected object shape on this chain — check for a typo or wrong network.`,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\treturn Effect.succeed('transient' as const);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t// A `null` strict result means the id exists but is not the\n\t\t// expected object kind — also a verify failure.\n\t\tif (found === null) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tverifyError(\n\t\t\t\t\t`known package id ${inputs.packageId} resolved to an unexpected object shape on this chain.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tconst resolved: ResolvedKnownPackage = {\n\t\t\tkind: 'known',\n\t\t\tname: inputs.packageName,\n\t\t\tpackageId: inputs.packageId,\n\t\t\tupgradeCapId: inputs.upgradeCapId,\n\t\t\tmvrPlaceholder,\n\t\t};\n\n\t\t// Distilled doc Invariant 6: register on EVERY cycle (here\n\t\t// always — known mode has no \"miss\" branch).\n\t\tyield* registry.set(resolved.name, resolved);\n\n\t\treturn { resolved };\n\t});\n"],"mappings":";;;;;;;AA8CA,MAAM,mBAAmB,OAAO,OAAO,EACtC,UAAU,OAAO,OAClB,CAAC;;;;;;;;;;;;;;;;;AA6BD,MAAa,gBACZ,OACA,UACA,WAEA,OAAO,IAAI,aAAa;CAGvB,MAAM,iBAAiB,wBAAwB,OAAO,aAAa,OAAO,WAAW;CAErF,MAAM,eAAe,YACpB,aAAa,UAAU;EACtB,YAAY,SAAS,OAAO;EAC5B,aAAa,OAAO;EACpB;CACD,CAAC;CAmCF,KAAI,OAhCqE,MACvE,IAAI;EAAE,MAAM;EAAU,UAAU,OAAO;CAAU,GAAG,kBAAkB,QAAQ,CAAC,CAC/E,KASA,OAAO,OAAO,QAAQ;EACrB,QAAQ,IAAI,QAAZ;GACC,KAAK,aACJ,OAAO,OAAO,KACb,YACC,oBAAoB,OAAO,UAAU,mEACtC,CACD;GACD,KAAK,iBACJ,OAAO,OAAO,KACb,YACC,oBAAoB,OAAO,UAAU,2FACtC,CACD;GACD,SACC,OAAO,OAAO,QAAQ,WAAoB;EAC5C;CACD,CAAC,CACF,OAGa,MACb,OAAO,OAAO,OAAO,KACpB,YACC,oBAAoB,OAAO,UAAU,uDACtC,CACD;CAGD,MAAM,WAAiC;EACtC,MAAM;EACN,MAAM,OAAO;EACb,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB;CACD;CAIA,OAAO,SAAS,IAAI,SAAS,MAAM,QAAQ;CAE3C,OAAO,EAAE,SAAS;AACnB,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { contentHash } from "../../substrate/brand.mjs";
|
|
|
2
2
|
import { withMoveBuildLock } from "../sui/move/index.mjs";
|
|
3
3
|
import "./registry.mjs";
|
|
4
4
|
import { artifactPublishError } from "../../primitives/artifact-publisher.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { normalizeMvrPlaceholder } from "./dep-resolution.mjs";
|
|
6
6
|
import { publishError } from "./errors.mjs";
|
|
7
7
|
import { hashMoveSources, scrubLocksHost } from "./build.mjs";
|
|
8
8
|
import { Duration, Effect, Schema } from "effect";
|
|
@@ -76,7 +76,7 @@ const acquireLocal = (publisher, probe, registry, inputs) => Effect.gen(function
|
|
|
76
76
|
message: `localPackage('${inputs.packageName}') is declared twice in the same stack with different sourcePaths (${existing.sourcePath} vs ${inputs.sourcePath}). The substrate is name-blind at runtime and the second declaration would silently overwrite the first registry entry. Pick distinct package names (the symbolic name is the registry key consumers look up).`
|
|
77
77
|
}));
|
|
78
78
|
const inputsHash = combineInputsHash(yield* hashMoveSources(inputs.sourcePath), inputs.publisherAddress);
|
|
79
|
-
const mvrPlaceholder =
|
|
79
|
+
const mvrPlaceholder = normalizeMvrPlaceholder(inputs.packageName, inputs.mvrOverride);
|
|
80
80
|
let producedOutput = null;
|
|
81
81
|
const entry = yield* publisher.publish({
|
|
82
82
|
namespace: "package",
|
|
@@ -129,7 +129,7 @@ const acquireLocal = (publisher, probe, registry, inputs) => Effect.gen(function
|
|
|
129
129
|
packageId: artifact.packageId,
|
|
130
130
|
upgradeCapId: artifact.upgradeCapId,
|
|
131
131
|
sourcePath: inputs.sourcePath,
|
|
132
|
-
mvrPlaceholder
|
|
132
|
+
mvrPlaceholder,
|
|
133
133
|
captured: artifact.captured
|
|
134
134
|
};
|
|
135
135
|
yield* registry.set(r.name, r);
|
|
@@ -151,7 +151,7 @@ const acquireLocal = (publisher, probe, registry, inputs) => Effect.gen(function
|
|
|
151
151
|
packageId: entry.packageId,
|
|
152
152
|
upgradeCapId: entry.upgradeCapId,
|
|
153
153
|
sourcePath: inputs.sourcePath,
|
|
154
|
-
mvrPlaceholder
|
|
154
|
+
mvrPlaceholder,
|
|
155
155
|
captured: recomputedCaptured
|
|
156
156
|
});
|
|
157
157
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mode-local.mjs","names":["brandContentHash"],"sources":["../../../src/plugins/package/mode-local.ts"],"sourcesContent":["// Local mode — build from source + publish.\n//\n// This is the canonical implementation of the\n// `ArtifactPublisher` substrate primitive (distilled doc\n// §Generic artifact-publisher-publish pattern). The five-phase shape:\n//\n// 1. Inputs — `{ sourceHash, signerAddress }` →\n// `contentHash(inputs)`.\n// 2. Cache key — `<namespace=package>/<chainId>/<contentHash>`.\n// Substrate folds in chainId for us; we hand\n// it the namespace + contentHash.\n// 3. Verify — `chainProbe.get({ objectId: cachedId },\n// PackageObjectSchema, 'lenient')`. Lenient mode\n// coerces transient RPC failure → null\n// → re-derive (cheap over-derive vs spurious\n// eviction — distilled doc §Constraint \"lenient\n// verify is cheaper than aggressive eviction\").\n// 4. Produce — scrub locks → build → publish-tx → wait-for-\n// index. Each phase narrates to TUI via span\n// annotation.\n// 5. Register — write registry entry on EVERY cycle (hit AND\n// miss). Distilled doc Invariant 6.\n//\n// The publish output is exposed on the resolved value and emitted as a\n// package-owned extension contribution for sibling folds.\n\nimport { Duration, Effect, Schema, type Scope } from 'effect';\n\nimport { contentHash as brandContentHash } from '../../substrate/brand.ts';\nimport type { LocalPackagePublishOutput } from './publish-output.ts';\nimport {\n\tartifactPublishError,\n\ttype ArtifactPublishError,\n\ttype ArtifactPublisher,\n} from '../../primitives/artifact-publisher.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { SuiProbeKey } from '../sui/index.ts';\nimport { hashMoveSources, scrubLocksHost, withMoveBuildLock, type BuildOutput } from './build.ts';\nimport { mvrNamedForm } from './dep-resolution.ts';\nimport { type PackageRegistry, type ResolvedLocalPackage } from './registry.ts';\nimport { publishError, type PublishError } from './errors.ts';\n\n/** Cache-stored payload — the stable id verify re-confirms on\n * every cycle. Distilled doc Invariant 8: the probe MUST consume\n * a stable identifier (the packageId), NOT a derived hash. */\nexport interface CachedPackageEntry {\n\treadonly packageId: string;\n\treadonly upgradeCapId?: string;\n\treadonly publisher: string;\n\treadonly mvrPlaceholder: string;\n\treadonly captured: Readonly<Record<string, string>>;\n\treadonly output?: LocalPackagePublishOutput;\n}\n\n/** Verify-schema: what we expect when probing `getObject(packageId)`.\n * Minimal — the substrate's `ChainProbe` decodes against this; any\n * decode failure surfaces structured. */\nexport const PackageVerifyShape = Schema.Struct({\n\tobjectId: Schema.String,\n\t// A Move package's `type` is the literal `\"package\"` wrapper;\n\t// the SDK's exact shape varies — we keep it open as `Unknown`\n\t// and rely on objectId presence for the \"exists\" signal.\n\ttype: Schema.Unknown,\n});\n\n/**\n * Publish-tx executor — narrow shim that abstracts the concrete\n * `@mysten/sui` wiring (Transaction builder, signer, fullnode\n * ready-probe). The mode-local produce body composes its 5-phase\n * shape against this interface; the concrete implementation lives in\n * `publish-executor.ts` and is constructed once per acquire by the\n * barrel from the resolved `SuiClient` + publisher `AccountValue` +\n * `ContainerRuntime`.\n *\n * The shim parallels `coin/mint.ts::MintSigner` + `MintSdkShim`. We\n * keep them split (publish vs sign vs ready-probe) so the produce\n * body can attribute span annotations per phase + so failures map\n * back to a typed `PublishError` phase enum.\n */\nexport interface PublishExecutor {\n\treadonly scrubsInsideContainer: boolean;\n\n\t/** Build the Move source tree into `{ modules, dependencies }`.\n\t * Dispatches between (a) per-app build container, (b) fresh\n\t * `docker run --rm`, (c) host `sui` CLI. The produce body just\n\t * calls and propagates `PublishError`. */\n\treadonly build: (inputs: {\n\t\treadonly sourcePath: string;\n\t\treadonly packageName: string;\n\t\treadonly chainId: string;\n\t}) => Effect.Effect<BuildOutput, PublishError, Scope.Scope>;\n\n\t/** Construct + sign + execute a `Transaction.publish({modules,\n\t * dependencies})` against the publisher account. Returns the\n\t * output projection that downstream consumers (Coin, manifest,\n\t * capture spec) need. Distilled doc §Move-specific concerns —\n\t * the output is the source of `published` change + the\n\t * `UpgradeCap` `created` change. */\n\treadonly publishTx: (inputs: {\n\t\treadonly modules: ReadonlyArray<Uint8Array>;\n\t\treadonly dependencies: ReadonlyArray<string>;\n\t\treadonly sourcePath: string;\n\t\treadonly packageName: string;\n\t}) => Effect.Effect<LocalPackagePublishOutput, PublishError, Scope.Scope>;\n\n\t/** Post-publish fullnode/indexer ready HINT. Distilled doc\n\t * Invariant 5: publish-tx commit precedes index visibility. This\n\t * is BEST-EFFORT — the concrete executor swallows transient\n\t * `getObject` misses (cold index races) because the publisher\n\t * account's `signAndExecute` already calls `waitForTransaction`\n\t * before returning, AND the downstream `parse` phase only\n\t * inspects the publish output. A typed `PublishError('parse')`\n\t * surfaces only when the `getObject` infrastructure itself\n\t * faults (network down / SDK throws non-recoverably), NOT when\n\t * the object is merely not-yet-indexed.\n\t *\n\t * The \"Hint\" suffix encodes the contract: callers MUST NOT treat\n\t * a successful return as \"package definitely queryable now\". */\n\treadonly postPublishReadyHint: (\n\t\tpackageId: string,\n\t) => Effect.Effect<void, PublishError, Scope.Scope>;\n}\n\nexport interface LocalModeInputs {\n\treadonly packageName: string;\n\treadonly sourcePath: string;\n\treadonly chainId: string;\n\treadonly publisherAddress: string;\n\treadonly mvrOverride?: string;\n\treadonly capture?: (output: LocalPackagePublishOutput) => Readonly<Record<string, string>>;\n\t/** Publish executor — constructed per-acquire by the barrel from\n\t * the resolved SuiClient + publisher account + ContainerRuntime\n\t * (see `publish-executor.ts`). */\n\treadonly executor: PublishExecutor;\n}\n\nexport interface LocalModeOutputs {\n\treadonly resolved: ResolvedLocalPackage;\n\treadonly output: LocalPackagePublishOutput | null;\n}\n\n/**\n * Build the content hash for the publish cache key. Distilled doc\n * §Move-specific: inputs are `(sourceHash, signerAddress)`. The\n * `hashMoveSources` helper already normalises Move.lock pinned\n * sections via the shared `stripPinnedSections`; we fold the\n * publisher address in here as a stable string so reusing the\n * same source under a different signer correctly misses (Invariant\n * 4: \"Signer MUST be an explicit upstream\").\n */\nconst LOCAL_PACKAGE_CACHE_SCHEMA_VERSION = 'v3';\n// Short verify budget covering RPC index-visibility lag only. The big\n// post-restore catch-up window — `sui start` re-executes its committed\n// checkpoint store from seq=0, and `getObject(packageId)` reads not-found\n// until the replay reaches the publish checkpoint — is now absorbed by the\n// sui plugin's caught-up-to-head ready-gate (`waitForCheckpointCatchUp` in\n// plugins/sui/mode/local.ts): the validator does not report ready until its\n// head stabilizes to live cadence, so by the time this verify runs the\n// committed package object is already served. This budget only needs to ride\n// out the small fullnode/index lag between publish-tx commit and `getObject`\n// visibility — NOT the whole replay. If it gives up too soon it returns null →\n// the substrate re-PRODUCES (re-publishes) the package, minting a FRESH\n// packageId (codegen `config.ts` loses the stable id; downstream\n// `deepbookOf(...).packageId` churns). A truly-wiped chain genuinely has no\n// package and re-publishes once the budget lapses. Mirrors\n// WALRUS_DEPLOY_VERIFY_READINESS_* in plugins/walrus/deploy.ts — the on-chain\n// artifact-publisher consumers share the same short RPC-lag tolerance.\nconst PACKAGE_CACHE_VERIFY_MAX_ATTEMPTS = 20;\nconst PACKAGE_CACHE_VERIFY_DELAY_MS = 250;\n\nconst combineInputsHash = (sourceHash: string, publisherAddress: string) =>\n\tbrandContentHash(\n\t\t`${LOCAL_PACKAGE_CACHE_SCHEMA_VERSION}::source=${sourceHash}::publisher=${publisherAddress}`,\n\t);\n\n/**\n * Verify probe — lenient `getObject(packageId)`. Returns the decoded\n * shape on success; null for BOTH not-found AND transient (distilled\n * doc Invariant 7 + the \"lenient verify cheaper than aggressive\n * eviction\" Constraint).\n *\n * The cached id is hinted in via parameter. The artifact publisher substrate is\n * expected to thread the cached payload through to this closure on\n * cache hit; today we get the id from the registry's previous-cycle\n * entry (in-process, per-supervisor lookup — see `acquireLocal`\n * below). When no hint exists (first cycle / cold boot), we\n * short-circuit to null so the substrate runs `produce`.\n *\n * Mirrors `coin/mint.ts::buildVerifyProbe` exactly so the two artifact publisher\n * consumers stay shape-aligned.\n */\nexport const buildVerifyProbe = (\n\tprobe: ChainProbe<SuiProbeKey>,\n\tcachedPackageIdHint: string | null,\n\topts?: { readonly maxAttempts?: number; readonly delayMs?: number },\n): Effect.Effect<typeof PackageVerifyShape.Type | null, never> =>\n\tEffect.gen(function* () {\n\t\tif (cachedPackageIdHint === null) return null;\n\t\tconst maxAttempts = opts?.maxAttempts ?? PACKAGE_CACHE_VERIFY_MAX_ATTEMPTS;\n\t\tconst delayMs = opts?.delayMs ?? PACKAGE_CACHE_VERIFY_DELAY_MS;\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\t// Lenient mode in the underlying probe already coerces\n\t\t\t// not-found AND transient → null. We retry nulls briefly to\n\t\t\t// ride out the small fullnode/index lag between publish-tx\n\t\t\t// commit and `getObject` visibility; the validator's larger\n\t\t\t// post-restore replay is already gated upstream by the sui\n\t\t\t// plugin's caught-up-to-head ready-gate, so a false cache miss\n\t\t\t// does not turn a warm restart into an unnecessary publish.\n\t\t\tconst result: typeof PackageVerifyShape.Type | null = yield* probe\n\t\t\t\t.get({ kind: 'object', objectId: cachedPackageIdHint }, PackageVerifyShape, 'lenient')\n\t\t\t\t.pipe(\n\t\t\t\t\t// The probe's own error channel is `ChainProbeError`; under\n\t\t\t\t\t// lenient mode only `decode-failed` surfaces (not-found +\n\t\t\t\t\t// transient already coerce to null). A decode failure on\n\t\t\t\t\t// verify is treated as \"stale shape\" — null so the substrate\n\t\t\t\t\t// re-publishes rather than carry forward a mismatch.\n\t\t\t\t\tEffect.catch(() => Effect.succeed(null as typeof PackageVerifyShape.Type | null)),\n\t\t\t\t);\n\t\t\tif (result !== null) return result;\n\t\t\tif (attempt < maxAttempts && delayMs > 0) {\n\t\t\t\tyield* Effect.sleep(Duration.millis(delayMs));\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t});\n\n/**\n * Compose the ArtifactSpec for a local publish.\n *\n * The substrate-side `ArtifactPublisher.publish` consumes\n * this spec and handles:\n *\n * - cache lookup under `<namespace>/<chainId>/<contentHash>`;\n * - verify (lenient) when there's a hit;\n * - re-running `produce` on miss OR verify-fail;\n * - calling `register` on EVERY cycle.\n */\nexport const acquireLocal = (\n\tpublisher: ArtifactPublisher,\n\tprobe: ChainProbe<SuiProbeKey>,\n\tregistry: PackageRegistry,\n\tinputs: LocalModeInputs,\n): Effect.Effect<LocalModeOutputs, PublishError | ArtifactPublishError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\t// Intra-stack name collision guard.\n\t\t//\n\t\t// The registry is keyed by symbolic `packageName`; two\n\t\t// `localPackage('foo', ...)` calls in the same stack would both\n\t\t// call `register({ name: 'foo', ... })` and the second `set`\n\t\t// would silently overwrite the first. Upstream's\n\t\t// `resolveGraph` (substrate/runtime/lifecycle/dep-graph.ts:127)\n\t\t// explicitly documents \"we don't enforce uniqueness at runtime;\n\t\t// the duplicate just resolves to the latest declaration\" —\n\t\t// compile-time `MissingProviders` only catches collisions on\n\t\t// the user-typed dependency path, NOT two members declared on\n\t\t// the same stack with the same id. We catch the collision here\n\t\t// so the user sees a typed parse-phase failure instead of a\n\t\t// confusing \"wrong packageId\" downstream.\n\t\t//\n\t\t// The check tolerates re-entry from the SAME `localPackage(...)`\n\t\t// call (warm restart / re-acquire on the same scope): a\n\t\t// pre-existing entry with the SAME `sourcePath` is the\n\t\t// previous cycle's register, not a collision.\n\t\tconst existing = yield* registry.find(inputs.packageName);\n\t\tif (\n\t\t\texisting !== null &&\n\t\t\texisting.kind === 'local' &&\n\t\t\texisting.sourcePath !== inputs.sourcePath\n\t\t) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpublishError('parse', {\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`localPackage('${inputs.packageName}') is declared twice in the same stack ` +\n\t\t\t\t\t\t`with different sourcePaths (${existing.sourcePath} vs ${inputs.sourcePath}). ` +\n\t\t\t\t\t\t`The substrate is name-blind at runtime and the second declaration would ` +\n\t\t\t\t\t\t`silently overwrite the first registry entry. Pick distinct package names ` +\n\t\t\t\t\t\t`(the symbolic name is the registry key consumers look up).`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// Distilled doc §Move-specific: hash inputs are `(sourceHash,\n\t\t// signerAddress)`. The hashing helper strips Move.lock pinned\n\t\t// sections (Invariant 2) so warm restarts hit the cache.\n\t\tconst sourceHash = yield* hashMoveSources(inputs.sourcePath);\n\t\tconst inputsHash = combineInputsHash(sourceHash, inputs.publisherAddress);\n\n\t\t// The MVR placeholder is the NAMED form (`@local/<slug>`), not the\n\t\t// bare slug — it is emitted as BOTH the generated-binding package\n\t\t// default (`options.package ?? '@local/<slug>'`) and\n\t\t// `config.packages.<name>.mvr`, so apps can resolve bindings by\n\t\t// name alone via an MvrClient override keyed on that string. The\n\t\t// bare slug survives only as file stems / Move `[addresses]`\n\t\t// resolution, which `mvrNamedForm` derives internally.\n\t\tconst mvrPlaceholder = mvrNamedForm(inputs.mvrOverride ?? inputs.packageName);\n\n\t\t// We capture the produce-side output out-of-band so the\n\t\t// returned `LocalModeOutputs.output` can expose it. The\n\t\t// substrate hands back the decoded `CachedPackageEntry` on\n\t\t// every path; `producedOutput` is the freshly-emitted output\n\t\t// from THIS cycle (cache miss only).\n\t\tlet producedOutput: LocalPackagePublishOutput | null = null;\n\n\t\tconst entry: CachedPackageEntry = yield* publisher.publish<\n\t\t\tCachedPackageEntry,\n\t\t\ttypeof PackageVerifyShape.Type\n\t\t>({\n\t\t\tnamespace: 'package',\n\t\t\tchain: inputs.chainId,\n\t\t\tcontentHash: inputsHash,\n\t\t\tverify: (cached) =>\n\t\t\t\tcached.output === undefined\n\t\t\t\t\t? Effect.succeed(null)\n\t\t\t\t\t: buildVerifyProbe(probe, cached.packageId),\n\t\t\t// Produce: scrub → build → publish-tx → wait-for-index → parse.\n\t\t\t// PublishError is the plugin-internal phase taxonomy; we map\n\t\t\t// it to `ArtifactPublishError` at the substrate boundary.\n\t\t\tproduce: Effect.gen(function* () {\n\t\t\t\t// Produce 1/5 + 2/5 — scrub locks, then build. Both touch the\n\t\t\t\t// shared `~/.move` git cache, so they run together under the\n\t\t\t\t// process-wide Move-build permit (`withMoveBuildLock`): otherwise a\n\t\t\t\t// concurrent build's host scrub (`gawk -i inplace`) races a sibling\n\t\t\t\t// container's live `git clone` into the same cache dir and corrupts\n\t\t\t\t// the in-flight fetch. Publish-tx (3/5 below) stays OUTSIDE the lock\n\t\t\t\t// so on-chain work of different packages still overlaps.\n\t\t\t\tconst buildOutput: BuildOutput = yield* withMoveBuildLock(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t// Produce 1/5 — scrub locks. Distilled doc §Move-specific\n\t\t\t\t\t\t// concerns + Invariant 14: strip pinned sections from every\n\t\t\t\t\t\t// `~/.move/git/**/Move.lock` (vendored dep caches) before\n\t\t\t\t\t\t// invoking the build. The package's own Move.lock is scrubbed\n\t\t\t\t\t\t// inside the container on a disposable copy, so the developer's\n\t\t\t\t\t\t// checked-in source is left untouched host-side. Uses the\n\t\t\t\t\t\t// unified `stripPinnedSections` (re-exported through `build.ts`\n\t\t\t\t\t\t// → from `../sui/move-lock-scrub.ts`) — NO duplicate.\n\t\t\t\t\t\tyield* scrubLocksHost(inputs.sourcePath, '~/.move');\n\n\t\t\t\t\t\t// Produce 2/5 — build. Executor dispatches between (a)\n\t\t\t\t\t\t// per-app build container, (b) `docker run --rm`, (c) host\n\t\t\t\t\t\t// `sui` CLI.\n\t\t\t\t\t\treturn yield* inputs.executor\n\t\t\t\t\t\t\t.build({\n\t\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\t\tchainId: inputs.chainId,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.catchTag(\n\t\t\t\t\t\t\t\t\t'PublishError',\n\t\t\t\t\t\t\t\t\t(err): Effect.Effect<BuildOutput, PublishError> =>\n\t\t\t\t\t\t\t\t\t\t// Re-stamp sourcePath/packageName if the underlying\n\t\t\t\t\t\t\t\t\t\t// caller omitted them (every throw site MUST surface\n\t\t\t\t\t\t\t\t\t\t// the context — see distilled doc §Opportunities).\n\t\t\t\t\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\t\t\t\t\terr.sourcePath\n\t\t\t\t\t\t\t\t\t\t\t\t? err\n\t\t\t\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t...err,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t),\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\n\t\t\t\t// Produce 3/5 — publish-tx. Construct `Transaction.publish`,\n\t\t\t\t// sign + execute via the publisher's account signer, decode\n\t\t\t\t// the output.\n\t\t\t\tconst output: LocalPackagePublishOutput = yield* inputs.executor.publishTx({\n\t\t\t\t\tmodules: buildOutput.modules,\n\t\t\t\t\tdependencies: buildOutput.dependencies,\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t});\n\t\t\t\tproducedOutput = output;\n\n\t\t\t\t// Produce 4/5 — wait-for-index. Distilled doc Invariant 5:\n\t\t\t\t// publish-tx commit precedes index visibility. This is a\n\t\t\t\t// hint-only probe (see `postPublishReadyHint` doc on\n\t\t\t\t// `PublishExecutor`): the publisher account's\n\t\t\t\t// `signAndExecute` already awaits `waitForTransaction`, so\n\t\t\t\t// the typical race is closed before we reach here.\n\t\t\t\t// Best-effort only: the executor's hint swallows transient\n\t\t\t\t// `getObject` misses and logs at debug, so its error channel\n\t\t\t\t// collapses to `never` (see `publish-executor.ts →\n\t\t\t\t// postPublishReadyHint`). No re-stamp is needed because no\n\t\t\t\t// failure surfaces here.\n\t\t\t\tyield* inputs.executor.postPublishReadyHint(output.packageId);\n\n\t\t\t\t// Capture spec — user-declared projection from output to\n\t\t\t\t// typed object id map. Distilled doc §Outputs: the callback\n\t\t\t\t// is user code, so a throw is a USER BUG (typo / missing\n\t\t\t\t// field) — surface as `PublishError('parse')` so the user\n\t\t\t\t// catches the mistake instead of silently shipping a stale\n\t\t\t\t// captured map. The cache-HIT path performs the symmetric\n\t\t\t\t// recompute post-`publisher.publish` (see comment block\n\t\t\t\t// below the `publisher.publish` call) — both paths bubble\n\t\t\t\t// the same `PublishError('parse')` shape.\n\t\t\t\tconst captured: Readonly<Record<string, string>> = inputs.capture\n\t\t\t\t\t? yield* Effect.try({\n\t\t\t\t\t\t\ttry: () => inputs.capture!(output),\n\t\t\t\t\t\t\tcatch: (cause): PublishError =>\n\t\t\t\t\t\t\t\tpublishError('parse', {\n\t\t\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\t\t\tmessage: 'capture callback threw',\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t})\n\t\t\t\t\t: {};\n\n\t\t\t\tconst entry: CachedPackageEntry = {\n\t\t\t\t\tpackageId: output.packageId,\n\t\t\t\t\tupgradeCapId: output.upgradeCapId,\n\t\t\t\t\tpublisher: inputs.publisherAddress,\n\t\t\t\t\tmvrPlaceholder,\n\t\t\t\t\tcaptured,\n\t\t\t\t\toutput,\n\t\t\t\t};\n\t\t\t\treturn entry;\n\t\t\t}).pipe(\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): ArtifactPublishError =>\n\t\t\t\t\t\tartifactPublishError('produce-failed', `package.publish ${err.phase}: ${err.message}`),\n\t\t\t\t),\n\t\t\t),\n\t\t\t// Register: on EVERY cycle. Distilled doc Invariant 6. The\n\t\t\t// substrate hands the decoded `CachedPackageEntry` payload\n\t\t\t// here on both verify-hit and freshly-produced paths.\n\t\t\t//\n\t\t\t// Register writes the entry verbatim using `artifact.captured`\n\t\t\t// (produce-time on miss; cached on hit). The user's `capture`\n\t\t\t// callback is NOT re-run here — `register`'s contract is\n\t\t\t// `Effect.Effect<void, never>` (primitives/artifact-publisher.ts)\n\t\t\t// and a user-bug throw needs a typed failure channel. The\n\t\t\t// post-publish recompute below (cache-hit branch) re-runs\n\t\t\t// `capture` so renamed/typo keys surface IDENTICALLY to the\n\t\t\t// cache-miss path (mode-local.ts:346-357) — i.e., as\n\t\t\t// `PublishError('parse')`.\n\t\t\tregister: (artifact) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst r: ResolvedLocalPackage = {\n\t\t\t\t\t\tkind: 'local',\n\t\t\t\t\t\tname: inputs.packageName,\n\t\t\t\t\t\tpackageId: artifact.packageId,\n\t\t\t\t\t\tupgradeCapId: artifact.upgradeCapId,\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tmvrPlaceholder: artifact.mvrPlaceholder,\n\t\t\t\t\t\tcaptured: artifact.captured,\n\t\t\t\t\t};\n\t\t\t\t\tyield* registry.set(r.name, r);\n\t\t\t\t}),\n\t\t});\n\n\t\t// Post-publish capture recompute — cache-hit ONLY.\n\t\t//\n\t\t// Symmetric with the produce-time capture (L346-357): the user's\n\t\t// `capture` callback is user code; a throw is a user bug (typo /\n\t\t// renamed key against a stale cached output) and MUST surface as\n\t\t// `PublishError('parse')` so the user sees the mistake instead of\n\t\t// silently carrying forward `artifact.captured` — swallowing the\n\t\t// throw with `catch { return artifact.captured }` would hide\n\t\t// renamed keys behind stale data.\n\t\t//\n\t\t// Cache miss: `producedOutput` was set inside `produce`, the\n\t\t// produce-time capture already ran (Effect.try → PublishError),\n\t\t// and the substrate cached the produced entry. Re-running here\n\t\t// would be redundant and would double-throw on user bugs.\n\t\t//\n\t\t// Cache hit: `producedOutput` is null, `entry.output` came from\n\t\t// the cached payload, and `entry.captured` is whatever the user's\n\t\t// `capture` returned at the time of the original publish. We\n\t\t// re-run `capture(entry.output)` so warm restarts with renamed\n\t\t// capture keys (a) reflect the new shape in the registry without\n\t\t// requiring a republish, and (b) FAIL LOUDLY on user-callback\n\t\t// throws — the symmetric counterpart to the produce-time guard.\n\t\tconst cacheHit = producedOutput === null;\n\t\tif (cacheHit && inputs.capture !== undefined && entry.output !== undefined) {\n\t\t\tconst recomputedCaptured: Readonly<Record<string, string>> = yield* Effect.try({\n\t\t\t\ttry: () => inputs.capture!(entry.output!),\n\t\t\t\tcatch: (cause): PublishError =>\n\t\t\t\t\tpublishError('parse', {\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\tmessage: 'capture callback threw',\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t}).pipe(\n\t\t\t\t// Match the produce-body `mapError` projection (L373-379)\n\t\t\t\t// so callers see ONE consistent failure shape on\n\t\t\t\t// `capture` throws — `ArtifactPublishError('produce-failed')`\n\t\t\t\t// with `detail: \"package.publish parse: capture callback\n\t\t\t\t// threw\"` — regardless of whether the bug surfaces on a\n\t\t\t\t// cache miss (inside `produce`) or a cache hit (here).\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): ArtifactPublishError =>\n\t\t\t\t\t\tartifactPublishError('produce-failed', `package.publish ${err.phase}: ${err.message}`),\n\t\t\t\t),\n\t\t\t);\n\t\t\tyield* registry.set(inputs.packageName, {\n\t\t\t\tkind: 'local',\n\t\t\t\tname: inputs.packageName,\n\t\t\t\tpackageId: entry.packageId,\n\t\t\t\tupgradeCapId: entry.upgradeCapId,\n\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\tmvrPlaceholder: entry.mvrPlaceholder,\n\t\t\t\tcaptured: recomputedCaptured,\n\t\t\t});\n\t\t}\n\n\t\t// Project the cached entry back to the resolved shape. The\n\t\t// publisher returned the decoded `CachedPackageEntry`; re-read\n\t\t// the registry to recover the canonical `ResolvedLocalPackage`\n\t\t// shape (`register` ran on every cycle and wrote it).\n\t\tconst final = yield* registry.find(inputs.packageName);\n\t\tif (!final || final.kind !== 'local') {\n\t\t\t// Defensive — register fires unconditionally; missing entry\n\t\t\t// here would mean substrate skipped register, violating\n\t\t\t// Invariant 6. Surface as a parse-phase failure since it's\n\t\t\t// indistinguishable from a missing 'published' change from\n\t\t\t// the consumer's perspective.\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpublishError('parse', {\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t'register did not surface entry — artifact publisher register invariant violation',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tresolved: final,\n\t\t\toutput: producedOutput ?? entry.output ?? null,\n\t\t};\n\t});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mode-local.mjs","names":["brandContentHash"],"sources":["../../../src/plugins/package/mode-local.ts"],"sourcesContent":["// Local mode — build from source + publish.\n//\n// This is the canonical implementation of the\n// `ArtifactPublisher` substrate primitive (distilled doc\n// §Generic artifact-publisher-publish pattern). The five-phase shape:\n//\n// 1. Inputs — `{ sourceHash, signerAddress }` →\n// `contentHash(inputs)`.\n// 2. Cache key — `<namespace=package>/<chainId>/<contentHash>`.\n// Substrate folds in chainId for us; we hand\n// it the namespace + contentHash.\n// 3. Verify — `chainProbe.get({ objectId: cachedId },\n// PackageObjectSchema, 'lenient')`. Lenient mode\n// coerces transient RPC failure → null\n// → re-derive (cheap over-derive vs spurious\n// eviction — distilled doc §Constraint \"lenient\n// verify is cheaper than aggressive eviction\").\n// 4. Produce — scrub locks → build → publish-tx → wait-for-\n// index. Each phase narrates to TUI via span\n// annotation.\n// 5. Register — write registry entry on EVERY cycle (hit AND\n// miss). Distilled doc Invariant 6.\n//\n// The publish output is exposed on the resolved value and emitted as a\n// package-owned extension contribution for sibling folds.\n\nimport { Duration, Effect, Schema, type Scope } from 'effect';\n\nimport { contentHash as brandContentHash } from '../../substrate/brand.ts';\nimport type { LocalPackagePublishOutput } from './publish-output.ts';\nimport {\n\tartifactPublishError,\n\ttype ArtifactPublishError,\n\ttype ArtifactPublisher,\n} from '../../primitives/artifact-publisher.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { SuiProbeKey } from '../sui/index.ts';\nimport { hashMoveSources, scrubLocksHost, withMoveBuildLock, type BuildOutput } from './build.ts';\nimport { normalizeMvrPlaceholder } from './dep-resolution.ts';\nimport { type PackageRegistry, type ResolvedLocalPackage } from './registry.ts';\nimport { publishError, type PublishError } from './errors.ts';\n\n/** Cache-stored payload — the stable id verify re-confirms on\n * every cycle. Distilled doc Invariant 8: the probe MUST consume\n * a stable identifier (the packageId), NOT a derived hash. */\nexport interface CachedPackageEntry {\n\treadonly packageId: string;\n\treadonly upgradeCapId?: string;\n\treadonly publisher: string;\n\treadonly mvrPlaceholder: string;\n\treadonly captured: Readonly<Record<string, string>>;\n\treadonly output?: LocalPackagePublishOutput;\n}\n\n/** Verify-schema: what we expect when probing `getObject(packageId)`.\n * Minimal — the substrate's `ChainProbe` decodes against this; any\n * decode failure surfaces structured. */\nexport const PackageVerifyShape = Schema.Struct({\n\tobjectId: Schema.String,\n\t// A Move package's `type` is the literal `\"package\"` wrapper;\n\t// the SDK's exact shape varies — we keep it open as `Unknown`\n\t// and rely on objectId presence for the \"exists\" signal.\n\ttype: Schema.Unknown,\n});\n\n/**\n * Publish-tx executor — narrow shim that abstracts the concrete\n * `@mysten/sui` wiring (Transaction builder, signer, fullnode\n * ready-probe). The mode-local produce body composes its 5-phase\n * shape against this interface; the concrete implementation lives in\n * `publish-executor.ts` and is constructed once per acquire by the\n * barrel from the resolved `SuiClient` + publisher `AccountValue` +\n * `ContainerRuntime`.\n *\n * The shim parallels `coin/mint.ts::MintSigner` + `MintSdkShim`. We\n * keep them split (publish vs sign vs ready-probe) so the produce\n * body can attribute span annotations per phase + so failures map\n * back to a typed `PublishError` phase enum.\n */\nexport interface PublishExecutor {\n\treadonly scrubsInsideContainer: boolean;\n\n\t/** Build the Move source tree into `{ modules, dependencies }`.\n\t * Dispatches between (a) per-app build container, (b) fresh\n\t * `docker run --rm`, (c) host `sui` CLI. The produce body just\n\t * calls and propagates `PublishError`. */\n\treadonly build: (inputs: {\n\t\treadonly sourcePath: string;\n\t\treadonly packageName: string;\n\t\treadonly chainId: string;\n\t}) => Effect.Effect<BuildOutput, PublishError, Scope.Scope>;\n\n\t/** Construct + sign + execute a `Transaction.publish({modules,\n\t * dependencies})` against the publisher account. Returns the\n\t * output projection that downstream consumers (Coin, manifest,\n\t * capture spec) need. Distilled doc §Move-specific concerns —\n\t * the output is the source of `published` change + the\n\t * `UpgradeCap` `created` change. */\n\treadonly publishTx: (inputs: {\n\t\treadonly modules: ReadonlyArray<Uint8Array>;\n\t\treadonly dependencies: ReadonlyArray<string>;\n\t\treadonly sourcePath: string;\n\t\treadonly packageName: string;\n\t}) => Effect.Effect<LocalPackagePublishOutput, PublishError, Scope.Scope>;\n\n\t/** Post-publish fullnode/indexer ready HINT. Distilled doc\n\t * Invariant 5: publish-tx commit precedes index visibility. This\n\t * is BEST-EFFORT — the concrete executor swallows transient\n\t * `getObject` misses (cold index races) because the publisher\n\t * account's `signAndExecute` already calls `waitForTransaction`\n\t * before returning, AND the downstream `parse` phase only\n\t * inspects the publish output. A typed `PublishError('parse')`\n\t * surfaces only when the `getObject` infrastructure itself\n\t * faults (network down / SDK throws non-recoverably), NOT when\n\t * the object is merely not-yet-indexed.\n\t *\n\t * The \"Hint\" suffix encodes the contract: callers MUST NOT treat\n\t * a successful return as \"package definitely queryable now\". */\n\treadonly postPublishReadyHint: (\n\t\tpackageId: string,\n\t) => Effect.Effect<void, PublishError, Scope.Scope>;\n}\n\nexport interface LocalModeInputs {\n\treadonly packageName: string;\n\treadonly sourcePath: string;\n\treadonly chainId: string;\n\treadonly publisherAddress: string;\n\treadonly mvrOverride?: string;\n\treadonly capture?: (output: LocalPackagePublishOutput) => Readonly<Record<string, string>>;\n\t/** Publish executor — constructed per-acquire by the barrel from\n\t * the resolved SuiClient + publisher account + ContainerRuntime\n\t * (see `publish-executor.ts`). */\n\treadonly executor: PublishExecutor;\n}\n\nexport interface LocalModeOutputs {\n\treadonly resolved: ResolvedLocalPackage;\n\treadonly output: LocalPackagePublishOutput | null;\n}\n\n/**\n * Build the content hash for the publish cache key. Distilled doc\n * §Move-specific: inputs are `(sourceHash, signerAddress)`. The\n * `hashMoveSources` helper already normalises Move.lock pinned\n * sections via the shared `stripPinnedSections`; we fold the\n * publisher address in here as a stable string so reusing the\n * same source under a different signer correctly misses (Invariant\n * 4: \"Signer MUST be an explicit upstream\").\n */\nconst LOCAL_PACKAGE_CACHE_SCHEMA_VERSION = 'v3';\n// Short verify budget covering RPC index-visibility lag only. The big\n// post-restore catch-up window — `sui start` re-executes its committed\n// checkpoint store from seq=0, and `getObject(packageId)` reads not-found\n// until the replay reaches the publish checkpoint — is now absorbed by the\n// sui plugin's caught-up-to-head ready-gate (`waitForCheckpointCatchUp` in\n// plugins/sui/mode/local.ts): the validator does not report ready until its\n// head stabilizes to live cadence, so by the time this verify runs the\n// committed package object is already served. This budget only needs to ride\n// out the small fullnode/index lag between publish-tx commit and `getObject`\n// visibility — NOT the whole replay. If it gives up too soon it returns null →\n// the substrate re-PRODUCES (re-publishes) the package, minting a FRESH\n// packageId (codegen `config.ts` loses the stable id; downstream\n// `deepbookOf(...).packageId` churns). A truly-wiped chain genuinely has no\n// package and re-publishes once the budget lapses. Mirrors\n// WALRUS_DEPLOY_VERIFY_READINESS_* in plugins/walrus/deploy.ts — the on-chain\n// artifact-publisher consumers share the same short RPC-lag tolerance.\nconst PACKAGE_CACHE_VERIFY_MAX_ATTEMPTS = 20;\nconst PACKAGE_CACHE_VERIFY_DELAY_MS = 250;\n\nconst combineInputsHash = (sourceHash: string, publisherAddress: string) =>\n\tbrandContentHash(\n\t\t`${LOCAL_PACKAGE_CACHE_SCHEMA_VERSION}::source=${sourceHash}::publisher=${publisherAddress}`,\n\t);\n\n/**\n * Verify probe — lenient `getObject(packageId)`. Returns the decoded\n * shape on success; null for BOTH not-found AND transient (distilled\n * doc Invariant 7 + the \"lenient verify cheaper than aggressive\n * eviction\" Constraint).\n *\n * The cached id is hinted in via parameter. The artifact publisher substrate is\n * expected to thread the cached payload through to this closure on\n * cache hit; today we get the id from the registry's previous-cycle\n * entry (in-process, per-supervisor lookup — see `acquireLocal`\n * below). When no hint exists (first cycle / cold boot), we\n * short-circuit to null so the substrate runs `produce`.\n *\n * Mirrors `coin/mint.ts::buildVerifyProbe` exactly so the two artifact publisher\n * consumers stay shape-aligned.\n */\nexport const buildVerifyProbe = (\n\tprobe: ChainProbe<SuiProbeKey>,\n\tcachedPackageIdHint: string | null,\n\topts?: { readonly maxAttempts?: number; readonly delayMs?: number },\n): Effect.Effect<typeof PackageVerifyShape.Type | null, never> =>\n\tEffect.gen(function* () {\n\t\tif (cachedPackageIdHint === null) return null;\n\t\tconst maxAttempts = opts?.maxAttempts ?? PACKAGE_CACHE_VERIFY_MAX_ATTEMPTS;\n\t\tconst delayMs = opts?.delayMs ?? PACKAGE_CACHE_VERIFY_DELAY_MS;\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\t// Lenient mode in the underlying probe already coerces\n\t\t\t// not-found AND transient → null. We retry nulls briefly to\n\t\t\t// ride out the small fullnode/index lag between publish-tx\n\t\t\t// commit and `getObject` visibility; the validator's larger\n\t\t\t// post-restore replay is already gated upstream by the sui\n\t\t\t// plugin's caught-up-to-head ready-gate, so a false cache miss\n\t\t\t// does not turn a warm restart into an unnecessary publish.\n\t\t\tconst result: typeof PackageVerifyShape.Type | null = yield* probe\n\t\t\t\t.get({ kind: 'object', objectId: cachedPackageIdHint }, PackageVerifyShape, 'lenient')\n\t\t\t\t.pipe(\n\t\t\t\t\t// The probe's own error channel is `ChainProbeError`; under\n\t\t\t\t\t// lenient mode only `decode-failed` surfaces (not-found +\n\t\t\t\t\t// transient already coerce to null). A decode failure on\n\t\t\t\t\t// verify is treated as \"stale shape\" — null so the substrate\n\t\t\t\t\t// re-publishes rather than carry forward a mismatch.\n\t\t\t\t\tEffect.catch(() => Effect.succeed(null as typeof PackageVerifyShape.Type | null)),\n\t\t\t\t);\n\t\t\tif (result !== null) return result;\n\t\t\tif (attempt < maxAttempts && delayMs > 0) {\n\t\t\t\tyield* Effect.sleep(Duration.millis(delayMs));\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t});\n\n/**\n * Compose the ArtifactSpec for a local publish.\n *\n * The substrate-side `ArtifactPublisher.publish` consumes\n * this spec and handles:\n *\n * - cache lookup under `<namespace>/<chainId>/<contentHash>`;\n * - verify (lenient) when there's a hit;\n * - re-running `produce` on miss OR verify-fail;\n * - calling `register` on EVERY cycle.\n */\nexport const acquireLocal = (\n\tpublisher: ArtifactPublisher,\n\tprobe: ChainProbe<SuiProbeKey>,\n\tregistry: PackageRegistry,\n\tinputs: LocalModeInputs,\n): Effect.Effect<LocalModeOutputs, PublishError | ArtifactPublishError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\t// Intra-stack name collision guard.\n\t\t//\n\t\t// The registry is keyed by symbolic `packageName`; two\n\t\t// `localPackage('foo', ...)` calls in the same stack would both\n\t\t// call `register({ name: 'foo', ... })` and the second `set`\n\t\t// would silently overwrite the first. Upstream's\n\t\t// `resolveGraph` (substrate/runtime/lifecycle/dep-graph.ts:127)\n\t\t// explicitly documents \"we don't enforce uniqueness at runtime;\n\t\t// the duplicate just resolves to the latest declaration\" —\n\t\t// compile-time `MissingProviders` only catches collisions on\n\t\t// the user-typed dependency path, NOT two members declared on\n\t\t// the same stack with the same id. We catch the collision here\n\t\t// so the user sees a typed parse-phase failure instead of a\n\t\t// confusing \"wrong packageId\" downstream.\n\t\t//\n\t\t// The check tolerates re-entry from the SAME `localPackage(...)`\n\t\t// call (warm restart / re-acquire on the same scope): a\n\t\t// pre-existing entry with the SAME `sourcePath` is the\n\t\t// previous cycle's register, not a collision.\n\t\tconst existing = yield* registry.find(inputs.packageName);\n\t\tif (\n\t\t\texisting !== null &&\n\t\t\texisting.kind === 'local' &&\n\t\t\texisting.sourcePath !== inputs.sourcePath\n\t\t) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpublishError('parse', {\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`localPackage('${inputs.packageName}') is declared twice in the same stack ` +\n\t\t\t\t\t\t`with different sourcePaths (${existing.sourcePath} vs ${inputs.sourcePath}). ` +\n\t\t\t\t\t\t`The substrate is name-blind at runtime and the second declaration would ` +\n\t\t\t\t\t\t`silently overwrite the first registry entry. Pick distinct package names ` +\n\t\t\t\t\t\t`(the symbolic name is the registry key consumers look up).`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// Distilled doc §Move-specific: hash inputs are `(sourceHash,\n\t\t// signerAddress)`. The hashing helper strips Move.lock pinned\n\t\t// sections (Invariant 2) so warm restarts hit the cache.\n\t\tconst sourceHash = yield* hashMoveSources(inputs.sourcePath);\n\t\tconst inputsHash = combineInputsHash(sourceHash, inputs.publisherAddress);\n\n\t\t// The placeholder is the package's current MVR named package. Defaults\n\t\t// use devstack's `@local/<slug>` form; explicit config values are\n\t\t// validated full MVR names and preserved verbatim.\n\t\tconst mvrPlaceholder = normalizeMvrPlaceholder(inputs.packageName, inputs.mvrOverride);\n\n\t\t// We capture the produce-side output out-of-band so the\n\t\t// returned `LocalModeOutputs.output` can expose it. The\n\t\t// substrate hands back the decoded `CachedPackageEntry` on\n\t\t// every path; `producedOutput` is the freshly-emitted output\n\t\t// from THIS cycle (cache miss only).\n\t\tlet producedOutput: LocalPackagePublishOutput | null = null;\n\n\t\tconst entry: CachedPackageEntry = yield* publisher.publish<\n\t\t\tCachedPackageEntry,\n\t\t\ttypeof PackageVerifyShape.Type\n\t\t>({\n\t\t\tnamespace: 'package',\n\t\t\tchain: inputs.chainId,\n\t\t\tcontentHash: inputsHash,\n\t\t\tverify: (cached) =>\n\t\t\t\tcached.output === undefined\n\t\t\t\t\t? Effect.succeed(null)\n\t\t\t\t\t: buildVerifyProbe(probe, cached.packageId),\n\t\t\t// Produce: scrub → build → publish-tx → wait-for-index → parse.\n\t\t\t// PublishError is the plugin-internal phase taxonomy; we map\n\t\t\t// it to `ArtifactPublishError` at the substrate boundary.\n\t\t\tproduce: Effect.gen(function* () {\n\t\t\t\t// Produce 1/5 + 2/5 — scrub locks, then build. Both touch the\n\t\t\t\t// shared `~/.move` git cache, so they run together under the\n\t\t\t\t// process-wide Move-build permit (`withMoveBuildLock`): otherwise a\n\t\t\t\t// concurrent build's host scrub (`gawk -i inplace`) races a sibling\n\t\t\t\t// container's live `git clone` into the same cache dir and corrupts\n\t\t\t\t// the in-flight fetch. Publish-tx (3/5 below) stays OUTSIDE the lock\n\t\t\t\t// so on-chain work of different packages still overlaps.\n\t\t\t\tconst buildOutput: BuildOutput = yield* withMoveBuildLock(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t// Produce 1/5 — scrub locks. Distilled doc §Move-specific\n\t\t\t\t\t\t// concerns + Invariant 14: strip pinned sections from every\n\t\t\t\t\t\t// `~/.move/git/**/Move.lock` (vendored dep caches) before\n\t\t\t\t\t\t// invoking the build. The package's own Move.lock is scrubbed\n\t\t\t\t\t\t// inside the container on a disposable copy, so the developer's\n\t\t\t\t\t\t// checked-in source is left untouched host-side. Uses the\n\t\t\t\t\t\t// unified `stripPinnedSections` (re-exported through `build.ts`\n\t\t\t\t\t\t// → from `../sui/move-lock-scrub.ts`) — NO duplicate.\n\t\t\t\t\t\tyield* scrubLocksHost(inputs.sourcePath, '~/.move');\n\n\t\t\t\t\t\t// Produce 2/5 — build. Executor dispatches between (a)\n\t\t\t\t\t\t// per-app build container, (b) `docker run --rm`, (c) host\n\t\t\t\t\t\t// `sui` CLI.\n\t\t\t\t\t\treturn yield* inputs.executor\n\t\t\t\t\t\t\t.build({\n\t\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\t\tchainId: inputs.chainId,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.catchTag(\n\t\t\t\t\t\t\t\t\t'PublishError',\n\t\t\t\t\t\t\t\t\t(err): Effect.Effect<BuildOutput, PublishError> =>\n\t\t\t\t\t\t\t\t\t\t// Re-stamp sourcePath/packageName if the underlying\n\t\t\t\t\t\t\t\t\t\t// caller omitted them (every throw site MUST surface\n\t\t\t\t\t\t\t\t\t\t// the context — see distilled doc §Opportunities).\n\t\t\t\t\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\t\t\t\t\terr.sourcePath\n\t\t\t\t\t\t\t\t\t\t\t\t? err\n\t\t\t\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t...err,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t),\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\n\t\t\t\t// Produce 3/5 — publish-tx. Construct `Transaction.publish`,\n\t\t\t\t// sign + execute via the publisher's account signer, decode\n\t\t\t\t// the output.\n\t\t\t\tconst output: LocalPackagePublishOutput = yield* inputs.executor.publishTx({\n\t\t\t\t\tmodules: buildOutput.modules,\n\t\t\t\t\tdependencies: buildOutput.dependencies,\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t});\n\t\t\t\tproducedOutput = output;\n\n\t\t\t\t// Produce 4/5 — wait-for-index. Distilled doc Invariant 5:\n\t\t\t\t// publish-tx commit precedes index visibility. This is a\n\t\t\t\t// hint-only probe (see `postPublishReadyHint` doc on\n\t\t\t\t// `PublishExecutor`): the publisher account's\n\t\t\t\t// `signAndExecute` already awaits `waitForTransaction`, so\n\t\t\t\t// the typical race is closed before we reach here.\n\t\t\t\t// Best-effort only: the executor's hint swallows transient\n\t\t\t\t// `getObject` misses and logs at debug, so its error channel\n\t\t\t\t// collapses to `never` (see `publish-executor.ts →\n\t\t\t\t// postPublishReadyHint`). No re-stamp is needed because no\n\t\t\t\t// failure surfaces here.\n\t\t\t\tyield* inputs.executor.postPublishReadyHint(output.packageId);\n\n\t\t\t\t// Capture spec — user-declared projection from output to\n\t\t\t\t// typed object id map. Distilled doc §Outputs: the callback\n\t\t\t\t// is user code, so a throw is a USER BUG (typo / missing\n\t\t\t\t// field) — surface as `PublishError('parse')` so the user\n\t\t\t\t// catches the mistake instead of silently shipping a stale\n\t\t\t\t// captured map. The cache-HIT path performs the symmetric\n\t\t\t\t// recompute post-`publisher.publish` (see comment block\n\t\t\t\t// below the `publisher.publish` call) — both paths bubble\n\t\t\t\t// the same `PublishError('parse')` shape.\n\t\t\t\tconst captured: Readonly<Record<string, string>> = inputs.capture\n\t\t\t\t\t? yield* Effect.try({\n\t\t\t\t\t\t\ttry: () => inputs.capture!(output),\n\t\t\t\t\t\t\tcatch: (cause): PublishError =>\n\t\t\t\t\t\t\t\tpublishError('parse', {\n\t\t\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\t\t\tmessage: 'capture callback threw',\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t})\n\t\t\t\t\t: {};\n\n\t\t\t\tconst entry: CachedPackageEntry = {\n\t\t\t\t\tpackageId: output.packageId,\n\t\t\t\t\tupgradeCapId: output.upgradeCapId,\n\t\t\t\t\tpublisher: inputs.publisherAddress,\n\t\t\t\t\tmvrPlaceholder,\n\t\t\t\t\tcaptured,\n\t\t\t\t\toutput,\n\t\t\t\t};\n\t\t\t\treturn entry;\n\t\t\t}).pipe(\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): ArtifactPublishError =>\n\t\t\t\t\t\tartifactPublishError('produce-failed', `package.publish ${err.phase}: ${err.message}`),\n\t\t\t\t),\n\t\t\t),\n\t\t\t// Register: on EVERY cycle. Distilled doc Invariant 6. The\n\t\t\t// substrate hands the decoded `CachedPackageEntry` payload\n\t\t\t// here on both verify-hit and freshly-produced paths.\n\t\t\t//\n\t\t\t// Register writes the entry verbatim using `artifact.captured`\n\t\t\t// (produce-time on miss; cached on hit). The user's `capture`\n\t\t\t// callback is NOT re-run here — `register`'s contract is\n\t\t\t// `Effect.Effect<void, never>` (primitives/artifact-publisher.ts)\n\t\t\t// and a user-bug throw needs a typed failure channel. The\n\t\t\t// post-publish recompute below (cache-hit branch) re-runs\n\t\t\t// `capture` so renamed/typo keys surface IDENTICALLY to the\n\t\t\t// cache-miss path (mode-local.ts:346-357) — i.e., as\n\t\t\t// `PublishError('parse')`.\n\t\t\tregister: (artifact) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst r: ResolvedLocalPackage = {\n\t\t\t\t\t\tkind: 'local',\n\t\t\t\t\t\tname: inputs.packageName,\n\t\t\t\t\t\tpackageId: artifact.packageId,\n\t\t\t\t\t\tupgradeCapId: artifact.upgradeCapId,\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tmvrPlaceholder,\n\t\t\t\t\t\tcaptured: artifact.captured,\n\t\t\t\t\t};\n\t\t\t\t\tyield* registry.set(r.name, r);\n\t\t\t\t}),\n\t\t});\n\n\t\t// Post-publish capture recompute — cache-hit ONLY.\n\t\t//\n\t\t// Symmetric with the produce-time capture (L346-357): the user's\n\t\t// `capture` callback is user code; a throw is a user bug (typo /\n\t\t// renamed key against a stale cached output) and MUST surface as\n\t\t// `PublishError('parse')` so the user sees the mistake instead of\n\t\t// silently carrying forward `artifact.captured` — swallowing the\n\t\t// throw with `catch { return artifact.captured }` would hide\n\t\t// renamed keys behind stale data.\n\t\t//\n\t\t// Cache miss: `producedOutput` was set inside `produce`, the\n\t\t// produce-time capture already ran (Effect.try → PublishError),\n\t\t// and the substrate cached the produced entry. Re-running here\n\t\t// would be redundant and would double-throw on user bugs.\n\t\t//\n\t\t// Cache hit: `producedOutput` is null, `entry.output` came from\n\t\t// the cached payload, and `entry.captured` is whatever the user's\n\t\t// `capture` returned at the time of the original publish. We\n\t\t// re-run `capture(entry.output)` so warm restarts with renamed\n\t\t// capture keys (a) reflect the new shape in the registry without\n\t\t// requiring a republish, and (b) FAIL LOUDLY on user-callback\n\t\t// throws — the symmetric counterpart to the produce-time guard.\n\t\tconst cacheHit = producedOutput === null;\n\t\tif (cacheHit && inputs.capture !== undefined && entry.output !== undefined) {\n\t\t\tconst recomputedCaptured: Readonly<Record<string, string>> = yield* Effect.try({\n\t\t\t\ttry: () => inputs.capture!(entry.output!),\n\t\t\t\tcatch: (cause): PublishError =>\n\t\t\t\t\tpublishError('parse', {\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\tmessage: 'capture callback threw',\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t}).pipe(\n\t\t\t\t// Match the produce-body `mapError` projection (L373-379)\n\t\t\t\t// so callers see ONE consistent failure shape on\n\t\t\t\t// `capture` throws — `ArtifactPublishError('produce-failed')`\n\t\t\t\t// with `detail: \"package.publish parse: capture callback\n\t\t\t\t// threw\"` — regardless of whether the bug surfaces on a\n\t\t\t\t// cache miss (inside `produce`) or a cache hit (here).\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): ArtifactPublishError =>\n\t\t\t\t\t\tartifactPublishError('produce-failed', `package.publish ${err.phase}: ${err.message}`),\n\t\t\t\t),\n\t\t\t);\n\t\t\tyield* registry.set(inputs.packageName, {\n\t\t\t\tkind: 'local',\n\t\t\t\tname: inputs.packageName,\n\t\t\t\tpackageId: entry.packageId,\n\t\t\t\tupgradeCapId: entry.upgradeCapId,\n\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\tmvrPlaceholder,\n\t\t\t\tcaptured: recomputedCaptured,\n\t\t\t});\n\t\t}\n\n\t\t// Project the cached entry back to the resolved shape. The\n\t\t// publisher returned the decoded `CachedPackageEntry`; re-read\n\t\t// the registry to recover the canonical `ResolvedLocalPackage`\n\t\t// shape (`register` ran on every cycle and wrote it).\n\t\tconst final = yield* registry.find(inputs.packageName);\n\t\tif (!final || final.kind !== 'local') {\n\t\t\t// Defensive — register fires unconditionally; missing entry\n\t\t\t// here would mean substrate skipped register, violating\n\t\t\t// Invariant 6. Surface as a parse-phase failure since it's\n\t\t\t// indistinguishable from a missing 'published' change from\n\t\t\t// the consumer's perspective.\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpublishError('parse', {\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t'register did not surface entry — artifact publisher register invariant violation',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tresolved: final,\n\t\t\toutput: producedOutput ?? entry.output ?? null,\n\t\t};\n\t});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chain-build-container.mjs","names":[],"sources":["../../../src/plugins/sui/chain-build-container.ts"],"sourcesContent":["// Per-app long-lived Move build container.\n//\n// Architecture Decision §5: the build container is PER-APP, NOT\n// per-stack. Two parallel stacks of the same app share the same\n// sleeper container — their concurrent Move builds serialise on\n// docker exec queueing. This is intentional: dep-cache reuse\n// across stacks outweighs the serialisation cost.\n//\n// Adopt-or-create state machine is delegated to the substrate's\n// `ContainerRuntime.ensureContainer` with `recreate: 'never'` —\n// daemon outages must fail loudly, not silently churn (distilled\n// doc S6: \"MUST reject the helper's auto-recreate-on-resume-failed\n// path\").\n//\n// Cross-process safety: the host-wide advisory lock is owned by\n// THIS module. The lock is held inside the build call (NOT at the\n// docker-exec layer), because three distinct execution paths (host\n// CLI, fresh `docker run --rm`, container exec) all need\n// protection. Lock path: `~/.devstack/locks/sui-move-build-<repoHash>.lock`.\n\nimport { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { Effect, type Scope } from 'effect';\n\nimport type {\n\tContainerHandle,\n\tContainerRuntime,\n\tContainerRuntimeError,\n\tEnsureContainerSpec,\n\tImageRef,\n} from '../../contracts/container-runtime.ts';\nimport { acquireStackLock } from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport {\n\tensureManagedContainer,\n\tPER_APP_SHARED_STACK,\n} from '../../substrate/runtime/managed-container.ts';\nimport { containerInnerScript } from './move/index.ts';\nimport { suiCliError, suiPluginError, type SuiCliError, type SuiPluginError } from './errors.ts';\n\n/** Default move-build lock timeout — five minutes, matching the\n * distilled-doc invariant. The lock is held during the build\n * body only; long-running stages never share it. */\nexport const MOVE_BUILD_LOCK_TIMEOUT_MS = 5 * 60_000;\n\nconst BUILD_CONTAINER_STOP_GRACE_SECONDS = 2;\nconst BUILD_CONTAINER_KEEPALIVE_COMMAND = [\n\t'child=;',\n\t'trap \\'[ -z \"$child\" ] || kill \"$child\" 2>/dev/null || true; exit 0\\' INT TERM;',\n\t'sleep infinity & child=$!;',\n\t'wait \"$child\"',\n].join(' ');\n\n/** Per-app container name. The substrate's `pluginKey('sui-build')`\n * + the app discriminator combine to a stable name; the container\n * intentionally omits the stack/network suffix so two stacks of\n * the same app share. */\nexport const containerNameForApp = (app: string): string => `devstack-${app}-build`;\n\nconst expandHome = (path: string): string =>\n\tpath === '~' ? homedir() : path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;\n\nconst repoHashForLock = (appDir: string): string =>\n\tcreateHash('sha256').update(resolve(appDir)).digest('hex').slice(0, 16);\n\nexport const moveBuildLockPathFor = (appDir: string, moveHome: string): string => {\n\tconst moveHomeRoot = dirname(resolve(expandHome(moveHome)));\n\treturn join(moveHomeRoot, '.devstack', 'locks', `sui-move-build-${repoHashForLock(appDir)}.lock`);\n};\n\n/** Build-container handle returned to plugin internals (the\n * cli-driver dispatches into this). */\nexport interface ChainBuildContainer {\n\treadonly handle: ContainerHandle;\n\t/** Translate a host-absolute path into a container-bind path.\n\t * Returns null when the host path escapes the bind-mounted app\n\t * dir — callers MUST fall back to `docker run --rm` in that\n\t * case (distilled-doc: \"Build container path translation MUST\n\t * refuse paths outside the bind-mounted app dir\"). */\n\treadonly toContainerPath: (hostPath: string) => string | null;\n\t/** Run a sui-cli capture inside the container. Acquires the\n\t * host-wide move-build lock before invoking docker-exec. */\n\treadonly runBuild: (\n\t\thostPackagePath: string,\n\t) => Effect.Effect<\n\t\t{ readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\t\tSuiCliError,\n\t\tScope.Scope\n\t>;\n\t/** Run a codegen-style \"summary\" build inside the container.\n\t * Same wire shape as runBuild; the codegen plugin consumes\n\t * this via the cross-service seam. */\n\treadonly runSummary: (\n\t\thostPackagePath: string,\n\t) => Effect.Effect<\n\t\t{ readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\t\tSuiCliError,\n\t\tScope.Scope\n\t>;\n}\n\n/** Spec passed to `ContainerRuntime.ensureContainer` for the\n * per-app sleeper. Image is content-hashed; labels carry the\n * app discriminator so `inspectByLabels` finds it across stacks. */\nexport interface ChainBuildContainerSpec {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly appDir: string;\n\treadonly moveHome: string;\n\treadonly image: ImageRef;\n}\n\n/**\n * Acquire (or adopt) the per-app build container. Returns a\n * `ChainBuildContainer` handle scoped to the caller's Scope.\n *\n * The build call holds the host-wide move-build lock for the docker\n * exec body. The lock uses the substrate's PID + start-time stale\n * holder reclaim path, so a crashed peer cannot wedge future builds.\n */\nexport const acquireChainBuildContainer = (\n\truntime: ContainerRuntime,\n\tspec: ChainBuildContainerSpec,\n): Effect.Effect<ChainBuildContainer, ContainerRuntimeError | SuiPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\t// The sleeper shell traps Docker's stop signal so scope-close\n\t\t// does not wait for Docker's default SIGKILL escalation path.\n\t\t// This also overrides the sui image's default `start\n\t\t// --with-faucet=...` entrypoint so the validator binary doesn't\n\t\t// run genesis inside a build container.\n\t\t// The appDir is bind-mounted at `/workspace`, with the user's\n\t\t// `~/.move` bind-mounted at `/root/.move` so the content-addressed\n\t\t// dep cache (`~/.move/git/<repo>@<sha>/…`) persists across builds.\n\t\tconst ensureSpec: EnsureContainerSpec = {\n\t\t\tname: containerNameForApp(spec.app),\n\t\t\timage: spec.image,\n\t\t\tlabels: {\n\t\t\t\tapp: spec.app,\n\t\t\t\t// Intentionally pin stack to a sentinel — see comment\n\t\t\t\t// above; this is per-app, not per-stack.\n\t\t\t\tstack: PER_APP_SHARED_STACK,\n\t\t\t\tplugin: 'sui',\n\t\t\t\trole: 'build',\n\t\t\t},\n\t\t\trecreate: 'never',\n\t\t\tentrypoint: 'sh',\n\t\t\tcommand: ['-c', BUILD_CONTAINER_KEEPALIVE_COMMAND],\n\t\t\tstopGraceSeconds: BUILD_CONTAINER_STOP_GRACE_SECONDS,\n\t\t\tmounts: [\n\t\t\t\t{ source: spec.appDir, target: '/workspace' },\n\t\t\t\t{ source: spec.moveHome, target: '/root/.move' },\n\t\t\t],\n\t\t};\n\t\tconst { labels, ...containerSpec } = ensureSpec;\n\t\tconst handle = yield* ensureManagedContainer({\n\t\t\truntime,\n\t\t\tlabels,\n\t\t\tspec: containerSpec,\n\t\t\tmapError: (cause) => cause,\n\t\t});\n\n\t\tconst toContainerPath = (hostPath: string): string | null => {\n\t\t\t// The runtime adapter bind-mounts `spec.appDir` at\n\t\t\t// `/workspace`. A host path outside that bind is not\n\t\t\t// accessible to the container.\n\t\t\tif (!hostPath.startsWith(spec.appDir)) return null;\n\t\t\tconst rel = hostPath.slice(spec.appDir.length).replace(/^\\/+/, '');\n\t\t\treturn rel === '' ? '/workspace' : `/workspace/${rel}`;\n\t\t};\n\t\tconst moveBuildLockPath = moveBuildLockPathFor(spec.appDir, spec.moveHome);\n\n\t\tconst runInContainer = (\n\t\t\top: 'build' | 'summary',\n\t\t\thostPackagePath: string,\n\t\t): Effect.Effect<\n\t\t\t{ readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\t\t\tSuiCliError,\n\t\t\tScope.Scope\n\t\t> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst containerPath = toContainerPath(hostPackagePath);\n\t\t\t\tif (containerPath === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tsuiCliError(op, {\n\t\t\t\t\t\t\tcause: new Error(\n\t\t\t\t\t\t\t\t`chain-build-container: host path ${hostPackagePath} escapes appDir bind (${spec.appDir}); ` +\n\t\t\t\t\t\t\t\t\t`caller MUST fall back to docker run --rm`,\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// The package's container path = `/workspace/<basename>`.\n\t\t\t\t// containerInnerScript copies that subdir into an in-container\n\t\t\t\t// scratch dir and scrubs + builds THERE, so the bind-mounted\n\t\t\t\t// appDir source (`spec.appDir` at `/workspace`) is never\n\t\t\t\t// rewritten by the build.\n\t\t\t\tconst pkgName = containerPath.replace(/^\\/workspace\\//, '').replace(/^\\/+|\\/+$/g, '');\n\t\t\t\tconst inner = containerInnerScript(pkgName);\n\t\t\t\tconst result = yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(moveBuildLockPath, MOVE_BUILD_LOCK_TIMEOUT_MS).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): SuiCliError =>\n\t\t\t\t\t\t\t\t\tsuiCliError(op, {\n\t\t\t\t\t\t\t\t\t\tcause: new Error(`move-build lock acquire failed: ${cause._tag}`, { cause }),\n\t\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\treturn yield* runtime.exec(handle, ['sh', '-c', inner]).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): SuiCliError =>\n\t\t\t\t\t\t\t\t\tsuiCliError(op, {\n\t\t\t\t\t\t\t\t\t\tcause: new Error(`runtime.exec failed: ${cause.reason}: ${cause.detail}`),\n\t\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}),\n\t\t\t\t);\n\t\t\t\treturn result;\n\t\t\t});\n\n\t\tconst runBuild = (hostPackagePath: string) => runInContainer('build', hostPackagePath);\n\t\tconst runSummary = (hostPackagePath: string) => runInContainer('summary', hostPackagePath);\n\n\t\treturn { handle, toContainerPath, runBuild, runSummary };\n\t}).pipe(\n\t\tEffect.mapError((cause) =>\n\t\t\tisContainerRuntimeError(cause)\n\t\t\t\t? cause\n\t\t\t\t: suiPluginError('image-build', 'chain-build-container.acquire failed', cause),\n\t\t),\n\t);\n\nconst isContainerRuntimeError = (e: unknown): e is ContainerRuntimeError =>\n\ttypeof e === 'object' && e !== null && (e as { _tag?: string })._tag === 'ContainerRuntimeError';\n"],"mappings":";;;;;;;;AA8C0C;CACzC;CACA;CACA;CACA;AACD,CAAC,CAAC,KAAK,GAAG"}
|
|
1
|
+
{"version":3,"file":"chain-build-container.mjs","names":[],"sources":["../../../src/plugins/sui/chain-build-container.ts"],"sourcesContent":["// Per-app long-lived Move build container.\n//\n// Architecture Decision §5: the build container is PER-APP, NOT\n// per-stack. Two parallel stacks of the same app share the same\n// sleeper container — their concurrent Move builds serialise on\n// docker exec queueing. This is intentional: dep-cache reuse\n// across stacks outweighs the serialisation cost.\n//\n// Adopt-or-create state machine is delegated to the substrate's\n// `ContainerRuntime.ensureContainer` with `recreate: 'never'` —\n// daemon outages must fail loudly, not silently churn (distilled\n// doc S6: \"MUST reject the helper's auto-recreate-on-resume-failed\n// path\").\n//\n// Cross-process safety: the host-wide advisory lock is owned by\n// THIS module. The lock is held inside the build call (NOT at the\n// docker-exec layer), because three distinct execution paths (host\n// CLI, fresh `docker run --rm`, container exec) all need\n// protection. Lock path: `~/.devstack/locks/sui-move-build-<repoHash>.lock`.\n\nimport { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { Effect, type Scope } from 'effect';\n\nimport type {\n\tContainerHandle,\n\tContainerRuntime,\n\tContainerRuntimeError,\n\tEnsureContainerSpec,\n\tImageRef,\n} from '../../contracts/container-runtime.ts';\nimport { acquireStackLock } from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport {\n\tensureManagedContainer,\n\tPER_APP_SHARED_STACK,\n} from '../../substrate/runtime/managed-container.ts';\nimport { containerInnerScript, type MoveBuildEnv, type MoveBuildOptions } from './move/index.ts';\nimport { suiCliError, suiPluginError, type SuiCliError, type SuiPluginError } from './errors.ts';\n\n/** Default move-build lock timeout — five minutes, matching the\n * distilled-doc invariant. The lock is held during the build\n * body only; long-running stages never share it. */\nexport const MOVE_BUILD_LOCK_TIMEOUT_MS = 5 * 60_000;\n\nconst BUILD_CONTAINER_STOP_GRACE_SECONDS = 2;\nconst BUILD_CONTAINER_KEEPALIVE_COMMAND = [\n\t'child=;',\n\t'trap \\'[ -z \"$child\" ] || kill \"$child\" 2>/dev/null || true; exit 0\\' INT TERM;',\n\t'sleep infinity & child=$!;',\n\t'wait \"$child\"',\n].join(' ');\n\n/** Per-app container name. The substrate's `pluginKey('sui-build')`\n * + the app discriminator combine to a stable name; the container\n * intentionally omits the stack/network suffix so two stacks of\n * the same app share. */\nexport const containerNameForApp = (app: string): string => `devstack-${app}-build`;\n\nconst expandHome = (path: string): string =>\n\tpath === '~' ? homedir() : path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;\n\nconst repoHashForLock = (appDir: string): string =>\n\tcreateHash('sha256').update(resolve(appDir)).digest('hex').slice(0, 16);\n\nexport const moveBuildLockPathFor = (appDir: string, moveHome: string): string => {\n\tconst moveHomeRoot = dirname(resolve(expandHome(moveHome)));\n\treturn join(moveHomeRoot, '.devstack', 'locks', `sui-move-build-${repoHashForLock(appDir)}.lock`);\n};\n\n/** Build-container handle returned to plugin internals (the\n * cli-driver dispatches into this). */\nexport interface ChainBuildContainer {\n\treadonly handle: ContainerHandle;\n\t/** Translate a host-absolute path into a container-bind path.\n\t * Returns null when the host path escapes the bind-mounted app\n\t * dir — callers MUST fall back to `docker run --rm` in that\n\t * case (distilled-doc: \"Build container path translation MUST\n\t * refuse paths outside the bind-mounted app dir\"). */\n\treadonly toContainerPath: (hostPath: string) => string | null;\n\t/** Run a sui-cli capture inside the container. Acquires the\n\t * host-wide move-build lock before invoking docker-exec. */\n\treadonly runBuild: (\n\t\thostPackagePath: string,\n\t\toptions?: MoveBuildOptions,\n\t) => Effect.Effect<\n\t\t{ readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\t\tSuiCliError,\n\t\tScope.Scope\n\t>;\n\t/** Run a codegen-style \"summary\" build inside the container.\n\t * Same wire shape as runBuild; the codegen plugin consumes\n\t * this via the cross-service seam. */\n\treadonly runSummary: (\n\t\thostPackagePath: string,\n\t\toptions?: MoveBuildOptions,\n\t) => Effect.Effect<\n\t\t{ readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\t\tSuiCliError,\n\t\tScope.Scope\n\t>;\n}\n\n/** Spec passed to `ContainerRuntime.ensureContainer` for the\n * per-app sleeper. Image is content-hashed; labels carry the\n * app discriminator so `inspectByLabels` finds it across stacks. */\nexport interface ChainBuildContainerSpec {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly appDir: string;\n\treadonly moveHome: string;\n\treadonly image: ImageRef;\n\treadonly buildEnv?: MoveBuildEnv;\n}\n\n/**\n * Acquire (or adopt) the per-app build container. Returns a\n * `ChainBuildContainer` handle scoped to the caller's Scope.\n *\n * The build call holds the host-wide move-build lock for the docker\n * exec body. The lock uses the substrate's PID + start-time stale\n * holder reclaim path, so a crashed peer cannot wedge future builds.\n */\nexport const acquireChainBuildContainer = (\n\truntime: ContainerRuntime,\n\tspec: ChainBuildContainerSpec,\n): Effect.Effect<ChainBuildContainer, ContainerRuntimeError | SuiPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\t// The sleeper shell traps Docker's stop signal so scope-close\n\t\t// does not wait for Docker's default SIGKILL escalation path.\n\t\t// This also overrides the sui image's default `start\n\t\t// --with-faucet=...` entrypoint so the validator binary doesn't\n\t\t// run genesis inside a build container.\n\t\t// The appDir is bind-mounted at `/workspace`, with the user's\n\t\t// `~/.move` bind-mounted at `/root/.move` so the content-addressed\n\t\t// dep cache (`~/.move/git/<repo>@<sha>/…`) persists across builds.\n\t\tconst ensureSpec: EnsureContainerSpec = {\n\t\t\tname: containerNameForApp(spec.app),\n\t\t\timage: spec.image,\n\t\t\tlabels: {\n\t\t\t\tapp: spec.app,\n\t\t\t\t// Intentionally pin stack to a sentinel — see comment\n\t\t\t\t// above; this is per-app, not per-stack.\n\t\t\t\tstack: PER_APP_SHARED_STACK,\n\t\t\t\tplugin: 'sui',\n\t\t\t\trole: 'build',\n\t\t\t},\n\t\t\trecreate: 'never',\n\t\t\tentrypoint: 'sh',\n\t\t\tcommand: ['-c', BUILD_CONTAINER_KEEPALIVE_COMMAND],\n\t\t\tstopGraceSeconds: BUILD_CONTAINER_STOP_GRACE_SECONDS,\n\t\t\tmounts: [\n\t\t\t\t{ source: spec.appDir, target: '/workspace' },\n\t\t\t\t{ source: spec.moveHome, target: '/root/.move' },\n\t\t\t],\n\t\t};\n\t\tconst { labels, ...containerSpec } = ensureSpec;\n\t\tconst handle = yield* ensureManagedContainer({\n\t\t\truntime,\n\t\t\tlabels,\n\t\t\tspec: containerSpec,\n\t\t\tmapError: (cause) => cause,\n\t\t});\n\n\t\tconst toContainerPath = (hostPath: string): string | null => {\n\t\t\t// The runtime adapter bind-mounts `spec.appDir` at\n\t\t\t// `/workspace`. A host path outside that bind is not\n\t\t\t// accessible to the container.\n\t\t\tif (!hostPath.startsWith(spec.appDir)) return null;\n\t\t\tconst rel = hostPath.slice(spec.appDir.length).replace(/^\\/+/, '');\n\t\t\treturn rel === '' ? '/workspace' : `/workspace/${rel}`;\n\t\t};\n\t\tconst moveBuildLockPath = moveBuildLockPathFor(spec.appDir, spec.moveHome);\n\n\t\tconst runInContainer = (\n\t\t\top: 'build' | 'summary',\n\t\t\thostPackagePath: string,\n\t\t\toptions?: MoveBuildOptions,\n\t\t): Effect.Effect<\n\t\t\t{ readonly exitCode: number; readonly stdout: string; readonly stderr: string },\n\t\t\tSuiCliError,\n\t\t\tScope.Scope\n\t\t> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst containerPath = toContainerPath(hostPackagePath);\n\t\t\t\tif (containerPath === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tsuiCliError(op, {\n\t\t\t\t\t\t\tcause: new Error(\n\t\t\t\t\t\t\t\t`chain-build-container: host path ${hostPackagePath} escapes appDir bind (${spec.appDir}); ` +\n\t\t\t\t\t\t\t\t\t`caller MUST fall back to docker run --rm`,\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// The package's container path = `/workspace/<basename>`.\n\t\t\t\t// containerInnerScript copies that subdir into an in-container\n\t\t\t\t// scratch dir and scrubs + builds THERE, so the bind-mounted\n\t\t\t\t// appDir source (`spec.appDir` at `/workspace`) is never\n\t\t\t\t// rewritten by the build.\n\t\t\t\tconst pkgName = containerPath.replace(/^\\/workspace\\//, '').replace(/^\\/+|\\/+$/g, '');\n\t\t\t\tconst buildEnv = options?.buildEnv ?? spec.buildEnv;\n\t\t\t\tconst inner =\n\t\t\t\t\tbuildEnv === undefined\n\t\t\t\t\t\t? containerInnerScript(pkgName)\n\t\t\t\t\t\t: containerInnerScript(pkgName, { buildEnv });\n\t\t\t\tconst result = yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(moveBuildLockPath, MOVE_BUILD_LOCK_TIMEOUT_MS).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): SuiCliError =>\n\t\t\t\t\t\t\t\t\tsuiCliError(op, {\n\t\t\t\t\t\t\t\t\t\tcause: new Error(`move-build lock acquire failed: ${cause._tag}`, { cause }),\n\t\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\treturn yield* runtime.exec(handle, ['sh', '-c', inner]).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): SuiCliError =>\n\t\t\t\t\t\t\t\t\tsuiCliError(op, {\n\t\t\t\t\t\t\t\t\t\tcause: new Error(`runtime.exec failed: ${cause.reason}: ${cause.detail}`),\n\t\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}),\n\t\t\t\t);\n\t\t\t\treturn result;\n\t\t\t});\n\n\t\tconst runBuild = (hostPackagePath: string, options?: MoveBuildOptions) =>\n\t\t\trunInContainer('build', hostPackagePath, options);\n\t\tconst runSummary = (hostPackagePath: string, options?: MoveBuildOptions) =>\n\t\t\trunInContainer('summary', hostPackagePath, options);\n\n\t\treturn { handle, toContainerPath, runBuild, runSummary };\n\t}).pipe(\n\t\tEffect.mapError((cause) =>\n\t\t\tisContainerRuntimeError(cause)\n\t\t\t\t? cause\n\t\t\t\t: suiPluginError('image-build', 'chain-build-container.acquire failed', cause),\n\t\t),\n\t);\n\nconst isContainerRuntimeError = (e: unknown): e is ContainerRuntimeError =>\n\ttypeof e === 'object' && e !== null && (e as { _tag?: string })._tag === 'ContainerRuntimeError';\n"],"mappings":";;;;;;;;AA8C0C;CACzC;CACA;CACA;CACA;AACD,CAAC,CAAC,KAAK,GAAG"}
|