@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.
- package/dist/build-root.js +1 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +472 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +284 -76
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +31 -24
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +52 -15
- package/dist/clients/attach/attach-cmd.js +706 -733
- package/dist/clients/attach/chat-view.js +11 -0
- package/dist/clients/web/server.js +5 -28
- package/dist/clients/web/web-cmd.js +1 -1
- package/dist/commands/__tests__/revive-now-gate.test.js +60 -0
- package/dist/commands/canvas-rebuild-index.js +5 -5
- package/dist/commands/node-snapshot.js +9 -1
- package/dist/commands/node.js +37 -17
- package/dist/commands/push.js +8 -0
- package/dist/commands/revive.d.ts +10 -0
- package/dist/commands/revive.js +28 -10
- package/dist/commands/sys/__tests__/setup-core.test.js +19 -0
- package/dist/commands/sys/doctor.js +1 -1
- package/dist/commands/sys/setup-core.js +3 -2
- package/dist/commands/sys/setup.js +1 -1
- package/dist/commands/worktree.d.ts +2 -0
- package/dist/commands/worktree.js +94 -0
- package/dist/core/__tests__/boot.test.js +4 -4
- package/dist/core/__tests__/canvas.test.js +19 -7
- package/dist/core/__tests__/child-followup.test.js +15 -5
- package/dist/core/__tests__/daemon-boot.test.js +6 -1
- package/dist/core/__tests__/daemon-wedge.test.js +18 -1
- package/dist/core/__tests__/fault-classifier.test.js +30 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +20 -0
- package/dist/core/__tests__/full/spike-harness.test.js +8 -5
- package/dist/core/__tests__/grace-clock.test.js +18 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +465 -0
- package/dist/core/__tests__/prune-to-limit.test.js +14 -0
- package/dist/core/__tests__/review-model-floor.test.js +32 -0
- package/dist/core/__tests__/revive.test.js +18 -4
- package/dist/core/__tests__/session-cycles.test.js +77 -0
- package/dist/core/__tests__/worktree.test.js +85 -0
- package/dist/core/canvas/boot.js +12 -7
- package/dist/core/canvas/canvas.d.ts +27 -8
- package/dist/core/canvas/canvas.js +54 -26
- package/dist/core/canvas/db.js +14 -0
- package/dist/core/canvas/history.js +1 -0
- package/dist/core/canvas/paths.d.ts +10 -9
- package/dist/core/canvas/paths.js +10 -9
- package/dist/core/canvas/pid.d.ts +155 -1
- package/dist/core/canvas/pid.js +306 -1
- package/dist/core/canvas/status-glyph.d.ts +7 -0
- package/dist/core/canvas/status-glyph.js +10 -1
- package/dist/core/canvas/types.d.ts +34 -0
- package/dist/core/fault-classifier.js +5 -1
- package/dist/core/runtime/bearings.js +4 -0
- package/dist/core/runtime/branded-host.d.ts +7 -0
- package/dist/core/runtime/branded-host.js +44 -17
- package/dist/core/runtime/broker-sdk.js +28 -68
- package/dist/core/runtime/broker.js +35 -4
- package/dist/core/runtime/host.d.ts +3 -3
- package/dist/core/runtime/host.js +148 -33
- package/dist/core/runtime/launch.d.ts +15 -15
- package/dist/core/runtime/launch.js +58 -4
- package/dist/core/runtime/naming.js +3 -2
- package/dist/core/runtime/nodes.d.ts +3 -1
- package/dist/core/runtime/nodes.js +1 -0
- package/dist/core/runtime/pi-cli.d.ts +2 -0
- package/dist/core/runtime/pi-cli.js +51 -0
- package/dist/core/runtime/placement.d.ts +1 -1
- package/dist/core/runtime/placement.js +5 -2
- package/dist/core/runtime/recap.js +2 -1
- package/dist/core/runtime/revive.d.ts +21 -15
- package/dist/core/runtime/revive.js +87 -52
- package/dist/core/runtime/session-cycles.d.ts +30 -0
- package/dist/core/runtime/session-cycles.js +77 -0
- package/dist/core/runtime/spawn.d.ts +4 -0
- package/dist/core/runtime/spawn.js +152 -100
- package/dist/core/runtime/tmux.d.ts +7 -10
- package/dist/core/runtime/tmux.js +9 -11
- package/dist/core/worktree.d.ts +35 -0
- package/dist/core/worktree.js +158 -0
- package/dist/daemon/crtrd.d.ts +17 -8
- package/dist/daemon/crtrd.js +191 -40
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +1 -0
- package/dist/pi-extensions/canvas-context-intro.js +34 -23
- package/dist/pi-extensions/canvas-stophook.js +11 -5
- package/dist/web-client/assets/{index-CbO8L0mN.js → index-B00YpRQ1.js} +20 -20
- package/dist/web-client/assets/index-DrkcvANq.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/docs/compat/hearth-crtr-v1.md +191 -0
- package/docs/public-api.md +75 -0
- package/package.json +3 -4
- package/dist/core/__tests__/hearth-bootstrap.test.js +0 -136
- package/dist/core/hearth/__tests__/model-auth-guest.test.js +0 -151
- package/dist/core/hearth/config.d.ts +0 -3
- package/dist/core/hearth/config.js +0 -108
- package/dist/core/hearth/guest-env.d.ts +0 -9
- package/dist/core/hearth/guest-env.js +0 -27
- package/dist/core/hearth/index.d.ts +0 -4
- package/dist/core/hearth/index.js +0 -4
- package/dist/core/hearth/model-auth-guest.d.ts +0 -8
- package/dist/core/hearth/model-auth-guest.js +0 -430
- package/dist/core/hearth/provider.d.ts +0 -36
- package/dist/core/hearth/provider.js +0 -10
- package/dist/core/hearth/providers/__tests__/sweep-and-release.test.js +0 -254
- package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +0 -12
- package/dist/core/hearth/providers/blaxel-bootstrap.js +0 -147
- package/dist/core/hearth/providers/blaxel-home.d.ts +0 -54
- package/dist/core/hearth/providers/blaxel-home.js +0 -386
- package/dist/core/hearth/providers/blaxel.d.ts +0 -36
- package/dist/core/hearth/providers/blaxel.js +0 -364
- package/dist/core/hearth/providers/types.d.ts +0 -93
- package/dist/core/hearth/types.d.ts +0 -155
- package/dist/hearth/control-plane/__tests__/error-serialization.test.js +0 -29
- package/dist/hearth/control-plane/__tests__/node-message.test.js +0 -60
- package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.d.ts +0 -1
- package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.js +0 -44
- package/dist/hearth/control-plane/__tests__/rate-limit-recurrence.test.d.ts +0 -1
- package/dist/hearth/control-plane/__tests__/rate-limit-recurrence.test.js +0 -49
- package/dist/hearth/control-plane/__tests__/relay-security.test.d.ts +0 -1
- package/dist/hearth/control-plane/__tests__/relay-security.test.js +0 -314
- package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.d.ts +0 -1
- package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.js +0 -133
- package/dist/hearth/control-plane/__tests__/trigger-delivery.test.d.ts +0 -1
- package/dist/hearth/control-plane/__tests__/trigger-delivery.test.js +0 -170
- package/dist/hearth/control-plane/__tests__/wake-roll.test.d.ts +0 -1
- package/dist/hearth/control-plane/__tests__/wake-roll.test.js +0 -230
- package/dist/hearth/control-plane/__tests__/webhook-ingress.test.d.ts +0 -1
- package/dist/hearth/control-plane/__tests__/webhook-ingress.test.js +0 -167
- package/dist/hearth/control-plane/config.d.ts +0 -21
- package/dist/hearth/control-plane/config.js +0 -77
- package/dist/hearth/control-plane/db.d.ts +0 -30
- package/dist/hearth/control-plane/db.js +0 -561
- package/dist/hearth/control-plane/hearth-target.d.ts +0 -23
- package/dist/hearth/control-plane/hearth-target.js +0 -68
- package/dist/hearth/control-plane/ingress/rate-limit.d.ts +0 -24
- package/dist/hearth/control-plane/ingress/rate-limit.js +0 -100
- package/dist/hearth/control-plane/ingress/route-store.d.ts +0 -31
- package/dist/hearth/control-plane/ingress/route-store.js +0 -61
- package/dist/hearth/control-plane/ingress/webhook-delivery-store.d.ts +0 -41
- package/dist/hearth/control-plane/ingress/webhook-delivery-store.js +0 -69
- package/dist/hearth/control-plane/ingress/webhook-route.d.ts +0 -55
- package/dist/hearth/control-plane/ingress/webhook-route.js +0 -285
- package/dist/hearth/control-plane/main.d.ts +0 -1
- package/dist/hearth/control-plane/main.js +0 -88
- package/dist/hearth/control-plane/node-message.d.ts +0 -31
- package/dist/hearth/control-plane/node-message.js +0 -98
- package/dist/hearth/control-plane/register.d.ts +0 -15
- package/dist/hearth/control-plane/register.js +0 -34
- package/dist/hearth/control-plane/registry.d.ts +0 -22
- package/dist/hearth/control-plane/registry.js +0 -168
- package/dist/hearth/control-plane/relay.d.ts +0 -44
- package/dist/hearth/control-plane/relay.js +0 -711
- package/dist/hearth/control-plane/scheduler/fire-store.d.ts +0 -36
- package/dist/hearth/control-plane/scheduler/fire-store.js +0 -73
- package/dist/hearth/control-plane/scheduler/recurrence.d.ts +0 -7
- package/dist/hearth/control-plane/scheduler/recurrence.js +0 -58
- package/dist/hearth/control-plane/scheduler/scan-loop.d.ts +0 -38
- package/dist/hearth/control-plane/scheduler/scan-loop.js +0 -138
- package/dist/hearth/control-plane/scheduler/schedule-store.d.ts +0 -32
- package/dist/hearth/control-plane/scheduler/schedule-store.js +0 -66
- package/dist/hearth/control-plane/secrets.d.ts +0 -31
- package/dist/hearth/control-plane/secrets.js +0 -134
- package/dist/hearth/control-plane/server.d.ts +0 -27
- package/dist/hearth/control-plane/server.js +0 -482
- package/dist/hearth/control-plane/serving.d.ts +0 -15
- package/dist/hearth/control-plane/serving.js +0 -106
- package/dist/hearth/control-plane/session.d.ts +0 -68
- package/dist/hearth/control-plane/session.js +0 -273
- package/dist/hearth/control-plane/triggers/acl.d.ts +0 -14
- package/dist/hearth/control-plane/triggers/acl.js +0 -52
- package/dist/hearth/control-plane/triggers/audit-store.d.ts +0 -38
- package/dist/hearth/control-plane/triggers/audit-store.js +0 -79
- package/dist/hearth/control-plane/triggers/deliver.d.ts +0 -43
- package/dist/hearth/control-plane/triggers/deliver.js +0 -76
- package/dist/hearth/control-plane/triggers/envelope.d.ts +0 -29
- package/dist/hearth/control-plane/triggers/envelope.js +0 -38
- package/dist/hearth/control-plane/types.d.ts +0 -86
- package/dist/hearth/control-plane/types.js +0 -1
- package/dist/hearth/control-plane/wake.d.ts +0 -86
- package/dist/hearth/control-plane/wake.js +0 -550
- package/dist/web-client/assets/index-DwO46Cs5.css +0 -2
- /package/dist/{core/__tests__/hearth-bootstrap.test.d.ts → commands/__tests__/revive-now-gate.test.d.ts} +0 -0
- /package/dist/{core/hearth/__tests__/model-auth-guest.test.d.ts → commands/sys/__tests__/setup-core.test.d.ts} +0 -0
- /package/dist/core/{hearth/providers/__tests__/sweep-and-release.test.d.ts → __tests__/fault-classifier.test.d.ts} +0 -0
- /package/dist/core/{hearth/providers/types.js → __tests__/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/{hearth/types.js → __tests__/review-model-floor.test.d.ts} +0 -0
- /package/dist/{hearth/control-plane/__tests__/error-serialization.test.d.ts → core/__tests__/session-cycles.test.d.ts} +0 -0
- /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;
|