@coreplane/switchboard 0.0.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +18 -1
  3. package/dist/assets/.dockerignore +27 -0
  4. package/dist/assets/.env.example +33 -0
  5. package/dist/assets/Dockerfile +111 -0
  6. package/dist/assets/config/config.example.yaml +359 -0
  7. package/dist/assets/deploy/bin/build-stamp.d.mts +15 -0
  8. package/dist/assets/deploy/bin/build-stamp.mjs +98 -0
  9. package/dist/assets/deploy/bin/cf-logs +32 -0
  10. package/dist/assets/deploy/cloudflare/package.json +29 -0
  11. package/dist/assets/deploy/cloudflare/preflight.mjs +243 -0
  12. package/dist/assets/deploy/cloudflare/tsconfig.json +18 -0
  13. package/dist/assets/deploy/cloudflare/worker.ts +382 -0
  14. package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +67 -0
  15. package/dist/assets/deploy/cloudflare/write-build.d.mts +7 -0
  16. package/dist/assets/deploy/cloudflare/write-build.mjs +53 -0
  17. package/dist/assets/deploy/cloudflare-docs/package.json +18 -0
  18. package/dist/assets/deploy/cloudflare-docs/wrangler.template.jsonc +30 -0
  19. package/dist/assets/deploy/cloudflare-memory/package.json +25 -0
  20. package/dist/assets/deploy/cloudflare-memory/tsconfig.json +17 -0
  21. package/dist/assets/deploy/cloudflare-memory/worker.ts +2635 -0
  22. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +50 -0
  23. package/dist/assets/deploy/cloudflare-resident/Dockerfile +91 -0
  24. package/dist/assets/deploy/cloudflare-resident/gc.ts +287 -0
  25. package/dist/assets/deploy/cloudflare-resident/node-async-hooks.d.ts +11 -0
  26. package/dist/assets/deploy/cloudflare-resident/package.json +29 -0
  27. package/dist/assets/deploy/cloudflare-resident/preflight.mjs +224 -0
  28. package/dist/assets/deploy/cloudflare-resident/tsconfig.json +19 -0
  29. package/dist/assets/deploy/cloudflare-resident/worker.ts +6637 -0
  30. package/dist/assets/deploy/cloudflare-resident/wrangler.template.jsonc +120 -0
  31. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +67 -0
  32. package/dist/assets/deploy/cloudflare-sandbox/docker-wrapper.sh +37 -0
  33. package/dist/assets/deploy/cloudflare-sandbox/package.json +26 -0
  34. package/dist/assets/deploy/cloudflare-sandbox/tsconfig.json +20 -0
  35. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +410 -0
  36. package/dist/assets/deploy/cloudflare-sandbox/wrangler.template.jsonc +67 -0
  37. package/dist/assets/deploy/profile.example.json +13 -0
  38. package/dist/assets/deploy/secrets.manifest.json +108 -0
  39. package/dist/assets/docker-entrypoint.sh +15 -0
  40. package/dist/assets/package-lock.json +18407 -0
  41. package/dist/assets/package.json +104 -0
  42. package/dist/assets/project.json +219 -0
  43. package/dist/assets/source.json +5 -0
  44. package/dist/assets/src/core/authz/actor.ts +100 -0
  45. package/dist/assets/src/core/authz/authorize.ts +169 -0
  46. package/dist/assets/src/core/authz/grants.ts +347 -0
  47. package/dist/assets/src/core/authz/policy.ts +281 -0
  48. package/dist/assets/src/core/authz/resource.ts +147 -0
  49. package/dist/assets/src/core/authz/types.ts +164 -0
  50. package/dist/assets/src/core/drain.ts +54 -0
  51. package/dist/assets/src/core/ingressTokens.ts +64 -0
  52. package/dist/assets/src/core/memory/engine.ts +115 -0
  53. package/dist/assets/src/core/memory/scorer.ts +147 -0
  54. package/dist/assets/src/core/memory/types.ts +120 -0
  55. package/dist/assets/src/core/normalizeSpans.ts +299 -0
  56. package/dist/assets/src/core/prDescriptionTypes.ts +54 -0
  57. package/dist/assets/src/core/redact.ts +113 -0
  58. package/dist/assets/src/core/runEvents.ts +537 -0
  59. package/dist/assets/src/core/runFriction.ts +665 -0
  60. package/dist/assets/src/core/runLedger/decisions.ts +126 -0
  61. package/dist/assets/src/core/runLedger/types.ts +177 -0
  62. package/dist/assets/src/core/runRecord.ts +627 -0
  63. package/dist/assets/src/core/runShape.ts +61 -0
  64. package/dist/assets/src/core/schedules.ts +452 -0
  65. package/dist/assets/src/core/time/formatDuration.ts +61 -0
  66. package/dist/assets/src/core/trace/attrs.ts +203 -0
  67. package/dist/assets/src/core/trace/classify.ts +49 -0
  68. package/dist/assets/src/core/trace/clock.ts +6 -0
  69. package/dist/assets/src/core/trace/context.ts +9 -0
  70. package/dist/assets/src/core/trace/ids.ts +23 -0
  71. package/dist/assets/src/core/trace/partition.ts +235 -0
  72. package/dist/assets/src/core/trace/sinks.ts +68 -0
  73. package/dist/assets/src/core/trace/streamSpans.ts +163 -0
  74. package/dist/assets/src/core/trace/traceparent.ts +29 -0
  75. package/dist/assets/src/core/trace/tracer.ts +247 -0
  76. package/dist/assets/src/core/trace/types.ts +125 -0
  77. package/dist/assets/src/core/trace/workerTrace.ts +97 -0
  78. package/dist/assets/src/deploy/buildStamp.ts +93 -0
  79. package/dist/assets/src/deploy/liveGate.ts +203 -0
  80. package/dist/assets/src/deploy/profile.ts +162 -0
  81. package/dist/assets/src/deploy/restart.ts +393 -0
  82. package/dist/assets/src/effort.ts +17 -0
  83. package/dist/assets/src/execution/bashTimeout.ts +78 -0
  84. package/dist/assets/src/execution/bindingPurge.ts +43 -0
  85. package/dist/assets/src/execution/residentBackupTransfer.ts +50 -0
  86. package/dist/assets/src/execution/residentCleanliness.ts +95 -0
  87. package/dist/assets/src/execution/residentCredentials.ts +81 -0
  88. package/dist/assets/src/execution/residentDepCache.ts +321 -0
  89. package/dist/assets/src/execution/residentDepsStore.ts +326 -0
  90. package/dist/assets/src/execution/residentDetach.ts +48 -0
  91. package/dist/assets/src/execution/residentDisk.ts +107 -0
  92. package/dist/assets/src/execution/residentDiskBudget.ts +448 -0
  93. package/dist/assets/src/execution/residentExecWrap.ts +100 -0
  94. package/dist/assets/src/execution/residentHead.ts +85 -0
  95. package/dist/assets/src/execution/residentReadonly.ts +72 -0
  96. package/dist/assets/src/execution/residentRefresh.ts +429 -0
  97. package/dist/assets/src/execution/residentRestoreExtract.ts +130 -0
  98. package/dist/assets/src/execution/residentState.ts +47 -0
  99. package/dist/assets/src/execution/residentStepReport.ts +98 -0
  100. package/dist/assets/src/execution/residentStepTrace.ts +97 -0
  101. package/dist/assets/src/execution/residentSteps.ts +99 -0
  102. package/dist/assets/src/execution/residentText.ts +83 -0
  103. package/dist/assets/src/execution/residentTrace.ts +119 -0
  104. package/dist/assets/src/execution/sandboxEnv.ts +42 -0
  105. package/dist/assets/src/execution/sandboxErrors.ts +159 -0
  106. package/dist/assets/src/execution/sandboxKeepalive.ts +118 -0
  107. package/dist/assets/src/execution/shellQuote.ts +8 -0
  108. package/dist/assets/src/mcp/registry.ts +242 -0
  109. package/dist/assets/src/providers/types.ts +152 -0
  110. package/dist/assets/web/dist/.vite/manifest.json +176 -0
  111. package/dist/assets/web/dist/assets/AppShell-Bk2gbvet.js +1 -0
  112. package/dist/assets/web/dist/assets/CostsPage-CTZcMYYx.js +1 -0
  113. package/dist/assets/web/dist/assets/NotFoundPage-C-BuaSm8.js +1 -0
  114. package/dist/assets/web/dist/assets/ResidentDetailPage-D3shEnzl.js +1 -0
  115. package/dist/assets/web/dist/assets/ResidentsIndexPage-DWIubQ05.js +1 -0
  116. package/dist/assets/web/dist/assets/RunRoutePage-BMjuE-oX.js +126 -0
  117. package/dist/assets/web/dist/assets/RunRoutePage-XVFj0XDc.css +1 -0
  118. package/dist/assets/web/dist/assets/RunsIndexPage-C3_jYIo0.js +1 -0
  119. package/dist/assets/web/dist/assets/RunsTabs-C4krAL9o.js +1 -0
  120. package/dist/assets/web/dist/assets/ScheduledPage-g1W58mtN.js +1 -0
  121. package/dist/assets/web/dist/assets/StatusDot-DcPRw3zu.js +1 -0
  122. package/dist/assets/web/dist/assets/Tooltip-DJUkMYjo.js +1 -0
  123. package/dist/assets/web/dist/assets/favicon-DL1rdWJt.js +1 -0
  124. package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
  125. package/dist/assets/web/dist/assets/main-BsBGUyMH.css +2 -0
  126. package/dist/assets/web/dist/assets/main-CyM5f4JC.js +28 -0
  127. package/dist/assets/web/dist/assets/residentDiskBudget-BMBKlYRH.js +1 -0
  128. package/dist/assets/web/dist/assets/seed-BglCRKLA.js +6 -0
  129. package/dist/assets/web/dist/assets/wallClock-Ckv3sKoR.js +1 -0
  130. package/dist/cli.js +34494 -0
  131. package/package.json +43 -10
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+ // Deploy preflight for the resident Worker (docs/reference/specs/resident-repos.md item 44).
3
+ //
4
+ // `wrangler deploy` replaces every ResidentDO isolate and invalidates the
5
+ // Sandbox SDK process handles held by in-flight runs ("Process handle refers
6
+ // to a previous runtime incarnation") — the container-restart deferral in
7
+ // `reconcileImage` cannot protect against a Worker deploy. So `npm run deploy`
8
+ // runs this first: it asks the live Worker's `GET /residents` for every
9
+ // resident's in-flight count and exits non-zero while anything is running.
10
+ //
11
+ // Fail closed: no bearer, unreachable Worker, or a resident whose live view
12
+ // errored all refuse — a deploy never proceeds blind. `RESIDENT_DEPLOY_FORCE=1`
13
+ // (or `--force` when run directly) is the explicit, warned bypass.
14
+ //
15
+ // Dependency-free Node (fetch is built in). `decide()` is pure and unit-tested
16
+ // (preflight.test.mjs); `main()` only does I/O around it.
17
+
18
+ import { pathToFileURL } from "node:url";
19
+
20
+ /** The env var naming this Worker's origin. `deploy all` sets it from the deployment profile; the
21
+ * preflight has no address of its own (deploy/profile.json is the only place the fleet's hostnames live). */
22
+ export const BASE_URL_ENV = "RESIDENT_BASE_URL";
23
+ export const TOKEN_ENV_VARS = ["RESIDENT_ADMIN_TOKEN", "RESIDENT_OPERATOR_TOKEN", "RESIDENT_READ_TOKEN"];
24
+
25
+ const HOW_TO_SET_TOKEN =
26
+ `set one of ${TOKEN_ENV_VARS.join(" / ")} in the environment (the same bearer ` +
27
+ "`npm run secrets` put on the Worker; any scope may read /residents), e.g. " +
28
+ "`RESIDENT_ADMIN_TOKEN=… npm run deploy`";
29
+ const HOW_TO_FORCE =
30
+ "to deploy anyway (this WILL kill in-flight runs): `RESIDENT_DEPLOY_FORCE=1 npm run deploy` (`node preflight.mjs --force` checks alone)";
31
+ /** Lifecycle states in which NOTHING is executing on the resident — the only
32
+ * states a deploy may land on. Every other state is either known mid-cycle
33
+ * (the engine is running a refresh's fetch/rebuild, a restore, or
34
+ * provisioning — an isolate swap kills that like a thread run) or a
35
+ * state this script does not know, which fails closed as `unknown`: this is
36
+ * plain JS outside the shared `ResidentLifecycleState` type, so an allow-list
37
+ * of settled states is what keeps vocabulary drift from silently allowing. */
38
+ const SETTLED_STATES = new Set(["warm", "degraded", "down"]);
39
+ /** The engine is executing and an isolate swap would FAIL it: provisioning
40
+ * (`onboarding`) has no checkpoint to resume from — a killed install or build
41
+ * is `provision-failed`, `down`, and only a rebuild recovers it. */
42
+ const PROVISIONING_STATES = new Set(["onboarding"]);
43
+ /** The engine is executing and an isolate swap merely INTERRUPTS it: a refresh
44
+ * cycle re-arms in 45 s and resumes from its disk checkpoints (resident-repos
45
+ * items 44 and 48); a restore is retried by the next hydrate, which first
46
+ * unmounts and removes whatever the interrupted one left (item 61). These
47
+ * used to refuse like `onboarding`, and a release deploy once spent its whole
48
+ * budget behind a resident stuck in `restoring` — a state an isolate swap
49
+ * would have HELPED. Allowed with a warning. */
50
+ const INTERRUPTIBLE_STATES = new Set(["refreshing", "restoring"]);
51
+
52
+ /** First present, non-blank bearer in preference order; null when none. */
53
+ export function readToken(env) {
54
+ for (const name of TOKEN_ENV_VARS) {
55
+ const v = env[name];
56
+ if (typeof v === "string" && v.trim() !== "") return v;
57
+ }
58
+ return null;
59
+ }
60
+
61
+ /** GET /residents. Never throws: `{ok:true,payload}` or `{ok:false,error}`. */
62
+ export async function fetchResidents(baseUrl, token, { timeoutMs = 15_000 } = {}) {
63
+ if (!token) return { ok: false, error: "no bearer in the environment" };
64
+ const url = new URL("/residents", baseUrl).toString();
65
+ try {
66
+ const res = await fetch(url, {
67
+ headers: { authorization: `Bearer ${token}` },
68
+ signal: AbortSignal.timeout(timeoutMs),
69
+ });
70
+ const text = await res.text();
71
+ if (!res.ok) return { ok: false, error: `GET ${url} → HTTP ${res.status}: ${text.slice(0, 200)}` };
72
+ try {
73
+ return { ok: true, payload: JSON.parse(text) };
74
+ } catch {
75
+ return { ok: false, error: `GET ${url} → non-JSON body: ${text.slice(0, 200)}` };
76
+ }
77
+ } catch (err) {
78
+ return { ok: false, error: `GET ${url} failed: ${err instanceof Error ? err.message : String(err)}` };
79
+ }
80
+ }
81
+
82
+ /**
83
+ * The decision, pure. `fetched` is the result of `fetchResidents`.
84
+ * @returns {{ allow: boolean, forced: boolean, busy: {resource:string,inFlight:number}[], provisioning: {resource:string,state:string}[], interrupting: {resource:string,state:string}[], unknown: {resource:string,error:string}[], message: string }}
85
+ */
86
+ export function decide(fetched, { force = false } = {}) {
87
+ const busy = [];
88
+ const provisioning = [];
89
+ const interrupting = [];
90
+ const unknown = [];
91
+ const problems = [];
92
+
93
+ if (!fetched || fetched.ok !== true) {
94
+ problems.push(`resident Worker not consulted: ${fetched?.error ?? "unknown error"}`);
95
+ } else {
96
+ const residents = fetched.payload?.residents;
97
+ if (!Array.isArray(residents)) {
98
+ problems.push("resident Worker answered without a `residents` array (malformed /residents payload)");
99
+ } else {
100
+ for (const r of residents) {
101
+ const resource = typeof r?.resource === "string" ? r.resource : "<unnamed resident>";
102
+ const live = r?.live;
103
+ if (!live || typeof live !== "object" || typeof live.error === "string") {
104
+ unknown.push({ resource, error: live?.error ?? "no live view" });
105
+ } else if (typeof live.inFlight !== "number") {
106
+ unknown.push({
107
+ resource,
108
+ error:
109
+ "live view carries no inFlight count (Worker predates the preflight — deploy once with RESIDENT_DEPLOY_FORCE=1)",
110
+ });
111
+ } else if (!Number.isInteger(live.inFlight) || live.inFlight < 0) {
112
+ // A count that is not a non-negative integer can only come from a
113
+ // counter bug (double release, unmatched decrement). We cannot tell
114
+ // idle from busy, so it is unknown — never an implicit "0 busy".
115
+ unknown.push({ resource, error: `live view carries an impossible inFlight=${live.inFlight} (counter bug)` });
116
+ } else if (
117
+ live.runsInFlight !== undefined &&
118
+ (typeof live.runsInFlight !== "number" || !Number.isInteger(live.runsInFlight) || live.runsInFlight < 0)
119
+ ) {
120
+ unknown.push({
121
+ resource,
122
+ error: `live view carries an impossible runsInFlight=${live.runsInFlight} (counter bug)`,
123
+ });
124
+ } else {
125
+ // `inFlight` counts the refresh cycle itself along with the runs, so a
126
+ // resident that is merely refreshing reads ≥ 1. The busy decision is
127
+ // about what a swap kills for a USER — the runs — which a Worker since
128
+ // item 44's revision reports apart as `runsInFlight`; an older Worker
129
+ // without the field is judged on `inFlight` as before (over-refusing,
130
+ // never under). Runs and cycles are independent facts; report BOTH so
131
+ // an operator who waits for the runs is not surprised by a second refusal.
132
+ const runs = typeof live.runsInFlight === "number" ? live.runsInFlight : live.inFlight;
133
+ if (runs > 0) busy.push({ resource, inFlight: runs });
134
+ if (PROVISIONING_STATES.has(live.state)) {
135
+ // Provisioning is mid-flight. An isolate swap kills it just like a
136
+ // thread run (seen live: `build-failed: exit 143: Session
137
+ // terminated` right after a deploy that passed the inFlight
138
+ // check), and unlike a refresh it cannot resume.
139
+ provisioning.push({ resource, state: live.state });
140
+ } else if (INTERRUPTIBLE_STATES.has(live.state)) {
141
+ interrupting.push({ resource, state: live.state });
142
+ } else if (!SETTLED_STATES.has(live.state)) {
143
+ // A state this script does not know: fail closed rather than assume settled.
144
+ unknown.push({
145
+ resource,
146
+ error: `live view carries an unrecognized state ${JSON.stringify(live.state)} (vocabulary drift — update preflight.mjs)`,
147
+ });
148
+ }
149
+ }
150
+ }
151
+ if (busy.length)
152
+ problems.push(`in flight: ${busy.map((b) => `${b.resource} (${b.inFlight} in flight)`).join(", ")}`);
153
+ if (provisioning.length)
154
+ problems.push(
155
+ `provisioning: ${provisioning.map((m) => `${m.resource} (${m.state})`).join(", ")} — the provision would be killed and only a rebuild recovers it`,
156
+ );
157
+ if (unknown.length)
158
+ problems.push(`unknown state: ${unknown.map((u) => `${u.resource} (${u.error})`).join(", ")}`);
159
+ }
160
+ }
161
+
162
+ // Interruptible cycles never refuse; they are named so the log says what the
163
+ // swap interrupts and why that is fine.
164
+ const warningText = interrupting.length
165
+ ? ` — WARNING: mid-cycle: ${interrupting.map((m) => `${m.resource} (${m.state})`).join(", ")} — the isolate swap interrupts it; a refresh re-arms in 45 s from its checkpoints, a restore is retried by the next hydrate (resident-repos items 44/61)`
166
+ : "";
167
+
168
+ if (problems.length === 0) {
169
+ const count = fetched.payload.residents.length;
170
+ return {
171
+ allow: true,
172
+ forced: false,
173
+ busy,
174
+ provisioning,
175
+ interrupting,
176
+ unknown,
177
+ message: `preflight ok: ${count} residents, no resident has work in flight or a provision running${warningText}`,
178
+ };
179
+ }
180
+ const detail = problems.map((p) => ` - ${p}`).join("\n");
181
+ if (force) {
182
+ return {
183
+ allow: true,
184
+ forced: true,
185
+ busy,
186
+ provisioning,
187
+ interrupting,
188
+ unknown,
189
+ message: `preflight WARNING: deploying by force despite —\n${detail}\n in-flight runs and provisions on the residents above WILL be killed (process handles invalidated by the isolate swap)${warningText}`,
190
+ };
191
+ }
192
+ const hints = [HOW_TO_FORCE];
193
+ if (fetched?.ok !== true) hints.unshift(HOW_TO_SET_TOKEN);
194
+ return {
195
+ allow: false,
196
+ forced: false,
197
+ busy,
198
+ provisioning,
199
+ interrupting,
200
+ unknown,
201
+ message: `preflight REFUSED: a Worker deploy swaps every ResidentDO isolate and kills in-flight runs and provisions —\n${detail}\n wait for them to finish and retry; ${hints.join("; ")}${warningText}`,
202
+ };
203
+ }
204
+
205
+ export async function main(argv = process.argv.slice(2), env = process.env) {
206
+ const force = argv.includes("--force") || env.RESIDENT_DEPLOY_FORCE === "1";
207
+ const baseUrl = env[BASE_URL_ENV];
208
+ if (!baseUrl) {
209
+ console.error(
210
+ `[resident-preflight] ${BASE_URL_ENV} is not set — the resident Worker's origin comes from the deployment profile; deploy through \`npm run cli -- deploy all\` (it sets it), or set it to https://<resident hostname> to run this alone`,
211
+ );
212
+ return 2;
213
+ }
214
+ const fetched = await fetchResidents(baseUrl, readToken(env));
215
+ const d = decide(fetched, { force });
216
+ (d.allow && !d.forced ? console.log : console.error)(`[resident-preflight] ${d.message}`);
217
+ return d.allow ? 0 : 1;
218
+ }
219
+
220
+ // Run only when executed directly (`node preflight.mjs`), not when imported by tests.
221
+ // pathToFileURL, not `file://${argv[1]}`, so the guard also holds on Windows paths.
222
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
223
+ process.exitCode = await main();
224
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ // Scoped config so `npm run typecheck` checks worker.ts on its own terms.
3
+ // Without it tsc falls back to the repo-root tsconfig, which excludes this
4
+ // directory — and TypeScript 7 refuses `tsc <file>` outright when any
5
+ // tsconfig.json is discoverable (TS5112), so the options live here instead
6
+ // of on the command line. Types resolve through node resolution, so they
7
+ // are found wherever npm installed them.
8
+ "compilerOptions": {
9
+ "target": "es2022",
10
+ "module": "es2022",
11
+ "moduleResolution": "bundler",
12
+ "lib": ["es2022"],
13
+ "types": ["@cloudflare/workers-types"],
14
+ "strict": true,
15
+ "noEmit": true,
16
+ "skipLibCheck": true
17
+ },
18
+ "include": ["worker.ts", "node-async-hooks.d.ts"]
19
+ }