@mysten-incubation/devstack 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -11
- package/dashboard-ui/assets/{grpc-Clz0oOtc.js → grpc-CpkDu4SA.js} +1 -1
- package/dashboard-ui/assets/{index-Cmqv9kiU.js → index-jLPRmjst.js} +2 -2
- package/dashboard-ui/index.html +1 -1
- package/dist/api/inference-network.d.mts +5 -5
- package/dist/api/inference-network.mjs +5 -5
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/run-stack-internal.mjs +1 -3
- package/dist/api/run-stack-internal.mjs.map +1 -1
- package/dist/build-integrations/dapp-kit/index.d.mts +12 -0
- package/dist/build-integrations/dapp-kit/index.mjs +14 -1
- package/dist/build-integrations/dapp-kit/index.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +1 -1
- package/dist/build-integrations/playwright/errors.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +2 -2
- package/dist/build-integrations/playwright/index.mjs +2 -2
- package/dist/build-integrations/playwright/wallet-context.d.mts +2 -1
- package/dist/build-integrations/playwright/wallet-context.mjs +141 -41
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
- package/dist/build-integrations/vite/index.d.mts +54 -42
- package/dist/build-integrations/vite/index.mjs +239 -106
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/build-integrations/vitest/global-setup.mjs +8 -8
- package/dist/build-integrations/vitest/global-setup.mjs.map +1 -1
- package/dist/cli/main.mjs +3 -2
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +0 -1
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/codegen.mjs +2 -3
- package/dist/cli/wirings/codegen.mjs.map +1 -1
- package/dist/cli/wirings/dump-deployment.mjs +141 -0
- package/dist/cli/wirings/dump-deployment.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +0 -1
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/contracts/codegenable.d.mts +22 -25
- package/dist/contracts/codegenable.mjs +35 -4
- package/dist/contracts/codegenable.mjs.map +1 -1
- package/dist/contracts/config-bindings.mjs +43 -18
- package/dist/contracts/config-bindings.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/orchestrators/boot.d.mts +0 -10
- package/dist/orchestrators/boot.mjs +19 -59
- package/dist/orchestrators/boot.mjs.map +1 -1
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/config-runtime.mjs +137 -112
- package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -1
- package/dist/orchestrators/codegen/deployment-network-file.mjs +130 -0
- package/dist/orchestrators/codegen/deployment-network-file.mjs.map +1 -0
- package/dist/orchestrators/codegen/deployment-strict.mjs +77 -0
- package/dist/orchestrators/codegen/deployment-strict.mjs.map +1 -0
- package/dist/orchestrators/codegen/deployment.d.mts +63 -0
- package/dist/orchestrators/codegen/deployment.mjs +153 -0
- package/dist/orchestrators/codegen/deployment.mjs.map +1 -0
- package/dist/orchestrators/codegen/errors.mjs +7 -9
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +20 -1
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/paths.mjs +1 -8
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +354 -141
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/layers.mjs +0 -1
- package/dist/orchestrators/layers.mjs.map +1 -1
- package/dist/orchestrators/network-options.d.mts +37 -35
- package/dist/orchestrators/network-options.mjs +13 -10
- package/dist/orchestrators/network-options.mjs.map +1 -1
- package/dist/plugins/account/codegen.d.mts +3 -4
- package/dist/plugins/account/codegen.mjs +5 -7
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/index.mjs +1 -3
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/coin/codegen.mjs +5 -5
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/root.mjs +3 -1
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
- package/dist/plugins/deepbook/codegen.mjs +4 -4
- package/dist/plugins/deepbook/codegen.mjs.map +1 -1
- package/dist/plugins/deepbook/index.d.mts +36 -6
- package/dist/plugins/deepbook/index.mjs +52 -16
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/internal/codegen-helpers.mjs +0 -1
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
- package/dist/plugins/package/codegen.d.mts +1 -12
- package/dist/plugins/package/codegen.mjs +43 -25
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +1 -1
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +14 -11
- package/dist/plugins/package/index.mjs +6 -7
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +5 -0
- package/dist/plugins/seal/codegen.mjs +55 -12
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +64 -19
- package/dist/plugins/seal/index.mjs +26 -36
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.d.mts +0 -2
- package/dist/plugins/seal/mode/fork-known.mjs +11 -22
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.d.mts +36 -13
- package/dist/plugins/seal/mode/live.mjs +104 -43
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +2 -1
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.d.mts +15 -3
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/sui/codegen.mjs +5 -4
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/index.mjs +1 -2
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +8 -12
- package/dist/plugins/wallet/codegen.mjs +22 -15
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +4 -3
- package/dist/plugins/wallet/index.mjs +1 -1
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +2 -2
- package/dist/plugins/wallet/service.mjs +2 -3
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.mjs +4 -4
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +137 -8
- package/dist/plugins/walrus/index.mjs +46 -9
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.d.mts +5 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +13 -10
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/substrate/manifest.d.mts +17 -22
- package/dist/substrate/manifest.mjs +4 -6
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/plugin-ctx.d.mts +6 -6
- package/dist/substrate/plugin-ctx.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +1 -1
- package/dist/surfaces/cli/command-tree.mjs +6 -6
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +6 -4
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/cli/wirings/dump-ids.mjs +0 -86
- package/dist/cli/wirings/dump-ids.mjs.map +0 -1
- package/dist/orchestrators/codegen/id-config.d.mts +0 -1
- package/dist/orchestrators/codegen/id-config.mjs +0 -107
- package/dist/orchestrators/codegen/id-config.mjs.map +0 -1
- package/dist/orchestrators/codegen/output-location.mjs +0 -32
- package/dist/orchestrators/codegen/output-location.mjs.map +0 -1
- package/dist/plugins/seal/service.mjs +0 -17
- package/dist/plugins/seal/service.mjs.map +0 -1
|
@@ -23,27 +23,23 @@ declare const ManifestExtrasLookupError_base: Schema.Class<ManifestExtrasLookupE
|
|
|
23
23
|
* classify via `catchTag('ManifestExtrasLookupError', ...)` rather
|
|
24
24
|
* than die-cause inspection. */
|
|
25
25
|
declare class ManifestExtrasLookupError extends ManifestExtrasLookupError_base {}
|
|
26
|
-
/** Codegen metadata recorded per stack. The supervisor writes the
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
26
|
+
/** Codegen metadata recorded per stack. The supervisor writes the live
|
|
27
|
+
* `deploymentFile` (the gitignored `deployment.json` injected as
|
|
28
|
+
* `__DEVSTACK_DEPLOYMENT__`) here at manifest-flush time so the read-side
|
|
29
|
+
* build integrations (the Vite plugin) consult the exact location the
|
|
30
|
+
* boot wrote — read and write share one decision. The dev-wallet
|
|
31
|
+
* connection + dev accounts ride the deployment envelope itself
|
|
32
|
+
* (`values['dev-wallet']` / `accounts`), so no separate dev tree is
|
|
33
|
+
* recorded. Bindings are NOT recorded either: the `@generated` alias
|
|
34
|
+
* always resolves to the committed `src/generated` tree written by the
|
|
35
|
+
* stack-free `codegen` verb. Optional + additive: manifests written
|
|
36
|
+
* before this field existed still decode; consumers fall back to their
|
|
37
|
+
* cold-start path. */
|
|
36
38
|
interface ManifestCodegen {
|
|
37
|
-
/** Absolute path to the
|
|
38
|
-
* for this stack (`<appRoot>/.devstack/stacks/<stack>/generated-extras`).
|
|
39
|
-
* The `@devstack-dev` Vite alias resolves here. Optional +
|
|
40
|
-
* additive — older manifests omit it; the reader falls back to
|
|
41
|
-
* `.devstack/stacks/<stack>/generated-extras`. */
|
|
42
|
-
readonly extrasDir?: string;
|
|
43
|
-
/** Absolute path to the gitignored `devstack-ids.json` the boot wrote
|
|
39
|
+
/** Absolute path to the gitignored `deployment.json` the boot wrote
|
|
44
40
|
* for this stack (the live on-chain ids). The Vite plugin reads it to
|
|
45
|
-
* inject `
|
|
46
|
-
readonly
|
|
41
|
+
* inject `__DEVSTACK_DEPLOYMENT__` in dev. Optional + additive. */
|
|
42
|
+
readonly deploymentFile?: string;
|
|
47
43
|
}
|
|
48
44
|
/** Manifest envelope. */
|
|
49
45
|
interface ManifestEnvelope {
|
|
@@ -98,10 +94,9 @@ declare const ManifestEnvelopeSchema: Schema.Struct<{
|
|
|
98
94
|
}>>;
|
|
99
95
|
readonly extras: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
100
96
|
readonly codegen: Schema.optional<Schema.Struct<{
|
|
101
|
-
|
|
102
|
-
/** Absolute path to the gitignored `devstack-ids.json` the boot
|
|
97
|
+
/** Absolute path to the gitignored `deployment.json` the boot
|
|
103
98
|
* wrote for this stack. The Vite plugin reads it in dev. */
|
|
104
|
-
readonly
|
|
99
|
+
readonly deploymentFile: Schema.optional<Schema.String>;
|
|
105
100
|
}>>;
|
|
106
101
|
}>;
|
|
107
102
|
//#endregion
|
|
@@ -54,12 +54,10 @@ const ManifestEnvelopeSchema = Schema.Struct({
|
|
|
54
54
|
endpointKey: Schema.String
|
|
55
55
|
})),
|
|
56
56
|
extras: Schema.Record(Schema.String, Schema.Unknown),
|
|
57
|
-
codegen: Schema.optional(Schema.Struct({
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
idsFile: Schema.optional(Schema.String)
|
|
62
|
-
}))
|
|
57
|
+
codegen: Schema.optional(Schema.Struct({
|
|
58
|
+
/** Absolute path to the gitignored `deployment.json` the boot
|
|
59
|
+
* wrote for this stack. The Vite plugin reads it in dev. */
|
|
60
|
+
deploymentFile: Schema.optional(Schema.String) }))
|
|
63
61
|
});
|
|
64
62
|
//#endregion
|
|
65
63
|
export { ManifestEnvelopeSchema, ManifestExtrasInvalid, ManifestExtrasLookupError, resolveManifestExtras };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.mjs","names":[],"sources":["../../src/substrate/manifest.ts"],"sourcesContent":["// Manifest envelope schema.\n//\n// Architecture § Manifest data model: envelope shape is fixed at L0,\n// per-service projection is each plugin's responsibility (lives in\n// the Codegenable contribution). Build integrations read the\n// envelope; codegen output carries the typed per-service slice.\n//\n// L0 owns:\n// - the envelope (identity tuple, manifestVersion, endpoints lookup,\n// opaque extras),\n// - the endpoint-declaration shape (decl emitted by Routable; the\n// manifest writer at L3 walks them).\n\nimport { Effect, Schema } from 'effect';\n\n/** Tagged failure when a plugin's manifest-extras contribution does\n * not resolve to a plain record. STYLE_GUIDE §2.2 — substrate (L0)\n * errors use `Schema.TaggedErrorClass`; downstream classifiers\n * `catchTag('ManifestExtrasInvalid', ...)` instead of sniffing the\n * message. */\nexport class ManifestExtrasInvalid extends Schema.TaggedErrorClass<ManifestExtrasInvalid>()(\n\t'ManifestExtrasInvalid',\n\t{\n\t\tdetail: Schema.String,\n\t},\n) {}\n\n/** Tagged failure when a plugin's `extras` factory references a\n * resource the host cannot resolve — either the resource id is not\n * registered with the supervisor, or it has not produced a value\n * yet at the point the factory ran. Thrown synchronously from the\n * user-supplied `ctx.value(...)` closure; `resolveManifestExtras`\n * catches the throw and surfaces it as a typed failure so callers\n * classify via `catchTag('ManifestExtrasLookupError', ...)` rather\n * than die-cause inspection. */\nexport class ManifestExtrasLookupError extends Schema.TaggedErrorClass<ManifestExtrasLookupError>()(\n\t'ManifestExtrasLookupError',\n\t{\n\t\tkind: Schema.Literals(['unknown-resource', 'unresolved-resource']),\n\t\tresourceId: Schema.String,\n\t},\n) {}\n\n/** Codegen metadata recorded per stack. The supervisor writes the
|
|
1
|
+
{"version":3,"file":"manifest.mjs","names":[],"sources":["../../src/substrate/manifest.ts"],"sourcesContent":["// Manifest envelope schema.\n//\n// Architecture § Manifest data model: envelope shape is fixed at L0,\n// per-service projection is each plugin's responsibility (lives in\n// the Codegenable contribution). Build integrations read the\n// envelope; codegen output carries the typed per-service slice.\n//\n// L0 owns:\n// - the envelope (identity tuple, manifestVersion, endpoints lookup,\n// opaque extras),\n// - the endpoint-declaration shape (decl emitted by Routable; the\n// manifest writer at L3 walks them).\n\nimport { Effect, Schema } from 'effect';\n\n/** Tagged failure when a plugin's manifest-extras contribution does\n * not resolve to a plain record. STYLE_GUIDE §2.2 — substrate (L0)\n * errors use `Schema.TaggedErrorClass`; downstream classifiers\n * `catchTag('ManifestExtrasInvalid', ...)` instead of sniffing the\n * message. */\nexport class ManifestExtrasInvalid extends Schema.TaggedErrorClass<ManifestExtrasInvalid>()(\n\t'ManifestExtrasInvalid',\n\t{\n\t\tdetail: Schema.String,\n\t},\n) {}\n\n/** Tagged failure when a plugin's `extras` factory references a\n * resource the host cannot resolve — either the resource id is not\n * registered with the supervisor, or it has not produced a value\n * yet at the point the factory ran. Thrown synchronously from the\n * user-supplied `ctx.value(...)` closure; `resolveManifestExtras`\n * catches the throw and surfaces it as a typed failure so callers\n * classify via `catchTag('ManifestExtrasLookupError', ...)` rather\n * than die-cause inspection. */\nexport class ManifestExtrasLookupError extends Schema.TaggedErrorClass<ManifestExtrasLookupError>()(\n\t'ManifestExtrasLookupError',\n\t{\n\t\tkind: Schema.Literals(['unknown-resource', 'unresolved-resource']),\n\t\tresourceId: Schema.String,\n\t},\n) {}\n\n/** Codegen metadata recorded per stack. The supervisor writes the live\n * `deploymentFile` (the gitignored `deployment.json` injected as\n * `__DEVSTACK_DEPLOYMENT__`) here at manifest-flush time so the read-side\n * build integrations (the Vite plugin) consult the exact location the\n * boot wrote — read and write share one decision. The dev-wallet\n * connection + dev accounts ride the deployment envelope itself\n * (`values['dev-wallet']` / `accounts`), so no separate dev tree is\n * recorded. Bindings are NOT recorded either: the `@generated` alias\n * always resolves to the committed `src/generated` tree written by the\n * stack-free `codegen` verb. Optional + additive: manifests written\n * before this field existed still decode; consumers fall back to their\n * cold-start path. */\nexport interface ManifestCodegen {\n\t/** Absolute path to the gitignored `deployment.json` the boot wrote\n\t * for this stack (the live on-chain ids). The Vite plugin reads it to\n\t * inject `__DEVSTACK_DEPLOYMENT__` in dev. Optional + additive. */\n\treadonly deploymentFile?: string;\n}\n\n/** Manifest envelope. */\nexport interface ManifestEnvelope {\n\treadonly identity: {\n\t\treadonly app: string;\n\t\treadonly stack: string;\n\t\treadonly network: string;\n\t};\n\treadonly manifestVersion: number;\n\t/** Optional per-plugin service slot. The read-side build-integration API\n\t * surfaces `{}` when absent. */\n\treadonly services?: Readonly<Record<string, unknown>>;\n\treadonly endpoints: Readonly<Record<string, EndpointEntry>>;\n\treadonly extras: Readonly<Record<string, unknown>>;\n\t/** Per-stack codegen metadata. When absent, readers use `src/generated/`. */\n\treadonly codegen?: ManifestCodegen;\n}\n\nexport type ManifestExtras = Readonly<Record<string, unknown>>;\n\nexport interface ManifestExtrasContext {\n\treadonly value: (resource: { readonly id: string }) => unknown;\n}\n\nexport type ManifestExtrasInput =\n\t| ManifestExtras\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t| Effect.Effect<ManifestExtras, any, never>\n\t| ((\n\t\t\tctx: ManifestExtrasContext,\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t ) => ManifestExtras | Effect.Effect<ManifestExtras, any, never>);\n\nconst isRecord = (value: unknown): value is ManifestExtras =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport const resolveManifestExtras = (\n\tinput: ManifestExtrasInput | undefined,\n\tctx: ManifestExtrasContext,\n): Effect.Effect<ManifestExtras, ManifestExtrasInvalid | ManifestExtrasLookupError, never> =>\n\tEffect.gen(function* () {\n\t\tif (input === undefined) return {};\n\t\t// The user-supplied `extras` factory invokes `ctx.value(...)`\n\t\t// synchronously; a missing/unresolved resource throws a\n\t\t// `ManifestExtrasLookupError`. `Effect.try` with a typed\n\t\t// `catch` mapper promotes that throw into the typed failure\n\t\t// channel so callers `catchTag` it instead of having to inspect\n\t\t// the die-cause. Any non-tagged throw is rethrown to preserve\n\t\t// the existing defect semantics for genuine programmer errors\n\t\t// inside the factory body.\n\t\tconst resolvedEffect = Effect.isEffect(input)\n\t\t\t? input\n\t\t\t: typeof input === 'function'\n\t\t\t\t? Effect.try({\n\t\t\t\t\t\ttry: () => input(ctx),\n\t\t\t\t\t\tcatch: (cause) => {\n\t\t\t\t\t\t\tif (cause instanceof ManifestExtrasLookupError) return cause;\n\t\t\t\t\t\t\tthrow cause;\n\t\t\t\t\t\t},\n\t\t\t\t\t}).pipe(\n\t\t\t\t\t\tEffect.flatMap((value) => (Effect.isEffect(value) ? value : Effect.succeed(value))),\n\t\t\t\t\t)\n\t\t\t\t: Effect.succeed(input);\n\t\tconst resolved = yield* resolvedEffect;\n\t\tif (!isRecord(resolved)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ManifestExtrasInvalid({\n\t\t\t\t\tdetail: 'manifest extras must resolve to a plain record',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn resolved;\n\t});\n\n/** Flat endpoint entry — the manifest's load-bearing surface for\n * build integrations. */\nexport interface EndpointEntry {\n\treadonly name: string;\n\treadonly url: string;\n\treadonly displayUrl: string | null;\n\treadonly wireProtocol: 'http' | 'h2c' | 'tcp';\n\treadonly pluginKey: string;\n\treadonly endpointKey: string;\n}\n\n/** Schema for runtime validation of an on-disk manifest. Build\n * integrations decode through this on read. */\nexport const ManifestEnvelopeSchema = Schema.Struct({\n\tidentity: Schema.Struct({\n\t\tapp: Schema.String,\n\t\tstack: Schema.String,\n\t\tnetwork: Schema.String,\n\t}),\n\tmanifestVersion: Schema.Number,\n\t// Optional service map for read-side integrations.\n\tservices: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),\n\tendpoints: Schema.Record(\n\t\tSchema.String,\n\t\tSchema.Struct({\n\t\t\tname: Schema.String,\n\t\t\turl: Schema.String,\n\t\t\tdisplayUrl: Schema.NullOr(Schema.String),\n\t\t\twireProtocol: Schema.Literals(['http', 'h2c', 'tcp']),\n\t\t\tpluginKey: Schema.String,\n\t\t\tendpointKey: Schema.String,\n\t\t}),\n\t),\n\textras: Schema.Record(Schema.String, Schema.Unknown),\n\t// Optional codegen metadata. The Vite plugin reads\n\t// `codegen.deploymentFile` to inject the live on-chain ids via\n\t// `__DEVSTACK_DEPLOYMENT__`; on a miss it injects `null`. Bindings are\n\t// not recorded here — `@generated` always resolves to the committed\n\t// `src/generated` tree.\n\tcodegen: Schema.optional(\n\t\tSchema.Struct({\n\t\t\t/** Absolute path to the gitignored `deployment.json` the boot\n\t\t\t * wrote for this stack. The Vite plugin reads it in dev. */\n\t\t\tdeploymentFile: Schema.optional(Schema.String),\n\t\t}),\n\t),\n});\n"],"mappings":";;;;;;;AAoBA,IAAa,wBAAb,cAA2C,OAAO,iBAAwC,CAAC,CAC1F,yBACA,EACC,QAAQ,OAAO,OAChB,CACD,CAAC,CAAC,CAAC;;;;;;;;;AAUH,IAAa,4BAAb,cAA+C,OAAO,iBAA4C,CAAC,CAClG,6BACA;CACC,MAAM,OAAO,SAAS,CAAC,oBAAoB,qBAAqB,CAAC;CACjE,YAAY,OAAO;AACpB,CACD,CAAC,CAAC,CAAC;AAqDH,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAEpE,MAAa,yBACZ,OACA,QAEA,OAAO,IAAI,aAAa;CACvB,IAAI,UAAU,KAAA,GAAW,OAAO,CAAC;CAsBjC,MAAM,WAAW,OAbM,OAAO,SAAS,KAAK,IACzC,QACA,OAAO,UAAU,aAChB,OAAO,IAAI;EACX,WAAW,MAAM,GAAG;EACpB,QAAQ,UAAU;GACjB,IAAI,iBAAiB,2BAA2B,OAAO;GACvD,MAAM;EACP;CACD,CAAC,CAAC,CAAC,KACF,OAAO,SAAS,UAAW,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,QAAQ,KAAK,CAAE,CACnF,IACC,OAAO,QAAQ,KAAK;CAExB,IAAI,CAAC,SAAS,QAAQ,GACrB,OAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB,EACzB,QAAQ,iDACT,CAAC,CACF;CAED,OAAO;AACR,CAAC;;;AAeF,MAAa,yBAAyB,OAAO,OAAO;CACnD,UAAU,OAAO,OAAO;EACvB,KAAK,OAAO;EACZ,OAAO,OAAO;EACd,SAAS,OAAO;CACjB,CAAC;CACD,iBAAiB,OAAO;CAExB,UAAU,OAAO,SAAS,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC;CACtE,WAAW,OAAO,OACjB,OAAO,QACP,OAAO,OAAO;EACb,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,YAAY,OAAO,OAAO,OAAO,MAAM;EACvC,cAAc,OAAO,SAAS;GAAC;GAAQ;GAAO;EAAK,CAAC;EACpD,WAAW,OAAO;EAClB,aAAa,OAAO;CACrB,CAAC,CACF;CACA,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO;CAMnD,SAAS,OAAO,SACf,OAAO,OAAO;;;AAGb,gBAAgB,OAAO,SAAS,OAAO,MAAM,EAC9C,CAAC,CACF;AACD,CAAC"}
|
|
@@ -21,12 +21,12 @@ interface PluginCtx {
|
|
|
21
21
|
* so it cannot be a static spec field; that is exactly why it is a
|
|
22
22
|
* buffered verb). Since codegen was decoupled from boot this is the
|
|
23
23
|
* LIVE half ONLY: at boot the orchestrator projects the decl into the
|
|
24
|
-
* loadable
|
|
25
|
-
*
|
|
26
|
-
* the committed `src/generated` tree. That committed,
|
|
27
|
-
* emitted separately by the `devstack codegen` verb
|
|
28
|
-
* spec's `staticCodegen` hook, derived from the SAME
|
|
29
|
-
* Returns void (the orchestrator owns rendering). */
|
|
24
|
+
* loadable deployment (`assembleDeployment`) — folding both typed fields
|
|
25
|
+
* and the values-only channels (dev accounts, the dev-wallet connection)
|
|
26
|
+
* — and does NOT write the committed `src/generated` tree. That committed,
|
|
27
|
+
* stack-free tree is emitted separately by the `devstack codegen` verb
|
|
28
|
+
* from the plugin spec's `staticCodegen` hook, derived from the SAME
|
|
29
|
+
* `ConfigBindingSet`. Returns void (the orchestrator owns rendering). */
|
|
30
30
|
readonly codegen: <E extends string>(decl: CodegenableDecl<E>) => void;
|
|
31
31
|
/** Buffered: declare a routable endpoint for the router orchestrator.
|
|
32
32
|
* Replayed in the supervisor frame after a successful `start`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-ctx.mjs","names":[],"sources":["../../src/substrate/plugin-ctx.ts"],"sourcesContent":["// PluginCtx — the minimal, closed, typed plugin-authoring surface.\n//\n// A plugin contributes to the stack through inline typed verbs it calls\n// from `start`. The plugin reaches this `ctx` by `yield* PluginContext` (an Effect\n// service tag, declared at the bottom of this file) — exactly like any\n// other substrate service it `yield*`s (`ContainerRuntimeService`,\n// `IdentityContext`, …). The supervisor PROVIDES a freshly-built\n// per-plugin ctx into `start`'s requirement (R) channel via\n// `Effect.provideService(start(deps), PluginContext, ctx)`. Delivering\n// ctx through the requirement channel — rather than as a 2nd positional\n// `start` argument — keeps `start` SINGLE-arg, which restores automatic\n// contextual typing of `deps` (no per-plugin `deps:` annotations), while\n// `ctx` stays always-present (the supervisor never omits it).\n//\n// -----------------------------------------------------------------------------\n// INV-5 — the closed 5-key set\n// -----------------------------------------------------------------------------\n//\n// `PluginCtx` has EXACTLY these 5 readonly keys and no more:\n//\n// codegen · endpoint · snapshotExtra · publish · provides\n//\n// This is a HARD invariant (pinned by `plugin-ctx-keyset.test-d.ts`).\n// Growing the set re-builds a god-object surface. The rule for what may\n// live here:\n//\n// - `codegen` / `endpoint` / `snapshotExtra` / `publish` are the four\n// BUFFERED declarative verbs. Their backing services\n// (CodegenOrchestratorService, RouterService +\n// ManifestEndpointRegistryService, SnapshotOrchestratorService, the\n// projection sink) are SUPERVISOR-FRAME-ONLY — they are absent from\n// plugin scope. A verb call therefore cannot run its backing\n// service inline; it pushes a typed buffer entry that the supervisor\n// REPLAYS in its own frame after the plugin's `start` succeeds.\n// - `provides` is the strategy bus writer (the faucet pattern\n// generalized): a plugin contributes to a sibling's\n// capability-keyed registry without a dep-graph edge. Siblings READ\n// it with `yield* StrategyRegistryService` directly (no ctx verb).\n//\n// What stays OUT of `ctx`, and WHY: every other substrate service a\n// plugin needs is already in plugin scope and is reached with\n// `yield* Service` directly — `CacheService` (whose `.publish` is the\n// folded-in artifact-publisher cycle),\n// `ContainerRuntimeService`, `IdentityContext`,\n// `PortBrokerService`, `LeaseBrokerService`, etc. The infra\n// orchestrators that are NOT in plugin scope\n// (Snapshot/Codegen/Router/ManifestEndpoint) are precisely the four\n// buffered verbs above — and infra contributions stay `yield*`-shaped\n// declarations, NEVER imperative `ctx.tx`-style Sui leaks. Adding a\n// service to `ctx` that a plugin could already `yield*` is the drift\n// INV-5 forbids.\n\nimport { Context } from 'effect';\n\nimport type { CodegenableDecl } from '../contracts/codegenable.ts';\nimport type { ProjectionDecl } from '../contracts/projection.ts';\nimport type { RoutableDecl } from '../contracts/routable.ts';\nimport type { SnapshotableDecl } from '../contracts/snapshotable.ts';\nimport type { StrategyContributorDecl } from '../contracts/strategy-contributor.ts';\n\n/**\n * The CLOSED union of contribution decls a plugin emits via the four\n * buffered verbs + `provides`. Discriminated by `kind`. A plugin's `start`\n * emits these inline — either by direct `ctx.*` verb calls or, for a\n * conditional/variadic set, through the shared {@link emitContributions}\n * router below. The supervisor's static dispatch switches over exactly\n * these five kinds.\n */\nexport type Contribution =\n\t| CodegenableDecl<string>\n\t| RoutableDecl\n\t| SnapshotableDecl\n\t| ProjectionDecl\n\t| StrategyContributorDecl<string, unknown>;\n\n/**\n * The ONE shared contribution router. A plugin's `start` builds an ordered\n * (possibly conditional/variadic) list of {@link Contribution} decls and\n * feeds it here; this routes each decl to its matching `ctx` verb by its\n * `kind` discriminant, IN LIST ORDER:\n *\n * snapshotable → `ctx.snapshotExtra`\n * codegenable → `ctx.codegen`\n * routable → `ctx.endpoint`\n * projection → `ctx.publish`\n * strategy-contributor → `ctx.provides`\n *\n * This is the single exhaustive `switch` over the closed `Contribution`\n * union — replacing the per-plugin `build*`/`emit*` wrapper pairs that each\n * re-implemented this same routing. The supervisor mirrors this dispatch\n * when it REPLAYS the buffered decls (`acquire-node.ts`); the verbs simply\n * buffer here. For a fixed, unconditional set of decls a plugin may prefer\n * direct `ctx.*` calls — but any conditional or variadic emission should\n * route through this helper so the routing lives in exactly one place.\n *\n * Exhaustive: the `default` arm narrows `decl` to `never`, so adding a\n * sixth `Contribution` kind is a compile error here until handled.\n */\nexport const emitContributions = (ctx: PluginCtx, decls: ReadonlyArray<Contribution>): void => {\n\tfor (const decl of decls) {\n\t\tswitch (decl.kind) {\n\t\t\tcase 'snapshotable':\n\t\t\t\tctx.snapshotExtra(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'codegenable':\n\t\t\t\tctx.codegen(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'routable':\n\t\t\t\tctx.endpoint(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'projection':\n\t\t\t\tctx.publish(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'strategy-contributor':\n\t\t\t\tctx.provides(decl);\n\t\t\t\tbreak;\n\t\t\tdefault: {\n\t\t\t\tconst _exhaustive: never = decl;\n\t\t\t\tvoid _exhaustive;\n\t\t\t}\n\t\t}\n\t}\n};\n\n/**\n * The minimal typed plugin-authoring context. A plugin reaches it from\n * `start` with `const ctx = yield* PluginContext` (the service tag\n * below); the supervisor provides a per-plugin instance into `start`'s\n * requirement channel.\n *\n * EXACTLY 5 keys (INV-5 — see file header). Do not add a 6th.\n */\nexport interface PluginCtx {\n\t/** Buffered: contribute the plugin's LIVE codegen decl to the codegen\n\t * orchestrator. Replayed in the supervisor frame after a successful\n\t * `start` (it carries post-acquire runtime state — resolved ids, rpc —\n\t * so it cannot be a static spec field; that is exactly why it is a\n\t * buffered verb). Since codegen was decoupled from boot this is the\n\t * LIVE half ONLY: at boot the orchestrator projects the decl into the\n\t * loadable
|
|
1
|
+
{"version":3,"file":"plugin-ctx.mjs","names":[],"sources":["../../src/substrate/plugin-ctx.ts"],"sourcesContent":["// PluginCtx — the minimal, closed, typed plugin-authoring surface.\n//\n// A plugin contributes to the stack through inline typed verbs it calls\n// from `start`. The plugin reaches this `ctx` by `yield* PluginContext` (an Effect\n// service tag, declared at the bottom of this file) — exactly like any\n// other substrate service it `yield*`s (`ContainerRuntimeService`,\n// `IdentityContext`, …). The supervisor PROVIDES a freshly-built\n// per-plugin ctx into `start`'s requirement (R) channel via\n// `Effect.provideService(start(deps), PluginContext, ctx)`. Delivering\n// ctx through the requirement channel — rather than as a 2nd positional\n// `start` argument — keeps `start` SINGLE-arg, which restores automatic\n// contextual typing of `deps` (no per-plugin `deps:` annotations), while\n// `ctx` stays always-present (the supervisor never omits it).\n//\n// -----------------------------------------------------------------------------\n// INV-5 — the closed 5-key set\n// -----------------------------------------------------------------------------\n//\n// `PluginCtx` has EXACTLY these 5 readonly keys and no more:\n//\n// codegen · endpoint · snapshotExtra · publish · provides\n//\n// This is a HARD invariant (pinned by `plugin-ctx-keyset.test-d.ts`).\n// Growing the set re-builds a god-object surface. The rule for what may\n// live here:\n//\n// - `codegen` / `endpoint` / `snapshotExtra` / `publish` are the four\n// BUFFERED declarative verbs. Their backing services\n// (CodegenOrchestratorService, RouterService +\n// ManifestEndpointRegistryService, SnapshotOrchestratorService, the\n// projection sink) are SUPERVISOR-FRAME-ONLY — they are absent from\n// plugin scope. A verb call therefore cannot run its backing\n// service inline; it pushes a typed buffer entry that the supervisor\n// REPLAYS in its own frame after the plugin's `start` succeeds.\n// - `provides` is the strategy bus writer (the faucet pattern\n// generalized): a plugin contributes to a sibling's\n// capability-keyed registry without a dep-graph edge. Siblings READ\n// it with `yield* StrategyRegistryService` directly (no ctx verb).\n//\n// What stays OUT of `ctx`, and WHY: every other substrate service a\n// plugin needs is already in plugin scope and is reached with\n// `yield* Service` directly — `CacheService` (whose `.publish` is the\n// folded-in artifact-publisher cycle),\n// `ContainerRuntimeService`, `IdentityContext`,\n// `PortBrokerService`, `LeaseBrokerService`, etc. The infra\n// orchestrators that are NOT in plugin scope\n// (Snapshot/Codegen/Router/ManifestEndpoint) are precisely the four\n// buffered verbs above — and infra contributions stay `yield*`-shaped\n// declarations, NEVER imperative `ctx.tx`-style Sui leaks. Adding a\n// service to `ctx` that a plugin could already `yield*` is the drift\n// INV-5 forbids.\n\nimport { Context } from 'effect';\n\nimport type { CodegenableDecl } from '../contracts/codegenable.ts';\nimport type { ProjectionDecl } from '../contracts/projection.ts';\nimport type { RoutableDecl } from '../contracts/routable.ts';\nimport type { SnapshotableDecl } from '../contracts/snapshotable.ts';\nimport type { StrategyContributorDecl } from '../contracts/strategy-contributor.ts';\n\n/**\n * The CLOSED union of contribution decls a plugin emits via the four\n * buffered verbs + `provides`. Discriminated by `kind`. A plugin's `start`\n * emits these inline — either by direct `ctx.*` verb calls or, for a\n * conditional/variadic set, through the shared {@link emitContributions}\n * router below. The supervisor's static dispatch switches over exactly\n * these five kinds.\n */\nexport type Contribution =\n\t| CodegenableDecl<string>\n\t| RoutableDecl\n\t| SnapshotableDecl\n\t| ProjectionDecl\n\t| StrategyContributorDecl<string, unknown>;\n\n/**\n * The ONE shared contribution router. A plugin's `start` builds an ordered\n * (possibly conditional/variadic) list of {@link Contribution} decls and\n * feeds it here; this routes each decl to its matching `ctx` verb by its\n * `kind` discriminant, IN LIST ORDER:\n *\n * snapshotable → `ctx.snapshotExtra`\n * codegenable → `ctx.codegen`\n * routable → `ctx.endpoint`\n * projection → `ctx.publish`\n * strategy-contributor → `ctx.provides`\n *\n * This is the single exhaustive `switch` over the closed `Contribution`\n * union — replacing the per-plugin `build*`/`emit*` wrapper pairs that each\n * re-implemented this same routing. The supervisor mirrors this dispatch\n * when it REPLAYS the buffered decls (`acquire-node.ts`); the verbs simply\n * buffer here. For a fixed, unconditional set of decls a plugin may prefer\n * direct `ctx.*` calls — but any conditional or variadic emission should\n * route through this helper so the routing lives in exactly one place.\n *\n * Exhaustive: the `default` arm narrows `decl` to `never`, so adding a\n * sixth `Contribution` kind is a compile error here until handled.\n */\nexport const emitContributions = (ctx: PluginCtx, decls: ReadonlyArray<Contribution>): void => {\n\tfor (const decl of decls) {\n\t\tswitch (decl.kind) {\n\t\t\tcase 'snapshotable':\n\t\t\t\tctx.snapshotExtra(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'codegenable':\n\t\t\t\tctx.codegen(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'routable':\n\t\t\t\tctx.endpoint(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'projection':\n\t\t\t\tctx.publish(decl);\n\t\t\t\tbreak;\n\t\t\tcase 'strategy-contributor':\n\t\t\t\tctx.provides(decl);\n\t\t\t\tbreak;\n\t\t\tdefault: {\n\t\t\t\tconst _exhaustive: never = decl;\n\t\t\t\tvoid _exhaustive;\n\t\t\t}\n\t\t}\n\t}\n};\n\n/**\n * The minimal typed plugin-authoring context. A plugin reaches it from\n * `start` with `const ctx = yield* PluginContext` (the service tag\n * below); the supervisor provides a per-plugin instance into `start`'s\n * requirement channel.\n *\n * EXACTLY 5 keys (INV-5 — see file header). Do not add a 6th.\n */\nexport interface PluginCtx {\n\t/** Buffered: contribute the plugin's LIVE codegen decl to the codegen\n\t * orchestrator. Replayed in the supervisor frame after a successful\n\t * `start` (it carries post-acquire runtime state — resolved ids, rpc —\n\t * so it cannot be a static spec field; that is exactly why it is a\n\t * buffered verb). Since codegen was decoupled from boot this is the\n\t * LIVE half ONLY: at boot the orchestrator projects the decl into the\n\t * loadable deployment (`assembleDeployment`) — folding both typed fields\n\t * and the values-only channels (dev accounts, the dev-wallet connection)\n\t * — and does NOT write the committed `src/generated` tree. That committed,\n\t * stack-free tree is emitted separately by the `devstack codegen` verb\n\t * from the plugin spec's `staticCodegen` hook, derived from the SAME\n\t * `ConfigBindingSet`. Returns void (the orchestrator owns rendering). */\n\treadonly codegen: <E extends string>(decl: CodegenableDecl<E>) => void;\n\t/** Buffered: declare a routable endpoint for the router orchestrator.\n\t * Replayed in the supervisor frame after a successful `start`.\n\t * Returns VOID — no URL is echoed back to the plugin (the router\n\t * mints and publishes the endpoint event itself). */\n\treadonly endpoint: (decl: RoutableDecl) => void;\n\t/** Buffered: declare extra snapshot subtrees / managed containers /\n\t * identity-guard hooks for the snapshot orchestrator. Replayed in\n\t * the supervisor frame after a successful `start`. */\n\treadonly snapshotExtra: (decl: SnapshotableDecl) => void;\n\t/** Buffered: publish a name-blind projection event through the\n\t * projection sink. Replayed in the supervisor frame after a\n\t * successful `start`. */\n\treadonly publish: (decl: ProjectionDecl) => void;\n\t/** Strategy bus (write): contribute a strategy under a capability\n\t * key for a sibling to read via `StrategyRegistryService`. Registers\n\t * on the scope-local StrategyRegistry, publishes `strategy.registered`,\n\t * and arms a finalizer publishing `strategy.unregistered`. */\n\treadonly provides: <K extends string, S>(decl: StrategyContributorDecl<K, S>) => void;\n}\n\n/**\n * Effect service tag carrying the per-plugin {@link PluginCtx}.\n *\n * A plugin's `start` body reaches its ctx with `const ctx = yield*\n * PluginContext`. The supervisor builds a fresh per-plugin ctx (+ its\n * replay buffer) in `runtime/supervisor/acquire-node.ts` and PROVIDES it\n * into `start`'s requirement channel\n * (`Effect.provideService(start(deps), PluginContext, ctx)`), so the tag\n * is always satisfied for code the supervisor runs.\n *\n * This ambient requirement lives only in `start`'s R-channel; it does NOT\n * surface in the public `Plugin<Id, Value, Needs>` contract (`Needs` is\n * `dependsOn` only — see `plugin.ts`). Declaring it as a service rather\n * than a `start` argument is what keeps `start` single-arg, restoring\n * automatic `deps` inference.\n */\nexport class PluginContext extends Context.Service<PluginContext, PluginCtx>()(\n\t'@devstack/substrate/PluginContext',\n) {}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,MAAa,qBAAqB,KAAgB,UAA6C;CAC9F,KAAK,MAAM,QAAQ,OAClB,QAAQ,KAAK,MAAb;EACC,KAAK;GACJ,IAAI,cAAc,IAAI;GACtB;EACD,KAAK;GACJ,IAAI,QAAQ,IAAI;GAChB;EACD,KAAK;GACJ,IAAI,SAAS,IAAI;GACjB;EACD,KAAK;GACJ,IAAI,QAAQ,IAAI;GAChB;EACD,KAAK;GACJ,IAAI,SAAS,IAAI;GACjB;EACD;CAID;AAEF;;;;;;;;;;;;;;;;;AA4DA,IAAa,gBAAb,cAAmC,QAAQ,QAAkC,CAAC,CAC7E,mCACD,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"liveness.mjs","names":["nodeHostname"],"sources":["../../../../src/substrate/runtime/cross-process/liveness.ts"],"sourcesContent":["// PID + start-time liveness check.\n//\n// Architecture § Cross-process safety protocol § Claim protocol step 3:\n// \"process exists AND its start-time (as read from `/proc/<pid>/stat` on\n// Linux, `ps -o lstart` on macOS, or equivalent) matches the recorded\n// `startTime`.\"\n//\n// Devstack is single-host: two checks combine. `kill(pid, 0)`\n// determines pid-in-use; `ps -o lstart` confirms it's the SAME process\n// (defending against pid reuse on long-uptime machines).\n//\n// This module is the ONLY place in the substrate that calls into\n// `process.kill` and shells out for start times. Roster/snapshot\n// reservation/stack-lock all consult it through the typed predicates.\n\nimport { execFileSync } from 'node:child_process';\nimport { hostname as nodeHostname } from 'node:os';\n\nimport { Context, Effect, Layer } from 'effect';\n\nimport type { RosterHolder } from '../../cross-process.ts';\nimport { selfPid } from './self-pid.ts';\n\n/** Cheap \"send signal 0\" pid liveness. ESRCH → dead; EPERM → alive\n * (foreign-user pid on shared dev hosts). Any other errno is\n * conservatively treated as dead so we don't refuse cleanup on exotic\n * platforms. */\nexport const isPidAlive = (pid: number): boolean => {\n\tif (!Number.isFinite(pid) || pid <= 0) return false;\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (err) {\n\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\treturn code === 'EPERM';\n\t}\n};\n\n/** Per-sweep cache mapping `pid → start-time stamp` (or `null` when the\n * pid is gone/unprobable). Callers that issue many probes in a single\n * sweep (roster step-3 stale-eviction, container-claim ledger prune,\n * etc.) instantiate one cache per pass and thread it through every\n * probe call so the same pid forks `ps`/`tasklist` AT MOST once per\n * sweep. Single-shot callers omit the cache and get the no-cache\n * behavior.\n *\n * Discriminator is `Map.has(pid)` — a cached `null` is a real result\n * (the pid is missing) and skipping it would re-fork pointlessly. */\nexport type LivenessCache = Map<number, number | null>;\n\n/** Best-effort start-time stamp for `pid`. Returns `null` when the\n * process is gone OR the platform can't produce a time. The stamp\n * itself is opaque text — its only contract is bytewise equality with\n * a previously-recorded sibling stamp.\n *\n * Architecture § Cross-process safety protocol §3 — `ps -o lstart` on\n * macOS/Linux, `tasklist` confirmation on Windows.\n *\n * Pass `cache` to reuse a previously-probed result across a sweep\n * pass — same `pid` only forks the underlying utility once. */\nexport const processStartTime = (pid: number, cache?: LivenessCache): number | null => {\n\tif (!Number.isFinite(pid) || pid <= 0) return null;\n\t// Our own pid's startTime never changes during the process\n\t// lifetime — cache the first SUCCESSFUL probe forever. Eliminates\n\t// the `ps` spawn under high in-process contention (e.g. N fibers\n\t// fighting over a single cross-process lock all probing each\n\t// other's shared pid). Without this cache, 8 concurrent fibers\n\t// each fork `ps -o lstart` with a 2s timeout, compounding into\n\t// seconds of latency that exhaust the claim budget (review fix\n\t// phase 22f reclaim-stress reproducer caught it).\n\t//\n\t// Only a non-null result is memoized: a `null` here means the\n\t// probe transiently FAILED (spawn hiccup / 2s timeout under load),\n\t// not that the pid is gone — it's our own pid, which is alive by\n\t// definition. Caching that null forever would surrender this\n\t// process's PID-reuse protection (it would write `startTime: null`\n\t// to disk for its whole lifetime) on a single flaky fork. Leaving\n\t// the cache UNSET re-probes on the next call; an occasional extra\n\t// `ps` on a previously-failing probe is negligible versus losing\n\t// reuse protection for the lifetime.\n\tif (pid === selfPid()) {\n\t\tif (ownStartTimeCache !== UNSET) return ownStartTimeCache;\n\t\tconst probed = probeStartTimeUncached(pid);\n\t\tif (probed !== null) ownStartTimeCache = probed;\n\t\treturn probed;\n\t}\n\tif (cache?.has(pid)) {\n\t\t// Map.get is `T | undefined` — but `has` is true, so the value\n\t\t// is one of the cached results (a `number` or `null`).\n\t\treturn cache.get(pid) ?? null;\n\t}\n\tconst probed = probeStartTimeUncached(pid);\n\tcache?.set(pid, probed);\n\treturn probed;\n};\n\nconst UNSET: unique symbol = Symbol('UNSET');\n// Only a successful (non-null) self-pid probe is ever stored here; a\n// failed probe leaves this UNSET so the next call re-probes.\nlet ownStartTimeCache: number | typeof UNSET = UNSET;\n\n/** Inner probe — always forks the platform utility. Split out so the\n * cache branch in `processStartTime` stays a single read/write. */\nconst probeStartTimeUncached = (pid: number): number | null => {\n\tif (process.platform === 'win32') {\n\t\ttry {\n\t\t\tconst out = execFileSync('tasklist', ['/fi', `PID eq ${pid}`, '/fo', 'csv', '/nh'], {\n\t\t\t\tencoding: 'utf8',\n\t\t\t\ttimeout: 2000,\n\t\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\t});\n\t\t\t// Windows path: just hash the line. Windows PID reuse on uptime\n\t\t\t// is a known v1 trade-off; the architecture says the protocol\n\t\t\t// stays POSIX-first.\n\t\t\treturn out.trim().startsWith('\"') ? hashStartTimeStamp(out.trim()) : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\ttry {\n\t\tconst out = execFileSync('ps', ['-o', 'lstart=', '-p', String(pid)], {\n\t\t\tencoding: 'utf8',\n\t\t\ttimeout: 2000,\n\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t});\n\t\tconst trimmed = out.trim();\n\t\treturn trimmed.length > 0 ? hashStartTimeStamp(trimmed) : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Stable hash of a start-time string. Reduces the textual `ps -o\n * lstart=` output to a single number so the roster carries a fixed-\n * width integer (the architecture's `startTime` field is `number`).\n *\n * FNV-1a 32-bit; collisions are negligible at the (pid, host, second)\n * granularity we ever compare. */\nconst hashStartTimeStamp = (stamp: string): number => {\n\tlet h = 2166136261;\n\tfor (let i = 0; i < stamp.length; i++) {\n\t\th ^= stamp.charCodeAt(i);\n\t\th = (h * 16777619) >>> 0;\n\t}\n\t// Stay inside Number.MAX_SAFE_INTEGER's positive 32-bit range so the\n\t// number round-trips through JSON without precision loss.\n\treturn h >>> 0;\n};\n\n/** Liveness probe for a roster holder. Used by the claim-protocol\n * sweep AND the stack-lock one-shot orphan check.\n *\n * Discipline (devstack is single-host): pid must be live AND start-time\n * must match the recorded stamp.\n *\n * Returns `'alive' | 'dead'`. Never throws. Effect-wrapped so callers\n * compose under spans. */\nexport const checkHolderLiveness = Effect.fn('cross-process.liveness.checkHolderLiveness')(\n\tfunction* (holder: RosterHolder, cache?: LivenessCache) {\n\t\tif (!isPidAlive(holder.pid)) return 'dead' as const;\n\t\tconst probedStart = processStartTime(holder.pid, cache);\n\t\t// pid alive but no stamp probable → conservative: ALIVE\n\t\t// (we have nothing to dispute the recorded startTime with).\n\t\tif (probedStart === null) return 'alive' as const;\n\t\t// Holder recorded a `null` startTime (writer's platform couldn't\n\t\t// probe at the time). The (pid, hostname) pair carries the\n\t\t// identity; same conservative policy as the probedStart-null\n\t\t// branch. Mismatching a real probed stamp against a recorded\n\t\t// `null` would otherwise harvest live holders as \"dead\".\n\t\tif (holder.startTime === null) return 'alive' as const;\n\t\treturn probedStart === holder.startTime ? ('alive' as const) : ('dead' as const);\n\t},\n);\n\n/** Build a holder snapshot for THIS process. The intent defaults to\n * `'normal'`; the snapshot bounce flips it to `'snapshot'`\n * under the stack lock and back when the bounce completes.\n *\n * A `null` `startTime` propagates verbatim — readers (`isOwnEntry`\n * in `roster.ts`, `checkHolderLiveness` above) honor the null-\n * conservative branch. Writing `0` for \"unprobable\" would cause a\n * false-dead harvest: a subsequent probe yielding a real stamp would\n * mismatch the recorded `0` and the process would fail to recognize\n * its own entry. */\nexport const ownHolder = (intent: 'normal' | 'snapshot' = 'normal'): RosterHolder => {\n\tconst pid = selfPid();\n\tconst startTime = processStartTime(pid);\n\treturn {\n\t\tpid,\n\t\tstartTime,\n\t\thostname: nodeHostname(),\n\t\tclaimedAt: Date.now(),\n\t\theartbeatAt: Date.now(),\n\t\tintent,\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// LivenessProbeScope — per-sweep cache, lifted out of the Map parameter.\n// -----------------------------------------------------------------------------\n//\n// The bare `LivenessCache = Map<pid, stamp | null>` was threaded through\n// `roster.sweepStaleHolders` as an explicit parameter, which is fine for\n// one site but doesn't scale to the other sweep loops (dispatch routes,\n// doctor probes, lifecycle-prune). Promoting the cache into a service\n// lets a sweep loop `yield* LivenessProbeScope` once and call the probe\n// methods without re-passing the Map. The optional-Map params on\n// `processStartTime` / `checkHolderLiveness` stay so unmigrated callers\n// can keep working from outside an Effect.\n\n/** Methods a per-sweep scope exposes — the captured cache backs both\n * so the same `pid` forks `ps`/`tasklist` at most once per scope. */\nexport interface LivenessProbeScopeShape {\n\treadonly probeStartTime: (pid: number) => number | null;\n\treadonly probeHolderLiveness: (holder: RosterHolder) => Effect.Effect<'alive' | 'dead'>;\n}\n\n/** A per-sweep liveness scope. Callers `yield* LivenessProbeScope`\n * once per sweep (typically inside an `Effect.scoped` block that\n * wraps the loop) and call `probeStartTime` / `probeHolderLiveness`\n * on each holder without threading the Map manually. */\nexport class LivenessProbeScope extends Context.Service<\n\tLivenessProbeScope,\n\tLivenessProbeScopeShape\n>()('@devstack/substrate/cross-process/LivenessProbeScope') {}\n\n/** Construct a fresh `LivenessProbeScope` whose underlying cache is\n * private to this layer — every yielding scope gets its own Map. */\nexport const layerLivenessProbeScope: Layer.Layer<LivenessProbeScope> = Layer.effect(\n\tLivenessProbeScope,\n\tEffect.sync(() => {\n\t\tconst cache: LivenessCache = new Map();\n\t\treturn LivenessProbeScope.of({\n\t\t\tprobeStartTime: (pid) => processStartTime(pid, cache),\n\t\t\tprobeHolderLiveness: (holder) => checkHolderLiveness(holder, cache),\n\t\t});\n\t}),\n);\n"],"mappings":";;;;;;;;;AA2BA,MAAa,cAAc,QAAyB;CACnD,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,GAAG,OAAO;CAC9C,IAAI;EACH,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACR,SAAS,KAAK;EAEb,OADc,IAA8B,SAC5B;CACjB;AACD;;;;;;;;;;;AAwBA,MAAa,oBAAoB,KAAa,UAAyC;CACtF,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,GAAG,OAAO;CAmB9C,IAAI,QAAQ,QAAQ,GAAG;EACtB,IAAI,sBAAsB,OAAO,OAAO;EACxC,MAAM,SAAS,uBAAuB,GAAG;EACzC,IAAI,WAAW,MAAM,oBAAoB;EACzC,OAAO;CACR;CACA,IAAI,OAAO,IAAI,GAAG,GAGjB,OAAO,MAAM,IAAI,GAAG,KAAK;CAE1B,MAAM,SAAS,uBAAuB,GAAG;CACzC,OAAO,IAAI,KAAK,MAAM;CACtB,OAAO;AACR;AAEA,MAAM,QAAuB,OAAO,OAAO;AAG3C,IAAI,oBAA2C;;;AAI/C,MAAM,0BAA0B,QAA+B;CAC9D,IAAI,QAAQ,aAAa,SACxB,IAAI;EACH,MAAM,MAAM,aAAa,YAAY;GAAC;GAAO,UAAU;GAAO;GAAO;GAAO;EAAK,GAAG;GACnF,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACnC,CAAC;EAID,OAAO,IAAI,KAAK,CAAC,CAAC,WAAW,IAAG,IAAI,mBAAmB,IAAI,KAAK,CAAC,IAAI;CACtE,QAAQ;EACP,OAAO;CACR;CAED,IAAI;EAMH,MAAM,UALM,aAAa,MAAM;GAAC;GAAM;GAAW;GAAM,OAAO,GAAG;EAAC,GAAG;GACpE,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACnC,CACkB,CAAC,CAAC,KAAK;EACzB,OAAO,QAAQ,SAAS,IAAI,mBAAmB,OAAO,IAAI;CAC3D,QAAQ;EACP,OAAO;CACR;AACD;;;;;;;AAQA,MAAM,sBAAsB,UAA0B;CACrD,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACtC,KAAK,MAAM,WAAW,CAAC;EACvB,IAAK,IAAI,aAAc;CACxB;CAGA,OAAO,MAAM;AACd;;;;;;;;;AAUA,MAAa,sBAAsB,OAAO,GAAG,4CAA4C,CAAC,CACzF,WAAW,QAAsB,OAAuB;CACvD,IAAI,CAAC,WAAW,OAAO,GAAG,GAAG,OAAO;CACpC,MAAM,cAAc,iBAAiB,OAAO,KAAK,KAAK;CAGtD,IAAI,gBAAgB,MAAM,OAAO;CAMjC,IAAI,OAAO,cAAc,MAAM,OAAO;CACtC,OAAO,gBAAgB,OAAO,YAAa,UAAqB;AACjE,CACD;;;;;;;;;;;AAYA,MAAa,aAAa,SAAgC,aAA2B;CACpF,MAAM,MAAM,QAAQ;CAEpB,OAAO;EACN;EACA,WAHiB,iBAAiB,GAG1B;EACR,UAAUA,SAAa;EACvB,WAAW,KAAK,IAAI;EACpB,aAAa,KAAK,IAAI;EACtB;CACD;AACD;;;;;AA0BA,IAAa,qBAAb,cAAwC,QAAQ,QAG9C,CAAC,CAAC,sDAAsD,CAAC,CAAC,CAAC;;;AAI7D,MAAa,0BAA2D,MAAM,OAC7E,oBACA,OAAO,WAAW;CACjB,MAAM,wBAAuB,IAAI,IAAI;CACrC,OAAO,mBAAmB,GAAG;EAC5B,iBAAiB,QAAQ,iBAAiB,KAAK,KAAK;EACpD,sBAAsB,WAAW,oBAAoB,QAAQ,KAAK;CACnE,CAAC;AACF,CAAC,CACF"}
|
|
1
|
+
{"version":3,"file":"liveness.mjs","names":["nodeHostname"],"sources":["../../../../src/substrate/runtime/cross-process/liveness.ts"],"sourcesContent":["// PID + start-time liveness check.\n//\n// Architecture § Cross-process safety protocol § Claim protocol step 3:\n// \"process exists AND its start-time (as read from `/proc/<pid>/stat` on\n// Linux, `ps -o lstart` on macOS, or equivalent) matches the recorded\n// `startTime`.\"\n//\n// Devstack is single-host: two checks combine. `kill(pid, 0)`\n// determines pid-in-use; `ps -o lstart` confirms it's the SAME process\n// (defending against pid reuse on long-uptime machines).\n//\n// This module is the ONLY place in the substrate that calls into\n// `process.kill` and shells out for start times. Roster/snapshot\n// reservation/stack-lock all consult it through the typed predicates.\n\nimport { execFileSync } from 'node:child_process';\nimport { hostname as nodeHostname } from 'node:os';\n\nimport { Context, Effect, Layer } from 'effect';\n\nimport type { RosterHolder } from '../../cross-process.ts';\nimport { selfPid } from './self-pid.ts';\n\n/** Cheap \"send signal 0\" pid liveness. ESRCH → dead; EPERM → alive\n * (foreign-user pid on shared dev hosts). Any other errno is\n * conservatively treated as dead so we don't refuse cleanup on exotic\n * platforms. */\nexport const isPidAlive = (pid: number): boolean => {\n\tif (!Number.isFinite(pid) || pid <= 0) return false;\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (err) {\n\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\treturn code === 'EPERM';\n\t}\n};\n\n/** Per-sweep cache mapping `pid → start-time stamp` (or `null` when the\n * pid is gone/unprobable). Callers that issue many probes in a single\n * sweep (roster step-3 stale-eviction, container-claim ledger prune,\n * etc.) instantiate one cache per pass and thread it through every\n * probe call so the same pid forks `ps`/`tasklist` AT MOST once per\n * sweep. Single-shot callers omit the cache and get the no-cache\n * behavior.\n *\n * Discriminator is `Map.has(pid)` — a cached `null` is a real result\n * (the pid is missing) and skipping it would re-fork pointlessly. */\nexport type LivenessCache = Map<number, number | null>;\n\n/** Best-effort start-time stamp for `pid`. Returns `null` when the\n * process is gone OR the platform can't produce a time. The stamp\n * itself is opaque text — its only contract is bytewise equality with\n * a previously-recorded sibling stamp.\n *\n * Architecture § Cross-process safety protocol §3 — `ps -o lstart` on\n * macOS/Linux, `tasklist` confirmation on Windows.\n *\n * Pass `cache` to reuse a previously-probed result across a sweep\n * pass — same `pid` only forks the underlying utility once. */\nexport const processStartTime = (pid: number, cache?: LivenessCache): number | null => {\n\tif (!Number.isFinite(pid) || pid <= 0) return null;\n\t// Our own pid's startTime never changes during the process\n\t// lifetime — cache the first SUCCESSFUL probe forever. Eliminates\n\t// the `ps` spawn under high in-process contention (e.g. N fibers\n\t// fighting over a single cross-process lock all probing each\n\t// other's shared pid). Without this cache, 8 concurrent fibers\n\t// each fork `ps -o lstart` with a 2s timeout, compounding into\n\t// seconds of latency that exhaust the claim budget (review fix\n\t// phase 22f reclaim-stress reproducer caught it).\n\t//\n\t// Only a non-null result is memoized: a `null` here means the\n\t// probe transiently FAILED (spawn hiccup / 2s timeout under load),\n\t// not that the pid is gone — it's our own pid, which is alive by\n\t// definition. Caching that null forever would surrender this\n\t// process's PID-reuse protection (it would write `startTime: null`\n\t// to disk for its whole lifetime) on a single flaky fork. Leaving\n\t// the cache UNSET re-probes on the next call; an occasional extra\n\t// `ps` on a previously-failing probe is negligible versus losing\n\t// reuse protection for the lifetime.\n\tif (pid === selfPid()) {\n\t\tif (ownStartTimeCache !== UNSET) return ownStartTimeCache;\n\t\tconst probed = probeStartTimeUncached(pid);\n\t\tif (probed !== null) ownStartTimeCache = probed;\n\t\treturn probed;\n\t}\n\tif (cache?.has(pid)) {\n\t\t// Map.get is `T | undefined` — but `has` is true, so the value\n\t\t// is one of the cached results (a `number` or `null`).\n\t\treturn cache.get(pid) ?? null;\n\t}\n\tconst probed = probeStartTimeUncached(pid);\n\tcache?.set(pid, probed);\n\treturn probed;\n};\n\nconst UNSET: unique symbol = Symbol('UNSET');\n// Only a successful (non-null) self-pid probe is ever stored here; a\n// failed probe leaves this UNSET so the next call re-probes.\nlet ownStartTimeCache: number | typeof UNSET = UNSET;\n\n/** Inner probe — always forks the platform utility. Split out so the\n * cache branch in `processStartTime` stays a single read/write. */\nconst probeStartTimeUncached = (pid: number): number | null => {\n\tif (process.platform === 'win32') {\n\t\ttry {\n\t\t\tconst out = execFileSync('tasklist', ['/fi', `PID eq ${pid}`, '/fo', 'csv', '/nh'], {\n\t\t\t\tencoding: 'utf8',\n\t\t\t\ttimeout: 2000,\n\t\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\t});\n\t\t\t// Windows path: just hash the line. Windows PID reuse on uptime\n\t\t\t// is a known v1 trade-off; the architecture says the protocol\n\t\t\t// stays POSIX-first.\n\t\t\treturn out.trim().startsWith('\"') ? hashStartTimeStamp(out.trim()) : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\ttry {\n\t\tconst out = execFileSync('ps', ['-o', 'lstart=', '-p', String(pid)], {\n\t\t\tencoding: 'utf8',\n\t\t\ttimeout: 2000,\n\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t});\n\t\tconst trimmed = out.trim();\n\t\treturn trimmed.length > 0 ? hashStartTimeStamp(trimmed) : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Stable hash of a start-time string. Reduces the textual `ps -o\n * lstart=` output to a single number so the roster carries a fixed-\n * width integer (the architecture's `startTime` field is `number`).\n *\n * FNV-1a 32-bit; collisions are negligible at the (pid, host, second)\n * granularity we ever compare. */\nconst hashStartTimeStamp = (stamp: string): number => {\n\tlet h = 2166136261;\n\tfor (let i = 0; i < stamp.length; i++) {\n\t\th ^= stamp.charCodeAt(i);\n\t\th = (h * 16777619) >>> 0;\n\t}\n\t// Stay inside Number.MAX_SAFE_INTEGER's positive 32-bit range so the\n\t// number round-trips through JSON without precision loss.\n\treturn h >>> 0;\n};\n\n/** Liveness probe for a roster holder. Used by the claim-protocol\n * sweep AND the stack-lock one-shot orphan check.\n *\n * Discipline (devstack is single-host): pid must be live AND start-time\n * must match the recorded stamp.\n *\n * Returns `'alive' | 'dead'`. Never throws. Effect-wrapped so callers\n * compose under spans. */\nexport const checkHolderLiveness = Effect.fn('cross-process.liveness.checkHolderLiveness')(\n\t// eslint-disable-next-line require-yield -- Effect.fn requires a generator; this probe is pure synchronous logic wrapped only for span composition, so it has no yield\n\tfunction* (holder: RosterHolder, cache?: LivenessCache) {\n\t\tif (!isPidAlive(holder.pid)) return 'dead' as const;\n\t\tconst probedStart = processStartTime(holder.pid, cache);\n\t\t// pid alive but no stamp probable → conservative: ALIVE\n\t\t// (we have nothing to dispute the recorded startTime with).\n\t\tif (probedStart === null) return 'alive' as const;\n\t\t// Holder recorded a `null` startTime (writer's platform couldn't\n\t\t// probe at the time). The (pid, hostname) pair carries the\n\t\t// identity; same conservative policy as the probedStart-null\n\t\t// branch. Mismatching a real probed stamp against a recorded\n\t\t// `null` would otherwise harvest live holders as \"dead\".\n\t\tif (holder.startTime === null) return 'alive' as const;\n\t\treturn probedStart === holder.startTime ? ('alive' as const) : ('dead' as const);\n\t},\n);\n\n/** Build a holder snapshot for THIS process. The intent defaults to\n * `'normal'`; the snapshot bounce flips it to `'snapshot'`\n * under the stack lock and back when the bounce completes.\n *\n * A `null` `startTime` propagates verbatim — readers (`isOwnEntry`\n * in `roster.ts`, `checkHolderLiveness` above) honor the null-\n * conservative branch. Writing `0` for \"unprobable\" would cause a\n * false-dead harvest: a subsequent probe yielding a real stamp would\n * mismatch the recorded `0` and the process would fail to recognize\n * its own entry. */\nexport const ownHolder = (intent: 'normal' | 'snapshot' = 'normal'): RosterHolder => {\n\tconst pid = selfPid();\n\tconst startTime = processStartTime(pid);\n\treturn {\n\t\tpid,\n\t\tstartTime,\n\t\thostname: nodeHostname(),\n\t\tclaimedAt: Date.now(),\n\t\theartbeatAt: Date.now(),\n\t\tintent,\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// LivenessProbeScope — per-sweep cache, lifted out of the Map parameter.\n// -----------------------------------------------------------------------------\n//\n// The bare `LivenessCache = Map<pid, stamp | null>` was threaded through\n// `roster.sweepStaleHolders` as an explicit parameter, which is fine for\n// one site but doesn't scale to the other sweep loops (dispatch routes,\n// doctor probes, lifecycle-prune). Promoting the cache into a service\n// lets a sweep loop `yield* LivenessProbeScope` once and call the probe\n// methods without re-passing the Map. The optional-Map params on\n// `processStartTime` / `checkHolderLiveness` stay so unmigrated callers\n// can keep working from outside an Effect.\n\n/** Methods a per-sweep scope exposes — the captured cache backs both\n * so the same `pid` forks `ps`/`tasklist` at most once per scope. */\nexport interface LivenessProbeScopeShape {\n\treadonly probeStartTime: (pid: number) => number | null;\n\treadonly probeHolderLiveness: (holder: RosterHolder) => Effect.Effect<'alive' | 'dead'>;\n}\n\n/** A per-sweep liveness scope. Callers `yield* LivenessProbeScope`\n * once per sweep (typically inside an `Effect.scoped` block that\n * wraps the loop) and call `probeStartTime` / `probeHolderLiveness`\n * on each holder without threading the Map manually. */\nexport class LivenessProbeScope extends Context.Service<\n\tLivenessProbeScope,\n\tLivenessProbeScopeShape\n>()('@devstack/substrate/cross-process/LivenessProbeScope') {}\n\n/** Construct a fresh `LivenessProbeScope` whose underlying cache is\n * private to this layer — every yielding scope gets its own Map. */\nexport const layerLivenessProbeScope: Layer.Layer<LivenessProbeScope> = Layer.effect(\n\tLivenessProbeScope,\n\tEffect.sync(() => {\n\t\tconst cache: LivenessCache = new Map();\n\t\treturn LivenessProbeScope.of({\n\t\t\tprobeStartTime: (pid) => processStartTime(pid, cache),\n\t\t\tprobeHolderLiveness: (holder) => checkHolderLiveness(holder, cache),\n\t\t});\n\t}),\n);\n"],"mappings":";;;;;;;;;AA2BA,MAAa,cAAc,QAAyB;CACnD,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,GAAG,OAAO;CAC9C,IAAI;EACH,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACR,SAAS,KAAK;EAEb,OADc,IAA8B,SAC5B;CACjB;AACD;;;;;;;;;;;AAwBA,MAAa,oBAAoB,KAAa,UAAyC;CACtF,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,GAAG,OAAO;CAmB9C,IAAI,QAAQ,QAAQ,GAAG;EACtB,IAAI,sBAAsB,OAAO,OAAO;EACxC,MAAM,SAAS,uBAAuB,GAAG;EACzC,IAAI,WAAW,MAAM,oBAAoB;EACzC,OAAO;CACR;CACA,IAAI,OAAO,IAAI,GAAG,GAGjB,OAAO,MAAM,IAAI,GAAG,KAAK;CAE1B,MAAM,SAAS,uBAAuB,GAAG;CACzC,OAAO,IAAI,KAAK,MAAM;CACtB,OAAO;AACR;AAEA,MAAM,QAAuB,OAAO,OAAO;AAG3C,IAAI,oBAA2C;;;AAI/C,MAAM,0BAA0B,QAA+B;CAC9D,IAAI,QAAQ,aAAa,SACxB,IAAI;EACH,MAAM,MAAM,aAAa,YAAY;GAAC;GAAO,UAAU;GAAO;GAAO;GAAO;EAAK,GAAG;GACnF,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACnC,CAAC;EAID,OAAO,IAAI,KAAK,CAAC,CAAC,WAAW,IAAG,IAAI,mBAAmB,IAAI,KAAK,CAAC,IAAI;CACtE,QAAQ;EACP,OAAO;CACR;CAED,IAAI;EAMH,MAAM,UALM,aAAa,MAAM;GAAC;GAAM;GAAW;GAAM,OAAO,GAAG;EAAC,GAAG;GACpE,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACnC,CACkB,CAAC,CAAC,KAAK;EACzB,OAAO,QAAQ,SAAS,IAAI,mBAAmB,OAAO,IAAI;CAC3D,QAAQ;EACP,OAAO;CACR;AACD;;;;;;;AAQA,MAAM,sBAAsB,UAA0B;CACrD,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACtC,KAAK,MAAM,WAAW,CAAC;EACvB,IAAK,IAAI,aAAc;CACxB;CAGA,OAAO,MAAM;AACd;;;;;;;;;AAUA,MAAa,sBAAsB,OAAO,GAAG,4CAA4C,CAAC,CAEzF,WAAW,QAAsB,OAAuB;CACvD,IAAI,CAAC,WAAW,OAAO,GAAG,GAAG,OAAO;CACpC,MAAM,cAAc,iBAAiB,OAAO,KAAK,KAAK;CAGtD,IAAI,gBAAgB,MAAM,OAAO;CAMjC,IAAI,OAAO,cAAc,MAAM,OAAO;CACtC,OAAO,gBAAgB,OAAO,YAAa,UAAqB;AACjE,CACD;;;;;;;;;;;AAYA,MAAa,aAAa,SAAgC,aAA2B;CACpF,MAAM,MAAM,QAAQ;CAEpB,OAAO;EACN;EACA,WAHiB,iBAAiB,GAG1B;EACR,UAAUA,SAAa;EACvB,WAAW,KAAK,IAAI;EACpB,aAAa,KAAK,IAAI;EACtB;CACD;AACD;;;;;AA0BA,IAAa,qBAAb,cAAwC,QAAQ,QAG9C,CAAC,CAAC,sDAAsD,CAAC,CAAC,CAAC;;;AAI7D,MAAa,0BAA2D,MAAM,OAC7E,oBACA,OAAO,WAAW;CACjB,MAAM,wBAAuB,IAAI,IAAI;CACrC,OAAO,mBAAmB,GAAG;EAC5B,iBAAiB,QAAQ,iBAAiB,KAAK,KAAK;EACpD,sBAAsB,WAAW,oBAAoB,QAAQ,KAAK;CACnE,CAAC;AACF,CAAC,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.mjs","names":[],"sources":["../../../../src/substrate/runtime/manifest/manifest.ts"],"sourcesContent":["// Manifest emitter — endpoint-keyed envelope, atomic write.\n//\n// Architecture § Manifest data model. The envelope is L0 (`identity`\n// tuple, `manifestVersion`, `endpoints` lookup, `extras`).\n//\n// The emitter:\n// - Walks endpoint entries to build the flat `endpoints` lookup\n// keyed by `endpointKey`. Entries carry the declared endpoint\n// `name` for build-integration lookup.\n// - Writes atomically via tempfile + fsync + rename. The atomic-\n// write primitive lives in L0 (per architecture § Collapsed:\n// \"Three tempfile+rename impls → one atomic-write primitive\").\n// - Validates the envelope against `ManifestEnvelopeSchema` on read.\n// - Pins `manifestVersion` so future-proofing migrations have a\n// compatibility seam. The pinned version is `1`.\n//\n// Discipline: zero service names. The schema validates the envelope\n// shape, not the per-plugin contents.\n\nimport { Effect } from 'effect';\nimport { Data } from 'effect';\nimport { FileSystem } from 'effect';\n\nimport type { EndpointKey, PluginKey } from '../../brand.ts';\nimport {\n\ttype EndpointEntry,\n\ttype ManifestCodegen,\n\ttype ManifestExtras,\n\ttype ManifestEnvelope,\n\tManifestEnvelopeSchema,\n} from '../../manifest.ts';\nimport { atomicWriteFile } from '../atomic-write.ts';\nimport { decodeJsonText } from '../runtime-decode.ts';\n\n// -----------------------------------------------------------------------------\n// Pinned schema version\n// -----------------------------------------------------------------------------\n\n/**\n * Schema version pinned at the envelope level. Bump only on a\n * breaking shape change to the envelope (NOT to a per-plugin service\n * slice — those carry their own version via Codegenable contribution).\n *\n * Reader policy:\n * - Equal version: accept.\n * - Older version: refuse (advise user to re-run `up`).\n * - Newer version: refuse (build integration is out of date).\n */\nexport const CURRENT_MANIFEST_VERSION = 1 as const;\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** One error class for every manifest IO failure. */\nexport class ManifestError extends Data.TaggedError('ManifestError')<{\n\treadonly reason:\n\t\t| 'write-failed'\n\t\t| 'read-failed'\n\t\t| 'decode-failed'\n\t\t| 'version-mismatch'\n\t\t| 'duplicate-endpoint';\n\treadonly path: string;\n\treadonly detail?: string;\n\treadonly cause?: unknown;\n}> {}\n\n// -----------------------------------------------------------------------------\n// Public writer interface\n// -----------------------------------------------------------------------------\n\nexport interface WriteManifestInput {\n\treadonly identity: ManifestEnvelope['identity'];\n\treadonly endpoints?: ReadonlyArray<EndpointEntry>;\n\treadonly extras?: ManifestExtras;\n\t/** Per-stack codegen metadata (the
|
|
1
|
+
{"version":3,"file":"manifest.mjs","names":[],"sources":["../../../../src/substrate/runtime/manifest/manifest.ts"],"sourcesContent":["// Manifest emitter — endpoint-keyed envelope, atomic write.\n//\n// Architecture § Manifest data model. The envelope is L0 (`identity`\n// tuple, `manifestVersion`, `endpoints` lookup, `extras`).\n//\n// The emitter:\n// - Walks endpoint entries to build the flat `endpoints` lookup\n// keyed by `endpointKey`. Entries carry the declared endpoint\n// `name` for build-integration lookup.\n// - Writes atomically via tempfile + fsync + rename. The atomic-\n// write primitive lives in L0 (per architecture § Collapsed:\n// \"Three tempfile+rename impls → one atomic-write primitive\").\n// - Validates the envelope against `ManifestEnvelopeSchema` on read.\n// - Pins `manifestVersion` so future-proofing migrations have a\n// compatibility seam. The pinned version is `1`.\n//\n// Discipline: zero service names. The schema validates the envelope\n// shape, not the per-plugin contents.\n\nimport { Effect } from 'effect';\nimport { Data } from 'effect';\nimport { FileSystem } from 'effect';\n\nimport type { EndpointKey, PluginKey } from '../../brand.ts';\nimport {\n\ttype EndpointEntry,\n\ttype ManifestCodegen,\n\ttype ManifestExtras,\n\ttype ManifestEnvelope,\n\tManifestEnvelopeSchema,\n} from '../../manifest.ts';\nimport { atomicWriteFile } from '../atomic-write.ts';\nimport { decodeJsonText } from '../runtime-decode.ts';\n\n// -----------------------------------------------------------------------------\n// Pinned schema version\n// -----------------------------------------------------------------------------\n\n/**\n * Schema version pinned at the envelope level. Bump only on a\n * breaking shape change to the envelope (NOT to a per-plugin service\n * slice — those carry their own version via Codegenable contribution).\n *\n * Reader policy:\n * - Equal version: accept.\n * - Older version: refuse (advise user to re-run `up`).\n * - Newer version: refuse (build integration is out of date).\n */\nexport const CURRENT_MANIFEST_VERSION = 1 as const;\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** One error class for every manifest IO failure. */\nexport class ManifestError extends Data.TaggedError('ManifestError')<{\n\treadonly reason:\n\t\t| 'write-failed'\n\t\t| 'read-failed'\n\t\t| 'decode-failed'\n\t\t| 'version-mismatch'\n\t\t| 'duplicate-endpoint';\n\treadonly path: string;\n\treadonly detail?: string;\n\treadonly cause?: unknown;\n}> {}\n\n// -----------------------------------------------------------------------------\n// Public writer interface\n// -----------------------------------------------------------------------------\n\nexport interface WriteManifestInput {\n\treadonly identity: ManifestEnvelope['identity'];\n\treadonly endpoints?: ReadonlyArray<EndpointEntry>;\n\treadonly extras?: ManifestExtras;\n\t/** Per-stack codegen metadata (the live `deploymentFile`). Optional +\n\t * additive — omitting it produces an envelope without the `codegen`\n\t * key, identical to a pre-field manifest. */\n\treadonly codegen?: ManifestCodegen;\n}\n\n/**\n * Build the envelope from endpoint entries + extras. Pure; the writer\n * calls this and then atomically writes the JSON.\n *\n * Endpoint-key collisions error: every endpoint has a unique\n * `(pluginKey, dispatchId)` digest by construction; a duplicate means\n * a substrate bug.\n */\nexport const buildEnvelope = (\n\tinput: WriteManifestInput,\n): Effect.Effect<ManifestEnvelope, ManifestError> =>\n\tEffect.gen(function* () {\n\t\tconst endpoints: Record<string, EndpointEntry> = {};\n\t\tconst extras: Record<string, unknown> = { ...input.extras };\n\n\t\tconst addEndpoint = (ep: EndpointEntry): Effect.Effect<void, ManifestError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ek = ep.endpointKey as string;\n\t\t\t\tif (ek in endpoints) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew ManifestError({\n\t\t\t\t\t\t\treason: 'duplicate-endpoint',\n\t\t\t\t\t\t\tpath: '(in-memory envelope)',\n\t\t\t\t\t\t\tdetail: `endpointKey ${ek} contributed twice`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tendpoints[ek] = ep;\n\t\t\t});\n\n\t\tfor (const ep of input.endpoints ?? []) {\n\t\t\tyield* addEndpoint(ep);\n\t\t}\n\n\t\treturn {\n\t\t\tidentity: input.identity,\n\t\t\tmanifestVersion: CURRENT_MANIFEST_VERSION,\n\t\t\tendpoints,\n\t\t\textras,\n\t\t\t// Spread only when present so an omitted `codegen` yields the\n\t\t\t// exact same envelope (and serialized bytes) as a pre-field\n\t\t\t// manifest — additive, no churn for stacks that don't record it.\n\t\t\t...(input.codegen !== undefined ? { codegen: input.codegen } : {}),\n\t\t};\n\t});\n\n// -----------------------------------------------------------------------------\n// Atomic write — delegates to the canonical primitive.\n// -----------------------------------------------------------------------------\n\n/**\n * Serialize and write the envelope at `path` atomically. Routes\n * through the canonical `atomicWriteFile` primitive (mkdir-parent →\n * O_EXCL temp → write → fsync → rename), so the manifest writer\n * shares ONE owner of the tempfile dance with the cache.\n */\nexport const writeManifest = (\n\tenvelope: ManifestEnvelope,\n\tpath: string,\n): Effect.Effect<void, ManifestError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst json = serializeEnvelope(envelope);\n\t\tconst bytes = new TextEncoder().encode(json);\n\t\tyield* atomicWriteFile(path, bytes).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(cause) =>\n\t\t\t\t\tnew ManifestError({\n\t\t\t\t\t\treason: 'write-failed',\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tdetail: `atomic write failed at stage ${cause.stage}`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t});\n\nconst serializeEnvelope = (envelope: ManifestEnvelope): string =>\n\t// Stable key ordering, deterministic re-emit. `JSON.stringify` with\n\t// 2-space indent is reproducible byte-for-byte given the same\n\t// input object.\n\tJSON.stringify(envelope, null, 2);\n\n// -----------------------------------------------------------------------------\n// Read + decode\n// -----------------------------------------------------------------------------\n\n/**\n * Read and decode the manifest at `path`. Validates against\n * `ManifestEnvelopeSchema` and the pinned `CURRENT_MANIFEST_VERSION`.\n *\n * Used by:\n * - Build integrations (Vitest, Playwright, generated app code) — consumes\n * `envelope.endpoints` lookup.\n *\n * The reader is read-only; no mutation, no IO beyond the single read.\n */\nexport const readManifest = (\n\tpath: string,\n): Effect.Effect<ManifestEnvelope, ManifestError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst text = yield* fs\n\t\t\t.readFileString(path)\n\t\t\t.pipe(Effect.mapError((cause) => new ManifestError({ reason: 'read-failed', path, cause })));\n\t\tconst decoded = yield* decodeJsonText(ManifestEnvelopeSchema, text, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew ManifestError({ reason: 'decode-failed', path, cause: issue.cause ?? issue }),\n\t\t});\n\t\tif (decoded.manifestVersion !== CURRENT_MANIFEST_VERSION) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ManifestError({\n\t\t\t\t\treason: 'version-mismatch',\n\t\t\t\t\tpath,\n\t\t\t\t\tdetail: `expected ${CURRENT_MANIFEST_VERSION}, got ${decoded.manifestVersion}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn decoded as ManifestEnvelope;\n\t});\n\n// -----------------------------------------------------------------------------\n// Re-export the envelope + endpoint types so downstream substrate\n// modules (codegen, build integrations) don't reach into substrate/\n// directly.\n// -----------------------------------------------------------------------------\n\nexport type { EndpointEntry, EndpointKey, ManifestCodegen, ManifestEnvelope, PluginKey };\nexport { ManifestEnvelopeSchema };\n"],"mappings":";;;;;AAuDA,IAAa,gBAAb,cAAmC,KAAK,YAAY,eAAe,CAAC,CAUjE,CAAC;;;;;;;;;AAwBJ,MAAa,iBACZ,UAEA,OAAO,IAAI,aAAa;CACvB,MAAM,YAA2C,CAAC;CAClD,MAAM,SAAkC,EAAE,GAAG,MAAM,OAAO;CAE1D,MAAM,eAAe,OACpB,OAAO,IAAI,aAAa;EACvB,MAAM,KAAK,GAAG;EACd,IAAI,MAAM,WACT,OAAO,OAAO,OAAO,KACpB,IAAI,cAAc;GACjB,QAAQ;GACR,MAAM;GACN,QAAQ,eAAe,GAAG;EAC3B,CAAC,CACF;EAED,UAAU,MAAM;CACjB,CAAC;CAEF,KAAK,MAAM,MAAM,MAAM,aAAa,CAAC,GACpC,OAAO,YAAY,EAAE;CAGtB,OAAO;EACN,UAAU,MAAM;EAChB,iBAAA;EACA;EACA;EAIA,GAAI,MAAM,YAAY,KAAA,IAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;CACjE;AACD,CAAC;;;;;;;AAYF,MAAa,iBACZ,UACA,SAEA,OAAO,IAAI,aAAa;CACvB,MAAM,OAAO,kBAAkB,QAAQ;CAEvC,OAAO,gBAAgB,MADT,IAAI,YAAY,CAAC,CAAC,OAAO,IACN,CAAC,CAAC,CAAC,KACnC,OAAO,UACL,UACA,IAAI,cAAc;EACjB,QAAQ;EACR;EACA,QAAQ,gCAAgC,MAAM;EAC9C;CACD,CAAC,CACH,CACD;AACD,CAAC;AAEF,MAAM,qBAAqB,aAI1B,KAAK,UAAU,UAAU,MAAM,CAAC"}
|
|
@@ -75,6 +75,6 @@ const layerStackPaths = Layer.effect(StackPathsService, Effect.gen(function* ()
|
|
|
75
75
|
});
|
|
76
76
|
}));
|
|
77
77
|
//#endregion
|
|
78
|
-
export { IdentityContext, RuntimeRoot, StackPathsService, layerIdentity, layerRuntimeRoot, layerStackPaths
|
|
78
|
+
export { IdentityContext, RuntimeRoot, StackPathsService, layerIdentity, layerRuntimeRoot, layerStackPaths };
|
|
79
79
|
|
|
80
80
|
//# sourceMappingURL=paths.mjs.map
|
|
@@ -100,7 +100,7 @@ const commands = [
|
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
name: "apply",
|
|
103
|
-
summary: "Re-emit the per-stack
|
|
103
|
+
summary: "Re-emit the per-stack deployment file from a live or one-shot stack.",
|
|
104
104
|
usage: "devstack apply [options]",
|
|
105
105
|
lifecycle: "live-aware",
|
|
106
106
|
sideEffects: "write",
|
|
@@ -118,17 +118,17 @@ const commands = [
|
|
|
118
118
|
options: configOptions
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
|
-
name: "dump-
|
|
122
|
-
summary: "Emit the stack
|
|
123
|
-
usage: "devstack dump-
|
|
121
|
+
name: "dump-deployment",
|
|
122
|
+
summary: "Emit the stack deployment for a real-network deploy.",
|
|
123
|
+
usage: "devstack dump-deployment [options]",
|
|
124
124
|
lifecycle: "live-aware",
|
|
125
125
|
sideEffects: "write",
|
|
126
126
|
requiresDocker: true,
|
|
127
|
-
description: "Emits the stack
|
|
127
|
+
description: "Emits the stack deployment.json deployment to --out (or stdout). Reads the existing file when a supervisor is live; otherwise runs a one-shot boot to produce it, then tears down. With --network <net>, instead emits a typed deployments/<net>.ts (satisfies AppNetworkDeployment) from that network entry. The supported way to obtain a committed deployment file for a real-network deploy.",
|
|
128
128
|
options: [...configOptions, {
|
|
129
129
|
name: "out",
|
|
130
130
|
value: "path",
|
|
131
|
-
description: "Write the
|
|
131
|
+
description: "Write the deployment JSON to this file instead of stdout."
|
|
132
132
|
}]
|
|
133
133
|
},
|
|
134
134
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-tree.mjs","names":[],"sources":["../../../src/surfaces/cli/command-tree.ts"],"sourcesContent":["import { ENV_VARS } from './flags.ts';\nimport { exitCodeTable } from './sysexits.ts';\n\nexport type CommandLifecycle = 'attached' | 'live-aware' | 'one-shot' | 'offline';\n\nexport interface CommandOption {\n\treadonly name: string;\n\treadonly value?: string;\n\treadonly description: string;\n}\n\nexport interface CommandNode {\n\treadonly name: string;\n\treadonly summary: string;\n\treadonly usage: string;\n\treadonly lifecycle: CommandLifecycle;\n\treadonly sideEffects: 'none' | 'read' | 'write' | 'destructive';\n\treadonly requiresDocker: boolean;\n\treadonly description?: string;\n\treadonly arguments?: ReadonlyArray<string>;\n\treadonly options?: ReadonlyArray<CommandOption>;\n\treadonly subcommands?: ReadonlyArray<CommandNode>;\n}\n\nconst identityOptions = [\n\t{ name: 'json', description: 'Emit JSON envelope output.' },\n\t{ name: 'app', value: 'name', description: 'Override app name.' },\n\t{ name: 'stack', value: 'name', description: 'Override stack name.' },\n\t{ name: 'state-dir', value: 'path', description: 'Override state directory.' },\n\t{ name: 'verbose', description: 'Enable more verbose logging.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst configOptions = [\n\t...identityOptions,\n\t{ name: 'config', value: 'path', description: 'Override devstack.config.ts path.' },\n\t{ name: 'network', value: 'name', description: 'Override network before config import.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst confirmOptions = [\n\t...identityOptions,\n\t{ name: 'yes', description: 'Assume yes on prompts.' },\n\t{ name: 'no-input', description: 'Forbid prompts.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst globalMaintenanceOptions = [\n\t{ name: 'json', description: 'Emit JSON envelope output.' },\n\t{ name: 'state-dir', value: 'path', description: 'Override state directory.' },\n\t{ name: 'verbose', description: 'Enable more verbose logging.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst commands = [\n\t{\n\t\tname: 'up',\n\t\tsummary: 'Boot a stack and stay attached until interrupted.',\n\t\tusage: 'devstack up [options]',\n\t\tlifecycle: 'attached',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\tdescription: 'Starts the supervisor and renders the attached operator surface.',\n\t\toptions: [\n\t\t\t...configOptions,\n\t\t\t{\n\t\t\t\tname: 'renderer',\n\t\t\t\tvalue: 'tui|plain|silent',\n\t\t\t\tdescription: 'Select the attached renderer.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'from-snapshot',\n\t\t\t\tvalue: 'name-or-id',\n\t\t\t\tdescription: 'Start by restoring a snapshot before the initial acquire.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'snapshot-cache',\n\t\t\t\tvalue: 'name',\n\t\t\t\tdescription: 'Use a named snapshot as a startup cache and refresh it when stale.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'snapshot-stale',\n\t\t\t\tvalue: 'warn|block|clean-start',\n\t\t\t\tdescription: 'Policy when --from-snapshot inputs differ from the current stack.',\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\tname: 'apply',\n\t\tsummary: 'Re-emit the per-stack ids file and dev extras from a live or one-shot stack.',\n\t\tusage: 'devstack apply [options]',\n\t\tlifecycle: 'live-aware',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\toptions: configOptions,\n\t},\n\t{\n\t\tname: 'codegen',\n\t\tsummary: 'Regenerate committed bindings from Move source (no stack boot).',\n\t\tusage: 'devstack codegen [options]',\n\t\tlifecycle: 'one-shot',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: false,\n\t\tdescription:\n\t\t\t'Move-compiles local package sources and emits the committed src/generated tree (type bindings + sentinel-id config stubs) without booting a stack.',\n\t\toptions: configOptions,\n\t},\n\t{\n\t\tname: 'dump-ids',\n\t\tsummary: 'Emit the stack id-config (devstack-ids.json) for a real-network deploy.',\n\t\tusage: 'devstack dump-ids [options]',\n\t\tlifecycle: 'live-aware',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\tdescription:\n\t\t\t'Emits the stack devstack-ids.json id-config to --out (or stdout). Reads the existing file when a supervisor is live; otherwise runs a one-shot boot to produce it, then tears down. The supported way to obtain a committed id-config file for a real-network deploy.',\n\t\toptions: [\n\t\t\t...configOptions,\n\t\t\t{\n\t\t\t\tname: 'out',\n\t\t\t\tvalue: 'path',\n\t\t\t\tdescription: 'Write the id-config JSON to this file instead of stdout.',\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\tname: 'status',\n\t\tsummary: 'Show the current stack projection (offline: from the manifest).',\n\t\tusage: 'devstack status [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'read',\n\t\trequiresDocker: false,\n\t\toptions: identityOptions,\n\t},\n\t{\n\t\tname: 'doctor',\n\t\tsummary: 'Run host and stack preflight checks.',\n\t\tusage: 'devstack doctor [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'read',\n\t\trequiresDocker: true,\n\t\toptions: identityOptions,\n\t},\n\t{\n\t\tname: 'config',\n\t\tsummary: 'Print resolved config inputs.',\n\t\tusage: 'devstack config [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'read',\n\t\trequiresDocker: false,\n\t\toptions: configOptions,\n\t},\n\t{\n\t\tname: 'schema',\n\t\tsummary: 'Emit the CLI schema.',\n\t\tusage: 'devstack schema --json',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'none',\n\t\trequiresDocker: false,\n\t\toptions: [{ name: 'json', description: 'Emit JSON schema output.' }],\n\t},\n\t{\n\t\tname: 'snapshot',\n\t\tsummary: 'Capture, restore, list, or delete stack snapshots.',\n\t\tusage: 'devstack snapshot <command> [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\tsubcommands: [\n\t\t\t{\n\t\t\t\tname: 'save',\n\t\t\t\tsummary: 'Capture a snapshot through a one-shot stack boot.',\n\t\t\t\tusage: 'devstack snapshot save [name] [options]',\n\t\t\t\tlifecycle: 'one-shot',\n\t\t\t\tsideEffects: 'write',\n\t\t\t\trequiresDocker: true,\n\t\t\t\targuments: ['name'],\n\t\t\t\toptions: [\n\t\t\t\t\t...configOptions,\n\t\t\t\t\t{ name: 'name', value: 'name', description: 'Human-readable snapshot name.' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'restore',\n\t\t\t\tsummary: 'Restore a snapshot by name or id.',\n\t\t\t\tusage: 'devstack snapshot restore <name-or-id> [options]',\n\t\t\t\tlifecycle: 'offline',\n\t\t\t\tsideEffects: 'destructive',\n\t\t\t\trequiresDocker: true,\n\t\t\t\targuments: ['name-or-id'],\n\t\t\t\toptions: confirmOptions,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'list',\n\t\t\t\tsummary: 'List snapshots for the selected stack.',\n\t\t\t\tusage: 'devstack snapshot list [options]',\n\t\t\t\tlifecycle: 'offline',\n\t\t\t\tsideEffects: 'read',\n\t\t\t\trequiresDocker: false,\n\t\t\t\toptions: identityOptions,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'delete',\n\t\t\t\tsummary: 'Delete a snapshot by name or id.',\n\t\t\t\tusage: 'devstack snapshot delete <name-or-id> [options]',\n\t\t\t\tlifecycle: 'offline',\n\t\t\t\tsideEffects: 'destructive',\n\t\t\t\trequiresDocker: false,\n\t\t\t\targuments: ['name-or-id'],\n\t\t\t\toptions: confirmOptions,\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\tname: 'prune',\n\t\tsummary: 'Inventory and prune devstack-labelled Docker resources.',\n\t\tusage: 'devstack prune [--list | --dry-run | --all --yes] [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'destructive',\n\t\trequiresDocker: true,\n\t\toptions: [\n\t\t\t...globalMaintenanceOptions,\n\t\t\t{ name: 'list', description: 'List resource groups without pruning.' },\n\t\t\t{ name: 'all', description: 'Prune every idle non-shared resource group.' },\n\t\t\t{ name: 'no-containers', description: 'Do not remove containers.' },\n\t\t\t{ name: 'no-networks', description: 'Do not remove networks.' },\n\t\t\t{ name: 'no-volumes', description: 'Do not remove volumes.' },\n\t\t\t{ name: 'include-images', description: 'Also remove devstack-labelled images.' },\n\t\t\t{ name: 'dry-run', description: 'Skip mutating effects.' },\n\t\t\t{ name: 'yes', description: 'Assume yes on prompts.' },\n\t\t\t{ name: 'no-input', description: 'Forbid prompts.' },\n\t\t],\n\t},\n\t{\n\t\tname: 'wipe',\n\t\tsummary: 'Destroy all state for the selected stack.',\n\t\tusage: 'devstack wipe [--dry-run] [--yes] [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'destructive',\n\t\trequiresDocker: true,\n\t\toptions: [\n\t\t\t...identityOptions,\n\t\t\t{ name: 'dry-run', description: 'Skip mutating effects.' },\n\t\t\t{ name: 'yes', description: 'Assume yes on prompts.' },\n\t\t\t{ name: 'no-input', description: 'Forbid prompts.' },\n\t\t],\n\t},\n] as const satisfies ReadonlyArray<CommandNode>;\n\nexport const COMMAND_TREE: CommandNode = {\n\tname: 'devstack',\n\tsummary: 'Sui development stack CLI.',\n\tusage: 'devstack <command> [options]',\n\tlifecycle: 'offline',\n\tsideEffects: 'none',\n\trequiresDocker: false,\n\tdescription: 'Boot, inspect, snapshot, and manage local Sui development stacks.',\n\tsubcommands: commands,\n};\n\nexport type Verb = (typeof commands)[number]['name'];\n\nexport const VERBS: ReadonlyArray<Verb> = commands.map((command) => command.name);\n\nexport const commandSchema = () => ({\n\tschemaVersion: 1,\n\tverbs: VERBS,\n\tcommands: COMMAND_TREE,\n\texitCodes: exitCodeTable,\n\tenvVars: Object.values(ENV_VARS),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command-tree.mjs","names":[],"sources":["../../../src/surfaces/cli/command-tree.ts"],"sourcesContent":["import { ENV_VARS } from './flags.ts';\nimport { exitCodeTable } from './sysexits.ts';\n\nexport type CommandLifecycle = 'attached' | 'live-aware' | 'one-shot' | 'offline';\n\nexport interface CommandOption {\n\treadonly name: string;\n\treadonly value?: string;\n\treadonly description: string;\n}\n\nexport interface CommandNode {\n\treadonly name: string;\n\treadonly summary: string;\n\treadonly usage: string;\n\treadonly lifecycle: CommandLifecycle;\n\treadonly sideEffects: 'none' | 'read' | 'write' | 'destructive';\n\treadonly requiresDocker: boolean;\n\treadonly description?: string;\n\treadonly arguments?: ReadonlyArray<string>;\n\treadonly options?: ReadonlyArray<CommandOption>;\n\treadonly subcommands?: ReadonlyArray<CommandNode>;\n}\n\nconst identityOptions = [\n\t{ name: 'json', description: 'Emit JSON envelope output.' },\n\t{ name: 'app', value: 'name', description: 'Override app name.' },\n\t{ name: 'stack', value: 'name', description: 'Override stack name.' },\n\t{ name: 'state-dir', value: 'path', description: 'Override state directory.' },\n\t{ name: 'verbose', description: 'Enable more verbose logging.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst configOptions = [\n\t...identityOptions,\n\t{ name: 'config', value: 'path', description: 'Override devstack.config.ts path.' },\n\t{ name: 'network', value: 'name', description: 'Override network before config import.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst confirmOptions = [\n\t...identityOptions,\n\t{ name: 'yes', description: 'Assume yes on prompts.' },\n\t{ name: 'no-input', description: 'Forbid prompts.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst globalMaintenanceOptions = [\n\t{ name: 'json', description: 'Emit JSON envelope output.' },\n\t{ name: 'state-dir', value: 'path', description: 'Override state directory.' },\n\t{ name: 'verbose', description: 'Enable more verbose logging.' },\n] as const satisfies ReadonlyArray<CommandOption>;\n\nconst commands = [\n\t{\n\t\tname: 'up',\n\t\tsummary: 'Boot a stack and stay attached until interrupted.',\n\t\tusage: 'devstack up [options]',\n\t\tlifecycle: 'attached',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\tdescription: 'Starts the supervisor and renders the attached operator surface.',\n\t\toptions: [\n\t\t\t...configOptions,\n\t\t\t{\n\t\t\t\tname: 'renderer',\n\t\t\t\tvalue: 'tui|plain|silent',\n\t\t\t\tdescription: 'Select the attached renderer.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'from-snapshot',\n\t\t\t\tvalue: 'name-or-id',\n\t\t\t\tdescription: 'Start by restoring a snapshot before the initial acquire.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'snapshot-cache',\n\t\t\t\tvalue: 'name',\n\t\t\t\tdescription: 'Use a named snapshot as a startup cache and refresh it when stale.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'snapshot-stale',\n\t\t\t\tvalue: 'warn|block|clean-start',\n\t\t\t\tdescription: 'Policy when --from-snapshot inputs differ from the current stack.',\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\tname: 'apply',\n\t\tsummary: 'Re-emit the per-stack deployment file from a live or one-shot stack.',\n\t\tusage: 'devstack apply [options]',\n\t\tlifecycle: 'live-aware',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\toptions: configOptions,\n\t},\n\t{\n\t\tname: 'codegen',\n\t\tsummary: 'Regenerate committed bindings from Move source (no stack boot).',\n\t\tusage: 'devstack codegen [options]',\n\t\tlifecycle: 'one-shot',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: false,\n\t\tdescription:\n\t\t\t'Move-compiles local package sources and emits the committed src/generated tree (type bindings + sentinel-id config stubs) without booting a stack.',\n\t\toptions: configOptions,\n\t},\n\t{\n\t\tname: 'dump-deployment',\n\t\tsummary: 'Emit the stack deployment for a real-network deploy.',\n\t\tusage: 'devstack dump-deployment [options]',\n\t\tlifecycle: 'live-aware',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\tdescription:\n\t\t\t'Emits the stack deployment.json deployment to --out (or stdout). Reads the existing file when a supervisor is live; otherwise runs a one-shot boot to produce it, then tears down. With --network <net>, instead emits a typed deployments/<net>.ts (satisfies AppNetworkDeployment) from that network entry. The supported way to obtain a committed deployment file for a real-network deploy.',\n\t\toptions: [\n\t\t\t...configOptions,\n\t\t\t{\n\t\t\t\tname: 'out',\n\t\t\t\tvalue: 'path',\n\t\t\t\tdescription: 'Write the deployment JSON to this file instead of stdout.',\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\tname: 'status',\n\t\tsummary: 'Show the current stack projection (offline: from the manifest).',\n\t\tusage: 'devstack status [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'read',\n\t\trequiresDocker: false,\n\t\toptions: identityOptions,\n\t},\n\t{\n\t\tname: 'doctor',\n\t\tsummary: 'Run host and stack preflight checks.',\n\t\tusage: 'devstack doctor [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'read',\n\t\trequiresDocker: true,\n\t\toptions: identityOptions,\n\t},\n\t{\n\t\tname: 'config',\n\t\tsummary: 'Print resolved config inputs.',\n\t\tusage: 'devstack config [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'read',\n\t\trequiresDocker: false,\n\t\toptions: configOptions,\n\t},\n\t{\n\t\tname: 'schema',\n\t\tsummary: 'Emit the CLI schema.',\n\t\tusage: 'devstack schema --json',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'none',\n\t\trequiresDocker: false,\n\t\toptions: [{ name: 'json', description: 'Emit JSON schema output.' }],\n\t},\n\t{\n\t\tname: 'snapshot',\n\t\tsummary: 'Capture, restore, list, or delete stack snapshots.',\n\t\tusage: 'devstack snapshot <command> [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'write',\n\t\trequiresDocker: true,\n\t\tsubcommands: [\n\t\t\t{\n\t\t\t\tname: 'save',\n\t\t\t\tsummary: 'Capture a snapshot through a one-shot stack boot.',\n\t\t\t\tusage: 'devstack snapshot save [name] [options]',\n\t\t\t\tlifecycle: 'one-shot',\n\t\t\t\tsideEffects: 'write',\n\t\t\t\trequiresDocker: true,\n\t\t\t\targuments: ['name'],\n\t\t\t\toptions: [\n\t\t\t\t\t...configOptions,\n\t\t\t\t\t{ name: 'name', value: 'name', description: 'Human-readable snapshot name.' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'restore',\n\t\t\t\tsummary: 'Restore a snapshot by name or id.',\n\t\t\t\tusage: 'devstack snapshot restore <name-or-id> [options]',\n\t\t\t\tlifecycle: 'offline',\n\t\t\t\tsideEffects: 'destructive',\n\t\t\t\trequiresDocker: true,\n\t\t\t\targuments: ['name-or-id'],\n\t\t\t\toptions: confirmOptions,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'list',\n\t\t\t\tsummary: 'List snapshots for the selected stack.',\n\t\t\t\tusage: 'devstack snapshot list [options]',\n\t\t\t\tlifecycle: 'offline',\n\t\t\t\tsideEffects: 'read',\n\t\t\t\trequiresDocker: false,\n\t\t\t\toptions: identityOptions,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'delete',\n\t\t\t\tsummary: 'Delete a snapshot by name or id.',\n\t\t\t\tusage: 'devstack snapshot delete <name-or-id> [options]',\n\t\t\t\tlifecycle: 'offline',\n\t\t\t\tsideEffects: 'destructive',\n\t\t\t\trequiresDocker: false,\n\t\t\t\targuments: ['name-or-id'],\n\t\t\t\toptions: confirmOptions,\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\tname: 'prune',\n\t\tsummary: 'Inventory and prune devstack-labelled Docker resources.',\n\t\tusage: 'devstack prune [--list | --dry-run | --all --yes] [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'destructive',\n\t\trequiresDocker: true,\n\t\toptions: [\n\t\t\t...globalMaintenanceOptions,\n\t\t\t{ name: 'list', description: 'List resource groups without pruning.' },\n\t\t\t{ name: 'all', description: 'Prune every idle non-shared resource group.' },\n\t\t\t{ name: 'no-containers', description: 'Do not remove containers.' },\n\t\t\t{ name: 'no-networks', description: 'Do not remove networks.' },\n\t\t\t{ name: 'no-volumes', description: 'Do not remove volumes.' },\n\t\t\t{ name: 'include-images', description: 'Also remove devstack-labelled images.' },\n\t\t\t{ name: 'dry-run', description: 'Skip mutating effects.' },\n\t\t\t{ name: 'yes', description: 'Assume yes on prompts.' },\n\t\t\t{ name: 'no-input', description: 'Forbid prompts.' },\n\t\t],\n\t},\n\t{\n\t\tname: 'wipe',\n\t\tsummary: 'Destroy all state for the selected stack.',\n\t\tusage: 'devstack wipe [--dry-run] [--yes] [options]',\n\t\tlifecycle: 'offline',\n\t\tsideEffects: 'destructive',\n\t\trequiresDocker: true,\n\t\toptions: [\n\t\t\t...identityOptions,\n\t\t\t{ name: 'dry-run', description: 'Skip mutating effects.' },\n\t\t\t{ name: 'yes', description: 'Assume yes on prompts.' },\n\t\t\t{ name: 'no-input', description: 'Forbid prompts.' },\n\t\t],\n\t},\n] as const satisfies ReadonlyArray<CommandNode>;\n\nexport const COMMAND_TREE: CommandNode = {\n\tname: 'devstack',\n\tsummary: 'Sui development stack CLI.',\n\tusage: 'devstack <command> [options]',\n\tlifecycle: 'offline',\n\tsideEffects: 'none',\n\trequiresDocker: false,\n\tdescription: 'Boot, inspect, snapshot, and manage local Sui development stacks.',\n\tsubcommands: commands,\n};\n\nexport type Verb = (typeof commands)[number]['name'];\n\nexport const VERBS: ReadonlyArray<Verb> = commands.map((command) => command.name);\n\nexport const commandSchema = () => ({\n\tschemaVersion: 1,\n\tverbs: VERBS,\n\tcommands: COMMAND_TREE,\n\texitCodes: exitCodeTable,\n\tenvVars: Object.values(ENV_VARS),\n});\n"],"mappings":"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"}
|
|
@@ -271,16 +271,18 @@ const app = buildApplication(buildRouteMap({
|
|
|
271
271
|
return runWithFlags(this, "codegen", flags, [], (global) => this.deps.codegen.run({ configPath: global.configPath }));
|
|
272
272
|
}
|
|
273
273
|
}),
|
|
274
|
-
|
|
274
|
+
dumpDeployment: buildCommand({
|
|
275
275
|
parameters: { flags: {
|
|
276
276
|
...configFlagParams,
|
|
277
|
-
out: stringFlag("Write the
|
|
277
|
+
out: stringFlag("Write the deployment JSON to this file instead of stdout", "path"),
|
|
278
|
+
network: stringFlag("Emit a typed deployments/<network>.ts (satisfies AppNetworkDeployment) instead of the raw envelope", "name")
|
|
278
279
|
} },
|
|
279
|
-
docs: { brief: "Emit the stack
|
|
280
|
+
docs: { brief: "Emit the stack deployment (deployment.json) for a real-network deploy" },
|
|
280
281
|
func: function(flags) {
|
|
281
|
-
return runWithFlags(this, "dump-
|
|
282
|
+
return runWithFlags(this, "dump-deployment", flags, [], (global) => this.deps.dumpDeployment.run({
|
|
282
283
|
configPath: global.configPath,
|
|
283
284
|
out: flags.out,
|
|
285
|
+
network: flags.network,
|
|
284
286
|
io: this.io,
|
|
285
287
|
outputMode: global.outputMode
|
|
286
288
|
}));
|