@crouton-kit/crouter 0.3.39 → 0.3.41

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 (188) hide show
  1. package/dist/build-root.js +1 -0
  2. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +472 -9
  3. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +284 -76
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +31 -24
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +52 -15
  6. package/dist/clients/attach/attach-cmd.js +706 -733
  7. package/dist/clients/attach/chat-view.js +11 -0
  8. package/dist/clients/web/server.js +5 -28
  9. package/dist/clients/web/web-cmd.js +1 -1
  10. package/dist/commands/__tests__/revive-now-gate.test.js +60 -0
  11. package/dist/commands/canvas-rebuild-index.js +5 -5
  12. package/dist/commands/node-snapshot.js +9 -1
  13. package/dist/commands/node.js +37 -17
  14. package/dist/commands/push.js +8 -0
  15. package/dist/commands/revive.d.ts +10 -0
  16. package/dist/commands/revive.js +28 -10
  17. package/dist/commands/sys/__tests__/setup-core.test.js +19 -0
  18. package/dist/commands/sys/doctor.js +1 -1
  19. package/dist/commands/sys/setup-core.js +3 -2
  20. package/dist/commands/sys/setup.js +1 -1
  21. package/dist/commands/worktree.d.ts +2 -0
  22. package/dist/commands/worktree.js +94 -0
  23. package/dist/core/__tests__/boot.test.js +4 -4
  24. package/dist/core/__tests__/canvas.test.js +19 -7
  25. package/dist/core/__tests__/child-followup.test.js +15 -5
  26. package/dist/core/__tests__/daemon-boot.test.js +6 -1
  27. package/dist/core/__tests__/daemon-wedge.test.js +18 -1
  28. package/dist/core/__tests__/fault-classifier.test.js +30 -0
  29. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -0
  30. package/dist/core/__tests__/fixtures/fake-engine.js +20 -0
  31. package/dist/core/__tests__/full/spike-harness.test.js +8 -5
  32. package/dist/core/__tests__/grace-clock.test.js +18 -2
  33. package/dist/core/__tests__/host-teardown-process-group.test.js +465 -0
  34. package/dist/core/__tests__/prune-to-limit.test.js +14 -0
  35. package/dist/core/__tests__/review-model-floor.test.js +32 -0
  36. package/dist/core/__tests__/revive.test.js +18 -4
  37. package/dist/core/__tests__/session-cycles.test.js +77 -0
  38. package/dist/core/__tests__/worktree.test.js +85 -0
  39. package/dist/core/canvas/boot.js +12 -7
  40. package/dist/core/canvas/canvas.d.ts +27 -8
  41. package/dist/core/canvas/canvas.js +54 -26
  42. package/dist/core/canvas/db.js +14 -0
  43. package/dist/core/canvas/history.js +1 -0
  44. package/dist/core/canvas/paths.d.ts +10 -9
  45. package/dist/core/canvas/paths.js +10 -9
  46. package/dist/core/canvas/pid.d.ts +155 -1
  47. package/dist/core/canvas/pid.js +306 -1
  48. package/dist/core/canvas/status-glyph.d.ts +7 -0
  49. package/dist/core/canvas/status-glyph.js +10 -1
  50. package/dist/core/canvas/types.d.ts +34 -0
  51. package/dist/core/fault-classifier.js +5 -1
  52. package/dist/core/runtime/bearings.js +4 -0
  53. package/dist/core/runtime/branded-host.d.ts +7 -0
  54. package/dist/core/runtime/branded-host.js +44 -17
  55. package/dist/core/runtime/broker-sdk.js +28 -68
  56. package/dist/core/runtime/broker.js +35 -4
  57. package/dist/core/runtime/host.d.ts +3 -3
  58. package/dist/core/runtime/host.js +148 -33
  59. package/dist/core/runtime/launch.d.ts +15 -15
  60. package/dist/core/runtime/launch.js +58 -4
  61. package/dist/core/runtime/naming.js +3 -2
  62. package/dist/core/runtime/nodes.d.ts +3 -1
  63. package/dist/core/runtime/nodes.js +1 -0
  64. package/dist/core/runtime/pi-cli.d.ts +2 -0
  65. package/dist/core/runtime/pi-cli.js +51 -0
  66. package/dist/core/runtime/placement.d.ts +1 -1
  67. package/dist/core/runtime/placement.js +5 -2
  68. package/dist/core/runtime/recap.js +2 -1
  69. package/dist/core/runtime/revive.d.ts +21 -15
  70. package/dist/core/runtime/revive.js +87 -52
  71. package/dist/core/runtime/session-cycles.d.ts +30 -0
  72. package/dist/core/runtime/session-cycles.js +77 -0
  73. package/dist/core/runtime/spawn.d.ts +4 -0
  74. package/dist/core/runtime/spawn.js +152 -100
  75. package/dist/core/runtime/tmux.d.ts +7 -10
  76. package/dist/core/runtime/tmux.js +9 -11
  77. package/dist/core/worktree.d.ts +35 -0
  78. package/dist/core/worktree.js +158 -0
  79. package/dist/daemon/crtrd.d.ts +17 -8
  80. package/dist/daemon/crtrd.js +191 -40
  81. package/dist/index.d.ts +3 -0
  82. package/dist/index.js +7 -0
  83. package/dist/pi-extensions/canvas-context-intro.d.ts +1 -0
  84. package/dist/pi-extensions/canvas-context-intro.js +34 -23
  85. package/dist/pi-extensions/canvas-stophook.js +11 -5
  86. package/dist/web-client/assets/{index-CbO8L0mN.js → index-B00YpRQ1.js} +20 -20
  87. package/dist/web-client/assets/index-DrkcvANq.css +2 -0
  88. package/dist/web-client/index.html +2 -2
  89. package/docs/compat/hearth-crtr-v1.md +191 -0
  90. package/docs/public-api.md +75 -0
  91. package/package.json +3 -4
  92. package/dist/core/__tests__/hearth-bootstrap.test.js +0 -136
  93. package/dist/core/hearth/__tests__/model-auth-guest.test.js +0 -151
  94. package/dist/core/hearth/config.d.ts +0 -3
  95. package/dist/core/hearth/config.js +0 -108
  96. package/dist/core/hearth/guest-env.d.ts +0 -9
  97. package/dist/core/hearth/guest-env.js +0 -27
  98. package/dist/core/hearth/index.d.ts +0 -4
  99. package/dist/core/hearth/index.js +0 -4
  100. package/dist/core/hearth/model-auth-guest.d.ts +0 -8
  101. package/dist/core/hearth/model-auth-guest.js +0 -430
  102. package/dist/core/hearth/provider.d.ts +0 -36
  103. package/dist/core/hearth/provider.js +0 -10
  104. package/dist/core/hearth/providers/__tests__/sweep-and-release.test.js +0 -254
  105. package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +0 -12
  106. package/dist/core/hearth/providers/blaxel-bootstrap.js +0 -147
  107. package/dist/core/hearth/providers/blaxel-home.d.ts +0 -54
  108. package/dist/core/hearth/providers/blaxel-home.js +0 -386
  109. package/dist/core/hearth/providers/blaxel.d.ts +0 -36
  110. package/dist/core/hearth/providers/blaxel.js +0 -364
  111. package/dist/core/hearth/providers/types.d.ts +0 -93
  112. package/dist/core/hearth/types.d.ts +0 -155
  113. package/dist/hearth/control-plane/__tests__/error-serialization.test.js +0 -29
  114. package/dist/hearth/control-plane/__tests__/node-message.test.js +0 -60
  115. package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.d.ts +0 -1
  116. package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.js +0 -44
  117. package/dist/hearth/control-plane/__tests__/rate-limit-recurrence.test.d.ts +0 -1
  118. package/dist/hearth/control-plane/__tests__/rate-limit-recurrence.test.js +0 -49
  119. package/dist/hearth/control-plane/__tests__/relay-security.test.d.ts +0 -1
  120. package/dist/hearth/control-plane/__tests__/relay-security.test.js +0 -314
  121. package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.d.ts +0 -1
  122. package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.js +0 -133
  123. package/dist/hearth/control-plane/__tests__/trigger-delivery.test.d.ts +0 -1
  124. package/dist/hearth/control-plane/__tests__/trigger-delivery.test.js +0 -170
  125. package/dist/hearth/control-plane/__tests__/wake-roll.test.d.ts +0 -1
  126. package/dist/hearth/control-plane/__tests__/wake-roll.test.js +0 -230
  127. package/dist/hearth/control-plane/__tests__/webhook-ingress.test.d.ts +0 -1
  128. package/dist/hearth/control-plane/__tests__/webhook-ingress.test.js +0 -167
  129. package/dist/hearth/control-plane/config.d.ts +0 -21
  130. package/dist/hearth/control-plane/config.js +0 -77
  131. package/dist/hearth/control-plane/db.d.ts +0 -30
  132. package/dist/hearth/control-plane/db.js +0 -561
  133. package/dist/hearth/control-plane/hearth-target.d.ts +0 -23
  134. package/dist/hearth/control-plane/hearth-target.js +0 -68
  135. package/dist/hearth/control-plane/ingress/rate-limit.d.ts +0 -24
  136. package/dist/hearth/control-plane/ingress/rate-limit.js +0 -100
  137. package/dist/hearth/control-plane/ingress/route-store.d.ts +0 -31
  138. package/dist/hearth/control-plane/ingress/route-store.js +0 -61
  139. package/dist/hearth/control-plane/ingress/webhook-delivery-store.d.ts +0 -41
  140. package/dist/hearth/control-plane/ingress/webhook-delivery-store.js +0 -69
  141. package/dist/hearth/control-plane/ingress/webhook-route.d.ts +0 -55
  142. package/dist/hearth/control-plane/ingress/webhook-route.js +0 -285
  143. package/dist/hearth/control-plane/main.d.ts +0 -1
  144. package/dist/hearth/control-plane/main.js +0 -88
  145. package/dist/hearth/control-plane/node-message.d.ts +0 -31
  146. package/dist/hearth/control-plane/node-message.js +0 -98
  147. package/dist/hearth/control-plane/register.d.ts +0 -15
  148. package/dist/hearth/control-plane/register.js +0 -34
  149. package/dist/hearth/control-plane/registry.d.ts +0 -22
  150. package/dist/hearth/control-plane/registry.js +0 -168
  151. package/dist/hearth/control-plane/relay.d.ts +0 -44
  152. package/dist/hearth/control-plane/relay.js +0 -711
  153. package/dist/hearth/control-plane/scheduler/fire-store.d.ts +0 -36
  154. package/dist/hearth/control-plane/scheduler/fire-store.js +0 -73
  155. package/dist/hearth/control-plane/scheduler/recurrence.d.ts +0 -7
  156. package/dist/hearth/control-plane/scheduler/recurrence.js +0 -58
  157. package/dist/hearth/control-plane/scheduler/scan-loop.d.ts +0 -38
  158. package/dist/hearth/control-plane/scheduler/scan-loop.js +0 -138
  159. package/dist/hearth/control-plane/scheduler/schedule-store.d.ts +0 -32
  160. package/dist/hearth/control-plane/scheduler/schedule-store.js +0 -66
  161. package/dist/hearth/control-plane/secrets.d.ts +0 -31
  162. package/dist/hearth/control-plane/secrets.js +0 -134
  163. package/dist/hearth/control-plane/server.d.ts +0 -27
  164. package/dist/hearth/control-plane/server.js +0 -482
  165. package/dist/hearth/control-plane/serving.d.ts +0 -15
  166. package/dist/hearth/control-plane/serving.js +0 -106
  167. package/dist/hearth/control-plane/session.d.ts +0 -68
  168. package/dist/hearth/control-plane/session.js +0 -273
  169. package/dist/hearth/control-plane/triggers/acl.d.ts +0 -14
  170. package/dist/hearth/control-plane/triggers/acl.js +0 -52
  171. package/dist/hearth/control-plane/triggers/audit-store.d.ts +0 -38
  172. package/dist/hearth/control-plane/triggers/audit-store.js +0 -79
  173. package/dist/hearth/control-plane/triggers/deliver.d.ts +0 -43
  174. package/dist/hearth/control-plane/triggers/deliver.js +0 -76
  175. package/dist/hearth/control-plane/triggers/envelope.d.ts +0 -29
  176. package/dist/hearth/control-plane/triggers/envelope.js +0 -38
  177. package/dist/hearth/control-plane/types.d.ts +0 -86
  178. package/dist/hearth/control-plane/types.js +0 -1
  179. package/dist/hearth/control-plane/wake.d.ts +0 -86
  180. package/dist/hearth/control-plane/wake.js +0 -550
  181. package/dist/web-client/assets/index-DwO46Cs5.css +0 -2
  182. /package/dist/{core/__tests__/hearth-bootstrap.test.d.ts → commands/__tests__/revive-now-gate.test.d.ts} +0 -0
  183. /package/dist/{core/hearth/__tests__/model-auth-guest.test.d.ts → commands/sys/__tests__/setup-core.test.d.ts} +0 -0
  184. /package/dist/core/{hearth/providers/__tests__/sweep-and-release.test.d.ts → __tests__/fault-classifier.test.d.ts} +0 -0
  185. /package/dist/core/{hearth/providers/types.js → __tests__/host-teardown-process-group.test.d.ts} +0 -0
  186. /package/dist/core/{hearth/types.js → __tests__/review-model-floor.test.d.ts} +0 -0
  187. /package/dist/{hearth/control-plane/__tests__/error-serialization.test.d.ts → core/__tests__/session-cycles.test.d.ts} +0 -0
  188. /package/dist/{hearth/control-plane/__tests__/node-message.test.d.ts → core/__tests__/worktree.test.d.ts} +0 -0
@@ -1,88 +0,0 @@
1
- // control-plane/main.ts — the CP process entrypoint. Generalizes the
2
- // wake-proxy's main.ts startup shape (load config, build server, install
3
- // signal handling, listen) onto the CP modules: config.ts, db.ts (open +
4
- // migrate, never seed), and server.ts. Run via the `hearth:control-plane`
5
- // package script.
6
- import { general } from '../../core/errors.js';
7
- import { loadControlPlaneConfig } from './config.js';
8
- import { getControlPlaneDb } from './db.js';
9
- import { redactSecrets } from './secrets.js';
10
- import { tenantSessionAuthRedactedMessage } from './session.js';
11
- import { createControlPlaneServer } from './server.js';
12
- import { controlPlaneRelay } from './relay.js';
13
- import { startSchedulerIngress } from './register.js';
14
- function redact(message) {
15
- return redactSecrets(tenantSessionAuthRedactedMessage(message));
16
- }
17
- function log(level, message, details = {}) {
18
- process.stderr.write(`${JSON.stringify({ level, message, details })}\n`);
19
- }
20
- async function shutdown(server, scheduler, signal) {
21
- log('info', 'hearth control plane shutting down', { signal });
22
- scheduler.stop();
23
- server.closeIdleConnections?.();
24
- server.closeAllConnections?.();
25
- await new Promise((resolve) => {
26
- server.close(() => resolve());
27
- });
28
- process.exit(0);
29
- }
30
- async function main() {
31
- const config = loadControlPlaneConfig();
32
- // Opens (or reuses) the CP db and runs the schema migration gate only —
33
- // no row is ever written here. The control plane registers no homes at
34
- // startup; `home` rows are created by the provisioner (P3.2) via
35
- // `createHome`, not by this process.
36
- const db = getControlPlaneDb();
37
- const server = createControlPlaneServer(config, { db, relay: controlPlaneRelay });
38
- // Start the always-on cron scan loop. Webhook ingress is mounted inside the
39
- // server route tree (register.ts); the scheduler is the one background loop
40
- // that needs an explicit start/stop handle.
41
- const scheduler = startSchedulerIngress();
42
- let shuttingDown = false;
43
- const beginShutdown = (signal) => {
44
- if (shuttingDown)
45
- return;
46
- shuttingDown = true;
47
- void shutdown(server, scheduler, signal).catch((error) => {
48
- const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
49
- log('error', 'hearth control plane shutdown failed', { error: redact(message) });
50
- process.exit(1);
51
- });
52
- };
53
- process.on('SIGINT', () => beginShutdown('SIGINT'));
54
- process.on('SIGTERM', () => beginShutdown('SIGTERM'));
55
- process.on('uncaughtException', (error) => {
56
- log('error', 'hearth control plane uncaught exception', { error: redact(error instanceof Error ? (error.stack ?? error.message) : String(error)) });
57
- process.exit(1);
58
- });
59
- process.on('unhandledRejection', (reason) => {
60
- log('error', 'hearth control plane unhandled rejection', { error: redact(reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)) });
61
- process.exit(1);
62
- });
63
- await new Promise((resolve, reject) => {
64
- server.once('error', reject);
65
- server.listen(config.port, config.host, () => {
66
- log('info', 'hearth control plane listening', {
67
- host: config.host,
68
- port: config.port,
69
- publicOrigin: config.publicOrigin,
70
- cookieSecure: config.cookieSecure,
71
- wakeTimeoutMs: config.wakeTimeoutMs,
72
- readyTimeoutMs: config.readyTimeoutMs,
73
- });
74
- resolve();
75
- });
76
- }).catch((error) => {
77
- throw general(`hearth control plane failed to bind ${config.host}:${config.port}`, {
78
- host: config.host,
79
- port: config.port,
80
- error: error instanceof Error ? error.message : String(error),
81
- });
82
- });
83
- }
84
- void main().catch((error) => {
85
- const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
86
- log('error', 'hearth control plane failed to start', { error: redact(message) });
87
- process.exit(1);
88
- });
@@ -1,31 +0,0 @@
1
- import { type HomeBackend } from '../../core/hearth/provider.js';
2
- import type { M0Config } from '../../core/hearth/types.js';
3
- import { getHomeById } from './registry.js';
4
- import type { HomeId } from './types.js';
5
- import { ensureAwake } from './wake.js';
6
- export type NodeMessageTier = 'critical' | 'urgent' | 'normal' | 'deferred';
7
- /** Indirection seam so a smoke script can fake `ensureAwake`/the registry/the
8
- * provider backend without a real DB/provider (mirrors `wake.ts`'s
9
- * `wakeInternals`). Production code never reassigns these. */
10
- export declare const nodeMessageInternals: {
11
- ensureAwake: typeof ensureAwake;
12
- getHomeById: typeof getHomeById;
13
- getHomeBackend: (config: M0Config) => HomeBackend;
14
- loadM0Config: () => M0Config;
15
- };
16
- /**
17
- * Wake `home_id` then deliver `body` to its home agent by running
18
- * `crtr node msg --to <home_agent_target> --tier <tier>` in-guest, through
19
- * the provider exec stdin seam (P2.6) — `body` is the exec `stdin`, never
20
- * part of `argv`/command text.
21
- *
22
- * Exec happens only AFTER `ensureAwake` resolves (R-TRIG-1), so the in-guest
23
- * broker + inbox watcher exist to receive it. `home_agent_target` is read
24
- * fresh from the registry both before and after the wake and asserted equal
25
- * — a provider recreate during `ensureAwake` may refresh
26
- * `provider_sandbox_id`/`preview_endpoint`, but the target node id is stable
27
- * across recreate (R-WAKE-6) and this primitive refuses to deliver if that
28
- * invariant is ever violated. The sandbox id used for exec is read
29
- * POST-wake, since a recreate makes the pre-wake sandbox id stale.
30
- */
31
- export declare function wakeAndExecNodeMessage(home_id: HomeId, body: string, tier: string): Promise<void>;
@@ -1,98 +0,0 @@
1
- // control-plane/node-message.ts — P4.4 lower-level node-message exec primitive.
2
- //
3
- // `wakeAndExecNodeMessage(home_id, body, tier)`: `ensureAwake(home_id)`
4
- // (wake.ts, T3.1) FIRST, then run `crtr node msg --to <home_agent_target>
5
- // --tier <tier>` in-guest through the P2.6 stdin-capable provider exec seam
6
- // (`HomeBackend.exec`, provider.ts) — `body` travels on stdin (the `msg`
7
- // command's body is positional-or-stdin; this primitive always uses stdin),
8
- // never as a positional argv element, shell-interpolated command string,
9
- // heredoc, or base64 blob. The ONLY things passed as argv tokens are the
10
- // static command words, `--to`/`--tier` flags, the resolved
11
- // `home_agent_target`, and the whitelisted tier — never the body.
12
- //
13
- // This is the LOWER-LEVEL primitive only (master plan P4.4): no envelope
14
- // construction, no ACL, no rate-limit, no audit. Those are P5's
15
- // `triggers/deliver.ts` chokepoint (P5.3) — it authorizes, audits, and calls
16
- // THIS primitive with the serialized envelope on stdin. The CP never writes
17
- // a home's inbox file directly; this primitive (via the in-guest `crtr`
18
- // CLI's own inbox-append) is the only path to it, and scheduler/ingress must
19
- // call this — never provider exec directly.
20
- import { general } from '../../core/errors.js';
21
- import { loadM0Config } from '../../core/hearth/config.js';
22
- import { getHomeBackend } from '../../core/hearth/provider.js';
23
- import { getHomeById } from './registry.js';
24
- import { ensureAwake } from './wake.js';
25
- const TIERS = ['critical', 'urgent', 'normal', 'deferred'];
26
- /** Reject anything outside the whitelist BEFORE any wake/exec is attempted —
27
- * an invalid tier never reaches the provider, let alone a home. */
28
- function requireTier(tier) {
29
- if (TIERS.includes(tier))
30
- return tier;
31
- throw general(`invalid node-message tier: ${tier}`, { tier, allowed: TIERS });
32
- }
33
- function requireHomeField(value, field, home_id) {
34
- if (value === null || value === undefined || (typeof value === 'string' && value.trim() === '')) {
35
- throw general(`home ${home_id} is missing ${field} — cannot deliver node message`, { home_id, field });
36
- }
37
- return value;
38
- }
39
- /** Indirection seam so a smoke script can fake `ensureAwake`/the registry/the
40
- * provider backend without a real DB/provider (mirrors `wake.ts`'s
41
- * `wakeInternals`). Production code never reassigns these. */
42
- export const nodeMessageInternals = {
43
- ensureAwake,
44
- getHomeById,
45
- getHomeBackend: (config) => getHomeBackend(config),
46
- loadM0Config: () => loadM0Config(),
47
- };
48
- function readHome(home_id) {
49
- const home = nodeMessageInternals.getHomeById(home_id);
50
- if (home === null)
51
- throw general(`home not found: ${home_id}`, { home_id });
52
- return home;
53
- }
54
- /**
55
- * Wake `home_id` then deliver `body` to its home agent by running
56
- * `crtr node msg --to <home_agent_target> --tier <tier>` in-guest, through
57
- * the provider exec stdin seam (P2.6) — `body` is the exec `stdin`, never
58
- * part of `argv`/command text.
59
- *
60
- * Exec happens only AFTER `ensureAwake` resolves (R-TRIG-1), so the in-guest
61
- * broker + inbox watcher exist to receive it. `home_agent_target` is read
62
- * fresh from the registry both before and after the wake and asserted equal
63
- * — a provider recreate during `ensureAwake` may refresh
64
- * `provider_sandbox_id`/`preview_endpoint`, but the target node id is stable
65
- * across recreate (R-WAKE-6) and this primitive refuses to deliver if that
66
- * invariant is ever violated. The sandbox id used for exec is read
67
- * POST-wake, since a recreate makes the pre-wake sandbox id stale.
68
- */
69
- export async function wakeAndExecNodeMessage(home_id, body, tier) {
70
- const validTier = requireTier(tier);
71
- const before = readHome(home_id);
72
- const target = requireHomeField(before.home_agent_target, 'home_agent_target', home_id);
73
- await nodeMessageInternals.ensureAwake(home_id);
74
- const after = readHome(home_id);
75
- if (after.home_agent_target !== target) {
76
- throw general(`home_agent_target changed during wake for home ${home_id} — refusing to deliver`, {
77
- home_id,
78
- before: target,
79
- after: after.home_agent_target,
80
- });
81
- }
82
- const sandboxId = requireHomeField(after.provider_sandbox_id, 'provider_sandbox_id', home_id);
83
- const backend = nodeMessageInternals.getHomeBackend(nodeMessageInternals.loadM0Config());
84
- // Static argv only: command words, the `--to`/`--tier` flags, the resolved
85
- // target, and the whitelisted tier. `body` is carried on `stdin` — never
86
- // appended here.
87
- const result = await backend.exec(sandboxId, {
88
- argv: ['crtr', 'node', 'msg', '--to', target, '--tier', validTier],
89
- stdin: body,
90
- });
91
- if (result.exitCode !== 0) {
92
- throw general(`node-message exec failed for home ${home_id} (exit ${String(result.exitCode)})`, {
93
- home_id,
94
- exitCode: result.exitCode,
95
- stderr: result.stderr,
96
- });
97
- }
98
- }
@@ -1,15 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import { type SchedulerLoopHandle } from './scheduler/scan-loop.js';
3
- /** True for `POST /webhook/:routing_key` (the only ingress method/shape). A
4
- * non-POST on a `/webhook/*` path is reported by the caller as 405; a
5
- * malformed key is handled as 404 inside `handleWebhook`. */
6
- export declare function isWebhookRequest(method: string | undefined, pathname: string): boolean;
7
- /** True when the path is under `/webhook/` regardless of method — lets the CP
8
- * server answer a wrong-method request on the webhook lane with 405 rather
9
- * than falling through to 404. */
10
- export declare function isWebhookPath(pathname: string): boolean;
11
- /** Dispatch a webhook request to the ingress handler with production deps. */
12
- export declare function handleWebhookRequest(req: IncomingMessage, res: ServerResponse): Promise<void>;
13
- /** Start the cron scan loop. Returns a handle whose `stop()` clears the loop
14
- * timer — call it from CP shutdown. */
15
- export declare function startSchedulerIngress(): SchedulerLoopHandle;
@@ -1,34 +0,0 @@
1
- // control-plane/register.ts — the single wiring entrypoint for the scheduler +
2
- // webhook ingress slice (master plan P5.6). The CP `server.ts` and `main.ts`
3
- // each carry ONE call site into here; this module owns the wiring body.
4
- //
5
- // Two seams:
6
- // - `isWebhookRequest` / `handleWebhookRequest`: the PUBLIC webhook ingress
7
- // lane. It is authenticated ONLY by the per-route HMAC signature (verified
8
- // inside `handleWebhook`), so it is mounted OUTSIDE the CP's session/
9
- // registry gate — a browser session is neither required nor consulted.
10
- // - `startSchedulerIngress`: launches the always-on cron scan loop at CP
11
- // startup and returns a stop handle for graceful shutdown.
12
- import { extractRoutingKey, handleWebhook } from './ingress/webhook-route.js';
13
- import { startSchedulerLoop } from './scheduler/scan-loop.js';
14
- /** True for `POST /webhook/:routing_key` (the only ingress method/shape). A
15
- * non-POST on a `/webhook/*` path is reported by the caller as 405; a
16
- * malformed key is handled as 404 inside `handleWebhook`. */
17
- export function isWebhookRequest(method, pathname) {
18
- return method === 'POST' && extractRoutingKey(pathname) !== null;
19
- }
20
- /** True when the path is under `/webhook/` regardless of method — lets the CP
21
- * server answer a wrong-method request on the webhook lane with 405 rather
22
- * than falling through to 404. */
23
- export function isWebhookPath(pathname) {
24
- return pathname === '/webhook' || pathname.startsWith('/webhook/');
25
- }
26
- /** Dispatch a webhook request to the ingress handler with production deps. */
27
- export async function handleWebhookRequest(req, res) {
28
- await handleWebhook(req, res);
29
- }
30
- /** Start the cron scan loop. Returns a handle whose `stop()` clears the loop
31
- * timer — call it from CP shutdown. */
32
- export function startSchedulerIngress() {
33
- return startSchedulerLoop();
34
- }
@@ -1,22 +0,0 @@
1
- import { DatabaseSync } from 'node:sqlite';
2
- import type { CreateHomeInput, Home, HomeHealthVerdict, HomeId, HomePatch, HomeStatus, RefreshProviderPointersInput, ResolvedHome, TenantSession, TouchWakeInput } from './types.js';
3
- export declare function createHome(input: CreateHomeInput, db?: DatabaseSync): Home;
4
- export declare function getHomeById(home_id: HomeId | null | undefined, db?: DatabaseSync): Home | null;
5
- /** Resolve a tenant session to its registry row. A null return is a hard
6
- * auth/404 boundary: callers must turn it into unauthorized/not-found before
7
- * wake or relay. This module intentionally performs a plain row read, so a
8
- * suspended home remains resolvable/readable (R-REG-1). */
9
- export declare function resolveHome(tenant_id: string, user_id: string, db?: DatabaseSync): ResolvedHome | null;
10
- export declare function resolveHomeForSession(session: TenantSession | null, db?: DatabaseSync): ResolvedHome | null;
11
- export declare function getHomeBySandbox(provider_sandbox_id: string | null | undefined, db?: DatabaseSync): Home | null;
12
- export declare function updateHome(home_id: HomeId, mutate: (current: Home) => HomePatch, db?: DatabaseSync): Home;
13
- export declare function refreshProviderPointers(home_id: HomeId, pointers: RefreshProviderPointersInput, db?: DatabaseSync): Home;
14
- export declare function setHomeStatus(home_id: HomeId, status: HomeStatus, db?: DatabaseSync): Home;
15
- /** Set the home's health verdict. The auto-upgrade roll (wake.ts `rollHome`)
16
- * is the first natural producer of `health_verdict`: a completed roll/
17
- * rollback writes `'healthy'`, a roll AND rollback both failing writes
18
- * `'degraded'` (design §4/§5). */
19
- export declare function setHealthVerdict(home_id: HomeId, health_verdict: HomeHealthVerdict, db?: DatabaseSync): Home;
20
- export declare function touchWake(home_id: HomeId, input?: TouchWakeInput, db?: DatabaseSync): Home;
21
- export declare function touchReady(home_id: HomeId, at?: string, db?: DatabaseSync): Home;
22
- export declare function touchSuspend(home_id: HomeId, at?: string, db?: DatabaseSync): Home;
@@ -1,168 +0,0 @@
1
- import { general } from '../../core/errors.js';
2
- import { getControlPlaneDb } from './db.js';
3
- const HOME_COLUMNS = [
4
- 'home_id',
5
- 'tenant_id',
6
- 'user_id',
7
- 'provider',
8
- 'provider_sandbox_id',
9
- 'provider_volume_id',
10
- 'preview_endpoint',
11
- 'home_agent_target',
12
- 'relay_auth_ref',
13
- 'status',
14
- 'template_version',
15
- 'last_ready_at',
16
- 'last_wake_at',
17
- 'last_suspend_at',
18
- 'last_wake_latency_ms',
19
- 'health_verdict',
20
- 'created_at',
21
- 'updated_at',
22
- ];
23
- const SELECT_HOME = `SELECT ${HOME_COLUMNS.join(', ')} FROM home`;
24
- function nowIso() {
25
- return new Date().toISOString();
26
- }
27
- function nonEmpty(name, value) {
28
- const trimmed = value.trim();
29
- if (trimmed === '')
30
- throw general(`${name} must not be empty`, { field: name });
31
- return trimmed;
32
- }
33
- function normalizeCreateHome(input) {
34
- const now = nowIso();
35
- return {
36
- home_id: nonEmpty('home_id', input.home_id),
37
- tenant_id: nonEmpty('tenant_id', input.tenant_id),
38
- user_id: nonEmpty('user_id', input.user_id),
39
- provider: input.provider ?? 'blaxel',
40
- provider_sandbox_id: input.provider_sandbox_id ?? null,
41
- provider_volume_id: input.provider_volume_id ?? null,
42
- preview_endpoint: input.preview_endpoint ?? null,
43
- home_agent_target: input.home_agent_target ?? null,
44
- relay_auth_ref: input.relay_auth_ref ?? null,
45
- status: input.status ?? 'provisioning',
46
- template_version: input.template_version ?? null,
47
- last_ready_at: input.last_ready_at ?? null,
48
- last_wake_at: input.last_wake_at ?? null,
49
- last_suspend_at: input.last_suspend_at ?? null,
50
- last_wake_latency_ms: input.last_wake_latency_ms ?? null,
51
- health_verdict: input.health_verdict ?? 'unknown',
52
- created_at: input.created_at ?? now,
53
- updated_at: input.updated_at ?? input.created_at ?? now,
54
- };
55
- }
56
- function mapHome(row) {
57
- if (row === undefined || row === null)
58
- return null;
59
- return row;
60
- }
61
- function changedColumns(patch) {
62
- return Object.keys(patch).filter((key) => patch[key] !== undefined);
63
- }
64
- function runTransaction(db, body) {
65
- db.exec('BEGIN IMMEDIATE');
66
- try {
67
- const value = body();
68
- db.exec('COMMIT');
69
- return value;
70
- }
71
- catch (error) {
72
- try {
73
- db.exec('ROLLBACK');
74
- }
75
- catch {
76
- // The original error is the useful one; rollback failure only means the
77
- // transaction was already closed by SQLite.
78
- }
79
- throw error;
80
- }
81
- }
82
- export function createHome(input, db = getControlPlaneDb()) {
83
- const home = normalizeCreateHome(input);
84
- db.prepare(`
85
- INSERT INTO home (${HOME_COLUMNS.join(', ')})
86
- VALUES (${HOME_COLUMNS.map(() => '?').join(', ')})
87
- `).run(...HOME_COLUMNS.map((column) => home[column]));
88
- return home;
89
- }
90
- export function getHomeById(home_id, db = getControlPlaneDb()) {
91
- if (home_id === null || home_id === undefined || home_id.trim() === '')
92
- return null;
93
- return mapHome(db.prepare(`${SELECT_HOME} WHERE home_id = ?`).get(home_id));
94
- }
95
- /** Resolve a tenant session to its registry row. A null return is a hard
96
- * auth/404 boundary: callers must turn it into unauthorized/not-found before
97
- * wake or relay. This module intentionally performs a plain row read, so a
98
- * suspended home remains resolvable/readable (R-REG-1). */
99
- export function resolveHome(tenant_id, user_id, db = getControlPlaneDb()) {
100
- const tenant = tenant_id.trim();
101
- const user = user_id.trim();
102
- if (tenant === '' || user === '')
103
- return null;
104
- return mapHome(db.prepare(`${SELECT_HOME} WHERE tenant_id = ? AND user_id = ?`).get(tenant, user));
105
- }
106
- export function resolveHomeForSession(session, db = getControlPlaneDb()) {
107
- if (session === null)
108
- return null;
109
- return resolveHome(session.tenant_id, session.user_id, db);
110
- }
111
- export function getHomeBySandbox(provider_sandbox_id, db = getControlPlaneDb()) {
112
- if (provider_sandbox_id === null || provider_sandbox_id === undefined || provider_sandbox_id.trim() === '')
113
- return null;
114
- return mapHome(db.prepare(`${SELECT_HOME} WHERE provider_sandbox_id = ?`).get(provider_sandbox_id));
115
- }
116
- export function updateHome(home_id, mutate, db = getControlPlaneDb()) {
117
- return runTransaction(db, () => {
118
- const current = getHomeById(home_id, db);
119
- if (current === null)
120
- throw general(`home not found: ${home_id}`, { home_id });
121
- const patch = mutate(current);
122
- const columns = changedColumns(patch);
123
- const updated_at = nowIso();
124
- if (columns.length === 0) {
125
- db.prepare('UPDATE home SET updated_at = ? WHERE home_id = ?').run(updated_at, home_id);
126
- return { ...current, updated_at };
127
- }
128
- const assignments = columns.map((column) => `${column} = ?`).join(', ');
129
- const values = columns.map((column) => {
130
- const value = patch[column];
131
- if (value === undefined)
132
- throw general(`home patch column ${column} was unexpectedly undefined`, { column });
133
- return value;
134
- });
135
- db.prepare(`UPDATE home SET ${assignments}, updated_at = ? WHERE home_id = ?`).run(...values, updated_at, home_id);
136
- return getHomeById(home_id, db);
137
- });
138
- }
139
- export function refreshProviderPointers(home_id, pointers, db = getControlPlaneDb()) {
140
- return updateHome(home_id, () => ({
141
- provider_sandbox_id: pointers.provider_sandbox_id,
142
- preview_endpoint: pointers.preview_endpoint,
143
- status: pointers.status,
144
- template_version: pointers.template_version,
145
- }), db);
146
- }
147
- export function setHomeStatus(home_id, status, db = getControlPlaneDb()) {
148
- return updateHome(home_id, () => ({ status }), db);
149
- }
150
- /** Set the home's health verdict. The auto-upgrade roll (wake.ts `rollHome`)
151
- * is the first natural producer of `health_verdict`: a completed roll/
152
- * rollback writes `'healthy'`, a roll AND rollback both failing writes
153
- * `'degraded'` (design §4/§5). */
154
- export function setHealthVerdict(home_id, health_verdict, db = getControlPlaneDb()) {
155
- return updateHome(home_id, () => ({ health_verdict }), db);
156
- }
157
- export function touchWake(home_id, input = {}, db = getControlPlaneDb()) {
158
- return updateHome(home_id, () => ({
159
- last_wake_at: input.at ?? nowIso(),
160
- last_wake_latency_ms: input.latency_ms,
161
- }), db);
162
- }
163
- export function touchReady(home_id, at = nowIso(), db = getControlPlaneDb()) {
164
- return updateHome(home_id, () => ({ last_ready_at: at }), db);
165
- }
166
- export function touchSuspend(home_id, at = nowIso(), db = getControlPlaneDb()) {
167
- return updateHome(home_id, () => ({ last_suspend_at: at }), db);
168
- }
@@ -1,44 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import type { Socket } from 'node:net';
3
- import { WebSocket } from 'ws';
4
- import { type ControlPlaneConfig } from './config.js';
5
- import { type WakeResult } from './wake.js';
6
- import type { HomeId, ResolvedHome } from './types.js';
7
- import type { RelayDispatcher } from './server.js';
8
- /** Serialize a non-`Error` rejection for a log line without collapsing it to `"[object Object]"`
9
- * (`String(error)` on a plain object) — recreate-race-fix.md §3.6. Falls back to `String` only
10
- * if the value itself isn't JSON-serializable (a circular structure, a BigInt, etc). */
11
- export declare function serializeRelayError(error: unknown): string;
12
- /** Join the validated origin-form request target onto `previewEndpoint` and
13
- * reject anything that would escape that origin (criterion 4: "reject
14
- * scheme-relative/absolute external escapes"). */
15
- export declare function buildUpstreamHttpUrl(previewEndpoint: string, rawRequestUrl: string | undefined): URL;
16
- export interface RelayDeps {
17
- ensureAwake?: (home_id: HomeId) => Promise<WakeResult>;
18
- resolveRunning?: (home_id: HomeId) => WakeResult | null;
19
- invalidateWarmVerdict?: (home_id: HomeId) => void;
20
- loadConfig?: () => ControlPlaneConfig;
21
- }
22
- /** Forward one HTTP request to `home` (criterion 1: only after the caller's
23
- * session/registry gate already resolved `home`, and only after
24
- * `ensureAwake(home.home_id)` succeeds). Mirrors M0 `proxy.ts`'s
25
- * fast-path-then-escalate shape: a fresh warm-verdict cache hit
26
- * (`resolveRunning`, P4.2) is forwarded directly without re-entering the
27
- * wake pipeline; only a missing/stale verdict, or a running route that just
28
- * failed on a safely-replayable method, escalates to a bounded
29
- * `ensureAwake`. */
30
- export declare function proxyHttp(req: IncomingMessage, res: ServerResponse, home: ResolvedHome, deps?: RelayDeps): Promise<void>;
31
- export interface ProxyWebSocketDeps extends RelayDeps {
32
- createUpstream?: (url: string, relayToken: string) => WebSocket;
33
- }
34
- /** `RelayDispatcher.proxyUpgrade`: complete the WS handshake on the already
35
- * session+registry-gated socket (`server.ts`'s `handleUpgrade`), then bridge
36
- * it to the home. Errors after this point close the browser socket in-band
37
- * with a WS close code (1011/1013) rather than a raw TCP reset, so the
38
- * client sees a clean failure (criterion 4: "keep ... close behavior from
39
- * M0 where applicable"). */
40
- export declare function proxyUpgrade(req: IncomingMessage, socket: Socket, head: Buffer, home: ResolvedHome, deps?: ProxyWebSocketDeps): void;
41
- /** The real `RelayDispatcher` `main.ts` wires in once the CP server is built
42
- * — `server.ts`'s default (`notImplementedRelay`) is only ever used when a
43
- * caller (test/smoke) doesn't inject one. */
44
- export declare const controlPlaneRelay: RelayDispatcher;