@kici-dev/compiler 0.1.26 → 0.2.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/dist/auth/headless-detect.d.ts +14 -5
- package/dist/auth/headless-detect.js +50 -9
- package/dist/cli-banner.d.ts +6 -3
- package/dist/cli-banner.js +13 -5
- package/dist/cli.js +180 -48
- package/dist/commands/check-mode.d.ts +1 -1
- package/dist/commands/compile.js +31 -7
- package/dist/commands/diagnostics.js +14 -2
- package/dist/commands/doctor.d.ts +64 -0
- package/dist/commands/doctor.js +413 -0
- package/dist/commands/endpoints.js +6 -0
- package/dist/commands/fixture.js +13 -0
- package/dist/commands/hook.js +3 -2
- package/dist/commands/index.d.ts +13 -1
- package/dist/commands/index.js +10 -3
- package/dist/commands/init-host-os.d.ts +34 -0
- package/dist/commands/init-host-os.js +44 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +160 -37
- package/dist/commands/local-trust-root.d.ts +6 -0
- package/dist/commands/local-trust-root.js +52 -0
- package/dist/commands/local.d.ts +65 -0
- package/dist/commands/local.js +206 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +41 -1
- package/dist/commands/logout.js +7 -0
- package/dist/commands/notifications.d.ts +61 -0
- package/dist/commands/notifications.js +274 -0
- package/dist/commands/preview.js +9 -2
- package/dist/commands/run-banner.d.ts +31 -0
- package/dist/commands/run-banner.js +25 -0
- package/dist/commands/run-routed.d.ts +53 -0
- package/dist/commands/run-routed.js +185 -0
- package/dist/commands/run.d.ts +0 -17
- package/dist/commands/run.js +44 -58
- package/dist/commands/runs/artifacts/download.d.ts +24 -0
- package/dist/commands/runs/artifacts/download.js +154 -0
- package/dist/commands/runs/artifacts/list.d.ts +5 -0
- package/dist/commands/runs/artifacts/list.js +47 -0
- package/dist/commands/runs/cancel.js +2 -2
- package/dist/commands/runs/list.js +8 -2
- package/dist/commands/secrets-list.d.ts +2 -2
- package/dist/commands/secrets-list.js +4 -4
- package/dist/commands/types.d.ts +3 -3
- package/dist/commands/types.js +4 -4
- package/dist/commands/verify-attestation.d.ts +3 -1
- package/dist/commands/verify-attestation.js +18 -7
- package/dist/errors/formatter.d.ts +14 -1
- package/dist/errors/formatter.js +13 -3
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +3 -2
- package/dist/errors/source-location.d.ts +20 -0
- package/dist/errors/source-location.js +45 -0
- package/dist/execution/executor.js +32 -12
- package/dist/fixtures/defaults/index.js +2 -1
- package/dist/generators/secrets-dts.d.ts +9 -9
- package/dist/generators/secrets-dts.js +12 -12
- package/dist/llm-context/llms-architecture.txt +50 -21
- package/dist/llm-context/llms-cli.txt +2667 -1454
- package/dist/llm-context/llms-features.txt +484 -414
- package/dist/llm-context/llms-full.txt +6703 -4687
- package/dist/llm-context/llms-getting-started.txt +311 -55
- package/dist/llm-context/llms-patterns.txt +17 -29
- package/dist/llm-context/llms-providers.txt +10 -1
- package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
- package/dist/llm-context/llms-sdk.txt +596 -2157
- package/dist/llm-context/llms.txt +31 -17
- package/dist/local-plane/orchestrator-process.d.ts +70 -0
- package/dist/local-plane/orchestrator-process.js +129 -0
- package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
- package/dist/local-plane/os-mismatch-hint.js +34 -0
- package/dist/local-plane/paths.d.ts +39 -0
- package/dist/local-plane/paths.js +54 -0
- package/dist/local-plane/plane-liveness.d.ts +68 -0
- package/dist/local-plane/plane-liveness.js +145 -0
- package/dist/local-plane/plane-manager.d.ts +193 -0
- package/dist/local-plane/plane-manager.js +480 -0
- package/dist/local-plane/plane-seed.d.ts +76 -0
- package/dist/local-plane/plane-seed.js +59 -0
- package/dist/local-plane/plane-trigger.d.ts +71 -0
- package/dist/local-plane/plane-trigger.js +110 -0
- package/dist/local-plane/platform-attach.d.ts +53 -0
- package/dist/local-plane/platform-attach.js +107 -0
- package/dist/local-plane/port-holder.d.ts +78 -0
- package/dist/local-plane/port-holder.js +227 -0
- package/dist/local-plane/postgres.d.ts +56 -0
- package/dist/local-plane/postgres.js +187 -0
- package/dist/local-plane/resolve-plane.d.ts +37 -0
- package/dist/local-plane/resolve-plane.js +76 -0
- package/dist/local-plane/run-follow.d.ts +61 -0
- package/dist/local-plane/run-follow.js +151 -0
- package/dist/local-plane/scaler-config.d.ts +68 -0
- package/dist/local-plane/scaler-config.js +132 -0
- package/dist/local-plane/secret-seed.d.ts +59 -0
- package/dist/local-plane/secret-seed.js +112 -0
- package/dist/local-plane/source-provider.d.ts +43 -0
- package/dist/local-plane/source-provider.js +159 -0
- package/dist/local-plane/trusted-routing.d.ts +32 -0
- package/dist/local-plane/trusted-routing.js +47 -0
- package/dist/lockfile/generator.d.ts +14 -1
- package/dist/lockfile/generator.js +103 -37
- package/dist/lockfile/index.d.ts +3 -1
- package/dist/lockfile/index.js +3 -2
- package/dist/lockfile/purity-diagnostics.d.ts +31 -0
- package/dist/lockfile/purity-diagnostics.js +52 -0
- package/dist/postinstall.js +2 -1
- package/dist/remote/artifact-extract.d.ts +8 -0
- package/dist/remote/artifact-extract.js +58 -0
- package/dist/remote/config.d.ts +21 -3
- package/dist/remote/config.js +24 -5
- package/dist/remote/dashboard-client.d.ts +46 -9
- package/dist/remote/dashboard-client.js +54 -11
- package/dist/remote/fs-case.d.ts +21 -0
- package/dist/remote/fs-case.js +45 -0
- package/dist/remote/history.d.ts +9 -3
- package/dist/remote/history.js +79 -10
- package/dist/remote/local-repo-identity.d.ts +1 -1
- package/dist/remote/local-repo-identity.js +2 -1
- package/dist/remote/notifications-client.d.ts +149 -0
- package/dist/remote/notifications-client.js +103 -0
- package/dist/remote/oauth.d.ts +13 -0
- package/dist/remote/oauth.js +31 -5
- package/dist/remote/output/summary.js +76 -14
- package/dist/remote/platform-client.d.ts +1 -1
- package/dist/remote/render.js +30 -9
- package/dist/{local-executor → remote}/secret-loader.d.ts +1 -1
- package/dist/{local-executor → remote}/secret-loader.js +4 -4
- package/dist/remote/secret-upload.d.ts +1 -1
- package/dist/remote/secret-upload.js +2 -2
- package/dist/remote/uploader.d.ts +1 -1
- package/dist/remote/uploader.js +7 -6
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +8 -8
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.js +2 -2
- package/dist/templates/package-json.d.ts +15 -0
- package/dist/templates/package-json.js +26 -5
- package/dist/test-runner/dry-run.d.ts +2 -1
- package/dist/test-runner/dry-run.js +18 -1
- package/dist/test-runner/event-types.d.ts +2 -0
- package/dist/test-runner/event-types.js +3 -0
- package/dist/test-runner/job-executor.d.ts +1 -0
- package/dist/test-runner/job-executor.js +82 -44
- package/dist/test-runner/output-formatter.d.ts +5 -2
- package/dist/test-runner/output-formatter.js +6 -4
- package/dist/test-runner/payload-builder.d.ts +7 -1
- package/dist/test-runner/payload-builder.js +11 -2
- package/dist/test-runner/rule-evaluator.d.ts +7 -3
- package/dist/test-runner/rule-evaluator.js +13 -12
- package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
- package/dist/test-runner/step-context.d.ts +23 -5
- package/dist/test-runner/step-context.js +29 -119
- package/dist/types.d.ts +26 -7
- package/dist/types.js +4 -2
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +2 -1
- package/dist/validation/typecheck.d.ts +28 -0
- package/dist/validation/typecheck.js +110 -0
- package/dist/validation/validator.d.ts +9 -4
- package/dist/validation/validator.js +38 -44
- package/package.json +13 -13
- package/sbom.spdx.json +9311 -1528
- package/dist/assets/api-TJJVHrjC.json +0 -118
- package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
- package/dist/assets/package-BpQF9kR8.json +0 -74
- package/dist/assets/package-Ceo2h27X.json +0 -89
- package/dist/assets/source_context-D0atuL28.json +0 -20
- package/dist/assets/type-BFqO8SCZ.json +0 -202
- package/dist/commands/cancel.d.ts +0 -22
- package/dist/commands/detect-package-manager.d.ts +0 -42
- package/dist/commands/held-run-resolve.d.ts +0 -50
- package/dist/commands/status.d.ts +0 -34
- package/dist/commands/test.d.ts +0 -88
- package/dist/errors/codes.d.ts +0 -25
- package/dist/local-executor/dag-scheduler.d.ts +0 -44
- package/dist/local-executor/dag-scheduler.js +0 -183
- package/dist/local-executor/index.d.ts +0 -23
- package/dist/local-executor/index.js +0 -378
- package/dist/local-executor/job-runner.d.ts +0 -47
- package/dist/local-executor/job-runner.js +0 -411
- package/dist/local-executor/materializer.d.ts +0 -44
- package/dist/local-executor/materializer.js +0 -132
- package/dist/local-executor/output-streamer.d.ts +0 -31
- package/dist/local-executor/output-streamer.js +0 -168
- package/dist/local-executor/payload-generator.d.ts +0 -16
- package/dist/local-executor/payload-generator.js +0 -138
- package/dist/local-executor/picker.d.ts +0 -33
- package/dist/local-executor/picker.js +0 -109
- package/dist/local-executor/to-event-payload.d.ts +0 -16
- package/dist/local-executor/to-event-payload.js +0 -21
- package/dist/local-executor/types.d.ts +0 -93
- package/dist/local-executor/types.js +0 -2
- package/dist/local-executor/workflow-lock.d.ts +0 -82
- package/dist/local-executor/workflow-lock.js +0 -0
- package/dist/package-F7UXSDHW.json +0 -74
- package/dist/remote/client.d.ts +0 -210
- package/dist/remote/observer.d.ts +0 -81
- package/dist/test-runner/summary.d.ts +0 -7
- /package/dist/{local-executor → test-runner}/runs-on-display.d.ts +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { type PlaneState } from './plane-liveness.js';
|
|
2
|
+
/**
|
|
3
|
+
* The current CLI build's identity — semver plus git build commit — read at
|
|
4
|
+
* call time from the Rolldown-injected build constants (`scripts/build-ts.mjs`).
|
|
5
|
+
* Read on each call rather than captured in a module const so unit tests can
|
|
6
|
+
* inject the constants via `globalThis`. Falls back to `0.0.0` / `unknown` when
|
|
7
|
+
* running from source (unbuilt tree / vitest), where the defines are absent.
|
|
8
|
+
*/
|
|
9
|
+
export declare function currentBuildIdentity(): {
|
|
10
|
+
version: string;
|
|
11
|
+
buildCommit: string;
|
|
12
|
+
};
|
|
13
|
+
/** Whether the plane runs offline (independent) or attached to the Platform (hybrid). */
|
|
14
|
+
export type PlaneMode = 'independent' | 'hybrid';
|
|
15
|
+
/**
|
|
16
|
+
* Attachment record for a hybrid plane: the Platform relay it dials, the org it
|
|
17
|
+
* is scoped to, and the Platform api_keys id of its minted orchestrator key (for
|
|
18
|
+
* later revoke). The orchestrator token itself is NEVER stored here — it lives
|
|
19
|
+
* in a sibling 0600 file so it can't land in a world-readable stamp.
|
|
20
|
+
*/
|
|
21
|
+
export interface PlaneAttachment {
|
|
22
|
+
/** Orchestrator KICI_PLATFORM_URL — a ws(s)://…/ws relay URL. */
|
|
23
|
+
platformWsUrl: string;
|
|
24
|
+
/** HTTPS Platform API base (provenance/verify context, key revoke). */
|
|
25
|
+
platformApiBase: string;
|
|
26
|
+
orgId: string;
|
|
27
|
+
/** Platform api_keys id of the minted orchestrator key. */
|
|
28
|
+
keyId: string;
|
|
29
|
+
}
|
|
30
|
+
/** On-disk record of the running plane, written on boot, read on status/down. */
|
|
31
|
+
export interface PlaneStamp {
|
|
32
|
+
orchestratorPid: number;
|
|
33
|
+
port: number;
|
|
34
|
+
pgKind: 'embedded' | 'podman';
|
|
35
|
+
kiciVersion: string;
|
|
36
|
+
/** Git build commit of the CLI that booted this plane (see planeBuildIsStale). */
|
|
37
|
+
buildCommit: string;
|
|
38
|
+
stampVersion: number;
|
|
39
|
+
/** Offline (independent) vs attached (hybrid). Absent (legacy) reads as independent. */
|
|
40
|
+
mode?: PlaneMode;
|
|
41
|
+
}
|
|
42
|
+
export interface PlaneStatus {
|
|
43
|
+
/** True only when the plane is serving — a live-but-unready plane is false. */
|
|
44
|
+
running: boolean;
|
|
45
|
+
/** Liveness of the plane port: stopped, serving, live-but-not-serving, or held by someone else. */
|
|
46
|
+
state: PlaneState;
|
|
47
|
+
/** Failing `/ready` checks, when the plane is live but not serving. */
|
|
48
|
+
checks?: Record<string, boolean>;
|
|
49
|
+
pid?: number;
|
|
50
|
+
port?: number;
|
|
51
|
+
pgKind?: 'embedded' | 'podman';
|
|
52
|
+
stampVersion?: number;
|
|
53
|
+
url?: string;
|
|
54
|
+
/** Bootstrap admin token the CLI presents to the plane's admin API. */
|
|
55
|
+
adminToken?: string;
|
|
56
|
+
/** Offline (independent) vs attached (hybrid). */
|
|
57
|
+
mode: PlaneMode;
|
|
58
|
+
/** Present iff mode === 'hybrid'. */
|
|
59
|
+
attachment?: PlaneAttachment;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Read the plane's persisted Platform orchestrator token (mode 0600), or null
|
|
63
|
+
* when the plane is independent/offline. Kept out of the stamp so the token
|
|
64
|
+
* never lands in a world-readable file.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readPlatformToken(): string | null;
|
|
67
|
+
/** Persist the Platform orchestrator token (mode 0600). */
|
|
68
|
+
export declare function writePlatformToken(token: string): void;
|
|
69
|
+
/** Remove the persisted Platform orchestrator token (on detach). */
|
|
70
|
+
export declare function clearPlatformToken(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Read the durable attachment record (survives `kici local down`), or null when
|
|
73
|
+
* the plane is not attached. This — not the running stamp — is the source of
|
|
74
|
+
* truth for "is this plane attached to the Platform".
|
|
75
|
+
*/
|
|
76
|
+
export declare function readAttachment(): PlaneAttachment | null;
|
|
77
|
+
/** Persist the durable attachment record (never the token). */
|
|
78
|
+
export declare function writeAttachment(attachment: PlaneAttachment): void;
|
|
79
|
+
/** Remove the durable attachment record (on detach). */
|
|
80
|
+
export declare function clearAttachment(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Read the plane's persisted bootstrap admin token, generating + persisting one
|
|
83
|
+
* (mode 0600) on first boot. A warm plane reuses the same token across CLI
|
|
84
|
+
* invocations so a running orchestrator keeps accepting the CLI's admin calls.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readOrCreateAdminToken(): string;
|
|
87
|
+
/** Read the plane's admin token without generating one (null when absent). */
|
|
88
|
+
export declare function planeAdminToken(): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Read the plane's local master secret key, generating + persisting a fresh 64
|
|
91
|
+
* hex-char key (mode 0600) on first boot. Stable across boots so DB-encrypted
|
|
92
|
+
* material stays decryptable. Freshly generated — never derived from any sops
|
|
93
|
+
* secret. Enables the orchestrator's secrets subsystem, which gates its admin
|
|
94
|
+
* API surface.
|
|
95
|
+
*/
|
|
96
|
+
export declare function readOrCreateSecretKey(): string;
|
|
97
|
+
/** Absolute path of the dev-signed identity's private JWK key file. */
|
|
98
|
+
export declare function devIdentityKeyFile(): string;
|
|
99
|
+
/** Absolute path of the dev-signed identity's public JWK (written by the plane orchestrator). */
|
|
100
|
+
export declare function devIdentityPublicJwkFile(): string;
|
|
101
|
+
/**
|
|
102
|
+
* Read the plane's dev-signed identity keypair, generating + persisting a fresh
|
|
103
|
+
* EC P-256 private JWK (mode 0600) on first boot. Stable across boots so a
|
|
104
|
+
* `kici local trust-root` export stays valid for previously-minted tokens.
|
|
105
|
+
* Freshly generated — NEVER derived from any sops secret or real key. Only the
|
|
106
|
+
* private key is written here; the plane orchestrator derives + writes the
|
|
107
|
+
* public JWK next to it. Returns the private-key file path passed to the
|
|
108
|
+
* orchestrator via KICI_DEV_IDENTITY_KEY_FILE.
|
|
109
|
+
*/
|
|
110
|
+
export declare function readOrCreateDevIdentity(): string;
|
|
111
|
+
/** Remove the plane's on-disk record (pidfile + stamp). */
|
|
112
|
+
export declare function clearStamp(): void;
|
|
113
|
+
/**
|
|
114
|
+
* Whether a running plane described by `existing` was booted from a different
|
|
115
|
+
* CLI build than the current one — a semver bump OR a git-commit change (the
|
|
116
|
+
* latter covers intermediate staging/E2E commits that share a semver). Returns
|
|
117
|
+
* false when there is no stamp, or when the current build has no concrete
|
|
118
|
+
* identity (`buildCommit === 'unknown'`, i.e. running from source / a test),
|
|
119
|
+
* so a source-context `planeUp` never reboots a healthy plane spuriously. An
|
|
120
|
+
* old stamp with no `buildCommit` field reads as `undefined` and therefore
|
|
121
|
+
* triggers a one-time reboot on the first upgrade past this feature.
|
|
122
|
+
*/
|
|
123
|
+
export declare function planeBuildIsStale(existing: PlaneStamp | null): boolean;
|
|
124
|
+
/** Absolute path of the plane orchestrator's log file. */
|
|
125
|
+
export declare function planeLogPath(): string;
|
|
126
|
+
/**
|
|
127
|
+
* Read the plane's current status from whatever holds the plane port.
|
|
128
|
+
*
|
|
129
|
+
* `running` means "serving": a live orchestrator whose `/ready` fails is
|
|
130
|
+
* reported as `unready`, with the failing checks, rather than as stopped —
|
|
131
|
+
* inferring "not running" from an unready probe hides a process that still owns
|
|
132
|
+
* the port.
|
|
133
|
+
*/
|
|
134
|
+
export declare function planeStatus(): Promise<PlaneStatus>;
|
|
135
|
+
/** Attach parameters for a hybrid plane boot (see attachPlane). */
|
|
136
|
+
export interface PlaneUpAttach {
|
|
137
|
+
platformWsUrl: string;
|
|
138
|
+
platformToken: string;
|
|
139
|
+
platformApiBase: string;
|
|
140
|
+
orgId: string;
|
|
141
|
+
keyId: string;
|
|
142
|
+
}
|
|
143
|
+
export interface PlaneUpOptions {
|
|
144
|
+
/** Boot the plane hybrid (attached to the Platform) instead of independent. */
|
|
145
|
+
attach?: PlaneUpAttach;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Start (or reuse) the local dev plane. Idempotent: a healthy running plane this
|
|
149
|
+
* config dir stamped, **whose stamped mode matches the requested mode**, is
|
|
150
|
+
* returned as-is. Otherwise the port is reclaimed if it is another KiCI plane,
|
|
151
|
+
* and Postgres + the orchestrator are booted with a fresh stamp + pidfile. When
|
|
152
|
+
* `attach` is present the orchestrator boots hybrid against the Platform relay;
|
|
153
|
+
* otherwise it boots independent with the dev-signed identity. Throws when the
|
|
154
|
+
* port is held by a process that is not a KiCI plane orchestrator.
|
|
155
|
+
*/
|
|
156
|
+
export declare function planeUp(opts?: PlaneUpOptions): Promise<PlaneStatus>;
|
|
157
|
+
/** Outcome of a teardown attempt. `stopped` is true only when the port is verified free. */
|
|
158
|
+
export interface PlaneDownResult {
|
|
159
|
+
stopped: boolean;
|
|
160
|
+
port: number;
|
|
161
|
+
holderPid?: number;
|
|
162
|
+
reason?: string;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Stop the local dev plane and confirm the port was released.
|
|
166
|
+
*
|
|
167
|
+
* Teardown is reconstructed from whatever holds the port, not only from the
|
|
168
|
+
* stamp: a plane this config dir never stamped still occupies the port and is
|
|
169
|
+
* still ours to stop. The pidfile and stamp are cleared only once the port is
|
|
170
|
+
* verified free, so a survivor never loses the pid that identifies it.
|
|
171
|
+
*/
|
|
172
|
+
export declare function planeDown(): Promise<PlaneDownResult>;
|
|
173
|
+
/**
|
|
174
|
+
* Attach the local dev plane to the hosted Platform: mint an org-scoped
|
|
175
|
+
* orchestrator key with the logged-in PAT, then (re)boot the plane hybrid
|
|
176
|
+
* against the Platform relay. Registration with the Platform is implicit — the
|
|
177
|
+
* hybrid orchestrator's PlatformClient sends `source.register` on WS auth.
|
|
178
|
+
*/
|
|
179
|
+
export declare function attachPlane(args: {
|
|
180
|
+
apiBase: string;
|
|
181
|
+
pat: string;
|
|
182
|
+
orgId: string;
|
|
183
|
+
}): Promise<PlaneStatus>;
|
|
184
|
+
/**
|
|
185
|
+
* Detach the local dev plane: best-effort revoke the minted orchestrator key on
|
|
186
|
+
* the Platform, clear the durable attachment + token, then reboot the plane
|
|
187
|
+
* independent (offline). The revoke is non-fatal — a detach must succeed even
|
|
188
|
+
* offline or with an expired PAT.
|
|
189
|
+
*/
|
|
190
|
+
export declare function detachPlane(opts?: {
|
|
191
|
+
pat?: string;
|
|
192
|
+
}): Promise<PlaneStatus>;
|
|
193
|
+
//# sourceMappingURL=plane-manager.d.ts.map
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { planePaths, planePorts } from "./paths.js";
|
|
3
|
+
import { awaitOrchestratorReady, resolveServerEntry, resolveStandaloneEntry, spawnOrchestratorProcess } from "./orchestrator-process.js";
|
|
4
|
+
import { isPortFree, processCommandLine, terminatePid } from "./port-holder.js";
|
|
5
|
+
import { classificationPid, classifyPlane, planeStateOf, reclaimPlanePort } from "./plane-liveness.js";
|
|
6
|
+
import { startPlanePostgres, stopPlanePostgres } from "./postgres.js";
|
|
7
|
+
import { writeScalerConfig } from "./scaler-config.js";
|
|
8
|
+
import { derivePlatformWsUrl, mintOrchestratorKey, revokeOrchestratorKey } from "./platform-attach.js";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import { generateKeyPairSync, randomBytes } from "node:crypto";
|
|
12
|
+
//#region src/local-plane/plane-manager.ts
|
|
13
|
+
/**
|
|
14
|
+
* The current CLI build's identity — semver plus git build commit — read at
|
|
15
|
+
* call time from the Rolldown-injected build constants (`scripts/build-ts.mjs`).
|
|
16
|
+
* Read on each call rather than captured in a module const so unit tests can
|
|
17
|
+
* inject the constants via `globalThis`. Falls back to `0.0.0` / `unknown` when
|
|
18
|
+
* running from source (unbuilt tree / vitest), where the defines are absent.
|
|
19
|
+
*/
|
|
20
|
+
function currentBuildIdentity() {
|
|
21
|
+
return {
|
|
22
|
+
version: "0.2.0",
|
|
23
|
+
buildCommit: "15d5e4447"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the plane's persisted Platform orchestrator token (mode 0600), or null
|
|
28
|
+
* when the plane is independent/offline. Kept out of the stamp so the token
|
|
29
|
+
* never lands in a world-readable file.
|
|
30
|
+
*/
|
|
31
|
+
function readPlatformToken() {
|
|
32
|
+
try {
|
|
33
|
+
return fs.readFileSync(planePaths().platformTokenFile, "utf-8").trim() || null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Persist the Platform orchestrator token (mode 0600). */
|
|
39
|
+
function writePlatformToken(token) {
|
|
40
|
+
const { platformTokenFile, root } = planePaths();
|
|
41
|
+
fs.mkdirSync(root, { recursive: true });
|
|
42
|
+
fs.writeFileSync(platformTokenFile, token, { mode: 384 });
|
|
43
|
+
}
|
|
44
|
+
/** Remove the persisted Platform orchestrator token (on detach). */
|
|
45
|
+
function clearPlatformToken() {
|
|
46
|
+
fs.rmSync(planePaths().platformTokenFile, { force: true });
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the durable attachment record (survives `kici local down`), or null when
|
|
50
|
+
* the plane is not attached. This — not the running stamp — is the source of
|
|
51
|
+
* truth for "is this plane attached to the Platform".
|
|
52
|
+
*/
|
|
53
|
+
function readAttachment() {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(fs.readFileSync(planePaths().attachmentFile, "utf-8"));
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Persist the durable attachment record (never the token). */
|
|
61
|
+
function writeAttachment(attachment) {
|
|
62
|
+
const { attachmentFile, root } = planePaths();
|
|
63
|
+
fs.mkdirSync(root, { recursive: true });
|
|
64
|
+
fs.writeFileSync(attachmentFile, JSON.stringify(attachment, null, 2));
|
|
65
|
+
}
|
|
66
|
+
/** Remove the durable attachment record (on detach). */
|
|
67
|
+
function clearAttachment() {
|
|
68
|
+
fs.rmSync(planePaths().attachmentFile, { force: true });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the plane's persisted bootstrap admin token, generating + persisting one
|
|
72
|
+
* (mode 0600) on first boot. A warm plane reuses the same token across CLI
|
|
73
|
+
* invocations so a running orchestrator keeps accepting the CLI's admin calls.
|
|
74
|
+
*/
|
|
75
|
+
function readOrCreateAdminToken() {
|
|
76
|
+
const { adminTokenFile, root } = planePaths();
|
|
77
|
+
try {
|
|
78
|
+
const existing = fs.readFileSync(adminTokenFile, "utf-8").trim();
|
|
79
|
+
if (existing) return existing;
|
|
80
|
+
} catch {}
|
|
81
|
+
fs.mkdirSync(root, { recursive: true });
|
|
82
|
+
const token = `kici-local-${randomBytes(24).toString("hex")}`;
|
|
83
|
+
fs.writeFileSync(adminTokenFile, token, { mode: 384 });
|
|
84
|
+
return token;
|
|
85
|
+
}
|
|
86
|
+
/** Read the plane's admin token without generating one (null when absent). */
|
|
87
|
+
function planeAdminToken() {
|
|
88
|
+
try {
|
|
89
|
+
return fs.readFileSync(planePaths().adminTokenFile, "utf-8").trim() || null;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Read the plane's local master secret key, generating + persisting a fresh 64
|
|
96
|
+
* hex-char key (mode 0600) on first boot. Stable across boots so DB-encrypted
|
|
97
|
+
* material stays decryptable. Freshly generated — never derived from any sops
|
|
98
|
+
* secret. Enables the orchestrator's secrets subsystem, which gates its admin
|
|
99
|
+
* API surface.
|
|
100
|
+
*/
|
|
101
|
+
function readOrCreateSecretKey() {
|
|
102
|
+
const { secretKeyFile, root } = planePaths();
|
|
103
|
+
try {
|
|
104
|
+
const existing = fs.readFileSync(secretKeyFile, "utf-8").trim();
|
|
105
|
+
if (existing) return existing;
|
|
106
|
+
} catch {}
|
|
107
|
+
fs.mkdirSync(root, { recursive: true });
|
|
108
|
+
const key = randomBytes(32).toString("hex");
|
|
109
|
+
fs.writeFileSync(secretKeyFile, key, { mode: 384 });
|
|
110
|
+
return key;
|
|
111
|
+
}
|
|
112
|
+
/** Absolute path of the dev-signed identity's private JWK key file. */
|
|
113
|
+
function devIdentityKeyFile() {
|
|
114
|
+
return path.join(planePaths().devIdentityDir, "identity.jwk");
|
|
115
|
+
}
|
|
116
|
+
/** Absolute path of the dev-signed identity's public JWK (written by the plane orchestrator). */
|
|
117
|
+
function devIdentityPublicJwkFile() {
|
|
118
|
+
return path.join(planePaths().devIdentityDir, "identity.pub.jwk");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Read the plane's dev-signed identity keypair, generating + persisting a fresh
|
|
122
|
+
* EC P-256 private JWK (mode 0600) on first boot. Stable across boots so a
|
|
123
|
+
* `kici local trust-root` export stays valid for previously-minted tokens.
|
|
124
|
+
* Freshly generated — NEVER derived from any sops secret or real key. Only the
|
|
125
|
+
* private key is written here; the plane orchestrator derives + writes the
|
|
126
|
+
* public JWK next to it. Returns the private-key file path passed to the
|
|
127
|
+
* orchestrator via KICI_DEV_IDENTITY_KEY_FILE.
|
|
128
|
+
*/
|
|
129
|
+
function readOrCreateDevIdentity() {
|
|
130
|
+
const keyFile = devIdentityKeyFile();
|
|
131
|
+
try {
|
|
132
|
+
if (fs.readFileSync(keyFile, "utf-8").trim()) return keyFile;
|
|
133
|
+
} catch {}
|
|
134
|
+
fs.mkdirSync(planePaths().devIdentityDir, {
|
|
135
|
+
recursive: true,
|
|
136
|
+
mode: 448
|
|
137
|
+
});
|
|
138
|
+
const { privateKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
139
|
+
const jwk = privateKey.export({ format: "jwk" });
|
|
140
|
+
fs.writeFileSync(keyFile, JSON.stringify(jwk, null, 2), { mode: 384 });
|
|
141
|
+
return keyFile;
|
|
142
|
+
}
|
|
143
|
+
function readStamp() {
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(fs.readFileSync(planePaths().stampFile, "utf-8"));
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function writeStamp(stamp) {
|
|
151
|
+
const { root, stampFile, pidfile } = planePaths();
|
|
152
|
+
fs.mkdirSync(root, { recursive: true });
|
|
153
|
+
fs.writeFileSync(stampFile, JSON.stringify(stamp, null, 2));
|
|
154
|
+
fs.writeFileSync(pidfile, String(stamp.orchestratorPid));
|
|
155
|
+
}
|
|
156
|
+
/** Remove the plane's on-disk record (pidfile + stamp). */
|
|
157
|
+
function clearStamp() {
|
|
158
|
+
const { pidfile, stampFile } = planePaths();
|
|
159
|
+
fs.rmSync(pidfile, { force: true });
|
|
160
|
+
fs.rmSync(stampFile, { force: true });
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Whether a running plane described by `existing` was booted from a different
|
|
164
|
+
* CLI build than the current one — a semver bump OR a git-commit change (the
|
|
165
|
+
* latter covers intermediate staging/E2E commits that share a semver). Returns
|
|
166
|
+
* false when there is no stamp, or when the current build has no concrete
|
|
167
|
+
* identity (`buildCommit === 'unknown'`, i.e. running from source / a test),
|
|
168
|
+
* so a source-context `planeUp` never reboots a healthy plane spuriously. An
|
|
169
|
+
* old stamp with no `buildCommit` field reads as `undefined` and therefore
|
|
170
|
+
* triggers a one-time reboot on the first upgrade past this feature.
|
|
171
|
+
*/
|
|
172
|
+
function planeBuildIsStale(existing) {
|
|
173
|
+
if (!existing) return false;
|
|
174
|
+
const { version, buildCommit } = currentBuildIdentity();
|
|
175
|
+
if (buildCommit === "unknown") return false;
|
|
176
|
+
return existing.kiciVersion !== version || existing.buildCommit !== buildCommit;
|
|
177
|
+
}
|
|
178
|
+
function orchestratorUrl(port) {
|
|
179
|
+
return `http://127.0.0.1:${port}`;
|
|
180
|
+
}
|
|
181
|
+
/** Absolute path of the plane orchestrator's log file. */
|
|
182
|
+
function planeLogPath() {
|
|
183
|
+
return planePaths().logFile;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Read the plane's current status from whatever holds the plane port.
|
|
187
|
+
*
|
|
188
|
+
* `running` means "serving": a live orchestrator whose `/ready` fails is
|
|
189
|
+
* reported as `unready`, with the failing checks, rather than as stopped —
|
|
190
|
+
* inferring "not running" from an unready probe hides a process that still owns
|
|
191
|
+
* the port.
|
|
192
|
+
*/
|
|
193
|
+
async function planeStatus() {
|
|
194
|
+
const stamp = readStamp();
|
|
195
|
+
const port = stamp?.port ?? planePorts().orchestrator;
|
|
196
|
+
const classification = await classifyPlane(port, stamp);
|
|
197
|
+
const mode = stamp?.mode ?? "independent";
|
|
198
|
+
const attachment = readAttachment() ?? void 0;
|
|
199
|
+
if (classification.kind === "free") return {
|
|
200
|
+
running: false,
|
|
201
|
+
state: "stopped",
|
|
202
|
+
mode,
|
|
203
|
+
attachment
|
|
204
|
+
};
|
|
205
|
+
if (classification.kind === "ours-ready") return {
|
|
206
|
+
running: true,
|
|
207
|
+
state: "ready",
|
|
208
|
+
pid: classification.pid,
|
|
209
|
+
port,
|
|
210
|
+
pgKind: stamp?.pgKind,
|
|
211
|
+
stampVersion: stamp?.stampVersion,
|
|
212
|
+
url: orchestratorUrl(port),
|
|
213
|
+
adminToken: planeAdminToken() ?? void 0,
|
|
214
|
+
mode,
|
|
215
|
+
attachment
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
running: false,
|
|
219
|
+
state: planeStateOf(classification),
|
|
220
|
+
pid: classificationPid(classification) ?? void 0,
|
|
221
|
+
port,
|
|
222
|
+
pgKind: stamp?.pgKind,
|
|
223
|
+
url: orchestratorUrl(port),
|
|
224
|
+
checks: classification.kind === "ours-unready" ? classification.checks : void 0,
|
|
225
|
+
mode,
|
|
226
|
+
attachment
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Tear the plane down ahead of a fresh boot, refusing to continue when the port
|
|
231
|
+
* was not released.
|
|
232
|
+
*
|
|
233
|
+
* Booting over a still-held port only re-fails later, as a readiness timeout
|
|
234
|
+
* that names the log instead of the survivor — so the teardown's own reason (the
|
|
235
|
+
* pid still holding the port) is raised here, while it is still the accurate
|
|
236
|
+
* one.
|
|
237
|
+
*/
|
|
238
|
+
async function tearDownForBoot() {
|
|
239
|
+
const result = await planeDown();
|
|
240
|
+
if (result.stopped) return;
|
|
241
|
+
const holder = result.holderPid === void 0 ? "" : ` (pid ${result.holderPid})`;
|
|
242
|
+
throw new Error(result.reason ?? `port ${result.port} is still held after stopping the local dev plane${holder}`);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Ensure the plane port is either reusable or vacant before a boot.
|
|
246
|
+
*
|
|
247
|
+
* Returns the running plane when it may be reused as-is, or null when the caller
|
|
248
|
+
* must boot fresh. A plane no stamp accounts for is reclaimed rather than
|
|
249
|
+
* adopted: reuse would serve `kici run --local` from an orchestrator of unknown
|
|
250
|
+
* build, with an unknown scaler configuration, which is exactly the state a
|
|
251
|
+
* stamped plane's staleness check exists to prevent.
|
|
252
|
+
*/
|
|
253
|
+
async function prepareForBoot(port, existing, requestedMode) {
|
|
254
|
+
const classification = await classifyPlane(port, existing);
|
|
255
|
+
switch (classification.kind) {
|
|
256
|
+
case "free": return null;
|
|
257
|
+
case "foreign-unknown": {
|
|
258
|
+
const reclaim = await reclaimPlanePort(port, classification);
|
|
259
|
+
throw new Error(reclaim.error ?? `port ${port} is held by an unrecognised process`);
|
|
260
|
+
}
|
|
261
|
+
case "foreign-kici": {
|
|
262
|
+
const reclaim = await reclaimPlanePort(port, classification);
|
|
263
|
+
if (!reclaim.freed) throw new Error(reclaim.error ?? `could not reclaim port ${port}`);
|
|
264
|
+
clearStamp();
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
case "ours-unready":
|
|
268
|
+
await tearDownForBoot();
|
|
269
|
+
return null;
|
|
270
|
+
case "ours-ready": {
|
|
271
|
+
const modeChanged = (existing?.mode ?? "independent") !== requestedMode;
|
|
272
|
+
if (planeBuildIsStale(existing) || modeChanged) {
|
|
273
|
+
await tearDownForBoot();
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
running: true,
|
|
278
|
+
state: "ready",
|
|
279
|
+
pid: classification.pid,
|
|
280
|
+
port,
|
|
281
|
+
pgKind: existing?.pgKind,
|
|
282
|
+
stampVersion: existing?.stampVersion,
|
|
283
|
+
url: orchestratorUrl(port),
|
|
284
|
+
adminToken: planeAdminToken() ?? void 0,
|
|
285
|
+
mode: existing?.mode ?? "independent",
|
|
286
|
+
attachment: readAttachment() ?? void 0
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Start (or reuse) the local dev plane. Idempotent: a healthy running plane this
|
|
293
|
+
* config dir stamped, **whose stamped mode matches the requested mode**, is
|
|
294
|
+
* returned as-is. Otherwise the port is reclaimed if it is another KiCI plane,
|
|
295
|
+
* and Postgres + the orchestrator are booted with a fresh stamp + pidfile. When
|
|
296
|
+
* `attach` is present the orchestrator boots hybrid against the Platform relay;
|
|
297
|
+
* otherwise it boots independent with the dev-signed identity. Throws when the
|
|
298
|
+
* port is held by a process that is not a KiCI plane orchestrator.
|
|
299
|
+
*/
|
|
300
|
+
async function planeUp(opts = {}) {
|
|
301
|
+
const { orchestrator: port } = planePorts();
|
|
302
|
+
const existing = readStamp();
|
|
303
|
+
const requestedMode = opts.attach ? "hybrid" : "independent";
|
|
304
|
+
if (existing && existing.stampVersion !== 3) {
|
|
305
|
+
await tearDownForBoot();
|
|
306
|
+
fs.rmSync(planePaths().pgData, {
|
|
307
|
+
recursive: true,
|
|
308
|
+
force: true
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
const reusable = await prepareForBoot(port, existing, requestedMode);
|
|
312
|
+
if (reusable) return reusable;
|
|
313
|
+
}
|
|
314
|
+
const adminToken = readOrCreateAdminToken();
|
|
315
|
+
const secretKey = readOrCreateSecretKey();
|
|
316
|
+
const scalerConfigFile = writeScalerConfig(port);
|
|
317
|
+
const pg = await startPlanePostgres();
|
|
318
|
+
let orch;
|
|
319
|
+
if (opts.attach) {
|
|
320
|
+
writePlatformToken(opts.attach.platformToken);
|
|
321
|
+
writeAttachment({
|
|
322
|
+
platformWsUrl: opts.attach.platformWsUrl,
|
|
323
|
+
platformApiBase: opts.attach.platformApiBase,
|
|
324
|
+
orgId: opts.attach.orgId,
|
|
325
|
+
keyId: opts.attach.keyId
|
|
326
|
+
});
|
|
327
|
+
orch = spawnOrchestratorProcess(pg.url, {
|
|
328
|
+
adminToken,
|
|
329
|
+
secretKey,
|
|
330
|
+
scalerConfigFile,
|
|
331
|
+
attach: {
|
|
332
|
+
platformWsUrl: opts.attach.platformWsUrl,
|
|
333
|
+
platformToken: opts.attach.platformToken
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
} else {
|
|
337
|
+
const devIdentityKey = readOrCreateDevIdentity();
|
|
338
|
+
orch = spawnOrchestratorProcess(pg.url, {
|
|
339
|
+
adminToken,
|
|
340
|
+
secretKey,
|
|
341
|
+
scalerConfigFile,
|
|
342
|
+
devIdentityKeyFile: devIdentityKey
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
const { version: stampVersionSemver, buildCommit: stampBuildCommit } = currentBuildIdentity();
|
|
346
|
+
writeStamp({
|
|
347
|
+
orchestratorPid: orch.pid,
|
|
348
|
+
port: orch.port,
|
|
349
|
+
pgKind: pg.kind,
|
|
350
|
+
kiciVersion: stampVersionSemver,
|
|
351
|
+
buildCommit: stampBuildCommit,
|
|
352
|
+
stampVersion: 3,
|
|
353
|
+
mode: requestedMode
|
|
354
|
+
});
|
|
355
|
+
try {
|
|
356
|
+
await awaitOrchestratorReady(orch.port);
|
|
357
|
+
} catch {
|
|
358
|
+
await terminatePid(orch.pid);
|
|
359
|
+
if (await isPortFree(orch.port)) clearStamp();
|
|
360
|
+
throw new Error(`local orchestrator did not become ready — see ${planeLogPath()}`);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
running: true,
|
|
364
|
+
state: "ready",
|
|
365
|
+
pid: orch.pid,
|
|
366
|
+
port: orch.port,
|
|
367
|
+
pgKind: pg.kind,
|
|
368
|
+
stampVersion: 3,
|
|
369
|
+
url: orchestratorUrl(orch.port),
|
|
370
|
+
adminToken,
|
|
371
|
+
mode: requestedMode,
|
|
372
|
+
attachment: readAttachment() ?? void 0
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Whether the stamped pid is still one of this build's plane orchestrators.
|
|
377
|
+
*
|
|
378
|
+
* The stamp outlives the process it names — a crash, a `kill -9`, or a reboot
|
|
379
|
+
* all leave it behind — so once the operating system recycles that pid onto an
|
|
380
|
+
* unrelated process, signalling it blind would stop a stranger's program. Every
|
|
381
|
+
* other signal path in the plane is identity-verified (`reclaimPlanePort` only
|
|
382
|
+
* ever signals a pid confirmed to hold the port or to answer `/health` as a KiCI
|
|
383
|
+
* orchestrator); this keeps the stamp-driven path to the same standard by
|
|
384
|
+
* matching the live command line against the entry this build would launch.
|
|
385
|
+
*
|
|
386
|
+
* Returns false whenever identity cannot be established — an unreadable command
|
|
387
|
+
* line, an unresolvable entry, or a plane launched from a different install.
|
|
388
|
+
* That is deliberate: not signalling leaves a process running, while signalling
|
|
389
|
+
* the wrong pid kills one.
|
|
390
|
+
*/
|
|
391
|
+
async function stampedPidIsOurOrchestrator(pid) {
|
|
392
|
+
const cmdline = await processCommandLine(pid);
|
|
393
|
+
if (!cmdline) return false;
|
|
394
|
+
for (const resolveEntry of [resolveStandaloneEntry, resolveServerEntry]) try {
|
|
395
|
+
if (cmdline.includes(resolveEntry())) return true;
|
|
396
|
+
} catch {}
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Stop the local dev plane and confirm the port was released.
|
|
401
|
+
*
|
|
402
|
+
* Teardown is reconstructed from whatever holds the port, not only from the
|
|
403
|
+
* stamp: a plane this config dir never stamped still occupies the port and is
|
|
404
|
+
* still ours to stop. The pidfile and stamp are cleared only once the port is
|
|
405
|
+
* verified free, so a survivor never loses the pid that identifies it.
|
|
406
|
+
*/
|
|
407
|
+
async function planeDown() {
|
|
408
|
+
const stamp = readStamp();
|
|
409
|
+
const port = stamp?.port ?? planePorts().orchestrator;
|
|
410
|
+
const classification = await classifyPlane(port, stamp);
|
|
411
|
+
const reclaim = await reclaimPlanePort(port, classification);
|
|
412
|
+
if (!reclaim.freed) return {
|
|
413
|
+
stopped: false,
|
|
414
|
+
port,
|
|
415
|
+
holderPid: reclaim.killedPid ?? classificationPid(classification) ?? void 0,
|
|
416
|
+
reason: reclaim.error
|
|
417
|
+
};
|
|
418
|
+
if (stamp && stamp.orchestratorPid !== reclaim.killedPid && await stampedPidIsOurOrchestrator(stamp.orchestratorPid)) {
|
|
419
|
+
await terminatePid(stamp.orchestratorPid);
|
|
420
|
+
if (!await isPortFree(port)) return {
|
|
421
|
+
stopped: false,
|
|
422
|
+
port,
|
|
423
|
+
holderPid: stamp.orchestratorPid,
|
|
424
|
+
reason: `port ${port} is still held after stopping pid ${stamp.orchestratorPid}`
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (stamp) await stopPlanePostgres(stamp.pgKind);
|
|
428
|
+
else if (classification.kind !== "free") await stopPlanePostgres("embedded");
|
|
429
|
+
clearStamp();
|
|
430
|
+
return {
|
|
431
|
+
stopped: true,
|
|
432
|
+
port,
|
|
433
|
+
holderPid: reclaim.killedPid
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Attach the local dev plane to the hosted Platform: mint an org-scoped
|
|
438
|
+
* orchestrator key with the logged-in PAT, then (re)boot the plane hybrid
|
|
439
|
+
* against the Platform relay. Registration with the Platform is implicit — the
|
|
440
|
+
* hybrid orchestrator's PlatformClient sends `source.register` on WS auth.
|
|
441
|
+
*/
|
|
442
|
+
async function attachPlane(args) {
|
|
443
|
+
const platformWsUrl = derivePlatformWsUrl(args.apiBase);
|
|
444
|
+
const minted = await mintOrchestratorKey({
|
|
445
|
+
apiBase: args.apiBase,
|
|
446
|
+
pat: args.pat,
|
|
447
|
+
orgId: args.orgId,
|
|
448
|
+
name: "local dev plane"
|
|
449
|
+
});
|
|
450
|
+
return planeUp({ attach: {
|
|
451
|
+
platformWsUrl,
|
|
452
|
+
platformToken: minted.key,
|
|
453
|
+
platformApiBase: args.apiBase,
|
|
454
|
+
orgId: args.orgId,
|
|
455
|
+
keyId: minted.keyId
|
|
456
|
+
} });
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Detach the local dev plane: best-effort revoke the minted orchestrator key on
|
|
460
|
+
* the Platform, clear the durable attachment + token, then reboot the plane
|
|
461
|
+
* independent (offline). The revoke is non-fatal — a detach must succeed even
|
|
462
|
+
* offline or with an expired PAT.
|
|
463
|
+
*/
|
|
464
|
+
async function detachPlane(opts = {}) {
|
|
465
|
+
const attachment = readAttachment();
|
|
466
|
+
if (attachment && opts.pat) await revokeOrchestratorKey({
|
|
467
|
+
apiBase: attachment.platformApiBase,
|
|
468
|
+
pat: opts.pat,
|
|
469
|
+
orgId: attachment.orgId,
|
|
470
|
+
keyId: attachment.keyId
|
|
471
|
+
}).catch(() => false);
|
|
472
|
+
clearAttachment();
|
|
473
|
+
clearPlatformToken();
|
|
474
|
+
await planeDown();
|
|
475
|
+
return planeUp();
|
|
476
|
+
}
|
|
477
|
+
//#endregion
|
|
478
|
+
export { attachPlane, clearAttachment, clearPlatformToken, clearStamp, currentBuildIdentity, detachPlane, devIdentityKeyFile, devIdentityPublicJwkFile, planeAdminToken, planeBuildIsStale, planeDown, planeLogPath, planeStatus, planeUp, readAttachment, readOrCreateAdminToken, readOrCreateDevIdentity, readOrCreateSecretKey, readPlatformToken, writeAttachment, writePlatformToken };
|
|
479
|
+
|
|
480
|
+
//# sourceMappingURL=plane-manager.js.map
|