@north-light/cli 0.3.445 → 0.3.447
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/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/tree-cache.json +728 -0
- package/dist/clients/attach/viewer.js +560 -559
- package/dist/core/canvas/migrations.js +65 -31
- package/dist/core/graph/events.js +6 -6
- package/dist/core/graph/kinds/node.js +5 -5
- package/dist/core/graph/objects.js +8 -8
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/graph/receivers.d.ts +2 -1
- package/dist/core/graph/receivers.js +5 -5
- package/dist/core/graph/spaces/app.js +1 -1
- package/dist/core/graph/types.d.ts +9 -1
- package/dist/core/peer/ingest.d.ts +26 -0
- package/dist/core/peer/ingest.js +1 -0
- package/dist/core/peer/outbox-store.d.ts +70 -0
- package/dist/core/peer/outbox-store.js +4 -0
- package/dist/core/peer/receiver.d.ts +8 -0
- package/dist/core/peer/receiver.js +1 -0
- package/dist/core/peer/stubs.d.ts +51 -0
- package/dist/core/peer/stubs.js +1 -0
- package/dist/daemon/api/app-listener.js +9 -9
- package/dist/daemon/api/principal.d.ts +4 -4
- package/dist/daemon/api/principal.js +1 -1
- package/dist/daemon/api/sync-notice.d.ts +17 -0
- package/dist/daemon/api/sync-notice.js +1 -0
- package/dist/daemon/crtrd.js +8 -8
- package/dist/daemon/peer/handlers.d.ts +4 -1
- package/dist/daemon/peer/handlers.js +1 -1
- package/dist/daemon/peer/local-ingress.d.ts +8 -0
- package/dist/daemon/peer/local-ingress.js +1 -0
- package/dist/daemon/peer/outbox.d.ts +38 -0
- package/dist/daemon/peer/outbox.js +1 -0
- package/dist/daemon/peer/relay-client.d.ts +35 -0
- package/dist/daemon/peer/relay-client.js +1 -0
- package/dist/daemon/peer/relay-protocol.d.ts +52 -0
- package/dist/daemon/peer/relay-protocol.js +1 -0
- package/dist/daemon/peer/relay-service.d.ts +26 -0
- package/dist/daemon/peer/relay-service.js +1 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var f=Object.defineProperty;var i=(e,t)=>f(e,"name",{value:t,configurable:!0});import{prepared as o}from"../sqlite-statements.js";import{randomBytes as
|
|
2
|
-
AND state = 'pending' AND canceled_at IS NULL`).get(t).n;if(
|
|
1
|
+
var f=Object.defineProperty;var i=(e,t)=>f(e,"name",{value:t,configurable:!0});import{prepared as o}from"../sqlite-statements.js";import{randomBytes as p}from"node:crypto";import{nodeInbox as g}from"./kinds/node.js";import{peerReceiver as _}from"../peer/receiver.js";function N(e,t){const r=o(e,"SELECT receiver_json FROM custom_objects WHERE object = ?").get(t.rowid);if(!r?.receiver_json)return;const n=JSON.parse(r.receiver_json);if(n==="queue")return"queue";if(n&&typeof n=="object"&&!Array.isArray(n)){if("url"in n)return"url";if("argv"in n)return"argv"}}i(N,"receiverForObject");const E=1e3,a=new Map;function w(e,t){const r=a.get(e)??new Set;return r.add(t),a.set(e,r),()=>{r.delete(t),r.size===0&&a.delete(e)}}i(w,"onQueueSignal");function m(e){setImmediate(()=>{for(const t of a.get(e)??[])t()})}i(m,"signalQueue");function R(e,t){const r=o(e,`SELECT COUNT(*) AS n FROM deliveries WHERE destination_id = ?
|
|
2
|
+
AND state = 'pending' AND canceled_at IS NULL`).get(t).n;if(r<=E)return;const n=o(e,`SELECT delivery_id, message_id FROM deliveries
|
|
3
3
|
WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL
|
|
4
|
-
ORDER BY created_at, rowid LIMIT ?`).all(t,
|
|
5
|
-
(SELECT rowid FROM objects WHERE id = ?)`).run(
|
|
4
|
+
ORDER BY created_at, rowid LIMIT ?`).all(t,r-E);for(const s of n)o(e,"DELETE FROM deliveries WHERE delivery_id = ?").run(s.delivery_id),o(e,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(s.message_id,s.message_id);n.length&&o(e,`UPDATE custom_objects SET dropped = dropped + ? WHERE object =
|
|
5
|
+
(SELECT rowid FROM objects WHERE id = ?)`).run(n.length,t)}i(R,"cap");function c(e,t){return{bodyOwner:i(()=>null,"bodyOwner"),enqueue(r,n,s){const d=Date.now(),l=o(r,`INSERT INTO deliveries
|
|
6
6
|
(delivery_id, message_id, destination_id, urgency, wake, state, next_attempt_at, created_at, updated_at)
|
|
7
|
-
VALUES (?, ?, ?, 'normal', 0, 'pending', ?, ?, ?)`);for(const u of s)l.run(
|
|
7
|
+
VALUES (?, ?, ?, 'normal', 0, 'pending', ?, ?, ?)`);for(const u of s)l.run(p(16).toString("hex"),n.messageId,u.id,e?d:null,d,d),R(r,u.id),t&&m(u.id)}}}i(c,"pushReceiver");const v=c(!0,!1),x={inbox:g,argv:v,url:c(!0,!1),queue:c(!1,!0),peer:_};export{E as BACKLOG_CAP,x as RECEIVERS,v as argvReceiver,w as onQueueSignal,N as receiverForObject,m as signalQueue};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var g=Object.defineProperty;var s=(t,n)=>g(t,"name",{value:n,configurable:!0});import{covers as u}from"@north-light/identity";import{identityOf as c}from"./index.js";import{objectPlace as $,runPlace as p,runsSeeEachOther as a}from"../../runs/confinement.js";const j={identity:c,sync:"runtime",canRead(t,n,o,e,r){if(n.class==="person"||e.length===0)return!0;if(n.app.rowid===o.rowid)return m(t,n,e);const i=c(t,o).external_id,l=s(
|
|
1
|
+
var g=Object.defineProperty;var s=(t,n)=>g(t,"name",{value:n,configurable:!0});import{covers as u}from"@north-light/identity";import{identityOf as c}from"./index.js";import{objectPlace as $,runPlace as p,runsSeeEachOther as a}from"../../runs/confinement.js";const j={identity:c,sync:"runtime",canRead(t,n,o,e,r){if(n.class==="person"||e.length===0)return!0;if(n.app.rowid===o.rowid)return m(t,n,e);const i=c(t,o).external_id,l=s(w=>n.scopes!==null&&n.scopes.some(d=>u(d,w)),"held");return e[0].type==="node"?l(`crtr:objects:read:${i}`)||r.type==="document"&&r.kind==="report"&&l(`crtr:conversations:read:${i}`):l(y(t,i,"read",e))},canChange(t,n,o,e){if(n.class==="person")return!0;if(n.app.rowid===o.rowid)return e.length===0||m(t,n,e);if(e.length===0||e[0].type==="node")return!1;const r=y(t,c(t,o).external_id,"rw",e);return n.scopes!==null&&n.scopes.some(i=>u(i,r))}},f=new WeakMap;function m(t,n,o){if(n.class==="person"||o.length===0||o[0].type!=="node"||n.class==="app"&&n.place===void 0)return!0;let e=f.get(n);if(e===void 0&&(e=n.class==="app"?n.place:n.class==="node"?p(n.node.id,t):n.place??$(n.object.rowid,t),f.set(n,e)),e===null)return!0;const r=p(o[0].id,t);return r===null?!e.isolated:a(e,r)}s(m,"runConfined");function y(t,n,o,e){const r=e[0];return r.type!=="profile"?`crtr:memory:${o}:${n}`:`crtr:memory:${o}:${n}:profile:${c(t,r).external_id}`}s(y,"memoryScope");export{j as appSpace};
|
|
@@ -12,6 +12,8 @@ export interface ObjectRow {
|
|
|
12
12
|
preview: string;
|
|
13
13
|
created_at: string;
|
|
14
14
|
deleted_at: string | null;
|
|
15
|
+
/** Set on a stub (a remote object's local representative): the machine (`runtime:<id>`) that owns the real object. */
|
|
16
|
+
home_machine?: string | null;
|
|
15
17
|
}
|
|
16
18
|
export type Level = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
|
17
19
|
export type ListenDelivery = 'interrupt' | 'steer' | 'after-turn' | 'next-turn';
|
|
@@ -38,6 +40,8 @@ export interface CanvasEvent {
|
|
|
38
40
|
/** The message whose handling caused this publish, named by the publisher. Its depth + 1 is this
|
|
39
41
|
* event's depth and is bounded by HOP_LIMIT; omitted, the event starts a new chain at depth 0. */
|
|
40
42
|
cause?: string;
|
|
43
|
+
/** Set by `peer.events` ingestion: each delivery this publish creates carries the unique key `<key>:<destination>`, so a replay adds nothing. */
|
|
44
|
+
idempotencyKey?: string;
|
|
41
45
|
}
|
|
42
46
|
/** A repo and the directory it is checked out at. */
|
|
43
47
|
export interface Checkout {
|
|
@@ -69,7 +73,11 @@ export type Reader = {
|
|
|
69
73
|
profile: ObjectRow | null;
|
|
70
74
|
} | {
|
|
71
75
|
class: 'object';
|
|
72
|
-
object: ObjectRow; /**
|
|
76
|
+
object: ObjectRow; /** A stub reader's saved run place (from its envelope); run-confines it within its own app. */
|
|
77
|
+
place?: {
|
|
78
|
+
run: string;
|
|
79
|
+
isolated: boolean;
|
|
80
|
+
}; /** Identity alias for shared actor access; never a node principal. */
|
|
73
81
|
node: ObjectRow;
|
|
74
82
|
app: ObjectRow;
|
|
75
83
|
scopes: readonly string[] | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { ObjectRow } from '../graph/types.js';
|
|
3
|
+
import { type ClaimedRow, type PeerEventPayload } from './outbox-store.js';
|
|
4
|
+
/** Most events one `peer.events` call carries. */
|
|
5
|
+
export declare const MAX_EVENTS_PER_CALL = 100;
|
|
6
|
+
export interface IngestResult {
|
|
7
|
+
/** Keys applied (or already applied): the sender marks them done. */
|
|
8
|
+
accepted: string[];
|
|
9
|
+
/** Keys this machine will never apply (unknown event, an id that is another machine's): the sender gives up on them. */
|
|
10
|
+
rejected: Array<{
|
|
11
|
+
key: string;
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
/** The delivery key prefix of one sender's message; a delivery's full key adds `:<destination>`. */
|
|
17
|
+
export declare const ingestKey: (via: string, messageId: string) => string;
|
|
18
|
+
/** Apply a batch from machine `via`. One event's failure rejects only that event. Runs in the caller's write transaction. */
|
|
19
|
+
export declare function ingestPeerEvents(db: DatabaseSync, via: string, events: readonly PeerEventPayload[]): IngestResult;
|
|
20
|
+
/** Whether `object` is a stub of `machine` (a sender may only describe what it homes). */
|
|
21
|
+
export declare const isStubOf: (db: DatabaseSync, object: ObjectRow, machine: string) => boolean;
|
|
22
|
+
export declare const claimFor: (db: DatabaseSync, via: string, limit: number) => ClaimedRow[];
|
|
23
|
+
export declare const ackFor: (db: DatabaseSync, via: string, acks: ReadonlyArray<{
|
|
24
|
+
key: string;
|
|
25
|
+
claim_token: string;
|
|
26
|
+
}>) => string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var a=Object.defineProperty;var r=(e,t)=>a(e,"name",{value:t,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{CrtrError as f}from"../errors.js";import{publish as m}from"../graph/events.js";import{ackRows as E,claimRows as y}from"./outbox-store.js";import{ensureStub as u,stubOf as _}from"./stubs.js";const A=100,p=r((e,t)=>`peer:${e}:${t}`,"ingestKey");function l(e,t){return d(e,"SELECT 1 FROM peer_ingested WHERE key = ?").get(t)!==void 0?!0:d(e,"SELECT 1 FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ? LIMIT 1").get(`${t}:`,`${t};`)!==void 0}r(l,"applied");function g(e,t,o){return u(e,t.id,t.type,o,void 0,t)}r(g,"actorFor");function R(e,t,o){const s=u(e,o.object.id,o.object.type,t,void 0,o.object),c=o.actor===null?null:g(e,o.actor,t),n=o.body_text,i=o.body===void 0&&n===void 0?void 0:{...o.body??{},...n===void 0?{}:{body:n}};m(e,{event:o.event,object:s,actor:c,level:o.level,attrs:o.attrs,...i===void 0?{}:{messageBody:i},idempotencyKey:p(t,o.message_id)}),d(e,"INSERT OR IGNORE INTO peer_ingested (key, ingested_at) VALUES (?, ?)").run(p(t,o.message_id),Date.now())}r(R,"apply");function T(e,t,o){const s={accepted:[],rejected:[]};for(const c of o){if(l(e,p(t,c.message_id))){s.accepted.push(c.key);continue}e.exec("SAVEPOINT peer_event");try{R(e,t,c),e.exec("RELEASE peer_event"),s.accepted.push(c.key)}catch(n){e.exec("ROLLBACK TO peer_event"),e.exec("RELEASE peer_event");const i=n instanceof f?n.code:"invalid_event";s.rejected.push({key:c.key,code:i,message:n instanceof Error?n.message:String(n)})}}return s}r(T,"ingestPeerEvents");const j=r((e,t,o)=>_(e,t)?.home_machine===o,"isStubOf"),I=r((e,t,o)=>y(e,t,o),"claimFor"),N=r((e,t,o)=>E(e,t,o),"ackFor");export{A as MAX_EVENTS_PER_CALL,N as ackFor,I as claimFor,p as ingestKey,T as ingestPeerEvents,j as isStubOf};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { ObjectRef } from './stubs.js';
|
|
3
|
+
export type OutboxState = 'pending' | 'waiting' | 'claimed' | 'done' | 'expired';
|
|
4
|
+
/** What one event looks like on the wire, in both directions (`peer.events` body, `peer.outbox.claim` rows). */
|
|
5
|
+
export interface PeerEventPayload {
|
|
6
|
+
key: string;
|
|
7
|
+
/** The sending machine's message id: the receiver's idempotency identity for the event. */
|
|
8
|
+
message_id: string;
|
|
9
|
+
event: string;
|
|
10
|
+
level: number;
|
|
11
|
+
object: ObjectRef;
|
|
12
|
+
actor: ObjectRef | null;
|
|
13
|
+
attrs: Record<string, unknown>;
|
|
14
|
+
/** The message's own fields other than `push` (a report's kind, label and disposition; a notice's summary), minus `bodyRef`. */
|
|
15
|
+
body?: Record<string, unknown>;
|
|
16
|
+
/** The text of the report document the event points at, inlined because the receiver cannot read the sender's documents. */
|
|
17
|
+
body_text?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface OutboxRow {
|
|
20
|
+
id: number;
|
|
21
|
+
peer: string;
|
|
22
|
+
kind: string;
|
|
23
|
+
payload_json: string;
|
|
24
|
+
idempotency_key: string;
|
|
25
|
+
state: OutboxState;
|
|
26
|
+
attempts: number;
|
|
27
|
+
next_at: number | null;
|
|
28
|
+
claimed_by: string | null;
|
|
29
|
+
claimed_at: number | null;
|
|
30
|
+
first_failed_at: number | null;
|
|
31
|
+
created_at: number;
|
|
32
|
+
}
|
|
33
|
+
/** A claim not acked within this long is lost (the caller crashed or its answer never arrived) and may be taken again. */
|
|
34
|
+
export declare const CLAIM_LEASE_MS: number;
|
|
35
|
+
/** A row not delivered within this long is given up on. */
|
|
36
|
+
export declare const OUTBOX_MAX_AGE_MS: number;
|
|
37
|
+
/** Subscribe to enqueues (the delivery lane pushes a relay peer's row at once). Returns the unsubscribe. */
|
|
38
|
+
export declare function onPeerEnqueued(listener: (peer: string) => void): () => void;
|
|
39
|
+
/** Write one row; a key already present adds nothing. Returns whether a row was added. Runs in the caller's transaction. */
|
|
40
|
+
export declare function enqueuePeerRow(db: DatabaseSync, row: {
|
|
41
|
+
peer: string;
|
|
42
|
+
kind: string;
|
|
43
|
+
key: string;
|
|
44
|
+
payload: unknown;
|
|
45
|
+
}, now?: number): boolean;
|
|
46
|
+
/** Rows the lane may push now: pending and due. */
|
|
47
|
+
export declare function dueRows(db: DatabaseSync, now: number, limit?: number): OutboxRow[];
|
|
48
|
+
/** Pending rows to one peer that are new or due (a relay peer's push at enqueue; a row that already failed waits for its backoff). */
|
|
49
|
+
export declare function pendingFor(db: DatabaseSync, peer: string, now: number, limit?: number): OutboxRow[];
|
|
50
|
+
export declare const markDone: (db: DatabaseSync, id: number) => void;
|
|
51
|
+
export declare const markWaiting: (db: DatabaseSync, id: number, attempts: number) => void;
|
|
52
|
+
export declare const markRetry: (db: DatabaseSync, id: number, attempts: number, nextAt: number, firstFailedAt: number) => void;
|
|
53
|
+
export declare const markExpired: (db: DatabaseSync, id: number) => void;
|
|
54
|
+
export interface ClaimedRow {
|
|
55
|
+
key: string;
|
|
56
|
+
claim_token: string;
|
|
57
|
+
payload: PeerEventPayload;
|
|
58
|
+
}
|
|
59
|
+
/** The calling machine's own rows (`peer = via`, never another's), fenced with a fresh token. Takes pending and waiting rows
|
|
60
|
+
* and claims whose lease ran out; a row still under a live lease is left alone. */
|
|
61
|
+
export declare function claimRows(db: DatabaseSync, via: string, limit: number, now?: number): ClaimedRow[];
|
|
62
|
+
/** Mark done the rows the caller still holds: `peer = via`, the same token, still `claimed`. Returns the keys acked. */
|
|
63
|
+
export declare function ackRows(db: DatabaseSync, via: string, acks: ReadonlyArray<{
|
|
64
|
+
key: string;
|
|
65
|
+
claim_token: string;
|
|
66
|
+
}>): string[];
|
|
67
|
+
/** Retire rows done long ago, so the table stays small. */
|
|
68
|
+
export declare function pruneOutbox(db: DatabaseSync, now: number, keepMs?: number): void;
|
|
69
|
+
/** Give up on rows older than `OUTBOX_MAX_AGE_MS` that were never delivered. Returns how many. */
|
|
70
|
+
export declare function expireOld(db: DatabaseSync, now: number): number;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var c=Object.defineProperty;var a=(e,t)=>c(e,"name",{value:t,configurable:!0});import{prepared as r}from"../sqlite-statements.js";import{randomBytes as _}from"node:crypto";const x=10*6e4,m=720*60*6e4,d=new Set;function l(e){return d.add(e),()=>{d.delete(e)}}a(l,"onPeerEnqueued");function A(e,t,n=Date.now()){const o=r(e,`INSERT INTO peer_outbox (peer, kind, payload_json, idempotency_key, state, attempts, next_at, created_at)
|
|
2
|
+
VALUES (?, ?, ?, ?, 'pending', 0, ?, ?) ON CONFLICT (idempotency_key) DO NOTHING`).run(t.peer,t.kind,JSON.stringify(t.payload),t.key,n,n);return Number(o.changes)===0?!1:(setImmediate(()=>{for(const i of[...d])try{i(t.peer)}catch{}}),!0)}a(A,"enqueuePeerRow");function L(e,t,n=200){return r(e,"SELECT * FROM peer_outbox WHERE state = 'pending' AND next_at <= ? ORDER BY id LIMIT ?").all(t,n)}a(L,"dueRows");function T(e,t,n,o=50){return r(e,"SELECT * FROM peer_outbox WHERE state = 'pending' AND peer = ? AND (attempts = 0 OR next_at <= ?) ORDER BY id LIMIT ?").all(t,n,o)}a(T,"pendingFor");const y=a((e,t)=>{r(e,"UPDATE peer_outbox SET state = 'done', claimed_by = NULL, next_at = NULL WHERE id = ? AND state IN ('pending', 'waiting')").run(t)},"markDone"),g=a((e,t,n)=>{r(e,"UPDATE peer_outbox SET state = 'waiting', attempts = ?, next_at = NULL WHERE id = ? AND state = 'pending'").run(n,t)},"markWaiting"),O=a((e,t,n,o,i)=>{r(e,"UPDATE peer_outbox SET attempts = ?, next_at = ?, first_failed_at = ? WHERE id = ? AND state = 'pending'").run(n,o,i,t)},"markRetry"),S=a((e,t)=>{r(e,"UPDATE peer_outbox SET state = 'expired', next_at = NULL WHERE id = ? AND state IN ('pending', 'waiting')").run(t)},"markExpired");function b(e,t,n,o=Date.now()){const i=Math.max(1,Math.min(n,100)),E=r(e,`SELECT * FROM peer_outbox WHERE peer = ?
|
|
3
|
+
AND (state IN ('pending', 'waiting') OR (state = 'claimed' AND claimed_at <= ?)) ORDER BY id LIMIT ?`).all(t,o-x,i),s=_(16).toString("hex"),u=r(e,"UPDATE peer_outbox SET state = 'claimed', claimed_by = ?, claimed_at = ?, next_at = NULL WHERE id = ?");return E.map(p=>(u.run(s,o,p.id),{key:p.idempotency_key,claim_token:s,payload:JSON.parse(p.payload_json)}))}a(b,"claimRows");function f(e,t,n){const o=r(e,`UPDATE peer_outbox SET state = 'done', claimed_by = NULL WHERE idempotency_key = ? AND peer = ?
|
|
4
|
+
AND state = 'claimed' AND claimed_by = ?`);return n.filter(i=>Number(o.run(i.key,t,i.claim_token).changes)>0).map(i=>i.key)}a(f,"ackRows");function k(e,t,n=1440*6e4){r(e,"DELETE FROM peer_outbox WHERE state IN ('done', 'expired') AND created_at < ?").run(t-n)}a(k,"pruneOutbox");function I(e,t){return Number(r(e,"UPDATE peer_outbox SET state = 'expired', next_at = NULL WHERE state IN ('pending', 'waiting') AND created_at < ?").run(t-m).changes)}a(I,"expireOld");export{x as CLAIM_LEASE_MS,m as OUTBOX_MAX_AGE_MS,f as ackRows,b as claimRows,L as dueRows,A as enqueuePeerRow,I as expireOld,y as markDone,S as markExpired,O as markRetry,g as markWaiting,l as onPeerEnqueued,T as pendingFor,k as pruneOutbox};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { Receiver } from '../graph/receivers.js';
|
|
3
|
+
import { type PeerEventPayload } from './outbox-store.js';
|
|
4
|
+
/** The outbox key of one event to one destination stub. */
|
|
5
|
+
export declare const eventKey: (messageId: string, destination: string) => string;
|
|
6
|
+
/** The event as it crosses the wire: the object and actor by identity, the attributes, and the body inline. */
|
|
7
|
+
export declare function payloadFor(db: DatabaseSync, messageId: string, key: string): PeerEventPayload;
|
|
8
|
+
export declare const peerReceiver: Receiver;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var p=Object.defineProperty;var s=(o,t)=>p(o,"name",{value:t,configurable:!0});import{prepared as a}from"../sqlite-statements.js";import{heads as _}from"../graph/documents.js";import{bodyOf as y}from"../graph/bodies.js";import{getById as i}from"../graph/objects.js";import{enqueuePeerRow as b}from"./outbox-store.js";import{describeObject as f,stubOf as j}from"./stubs.js";const g=s((o,t)=>`ev:${o}:${t}`,"eventKey");function h(o,t){const d=typeof t?.pointer=="string"?t.pointer:void 0,e=d===void 0?null:i(o,d);if(e===null||e.type!=="document"||e.deleted_at!==null)return;const n=_(o,e)[0];if(n===void 0)return;const r=a(o,"SELECT source_path FROM documents WHERE object = ?").get(e.rowid);return y(n.body_hash,r?.source_path??null)}s(h,"reportText");function v(o,t,d){const e=a(o,"SELECT message_id, event, object_id, level, actor_id, body_ref, body_json FROM messages WHERE message_id = ?").get(t);if(e===void 0)throw new Error(`no message ${t}`);const n=i(o,e.object_id);if(n===null)throw new Error(`no object ${e.object_id}`);const r=e.actor_id===null?null:i(o,e.actor_id),m=e.body_json===null?{}:JSON.parse(e.body_json),{push:c,bodyRef:E,...u}=m,l=e.body_ref===null?void 0:h(o,c);return{key:d,message_id:e.message_id,event:e.event,level:e.level,object:f(o,n),actor:r===null?null:f(o,r),attrs:c?.attrs??{},...Object.keys(u).length>0?{body:u}:{},...l===void 0?{}:{body_text:l}}}s(v,"payloadFor");const k={bodyOwner:s(()=>null,"bodyOwner"),enqueue(o,t,d){for(const e of d){const n=j(o,e.id);if(n===null)continue;const r=g(t.messageId,e.id);b(o,{peer:n.home_machine,kind:"event",key:r,payload:v(o,t.messageId,r)})}}};export{g as eventKey,v as payloadFor,k as peerReceiver};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { ObjectRow } from '../graph/types.js';
|
|
3
|
+
import type { Envelope } from './envelope.js';
|
|
4
|
+
/** The part of an envelope a stub stores: who the remote watcher is, never what it may do. */
|
|
5
|
+
export interface StubIdentity {
|
|
6
|
+
sub: string;
|
|
7
|
+
/** `null` for a person. */
|
|
8
|
+
app: string | null;
|
|
9
|
+
node: string | null;
|
|
10
|
+
node_scopes: string[] | null;
|
|
11
|
+
place: {
|
|
12
|
+
run: string;
|
|
13
|
+
isolated: boolean;
|
|
14
|
+
} | null;
|
|
15
|
+
}
|
|
16
|
+
/** An owner as the sending machine names it, so the receiver can map it onto its own owner object. */
|
|
17
|
+
export interface OwnerRef {
|
|
18
|
+
id: string;
|
|
19
|
+
type: 'person' | 'app' | 'repo' | 'profile';
|
|
20
|
+
scheme: string | null;
|
|
21
|
+
external_id: string | null;
|
|
22
|
+
handle: string;
|
|
23
|
+
}
|
|
24
|
+
/** An object as it crosses the wire: identity only, never content. */
|
|
25
|
+
export interface ObjectRef {
|
|
26
|
+
id: string;
|
|
27
|
+
type: string;
|
|
28
|
+
kind: string | null;
|
|
29
|
+
name: string;
|
|
30
|
+
preview?: string;
|
|
31
|
+
owner: OwnerRef | null;
|
|
32
|
+
}
|
|
33
|
+
export interface StubRecord {
|
|
34
|
+
object: ObjectRow;
|
|
35
|
+
home_machine: string;
|
|
36
|
+
/** Absent on a stub that only stands for a watched object. */
|
|
37
|
+
identity: StubIdentity | null;
|
|
38
|
+
}
|
|
39
|
+
export declare const identityOfEnvelope: (envelope: Envelope) => StubIdentity;
|
|
40
|
+
export declare function stubOf(db: DatabaseSync, object: ObjectRow | string): StubRecord | null;
|
|
41
|
+
export declare const isStub: (db: DatabaseSync, object: ObjectRow) => boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The stub of the object `id` that lives on `machine`, created on first sight. Idempotent: a second
|
|
44
|
+
* call returns the same row and, given an `envelope`, replaces the identity it stores. Refuses
|
|
45
|
+
* (`conflict`) an id that is a real local object or a stub of another machine. The owner is the
|
|
46
|
+
* local app (or person) object of the remote owner, so the stub sits in a space the access rules
|
|
47
|
+
* understand. No `nodes` row is written.
|
|
48
|
+
*/
|
|
49
|
+
export declare function ensureStub(db: DatabaseSync, id: string, type: string, machine: string, envelope?: Envelope, ref?: ObjectRef): ObjectRow;
|
|
50
|
+
/** How `o` is described to another machine. */
|
|
51
|
+
export declare function describeObject(db: DatabaseSync, o: ObjectRow): ObjectRef;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var S=Object.defineProperty;var r=(n,e)=>S(n,"name",{value:e,configurable:!0});import{prepared as l}from"../sqlite-statements.js";import{createObject as R,ensureOwner as w,getById as f,getByRowid as m}from"../graph/objects.js";import{CrtrError as d}from"../errors.js";import{ExitCode as _}from"../../types.js";const j=r(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");function O(n,e){const t=typeof e=="string"?f(n,e):e;if(t===null)return null;const o=l(n,"SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(t.rowid);return o?{object:t,home_machine:o.home_machine,identity:o.envelope_json===null?null:JSON.parse(o.envelope_json)}:null}r(O,"stubOf");const H=r((n,e)=>l(n,"SELECT 1 FROM peer_stubs WHERE object = ?").get(e.rowid)!==void 0,"isStub");function E(n){return w(n,"person","local-user","user:local","user")}r(E,"localPerson");function h(n,e){return e==="user:local"?E(n):w(n,"app","app",e,e.replace(/^app:/,""))}r(h,"localApp");function T(n,e,t){if(e!==null)return e.app===null?E(n):h(n,e.app);const o=t?.owner;return o?.type==="app"?h(n,o.external_id??`app:${o.handle}`):E(n)}r(T,"ownerFor");function N(n,e,t,o,p,s){const i=p===void 0?null:j(p),u=f(n,e);if(u!==null){const a=O(n,u);if(a===null)throw new d("conflict",`${e} is a local ${u.type}, not a stub of ${o}`,_.GENERAL,{id:e});if(a.home_machine!==o)throw new d("conflict",`${e} is already a stub of ${a.home_machine}, not ${o}`,_.GENERAL,{id:e,home_machine:a.home_machine});return i!==null&&(l(n,"UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(i),u.rowid),l(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(u.rowid)),u}const y=T(n,i,s),c=R(n,{id:e,type:t,kind:s?.kind??null,owner:y.rowid,privacy:"public",name:s?.name??e,preview:s?.preview??""});return l(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(o,c.rowid),l(n,"INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(c.rowid,o,i===null?null:JSON.stringify(i),new Date().toISOString()),i!==null&&l(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(c.rowid),m(n,c.rowid)}r(N,"ensureStub");function W(n,e){const t=e.owner===null?null:m(n,e.owner);let o=null;if(t!==null&&(t.type==="person"||t.type==="app"||t.type==="repo"||t.type==="profile")){const p=l(n,"SELECT scheme, external_id FROM identities WHERE object = ?").get(t.rowid);o={id:t.id,type:t.type,scheme:p?.scheme??null,external_id:p?.external_id??null,handle:t.name}}return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:o}}r(W,"describeObject");export{W as describeObject,N as ensureStub,j as identityOfEnvelope,H as isStub,O as stubOf};
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
var X=Object.defineProperty;var s=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as $}from"node:crypto";import{createServer as
|
|
1
|
+
var X=Object.defineProperty;var s=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as $}from"node:crypto";import{createServer as F}from"node:http";import{Router as q}from"./router.js";import{OPERATIONS as G}from"./operations.js";import{errorCodes as L}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as Y,RUNTIME_VERSION_HEADER as z,minimumRuntimeRefusal as Q}from"../../api/runtime-version.js";import{CONTRACT_HEADER as W,CONTRACT_LEVEL as U,REQUIRED_CONTRACT_HEADER as K,contractRefusal as Z}from"../../api/contract.js";import{crtrVersion as M}from"../../shared/crtr-version.js";import{decodeJwt as ee}from"jose";import{AppPrincipalResolver as te}from"./principal.js";import{runtimeOwner as ne,servePeer as re}from"../peer/ingress.js";import{applySyncNotice as ae}from"./sync-notice.js";import{ownMachineId as oe}from"../../core/peer/directory.js";import{handleRunOperation as se,integer as ie}from"./handlers/runs.js";import{parseLlmRequest as ue,runLlm as ce}from"../../core/llm/run.js";import{parseUsageMonth as de,usageFor as le}from"../../core/usage/ledger.js";import{capFields as fe}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as me,MODEL_CONTEXT_BODY_MAX_BYTES as pe}from"../../core/llm/body-limits.js";import{RunError as d,runIsRunning as he}from"../../core/runs/operations.js";import{requireRun as H}from"./router.js";import{deferHead as _e}from"./deferred-head.js";import{readRunEvents as B,registerGrantStream as we,subscribeRunEvents as ye}from"../../core/runs/events.js";import{activeGrant as ve}from"../../core/grants/mirror.js";import{releaseGrantWaiters as ge}from"../../core/grants/watch.js";import{runNotices as Oe}from"../run-notices.js";import{handleVendorSignIn as Re,isVendorSignInPath as Ee}from"./vendor-signin.js";import{listAppProfiles as Se}from"./handlers/app-profiles.js";import{handleTransferDownload as Te,handleTransferOperation as Ae,TRANSFER_OPERATIONS as Ne}from"./handlers/transfer.js";import{handleQuestionOperation as be,QUESTION_OPERATIONS as Ce}from"./handlers/questions.js";import{vendorStatus as Ie}from"./handlers/vendor-status.js";import{handleMemoryOperation as Pe,MEMORY_OPERATIONS as Me}from"./handlers/memory.js";import{abortOnClose as He,callerFromApp as De,handleProviderOperation as ke,PROVIDER_OPERATIONS as xe,providerFailure as Je}from"./handlers/providers.js";function Le(e){return Object.prototype.hasOwnProperty.call(L,e)?L[e]:void 0}s(Le,"fixedRow");function Ue(e){const n=Le(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}s(Ue,"errorType");function Be(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...a}=e;return a}s(Be,"appDetails");function I(e,n,a){const u=Be(e.details);return{error:{code:e.code,message:e.message,...u===void 0?{}:{details:u},type:Ue(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...a?{run_id:a}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...fe(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}s(I,"envelope");function je(e,n,a,u){const i=n instanceof d?n:new d(500,"server_failure","runtime request failed");if(n instanceof d||console.error("[app-listener]",n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
|
|
2
2
|
data: ${JSON.stringify(I(i,a,u))}
|
|
3
3
|
|
|
4
|
-
`);return}e.writeHead(i.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":a,"X-Content-Type-Options":"nosniff",...i.code==="all_routes_exhausted"?{"Retry-After":String(i.details.retry_after_s)}:{}}),e.end(JSON.stringify(I(i,a,u)))}s(je,"fail");function j(e){return Array.isArray(e)?e[0]:e}s(j,"headerValue");function Ve(e){if(!e?.startsWith("Bearer ")||e.length<=7)return!1;try{return typeof ee(e.slice(7)).machine_id=="string"}catch{return!1}}s(Ve,"isMachineBearer");function v(e,n,a){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(a))}s(v,"json");async function C(e,n=
|
|
4
|
+
`);return}e.writeHead(i.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":a,"X-Content-Type-Options":"nosniff",...i.code==="all_routes_exhausted"?{"Retry-After":String(i.details.retry_after_s)}:{}}),e.end(JSON.stringify(I(i,a,u)))}s(je,"fail");function j(e){return Array.isArray(e)?e[0]:e}s(j,"headerValue");function Ve(e){if(!e?.startsWith("Bearer ")||e.length<=7)return!1;try{return typeof ee(e.slice(7)).machine_id=="string"}catch{return!1}}s(Ve,"isMachineBearer");function v(e,n,a){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(a))}s(v,"json");async function C(e,n=me){const a=[];let u=0;for await(const i of e){if(u+=i.length,u>n)throw new d(400,"invalid_request","request body too large");a.push(i)}if(u)try{return JSON.parse(Buffer.concat(a).toString("utf8"))}catch{throw new d(400,"invalid_request","invalid JSON body")}}s(C,"bodyOf");function Xe(e,n){const a=e.headers["last-event-id"]??n.get("after");if(Array.isArray(a))throw new d(400,"invalid_request","invalid cursor","after");return ie(a??null,"after",0,Number.MAX_SAFE_INTEGER,0)}s(Xe,"cursor");function $e(e){return`id: ${e.sequence_number}
|
|
5
5
|
event: ${e.type}
|
|
6
6
|
data: ${JSON.stringify(e)}
|
|
7
7
|
|
|
8
|
-
`}s($e,"frame");const
|
|
8
|
+
`}s($e,"frame");const Fe=6e4;function qe(e,n,a,u,i,w,g,A={}){H(u,a);let l=B(a,i);if(i>l.latest_sequence)throw new d(400,"invalid_request","cursor is beyond the run","after");if(i<l.latest_sequence&&(l.earliest_sequence===null||i<l.earliest_sequence-1))throw new d(409,"stream_gap","events have expired",void 0,{earliest_sequence:l.earliest_sequence,latest_sequence:l.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":g==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),g==="sse"?n.write(`: connected
|
|
9
9
|
|
|
10
|
-
`):n.flushHeaders();let E=i;const r=Oe().streamOpened(a);let t=!1,p=!1,_=!1,S;const y=new Promise(h=>{S=h}),f=s(()=>{t||(t=!0,r(E),clearInterval(T),clearInterval(x),
|
|
10
|
+
`):n.flushHeaders();let E=i;const r=Oe().streamOpened(a);let t=!1,p=!1,_=!1,S;const y=new Promise(h=>{S=h}),f=s(()=>{t||(t=!0,r(E),clearInterval(T),clearInterval(x),N(),R(),n.off("close",f),n.off("drain",O),n.end(),S())},"end"),O=s(()=>{_=!1,c()},"onDrain"),o=s(h=>{t||n.write(g==="sse"?`event: error
|
|
11
11
|
data: ${JSON.stringify(I(h,w,a))}
|
|
12
12
|
|
|
13
13
|
`:`${JSON.stringify({type:"error",...I(h,w,a)})}
|
|
14
|
-
`),f()},"error"),c=s(()=>{if(!(t||p)){p=!0;try{if(!m()){
|
|
15
|
-
`),E=h.sequence_number}}catch(h){o(h instanceof d?h:new d(500,"server_failure","stream failed"))}finally{p=!1}}},"flush"),
|
|
14
|
+
`),f()},"error"),c=s(()=>{if(!(t||p)){p=!0;try{if(!m()){b();return}if(H(u,a),l=B(a,E),l.backlog_count>1e3||l.backlog_bytes>32*1024*1024){o(new d(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:E}));return}for(const h of l.events){if(_)break;_=!n.write(g==="sse"?$e(h):`${JSON.stringify(h)}
|
|
15
|
+
`),E=h.sequence_number}}catch(h){o(h instanceof d?h:new d(500,"server_failure","stream failed"))}finally{p=!1}}},"flush"),b=s(()=>o(new d(401,"grant_revoked","grant is not active")),"revoked"),m=s(()=>"class"in u||ve(u.grant)!==null,"grantActive"),N=ye(a,c),R="class"in u?()=>{}:we(u.grant,b),T=setInterval(()=>{!t&&!_&&g==="sse"&&n.write(`: keepalive
|
|
16
16
|
|
|
17
|
-
`)},15e3),P=A.idleMs??
|
|
17
|
+
`)},15e3),P=A.idleMs??Fe,V=A.isRunning??he;let D=Date.now(),k=E;const x=setInterval(()=>{if(t||g!=="sse")return;const h=Date.now();let J=!0;try{J=V(a)}catch{}if(J||E!==k){D=h,k=E;return}h-D<P||_||(n.write(`event: idle
|
|
18
18
|
data: {}
|
|
19
19
|
|
|
20
|
-
`),f())},A.idleCheckMs??1e3);return x.unref(),n.on("drain",O),n.on("close",f),c(),y}s(
|
|
20
|
+
`),f())},A.idleCheckMs??1e3);return x.unref(),n.on("drain",O),n.on("close",f),c(),y}s(qe,"streamRunEvents");function At(e,n,a,u={}){const i=e.listener;if(!e.issuer||!e.audience||!i?.address||!Number.isInteger(i.port)||i.port<1||i.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(i.address))throw new Error("runtime listener must bind a specific address");const w=new te(e.issuer,e.audience,a,e.jwksUrl),g=new q,A=new Map;for(const r of G.filter(t=>t.stage!==void 0)){const t=s(()=>({status:500}),"handler");g.register({method:r.method,pattern:r.path,handler:t}),A.set(t,r)}const l=F((r,t)=>{(async()=>{_e(r,t,u.deferHead);const p=$();t.setHeader("Request-Id",p),t.setHeader(z,M()),t.setHeader(W,U);let _;try{const S=j(r.headers[Y.toLowerCase()]),y=Q(S,M())??Z(j(r.headers[K.toLowerCase()]),U,{legacyMinimum:S,runtimeVersion:M()});if(y!==null)throw y.status===400?new d(400,y.code,y.message,y.param):new d(426,y.code,y.message,void 0,y.details);const f=new URL(r.url??"/","http://runtime.local");if(Ee(f.pathname)){await Re(r,t,f,w);return}if(Ve(r.headers.authorization)){await re(r,t,{issuer:e.issuer,jwks:w.jwks,audience:s(()=>e.audience,"audience"),owner:ne,self:oe,ready:s(()=>w.isReady()&&n(),"ready")});return}if((r.method!=="GET"||f.pathname!=="/v1/health")&&(!w.isReady()||!n()))throw new d(503,"runtime_starting","runtime_starting");const O=g.match(r.method??"GET",f.pathname);if(!O)throw new d(404,"not_found","unknown operation");const o=A.get(O.handler);if(!o)throw new d(404,"not_found","unknown operation");if(_=O.params.run_id,o.name==="health"){v(t,200,{status:"ok",api_version:"2026-09-23"});return}if(o.name==="runtime.sync-notice"){await ae(await w.verifySyncNotice(r.headers.authorization),a,p),t.writeHead(204),t.end();return}if(o.name==="runtime.vendor-status"){await w.verifyDirectoryRead(r.headers.authorization,"vendor_status"),v(t,200,Ie());return}const c=await w.resolve(r.headers.authorization,o.scope);if(t.setHeader("Request-Id",p),_&&H(c,_,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(o.name),o.name==="run.delete"),o.stream==="bytes"){await Te(r,t,c,f.searchParams);return}if(o.stream){qe(r,t,_,c,Xe(r,f.searchParams),p,"sse",u.stream);return}if(o.name==="llm"){const m=ue(await C(r,pe)),N=new AbortController,R=s(()=>N.abort(),"abort");t.on("close",R);try{const T=await ce(m,N.signal,m.stream?P=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(P)}
|
|
21
21
|
|
|
22
22
|
`)}:void 0,void 0,{grantee:c.grantee,spend_cap:c.spend_cap});m.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
|
|
23
23
|
|
|
24
|
-
`)):v(t,200,T.completion)}finally{t.off("close",R)}return}if(o.name==="usage.get"){v(t,200,
|
|
24
|
+
`)):v(t,200,T.completion)}finally{t.off("close",R)}return}if(o.name==="usage.get"){v(t,200,le(c.grantee,c.spend_cap,de(f.searchParams.get("month"))));return}if(o.name==="profile.list"){v(t,200,Se(c));return}if(Ce.has(o.name)){const m=await be(o.name,c,O.params.question_id,o.method==="POST"?await C(r):void 0);v(t,m.status,m.body);return}if(xe.has(o.name)){const{signal:m,release:N}=He(t);try{const R=await ke(o.name,De(c),{params:O.params,body:o.method==="POST"?await C(r):void 0,query:f.searchParams},{requestId:p,signal:m});v(t,R.status,R.body)}catch(R){const T=Je(R,p,!0);t.writeHead(T.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...T.headers}),t.end(JSON.stringify(T.body))}finally{N()}return}if(Me.has(o.name)){const m=Pe(o.name,{class:"app",app:c.grantee,scopes:c.resolved},f.searchParams);v(t,m.status,m.body);return}if(Ne.has(o.name)){const m=await Ae(o,c,O.params,f.searchParams,await C(r));v(t,m.status,m.body);return}const b=await se(o,c,_,f.searchParams,await C(r),r.headers["idempotency-key"]);v(t,b.status,b.body)}catch(S){je(t,S,p,_)}})()});return{ready:new Promise((r,t)=>{l.once("error",t),l.listen(i.port,i.address,()=>{l.off("error",t),r()})}),close:s(async()=>{w.close(),ge(),l.closeAllConnections(),await new Promise((r,t)=>l.close(p=>p?t(p):r()))},"close")}}s(At,"createAppListener");export{Fe as RUN_STREAM_IDLE_MS,At as createAppListener,Xe as cursor,qe as streamRunEvents};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JwksCache, type Scope } from '@north-light/identity';
|
|
2
2
|
import { type ActiveGrant, type GrantDependencies, type GrantScope } from '../../core/grants/mirror.js';
|
|
3
|
+
import { type NoticeTicket } from './sync-notice.js';
|
|
3
4
|
export interface AppPrincipal {
|
|
4
5
|
sub: string;
|
|
5
6
|
grantee: string;
|
|
@@ -33,11 +34,10 @@ export declare class AppPrincipalResolver {
|
|
|
33
34
|
/** The directory key set, for the peer ingress's machine-token check. */
|
|
34
35
|
get jwks(): JwksCache;
|
|
35
36
|
close(): void;
|
|
36
|
-
|
|
37
|
-
private readonly notices;
|
|
37
|
+
private readonly ledger;
|
|
38
38
|
/** A directory sync notice authorizes one grant sync and nothing else.
|
|
39
|
-
* Returns
|
|
40
|
-
verifySyncNotice(authorization: string | undefined): Promise<
|
|
39
|
+
* Returns null for a replay of a notice already accepted. */
|
|
40
|
+
verifySyncNotice(authorization: string | undefined): Promise<NoticeTicket | null>;
|
|
41
41
|
/** Verify a directory `vendor_sign_in` handoff: ES256 signature against the
|
|
42
42
|
* JWKS mirror, `iss`, `aud`, `exp`, `purpose`, and that `jti` and `sub` are
|
|
43
43
|
* present (runtime spec §7 "Vendor sign-in page", RT-LLM-52). The owner and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var p=Object.defineProperty;var o=(a,e)=>p(a,"name",{value:e,configurable:!0});import{covers as m,JwksCache as _,MissingRevClaimError as b,verifyAccessToken as k
|
|
1
|
+
var p=Object.defineProperty;var o=(a,e)=>p(a,"name",{value:e,configurable:!0});import{covers as m,JwksCache as _,MissingRevClaimError as b,verifyAccessToken as k}from"@north-light/identity";import{createLocalJWKSet as g,decodeProtectedHeader as f,jwtVerify as w}from"jose";import{emitEvent as x}from"../../core/events/emit.js";import{activeGrant as G,grantSyncState as c,grantsGeneration as R}from"../../core/grants/mirror.js";import{RunError as n}from"../../core/runs/operations.js";import{NoticeLedger as S}from"./sync-notice.js";class A{static{o(this,"AppPrincipalResolver")}issuer;audience;syncGrants;cache;ready=!1;timer;constructor(e,r,t,i){this.issuer=e,this.audience=r,this.syncGrants=t,this.cache=new _(e,fetch,i),this.load(),this.timer=setInterval(()=>{this.ready||this.load()},5e3),this.timer.unref()}async load(){try{await this.cache.load(),this.ready=!0}catch{}}isReady(){return this.ready}get jwks(){return this.cache}close(){this.timer&&clearInterval(this.timer)}ledger=new S;async verifySyncNotice(e){if(!this.ready)throw new n(503,"runtime_starting","runtime_starting");return this.ledger.accept(e,this.cache,this.issuer,this.audience)}async verifyHandoff(e){if(!this.ready)throw new n(503,"runtime_starting","runtime_starting");const r=o(()=>new n(401,"unauthorized","invalid vendor sign-in handoff"),"refuse");if(!e)throw r();let t;try{const u=f(e);if(u.alg!=="ES256")throw r();const l=await this.cache.forKid(u.kid);({payload:t}=await w(e,g(l),{issuer:this.issuer,audience:this.audience,algorithms:["ES256"],requiredClaims:["exp","jti","sub"]}))}catch{throw r()}const{sub:i,jti:s,exp:h,purpose:d,aud:y}=t;if(d!=="vendor_sign_in"||y!==this.audience||typeof i!="string"||typeof s!="string"||s===""||typeof h!="number")throw r();return{sub:i,jti:s,exp:h}}async verifyDirectoryRead(e,r){if(!this.ready)throw new n(503,"runtime_starting","runtime_starting");const t=o(()=>new n(401,"unauthorized","invalid directory token"),"refuse");if(!e?.startsWith("Bearer ")||e.length<=7)throw t();const i=e.slice(7);let s;try{const h=f(i);if(h.alg!=="ES256")throw t();const d=await this.cache.forKid(h.kid);({payload:s}=await w(i,g(d),{issuer:this.issuer,audience:this.audience,algorithms:["ES256"],requiredClaims:["exp"]}))}catch{throw t()}if(s.purpose!==r||s.aud!==this.audience)throw t()}behind=new Map;behindGeneration=-1;behindRev(e){const r=R();return r!==this.behindGeneration&&(this.behind=new Map,this.behindGeneration=r),this.behind.get(e)}async ensureCovers(e){if(v(c(e.grant),e.rev))return;const r=this.behindRev(e.grant);if(r===void 0||e.rev>r){try{await this.syncGrants({fresh:!0})}catch{throw new n(503,"service_unavailable","grant sync failed")}const t=c(e.grant);if(v(t,e.rev))return;this.behindRev(e.grant),this.behind.set(e.grant,Math.max(e.rev,this.behind.get(e.grant)??0)),x({level:"error",event:"daemon.grants.behind_token",fields:{grant:e.grant,token_rev:e.rev,mirror_rev:t?.rev??0}})}throw c(e.grant)?new n(503,"service_unavailable","the directory answered grant sync below the access token's grant revision"):new n(401,"grant_revoked","grant is not active")}async resolve(e,r){if(!this.ready)throw new n(503,"runtime_starting","runtime_starting");if(!e?.startsWith("Bearer ")||e.length<=7)throw new n(401,"unauthorized","a directory access token is required");let t;try{t=await k({token:e.slice(7),jwks:this.cache,issuer:this.issuer,audience:this.audience})}catch(s){throw s instanceof b?new n(401,"unauthorized",s.message):new n(401,"unauthorized","invalid directory access token")}await this.ensureCovers(t);const i=E(t);if(r&&!i.resolved.some(s=>m(s,r)))throw new n(403,"scope_missing",`missing scope ${r}`,void 0,void 0,r);return{sub:t.sub,grantee:t.grantee,client_id:t.client_id,grant:t.grant,rev:i.rev,resolved:i.resolved,resolved_scopes:i.resolved_scopes,spend_cap:i.spend_cap,dependencies:i.dependencies??{},exp:t.exp}}}function E(a){const e=G(a.grant);if(!e||e.sub!==a.sub||e.grantee!==a.grantee||e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now()))throw new n(401,"grant_revoked","grant is not active");return e}o(E,"callerGrant");function v(a,e){return a!==null&&a.rev>=e}o(v,"covered");export{A as AppPrincipalResolver,E as callerGrant};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type JwksCache } from '@north-light/identity';
|
|
2
|
+
/** An accepted notice whose sync has not yet finished. `done(true)` records it as applied; `done(false)` releases it so the directory's retry syncs. */
|
|
3
|
+
export interface NoticeTicket {
|
|
4
|
+
jti: string;
|
|
5
|
+
done(applied: boolean): void;
|
|
6
|
+
}
|
|
7
|
+
/** jti → exp of every notice applied (or being applied) within its lifetime. */
|
|
8
|
+
export declare class NoticeLedger {
|
|
9
|
+
private readonly seen;
|
|
10
|
+
/** Verify the bearer as a sync notice for `audience`. Null for a replay of a notice already applied or being applied. Throws `RunError` 401 when invalid. */
|
|
11
|
+
accept(authorization: string | undefined, jwks: JwksCache, issuer: string, audience: string): Promise<NoticeTicket | null>;
|
|
12
|
+
}
|
|
13
|
+
/** Apply an accepted notice: pull grants fresh and answer only after the sync commits (a 2xx tells the directory the change is applied).
|
|
14
|
+
* A replay (`ticket` null) triggers no second sync. Throws `RunError` 503 when the sync fails, so the directory retries. */
|
|
15
|
+
export declare function applySyncNotice(ticket: NoticeTicket | null, syncGrants: (options?: {
|
|
16
|
+
fresh?: boolean;
|
|
17
|
+
}) => Promise<void>, requestId: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var d=Object.defineProperty;var r=(t,e)=>d(t,"name",{value:e,configurable:!0});import{verifySyncNotice as h}from"@north-light/identity";import{emitEvent as u}from"../../core/events/emit.js";import{RunError as s}from"../../core/runs/operations.js";class v{static{r(this,"NoticeLedger")}seen=new Map;async accept(e,i,c,a){if(!e?.startsWith("Bearer ")||e.length<=7)throw new s(401,"unauthorized","a directory sync notice token is required");let n;try{n=await h({token:e.slice(7),jwks:i,issuer:c,audience:a})}catch{throw new s(401,"unauthorized","invalid directory sync notice token")}const l=Math.floor(Date.now()/1e3);for(const[o,f]of this.seen)f+30<l&&this.seen.delete(o);return this.seen.has(n.jti)?null:(this.seen.set(n.jti,n.exp),{jti:n.jti,done:r(o=>{o||this.seen.delete(n.jti)},"done")})}}async function j(t,e,i){if(t!==null){u({level:"info",event:"daemon.grants.sync_notice",fields:{request_id:i}});try{await e({fresh:!0})}catch{throw t.done(!1),new s(503,"service_unavailable","grant sync failed")}t.done(!0)}}r(j,"applySyncNotice");export{v as NoticeLedger,j as applySyncNotice};
|
package/dist/daemon/crtrd.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
var Se=Object.defineProperty;var s=(t,n)=>Se(t,"name",{value:n,configurable:!0});import{reportMachineStatus as _e}from"../core/peer/directory.js";import{writeFileSync as be,readFileSync as ke,rmSync as Ee,mkdirSync as Y,existsSync as k,chmodSync as Re}from"node:fs";import{createServer as X}from"node:http";import{dirname as Ie}from"node:path";import{envCrtrdTcp as xe}from"../shared/env.js";import{serveCanvasConversationStore as Pe}from"../core/canvas/sessions.js";import{pollRunObservers as Te}from"./runs/observer.js";import{randomBytes as De}from"node:crypto";import{apiSocketPath as Ce,crtrHome as Le}from"../core/canvas/paths.js";import{activeAppUsers as $e}from"../core/spaces/users.js";import{ensureAppSpace as Ae,ensureUserFiles as Ne}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as R}from"../launcher/spec.js";import{launcherRequest as P}from"../launcher/client.js";import{installLauncherEventSink as Me}from"./launcher-events.js";import{initializeEmptyLayout as Ge,layoutNeedsMigration as Oe,socketDir as Z}from"../core/layout.js";import{migrateLooseKeys as Fe}from"../core/layout-migrate/loose-keys.js";import{getNode as He,listNodesForSupervision as Be,setFrozen as qe}from"../core/canvas/index.js";import{createApiServer as je}from"./api/server.js";import{createAppListener as ze}from"./api/app-listener.js";import{installLocalTerminalGrant as We}from"../core/grants/local.js";import{onRunEventAppended as Ue}from"../core/runs/events.js";import{runNotices as Ke}from"./run-notices.js";import{createGrantSync as Je,GRANT_SYNC_INTERVAL_S as Ve}from"../core/grants/sync.js";import{runRemovalCascades as Qe}from"../core/grants/remove.js";import{sweepSandboxes as Ye}from"../core/grants/on-sync.js";import{rebuildCronExecutions as Xe}from"./cron/run-socket.js";import{hasSyncedGrants as Ze}from"../core/grants/mirror.js";import{readConfig as er}from"../core/config.js";import{reconcileUndeliveredTickets as rr}from"./human/sweep.js";import{resetHandedOffDeliveries as tr}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as nr}from"../core/canvas/daemon-actions.js";import{sweepReviews as or}from"./review/sweep.js";import{pidfilePath as G}from"./pidfile.js";import{rendererWarning as ir}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as sr}from"./supervise-cadence.js";import{createResumeDetector as ar}from"./resume-detector.js";import{BrokerSupervisionReconciler as cr}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as dr}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as lr}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as pr}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as mr}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as fr}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as ur}from"./reconcilers/action-lane.js";import{endLiveExecution as hr,migrateLegacyExecutionIdentities as vr}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as wr}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as gr}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as yr}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as Sr}from"./reconcilers/storage-due-work.js";import{BashDeadlineReconciler as _r}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as br}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as kr}from"./reconcilers/structured-request-lane.js";import{reconcileStructuredRequestSidecars as Er}from"./structured-requests/boot.js";import{captureLivenessSnapshot as ee,capturePidCommand as Rr,captureTeardownSnapshot as re,isPidAlive as Ir,isAnyPidAlive as te,killProcessTreePids as ne}from"../core/canvas/pid.js";import{bindFleet as xr,boundFleet as Pr}from"../core/runtime/fleet.js";import{bindDaemonControl as Tr,clearDaemonControl as Dr}from"./control.js";import{execDaemon as Cr,spawnDaemon as Lr}from"./manage.js";import{reconcileAndMigrateBoot as $r}from"../core/canvas/boot.js";import{resolveInstallId as Ar}from"../core/canvas/install-id.js";import{setInstallId as Nr}from"../core/runtime/nodes.js";import{claimDaemonOwnership as Mr,releaseDaemonOwnership as oe}from"../core/canvas/daemon-owner.js";import{migrate as Gr,openDb as ie,withCanvasWrite as Or,bindDaemonCanvasOpenHook as Fr,clearDaemonCanvasOpenHook as se}from"../core/canvas/db.js";import{brokerSignalBus as O}from"./broker-signals/bus.js";import{brokerSignalStreams as Hr}from"./api/handlers/broker-signals.js";import{reconcilePackageDocuments as Br}from"../core/graph/package-docs.js";import{startRepoSync as qr}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as jr,reapStaleSpares as zr}from"../core/runtime/warm-pool.js";import{DaemonFleet as Wr}from"./fleet.js";import{BOOT_MIGRATIONS as Ur}from"./boot-migrations.js";import{applyJoinedOwner as Kr}from"./joined-owner.js";import{emitEvent as u}from"../core/events/emit.js";import{operationIdContext as _}from"../core/events/operation-id.js";import{bindDaemonEventSource as Jr}from"../core/events/source.js";import{activateCanvasImport as Vr,MigrationBlockedError as Qr,runForcedCanvasImport as Yr}from"../migrations/activation.js";import{convertLegacyProviderStore as Xr,convertLegacyScopedStore as Zr}from"../core/scoped-state/migrate.js";import{userScopeRoot as et}from"../core/scope.js";import{secretsDir as rt}from"../core/layout.js";function F(t,n){return t.includes(` --canvas-home ${n} `)}s(F,"brokerCommandDeclaresCanvasHome");function H(t){return/ --epoch (\S+) /.exec(t)?.[1]??null}s(H,"brokerCommandEpoch");const tt=5e3,ae=150;async function nt(t){const n=ee();if(n===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness"}}),new Error("prior-epoch broker census could not capture the process table");const a=Le(),m=[];for(const[i,p]of n.processes){const y=p.command;if(p.zombie||!y.includes("broker-cli.js")||!F(y,a)||H(y)===t)continue;const S=y.trim().split(/\s+/).at(-1)??"";S===""||S.endsWith("broker-cli.js")||m.push({pid:i,nodeId:S})}let f=0;const o=[];for(const i of m){const p=Rr(i.pid);if(p===null||!p.includes("broker-cli.js")||!F(p,a)||H(p)===t||p.trim().split(/\s+/).at(-1)!==i.nodeId)continue;const y=re(i.pid,null);if(!y.reused){try{process.platform==="linux"&&k(R)?await P({operation:"stop",pid:i.pid}):process.kill(i.pid,"SIGTERM")}catch{continue}f++,o.push({pid:i.pid,snap:y})}}const w=Date.now()+tt;for(;Date.now()<w&&o.some(i=>te(i.snap.tree.length>0?i.snap.tree:[i.pid]));)await new Promise(i=>setTimeout(i,ae));let v=0;for(const i of o){const p=i.snap.tree.length>0?i.snap.tree:[i.pid];te(p)&&(process.platform==="linux"&&k(R)?await P({operation:"stop",pid:i.pid}):ne(p,"SIGKILL",i.snap.identities),v++)}v>0&&await new Promise(i=>setTimeout(i,ae));const d=ee();if(d===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness",stage:"final"}}),new Error("prior-epoch broker census could not capture the final process table");const l=[];for(const[i,p]of d.processes){const y=p.command;p.zombie||!y.includes("broker-cli.js")||F(y,a)&&H(y)!==t&&l.push(i)}if(l.length>0)throw new Error(`prior-epoch broker census could not prove termination of pid(s): ${l.join(", ")}`);u({level:"info",event:"daemon.census.completed",fields:{prior_epoch_brokers:m.length,sigtermed:f,sigkilled:v}})}s(nt,"reapPriorEpochBrokers");const ot=5e3;async function it(t){const n=Array.from(t.entries());if(n.length===0)return;for(const[o]of n){const w=He(o)?.status;(w==="active"||w==="idle")&&qe(o,new Date().toISOString())}const a=n.map(([,o])=>({pid:o.pid,snap:re(o.pid,null),exited:o.handle.exited}));for(const o of a)if(!o.snap.reused)try{process.platform==="linux"&&k(R)?await P({operation:"stop",pid:o.pid}):process.kill(o.pid,"SIGTERM")}catch{}const m=new Promise(o=>{setTimeout(o,ot).unref()});await Promise.allSettled(a.map(o=>Promise.race([o.exited.then(()=>{}),m])));const f=[];for(const o of a)Ir(o.pid)&&(process.platform==="linux"&&k(R)?await P({operation:"stop",pid:o.pid}):ne(o.snap.tree.length>0?o.snap.tree:[o.pid],"SIGKILL",o.snap.identities),f.push(o));await Promise.all(f.map(o=>o.exited));for(const[o]of n)hr(o,{reason:"daemon-teardown"});u({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:a.length,sigkilled:f.length}})}s(it,"teardownFleet");function ce(){Y(Ie(G()),{recursive:!0}),be(G(),String(process.pid),"utf8")}s(ce,"writePidfile");function de(t){return typeof t=="object"&&t!==null&&t.code==="ENOENT"}s(de,"isMissingPathError");function le(t){const n=G();let a;try{a=ke(n,"utf8")}catch(m){if(de(m))return;throw m}if(a.trim()!==String(t))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${t})`);try{Ee(n)}catch(m){if(de(m))return;throw m}}s(le,"removeOwnedPidfileMirror");const st={acceptsDetachedWork:s(()=>!0,"acceptsDetachedWork"),registerDetached:s(t=>{t.catch(()=>{})},"registerDetached")},B=new cr,at=new dr,ct=new lr,dt=new pr,lt=new mr,pt=new fr,mt=new ur,ft=new gr,ut=new yr,ht=new Sr,vt=new _r,wt=new br,gt=new kr;async function yt(t=Date.now(),n=st){const a=Pr(),m=await B.runFleetPreamble(a);let f,o;try{const v=Be();f=v.filter(d=>d.status==="active"||d.status==="idle"),o=v.filter(d=>d.status==="done"||d.status==="canceled"||d.status==="dead")}catch(v){u({level:"error",event:"canvas.node_list.failed",error:v});return}await B.run(t,{rows:f,fleet:a,lifecycle:n}),at.run(t,{rows:f});const w=await ct.run(t,{rows:[...f,...o],fleet:a});dt.run(t),gt.run(t),lt.run(t,{lifecycle:n}),pt.run(t,{lifecycle:n}),mt.run(t,{lifecycle:n}),ft.run(t,{lifecycle:n}),ut.run(t,{lifecycle:n,fleet:a}),ht.run(t,{lifecycle:n}),Te(),wt.run(t,{fleet:a,lifecycle:n}),await vt.run(t,{rows:f}),B.warnIfSaturated({fleet:a,lifecycle:n,thresholds:m,frozen:w})}s(yt,"superviseTick");async function St(t){Y(Z(),{recursive:!0,mode:493}),process.platform==="linux"&&Re(Z(),493);const n=Ce();if(k(n))throw new Error(`layout refusal cannot bind occupied socket: ${n}`);const a=X((d,l)=>{if(d.method==="GET"&&d.url==="/healthz"){l.writeHead(200,{"content-type":"application/json; charset=utf-8"}),l.end(JSON.stringify({ok:!1,daemon_up:!0,store_accessible:!1,brokers_reconciled:!1,startup_phase:"blocked",startup_blocked:"layout_migration_required"}));return}l.writeHead(503,{"content-type":"application/json; charset=utf-8"}),l.end(JSON.stringify({error:{code:"layout_migration_required",message:"run crtr sys migrate while the runtime is offline",type:"server_error",origin:"runtime",retryable:!1}}))});await new Promise((d,l)=>{a.once("error",l),a.listen(n,()=>{a.off("error",l),d()})});let m;const f=t.tcp??xe();if(f){const d=/^(.*):(\d+)$/.exec(f);if(!d||Number(d[2])>65535)throw new Error(`invalid refusal TCP address: ${f}`);const l=X(a.listeners("request")[0]);await new Promise((i,p)=>{l.once("error",p),l.listen(Number(d[2]),d[1],()=>{l.off("error",p),i()})}),m=l}ce();let o;const w=s(()=>(o??=(async()=>{a.closeAllConnections(),m?.closeAllConnections(),await Promise.all([a,m].filter(d=>d!==void 0).map(d=>new Promise((l,i)=>d.close(p=>p?i(p):l())))),le(process.pid),process.off("SIGINT",v),process.off("SIGTERM",v)})(),o),"cleanup"),v=s(()=>{w().then(()=>process.exit(0),d=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(d)}
|
|
2
|
-
`),process.exit(1)})},"onSignal");return process.on("SIGINT",v),process.on("SIGTERM",v),
|
|
1
|
+
var _e=Object.defineProperty;var s=(t,n)=>_e(t,"name",{value:n,configurable:!0});import{startPeerOutbox as ke}from"./peer/outbox.js";import{reportMachineStatus as Ee}from"../core/peer/directory.js";import{createPeerRelay as Re}from"./peer/relay-service.js";import{writeFileSync as xe,readFileSync as Pe,rmSync as Ie,mkdirSync as Z,existsSync as k,chmodSync as Te}from"node:fs";import{createServer as ee}from"node:http";import{dirname as De}from"node:path";import{envCrtrdTcp as Ce}from"../shared/env.js";import{serveCanvasConversationStore as Le}from"../core/canvas/sessions.js";import{pollRunObservers as $e}from"./runs/observer.js";import{randomBytes as Ae}from"node:crypto";import{apiSocketPath as Ne,crtrHome as Me}from"../core/canvas/paths.js";import{activeAppUsers as Oe}from"../core/spaces/users.js";import{ensureAppSpace as Ge,ensureUserFiles as Fe}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as x}from"../launcher/spec.js";import{launcherRequest as I}from"../launcher/client.js";import{installLauncherEventSink as He}from"./launcher-events.js";import{initializeEmptyLayout as Be,layoutNeedsMigration as je,socketDir as re}from"../core/layout.js";import{migrateLooseKeys as qe}from"../core/layout-migrate/loose-keys.js";import{getNode as ze,listNodesForSupervision as We,setFrozen as Ue}from"../core/canvas/index.js";import{createApiServer as Ke}from"./api/server.js";import{createAppListener as Je}from"./api/app-listener.js";import{installLocalTerminalGrant as Ve}from"../core/grants/local.js";import{onRunEventAppended as Qe}from"../core/runs/events.js";import{runNotices as Ye}from"./run-notices.js";import{createGrantSync as Xe,GRANT_SYNC_INTERVAL_S as Ze}from"../core/grants/sync.js";import{runRemovalCascades as er}from"../core/grants/remove.js";import{sweepSandboxes as rr}from"../core/grants/on-sync.js";import{rebuildCronExecutions as tr}from"./cron/run-socket.js";import{hasSyncedGrants as nr}from"../core/grants/mirror.js";import{readConfig as or}from"../core/config.js";import{reconcileUndeliveredTickets as ir}from"./human/sweep.js";import{resetHandedOffDeliveries as sr}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as ar}from"../core/canvas/daemon-actions.js";import{sweepReviews as cr}from"./review/sweep.js";import{pidfilePath as F}from"./pidfile.js";import{rendererWarning as dr}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as lr}from"./supervise-cadence.js";import{createResumeDetector as pr}from"./resume-detector.js";import{BrokerSupervisionReconciler as mr}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as fr}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as ur}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as hr}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as vr}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as wr}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as gr}from"./reconcilers/action-lane.js";import{endLiveExecution as yr,migrateLegacyExecutionIdentities as Sr}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as br}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as _r}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as kr}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as Er}from"./reconcilers/storage-due-work.js";import{BashDeadlineReconciler as Rr}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as xr}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as Pr}from"./reconcilers/structured-request-lane.js";import{reconcileStructuredRequestSidecars as Ir}from"./structured-requests/boot.js";import{captureLivenessSnapshot as te,capturePidCommand as Tr,captureTeardownSnapshot as ne,isPidAlive as Dr,isAnyPidAlive as oe,killProcessTreePids as ie}from"../core/canvas/pid.js";import{bindFleet as Cr,boundFleet as Lr}from"../core/runtime/fleet.js";import{bindDaemonControl as $r,clearDaemonControl as Ar}from"./control.js";import{execDaemon as Nr,spawnDaemon as Mr}from"./manage.js";import{reconcileAndMigrateBoot as Or}from"../core/canvas/boot.js";import{resolveInstallId as Gr}from"../core/canvas/install-id.js";import{setInstallId as Fr}from"../core/runtime/nodes.js";import{claimDaemonOwnership as Hr,releaseDaemonOwnership as se}from"../core/canvas/daemon-owner.js";import{migrate as Br,openDb as ae,withCanvasWrite as jr,bindDaemonCanvasOpenHook as qr,clearDaemonCanvasOpenHook as ce}from"../core/canvas/db.js";import{brokerSignalBus as H}from"./broker-signals/bus.js";import{brokerSignalStreams as zr}from"./api/handlers/broker-signals.js";import{reconcilePackageDocuments as Wr}from"../core/graph/package-docs.js";import{startRepoSync as Ur}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as Kr,reapStaleSpares as Jr}from"../core/runtime/warm-pool.js";import{DaemonFleet as Vr}from"./fleet.js";import{BOOT_MIGRATIONS as Qr}from"./boot-migrations.js";import{applyJoinedOwner as Yr}from"./joined-owner.js";import{emitEvent as u}from"../core/events/emit.js";import{operationIdContext as b}from"../core/events/operation-id.js";import{bindDaemonEventSource as Xr}from"../core/events/source.js";import{activateCanvasImport as Zr,MigrationBlockedError as et,runForcedCanvasImport as rt}from"../migrations/activation.js";import{convertLegacyProviderStore as tt,convertLegacyScopedStore as nt}from"../core/scoped-state/migrate.js";import{userScopeRoot as ot}from"../core/scope.js";import{secretsDir as it}from"../core/layout.js";function B(t,n){return t.includes(` --canvas-home ${n} `)}s(B,"brokerCommandDeclaresCanvasHome");function j(t){return/ --epoch (\S+) /.exec(t)?.[1]??null}s(j,"brokerCommandEpoch");const st=5e3,de=150;async function at(t){const n=te();if(n===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness"}}),new Error("prior-epoch broker census could not capture the process table");const a=Me(),m=[];for(const[i,p]of n.processes){const y=p.command;if(p.zombie||!y.includes("broker-cli.js")||!B(y,a)||j(y)===t)continue;const S=y.trim().split(/\s+/).at(-1)??"";S===""||S.endsWith("broker-cli.js")||m.push({pid:i,nodeId:S})}let f=0;const o=[];for(const i of m){const p=Tr(i.pid);if(p===null||!p.includes("broker-cli.js")||!B(p,a)||j(p)===t||p.trim().split(/\s+/).at(-1)!==i.nodeId)continue;const y=ne(i.pid,null);if(!y.reused){try{process.platform==="linux"&&k(x)?await I({operation:"stop",pid:i.pid}):process.kill(i.pid,"SIGTERM")}catch{continue}f++,o.push({pid:i.pid,snap:y})}}const g=Date.now()+st;for(;Date.now()<g&&o.some(i=>oe(i.snap.tree.length>0?i.snap.tree:[i.pid]));)await new Promise(i=>setTimeout(i,de));let v=0;for(const i of o){const p=i.snap.tree.length>0?i.snap.tree:[i.pid];oe(p)&&(process.platform==="linux"&&k(x)?await I({operation:"stop",pid:i.pid}):ie(p,"SIGKILL",i.snap.identities),v++)}v>0&&await new Promise(i=>setTimeout(i,de));const d=te();if(d===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness",stage:"final"}}),new Error("prior-epoch broker census could not capture the final process table");const l=[];for(const[i,p]of d.processes){const y=p.command;p.zombie||!y.includes("broker-cli.js")||B(y,a)&&j(y)!==t&&l.push(i)}if(l.length>0)throw new Error(`prior-epoch broker census could not prove termination of pid(s): ${l.join(", ")}`);u({level:"info",event:"daemon.census.completed",fields:{prior_epoch_brokers:m.length,sigtermed:f,sigkilled:v}})}s(at,"reapPriorEpochBrokers");const ct=5e3;async function dt(t){const n=Array.from(t.entries());if(n.length===0)return;for(const[o]of n){const g=ze(o)?.status;(g==="active"||g==="idle")&&Ue(o,new Date().toISOString())}const a=n.map(([,o])=>({pid:o.pid,snap:ne(o.pid,null),exited:o.handle.exited}));for(const o of a)if(!o.snap.reused)try{process.platform==="linux"&&k(x)?await I({operation:"stop",pid:o.pid}):process.kill(o.pid,"SIGTERM")}catch{}const m=new Promise(o=>{setTimeout(o,ct).unref()});await Promise.allSettled(a.map(o=>Promise.race([o.exited.then(()=>{}),m])));const f=[];for(const o of a)Dr(o.pid)&&(process.platform==="linux"&&k(x)?await I({operation:"stop",pid:o.pid}):ie(o.snap.tree.length>0?o.snap.tree:[o.pid],"SIGKILL",o.snap.identities),f.push(o));await Promise.all(f.map(o=>o.exited));for(const[o]of n)yr(o,{reason:"daemon-teardown"});u({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:a.length,sigkilled:f.length}})}s(dt,"teardownFleet");function le(){Z(De(F()),{recursive:!0}),xe(F(),String(process.pid),"utf8")}s(le,"writePidfile");function pe(t){return typeof t=="object"&&t!==null&&t.code==="ENOENT"}s(pe,"isMissingPathError");function me(t){const n=F();let a;try{a=Pe(n,"utf8")}catch(m){if(pe(m))return;throw m}if(a.trim()!==String(t))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${t})`);try{Ie(n)}catch(m){if(pe(m))return;throw m}}s(me,"removeOwnedPidfileMirror");const lt={acceptsDetachedWork:s(()=>!0,"acceptsDetachedWork"),registerDetached:s(t=>{t.catch(()=>{})},"registerDetached")},q=new mr,pt=new fr,mt=new ur,ft=new hr,ut=new vr,ht=new wr,vt=new gr,wt=new _r,gt=new kr,yt=new Er,St=new Rr,bt=new xr,_t=new Pr;async function kt(t=Date.now(),n=lt){const a=Lr(),m=await q.runFleetPreamble(a);let f,o;try{const v=We();f=v.filter(d=>d.status==="active"||d.status==="idle"),o=v.filter(d=>d.status==="done"||d.status==="canceled"||d.status==="dead")}catch(v){u({level:"error",event:"canvas.node_list.failed",error:v});return}await q.run(t,{rows:f,fleet:a,lifecycle:n}),pt.run(t,{rows:f});const g=await mt.run(t,{rows:[...f,...o],fleet:a});ft.run(t),_t.run(t),ut.run(t,{lifecycle:n}),ht.run(t,{lifecycle:n}),vt.run(t,{lifecycle:n}),wt.run(t,{lifecycle:n}),gt.run(t,{lifecycle:n,fleet:a}),yt.run(t,{lifecycle:n}),$e(),bt.run(t,{fleet:a,lifecycle:n}),await St.run(t,{rows:f}),q.warnIfSaturated({fleet:a,lifecycle:n,thresholds:m,frozen:g})}s(kt,"superviseTick");async function Et(t){Z(re(),{recursive:!0,mode:493}),process.platform==="linux"&&Te(re(),493);const n=Ne();if(k(n))throw new Error(`layout refusal cannot bind occupied socket: ${n}`);const a=ee((d,l)=>{if(d.method==="GET"&&d.url==="/healthz"){l.writeHead(200,{"content-type":"application/json; charset=utf-8"}),l.end(JSON.stringify({ok:!1,daemon_up:!0,store_accessible:!1,brokers_reconciled:!1,startup_phase:"blocked",startup_blocked:"layout_migration_required"}));return}l.writeHead(503,{"content-type":"application/json; charset=utf-8"}),l.end(JSON.stringify({error:{code:"layout_migration_required",message:"run crtr sys migrate while the runtime is offline",type:"server_error",origin:"runtime",retryable:!1}}))});await new Promise((d,l)=>{a.once("error",l),a.listen(n,()=>{a.off("error",l),d()})});let m;const f=t.tcp??Ce();if(f){const d=/^(.*):(\d+)$/.exec(f);if(!d||Number(d[2])>65535)throw new Error(`invalid refusal TCP address: ${f}`);const l=ee(a.listeners("request")[0]);await new Promise((i,p)=>{l.once("error",p),l.listen(Number(d[2]),d[1],()=>{l.off("error",p),i()})}),m=l}le();let o;const g=s(()=>(o??=(async()=>{a.closeAllConnections(),m?.closeAllConnections(),await Promise.all([a,m].filter(d=>d!==void 0).map(d=>new Promise((l,i)=>d.close(p=>p?i(p):l())))),me(process.pid),process.off("SIGINT",v),process.off("SIGTERM",v)})(),o),"cleanup"),v=s(()=>{g().then(()=>process.exit(0),d=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(d)}
|
|
2
|
+
`),process.exit(1)})},"onSignal");return process.on("SIGINT",v),process.on("SIGTERM",v),g}s(Et,"serveLayoutRefusal");async function Rt(t={}){if(je())return Et(t);Be();const n=Hr(Ae(16).toString("hex"));if(!n.claimed){if(n.reason==="incumbent_alive")process.stderr.write(`[crtrd] already running (pid ${n.incumbentPid??"?"})
|
|
3
3
|
`);else{const r=[n.incumbentPid!==void 0?`pid ${n.incumbentPid}`:void 0,n.verdict!==void 0?`verdict ${n.verdict}`:void 0].filter(c=>c!==void 0),e=r.length>0?` (${r.join(", ")})`:"";process.stderr.write(`[crtrd] claim lost: ${n.reason}${e}
|
|
4
|
-
`),n.reason==="own_identity_indeterminate"&&process.stderr.write("[crtrd] cannot read its own process identity. crouter needs /proc on Linux, or the `ps` command elsewhere (Debian/Ubuntu: apt-get install procps).\n"),(n.reason==="own_identity_indeterminate"||n.reason==="incumbent_malformed")&&(process.exitCode=1)}return async()=>{}}const a=n.claim;let m=null;try{if(await
|
|
5
|
-
`);
|
|
4
|
+
`),n.reason==="own_identity_indeterminate"&&process.stderr.write("[crtrd] cannot read its own process identity. crouter needs /proc on Linux, or the `ps` command elsewhere (Debian/Ubuntu: apt-get install procps).\n"),(n.reason==="own_identity_indeterminate"||n.reason==="incumbent_malformed")&&(process.exitCode=1)}return async()=>{}}const a=n.claim;let m=null;try{if(await at(a.token),nt({kind:"user",root:ot()}),process.platform==="darwin"){const{readConfig:e,updateConfig:c}=await import("../core/config.js");if(e("user").paths?.user_files===void 0){const{homedir:w}=await import("node:os");c("user",X=>{X.paths??={},X.paths.user_files??=w()})}}await tt(it());for(const e of qe())process.stderr.write(`[crtrd] ${e}
|
|
5
|
+
`);Br(ae()),qr(e=>H.install(e));const r=jr(e=>Wr(e));for(const e of r.conflicts)process.stderr.write(`[crtrd] plugin documents not reconciled: ${e.message}
|
|
6
6
|
`);for(const e of r.invalid)process.stderr.write(`[crtrd] package file is not a document: ${e.path}: ${e.error}
|
|
7
7
|
`);for(const e of r.quarantined)process.stderr.write(`[crtrd] plugin "${e.name}" has a damaged ${e.file} (${e.reason}); ${e.movedTo!==void 0?`moved to ${e.movedTo}`:"left in place and skipped"}
|
|
8
|
-
`);m=
|
|
8
|
+
`);m=Yr(),process.platform==="linux"&&k(x)&&(He(),await I({operation:"users.sync",users:Oe()})),br(a.token)}catch(r){ce(),H.close();try{const{released:e}=se(a);if(!e)throw new Error("daemon ownership claim was not released")}catch(e){throw new AggregateError([r,e],"daemon startup failed and cleanup also failed")}throw r}let f=!0,o,g=Promise.resolve(),v=!1,d,l;const i=new Set;let p=!1,y=!1,S,$,z=!1,A,T,D,C,M;const fe=pr();let E,h={phase:"initializing",message:"daemon startup is running the canvas import and normal services are unavailable",blockerPaths:[]},P="initial startup activation",ue=null;function he(){return h.phase==="ready"?null:{phase:h.phase,message:h.message,blockerPaths:h.blockerPaths}}s(he,"startupBlock");function _(r){i.add(r),r.finally(()=>{i.delete(r)}).catch(()=>{})}s(_,"registerDetached");const W={acceptsDetachedWork:s(()=>f,"acceptsDetachedWork"),registerDetached:_};Ur(W,ae);async function ve(){try{f=!1,zr.close(),ce(),H.close(),d!==void 0&&(clearInterval(d),d=void 0),D!==void 0&&(clearInterval(D),D=void 0),M?.(),M=void 0;let r;do r=g,await r;while(r!==g);for(;i.size>0;)await Promise.allSettled([...i]);if(l!==void 0&&(await dt(l),l.dispose()),$!==void 0&&!z&&(z=!0,await $.close()),C!==void 0){const e=C;C=void 0,await e.stop()}if(T!==void 0){const e=T;T=void 0,await e.close()}if(A!==void 0){const e=A;A=void 0,await e.close()}if(p||(me(process.pid),p=!0),p&&!y){const{released:e}=se(a);if(!e)throw new Error("daemon ownership claim was not released");y=!0}}finally{Ar(),process.off("SIGINT",J),process.off("SIGTERM",V),process.off("exit",Q)}}s(ve,"cleanupAttempt");const N=1e3;let U=!1;function we(){return U?{graceMs:N}:(U=!0,b.fresh(()=>{u({level:"info",event:"daemon.handover.armed",fields:{pid:process.pid,grace_ms:N}})}),setTimeout(()=>{ge()},N),{graceMs:N})}s(we,"requestHandover");async function ge(){try{await R()}catch(r){const e=r instanceof Error?r.stack??r.message:String(r);process.stderr.write(`[crtrd] handover cleanup failed:
|
|
9
9
|
${e}
|
|
10
|
-
`),process.exit(1)}try{process.platform==="linux"&&k(
|
|
11
|
-
`),process.exit(1)}process.exit(0)}s(
|
|
10
|
+
`),process.exit(1)}try{process.platform==="linux"&&k(x)&&(await I({operation:"daemon.handover"}),process.exit(0)),process.pid===1&&await Nr();const r=await Mr({waitForJoinLock:!0});b.fresh(()=>{u({level:"info",event:"daemon.handover.completed",fields:{predecessor_pid:process.pid,successor_pid:r.pid??r.existing_pid??null,spawned:r.started}})})}catch(r){b.fresh(()=>{u({level:"error",event:"daemon.handover.successor_failed",error:r})});const e=r instanceof Error?r.message:String(r);process.stderr.write(`[crtrd] handover successor failed to start: ${e}
|
|
11
|
+
`),process.exit(1)}process.exit(0)}s(ge,"performHandover");function R(){if(S!==void 0)return S;const r=ve();return S=r,r.then(()=>{S===r&&(S=void 0)},()=>{S===r&&(S=void 0)}),r}s(R,"requestCleanup");async function K(r){try{await R(),process.exit(0)}catch(e){process.exitCode=1;const c=e instanceof Error?e.stack??`${e.name}: ${e.message}`:String(e);process.stderr.write(`[crtrd] ${r} cleanup failed:
|
|
12
12
|
${c}
|
|
13
|
-
`),process.exit(1)}}s(
|
|
13
|
+
`),process.exit(1)}}s(K,"handleSignal");function J(){K("SIGINT")}s(J,"onSigint");function V(){K("SIGTERM")}s(V,"onSigterm");function Q(){R().catch(()=>{})}s(Q,"onExit");function O(r){f&&(g=g.then(r).then(()=>{},e=>{b.fresh(()=>{u({level:"error",event:"daemon.lane_job.failed",error:e})})}))}s(O,"enqueue");function Y(){!f||v||(fe.check()&&E&&(u({level:"info",event:"daemon.resume.detected"}),_(E().catch(()=>{}))),v=!0,O(async()=>{if(v=!1,!!f)try{await kt(Date.now(),W)}catch(r){b.fresh(()=>{u({level:"error",event:"daemon.tick.failed",error:r})})}}))}s(Y,"enqueueTick");let L=null;async function ye(){if(L!==null)throw{status:500,code:"admission_failed",message:`daemon admission previously failed: ${L instanceof Error?L.message:String(L)}`};if(h.phase==="ready")return{admitted:!1,already_admitted:!0,startup_phase:"ready"};if(h.phase!=="prepared"&&h.phase!=="recovering")throw{status:409,code:"not_prepared",message:h.message};const r=h.phase==="prepared";try{return await G(),h={phase:"ready",message:"",blockerPaths:[]},r?{admitted:!0,already_admitted:!1,startup_phase:"ready"}:{admitted:!1,already_admitted:!0,startup_phase:"ready"}}catch(e){throw L=e,{status:500,code:"admission_failed",message:`daemon admission failed: ${e instanceof Error?e.message:String(e)}`}}}s(ye,"admitStartup");async function Se(r){if(P!==null)throw{status:409,code:"migration_in_progress",message:`the canvas import is already running (${P})`};P="repair";try{const e=await rt({dryRun:r.dryRun});if(r.dryRun||e.pending||h.phase!=="blocked")return{result:e,resumed:!1};if(t.prepare)return h={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]},{result:e,resumed:!1};try{return await G(),h={phase:"ready",message:"",blockerPaths:[]},{result:e,resumed:!0}}catch(c){const w=c instanceof Error?c.message:String(c);return setTimeout(()=>{R().catch(()=>{})},1e3),{result:e,resumed:!1,resumeError:w}}}finally{P=null}}s(Se,"repairMigrations");async function be(){o=t.intervalMs??lr;const r=a.token;l=new Vr({epoch:r,enqueue:O}),Cr(l),h={phase:"recovering",message:"daemon startup recovery is running and normal services are unavailable",blockerPaths:[]},b.fresh(()=>{u({level:"info",event:"daemon.started",fields:{pid:process.pid,interval_ms:o}})});const e=Gr();if(Fr(e),b.fresh(()=>{u({level:"info",event:"daemon.install_id.resolved",fields:{install_id:e}})}),b.fresh(()=>{let c;try{c=dr()}catch{return}c!==null&&u({level:"warn",event:"daemon.startup.warning",fields:{stage:"termrender",warning:c.slice(0,2048)}})}),await b.fresh(async()=>{await cr(),await ir(),sr(Date.now()),ar(Date.now())}),b.fresh(()=>{const{reconcile:c,migrated:w}=Or(Sr);c.changed&&u({level:"info",event:"daemon.boot_change.reconciled",fields:{cleared_count:c.cleared.length,same_boot_proven:c.sameBootProven}}),w.length>0&&u({level:"info",event:"daemon.pid_identity.migrated",fields:{migrated_count:w.length}})}),b.fresh(()=>{Ir();for(const c of Qr){const w=c.apply();w.changed&&u({level:"info",event:"daemon.boot_migration.applied",fields:{migration:c.id,...w.summary}})}}),_(tr()),E){const c=s(()=>{_(E().catch(()=>{})),_(Ee().then(w=>C?.observe(w)).then(()=>{},w=>{u({level:"warn",event:"daemon.peer.status_failed",error:w})}))},"refresh");_(er().catch(()=>{})),_(rr().then(({completion:w})=>w).catch(w=>{u({level:"error",event:"daemon.grants.sweep_failed",error:w})})),c(),D=setInterval(c,Ze*1e3),D.unref(),M=ke({joined:!0,register:_})}O(()=>{Jr(),Kr()}),d=setInterval(Y,o),Y()}s(be,"completeStartup");function G(){return ue??=be().catch(r=>{throw h={phase:"initializing",message:"daemon startup recovery failed and normal services are unavailable",blockerPaths:[]},r})}s(G,"resumeStartup");try{process.on("SIGINT",J),process.on("SIGTERM",V),process.on("exit",Q),Xr(),m!==null&&u({level:"info",event:"daemon.joined_owner.applied",fields:{owner:m.owner,rows:m.rows}}),A=await Le();const r=a.token;$r({epoch:r,requestHandover:we,registerDetached:_,startupBlock:he,admitStartup:ye,repairMigrations:Se}),le(),$=Ke({tcp:t.tcp}),await $.ready;const e=or("user").runtime;e?.issuer?(E=Xe(e.issuer,_),e.unsandboxed===!0&&(C=Re({syncGrants:E})),Qe(c=>Ye().appended(c))):Ve(),e?.unsandboxed===!0&&e.listener!==void 0&&u({level:"warn",event:"daemon.app_listener.refused_unsandboxed",fields:{reason:"runtime.unsandboxed is set; runtime.listener is ignored"}}),e!==void 0&&e.listener!==void 0&&e.unsandboxed!==!0&&(T=Je(e,()=>nr(e.issuer.replace(/\/$/,"")),E),await T.ready);try{await Zr()}catch(c){if(P=null,c instanceof et)return h={phase:"blocked",message:c.message,blockerPaths:c.blockerPaths},R;throw c}process.platform==="linux"&&k(x)&&(Fe(),await Ge("app:terminal"));try{t.prepare?h={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]}:(await G(),h={phase:"ready",message:"",blockerPaths:[]})}finally{P=null}return R}catch(r){try{await R()}catch(e){throw new AggregateError([r,e],"daemon startup failed and cleanup also failed")}throw r}}s(Rt,"runDaemon");var Xn=Rt;export{B as brokerCommandDeclaresCanvasHome,j as brokerCommandEpoch,Xn as default,Rt as runDaemon,kt as superviseTick,dt as teardownFleet};
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import type { RouteTable } from '../api/router.js';
|
|
1
|
+
import type { RouteHandler, RouteTable } from '../api/router.js';
|
|
2
|
+
export declare const peerEvents: RouteHandler;
|
|
3
|
+
export declare const peerClaim: RouteHandler;
|
|
4
|
+
export declare const peerAck: RouteHandler;
|
|
2
5
|
export declare const peerRoutes: RouteTable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var d=Object.defineProperty;var o=(e,t)=>d(e,"name",{value:t,configurable:!0});import{ApiError as i}from"../../api/errors.js";import{withCanvasWrite as l}from"../../core/canvas/db.js";import{ackFor as v,claimFor as u,ingestPeerEvents as y,MAX_EVENTS_PER_CALL as m}from"../../core/peer/ingest.js";const f=o(e=>()=>{throw new i(501,"not_implemented",`${e} is not implemented on this machine yet`)},"notImplemented");function p(e){if(e.principal.class!=="machine")throw new i(403,"permission_error","peer routes are for a machine");return e.principal.via}o(p,"via");const n=o(e=>e!==null&&typeof e=="object"&&!Array.isArray(e)?e:{},"fields"),a=o(e=>typeof e=="string"&&e!=="","text");function h(e){const t=n(e),r=n(t.object);return a(t.key)&&a(t.message_id)&&a(t.event)&&typeof t.level=="number"&&a(r.id)&&a(r.type)&&typeof r.name=="string"&&(t.actor===null||a(n(t.actor).id)&&a(n(t.actor).type))&&n(t.attrs)===t.attrs}o(h,"validEvent");const w=o(e=>{const t=p(e),r=n(e.body).events;if(!Array.isArray(r))throw new i(400,"invalid_request","events must be an array");if(r.length>m)throw new i(400,"invalid_request",`at most ${m} events per call`);const s=r.findIndex(c=>!h(c));if(s>=0)throw new i(400,"invalid_request",`events[${s}] is not a well-formed event`);return{status:200,body:l(c=>y(c,t,r))}},"peerEvents"),b=o(e=>{const t=p(e),r=n(e.body).limit;return{status:200,body:{rows:l(c=>u(c,t,typeof r=="number"?r:50))}}},"peerClaim"),_=o(e=>{const t=p(e),r=n(e.body).acks;if(!Array.isArray(r)||r.some(s=>!a(n(s).key)||!a(n(s).claim_token)))throw new i(400,"invalid_request","acks must be an array of {key, claim_token}");return{status:200,body:{acked:l(s=>v(s,t,r))}}},"peerAck"),g=[{method:"POST",pattern:"/v1/peer/events",handler:w},{method:"POST",pattern:"/v1/peer/outbox/claim",handler:b},{method:"POST",pattern:"/v1/peer/outbox/ack",handler:_},{method:"POST",pattern:"/v1/peer/locate",handler:f("peer.locate")}];export{_ as peerAck,b as peerClaim,w as peerEvents,g as peerRoutes};
|