@north-light/cli 0.3.580 → 0.3.581
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/api/client.d.ts +6 -1
- package/dist/api/client.js +3 -3
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/execution-start.d.ts +14 -0
- package/dist/api/dto/health.d.ts +20 -0
- package/dist/api/dto/imports.d.ts +142 -0
- package/dist/api/dto/imports.js +0 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -1
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +1 -1
- package/dist/clients/attach/session/connection.d.ts +3 -1
- package/dist/clients/attach/session/connection.js +1 -1
- package/dist/clients/attach/viewer.js +510 -510
- package/dist/commands/api-client.d.ts +7 -4
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/import/resume.d.ts +1 -0
- package/dist/commands/sys/import/resume.js +1 -0
- package/dist/commands/sys/import/start.d.ts +5 -0
- package/dist/commands/sys/import/start.js +4 -0
- package/dist/commands/sys/join.d.ts +2 -0
- package/dist/commands/sys/join.js +1 -1
- package/dist/commands/sys/machine/rotate.d.ts +1 -0
- package/dist/commands/sys/machine/rotate.js +1 -0
- package/dist/commands/sys/signin.d.ts +7 -0
- package/dist/commands/sys/signin.js +2 -0
- package/dist/commands/sys.js +1 -1
- package/dist/core/blobs/r2.d.ts +4 -0
- package/dist/core/blobs/r2.js +7 -4
- package/dist/core/canvas/daemon-owner.d.ts +1 -1
- package/dist/core/canvas/executions.d.ts +1 -1
- package/dist/core/canvas/node-tokens.d.ts +2 -0
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/directory/person-credential.d.ts +36 -0
- package/dist/core/directory/person-credential.js +1 -0
- package/dist/core/graph/deletion.d.ts +4 -1
- package/dist/core/graph/deletion.js +3 -3
- package/dist/core/graph/documents.d.ts +4 -0
- package/dist/core/graph/documents.js +10 -10
- package/dist/core/graph/reader.js +2 -2
- package/dist/core/import/bundle/blob-keys.d.ts +10 -0
- package/dist/core/import/bundle/blob-keys.js +1 -0
- package/dist/core/import/bundle/layout.d.ts +89 -0
- package/dist/core/import/bundle/layout.js +5 -0
- package/dist/core/import/export/archive.d.ts +11 -0
- package/dist/core/import/export/archive.js +1 -0
- package/dist/core/import/export/cloud.d.ts +21 -0
- package/dist/core/import/export/cloud.js +1 -0
- package/dist/core/import/export/dump.d.ts +34 -0
- package/dist/core/import/export/dump.js +3 -0
- package/dist/core/import/export/files.d.ts +22 -0
- package/dist/core/import/export/files.js +1 -0
- package/dist/core/import/export/inline.d.ts +3 -0
- package/dist/core/import/export/inline.js +1 -0
- package/dist/core/import/export/source.d.ts +2 -0
- package/dist/core/import/export/source.js +1 -0
- package/dist/core/import/export/stores.d.ts +9 -0
- package/dist/core/import/export/stores.js +1 -0
- package/dist/core/import/load/bind.d.ts +19 -0
- package/dist/core/import/load/bind.js +15 -0
- package/dist/core/import/load/blobs.d.ts +10 -0
- package/dist/core/import/load/blobs.js +1 -0
- package/dist/core/import/load/catalog.d.ts +32 -0
- package/dist/core/import/load/catalog.js +9 -0
- package/dist/core/import/load/copy.d.ts +26 -0
- package/dist/core/import/load/copy.js +6 -0
- package/dist/core/import/load/path-map.d.ts +7 -0
- package/dist/core/import/load/path-map.js +1 -0
- package/dist/core/import/load/prepare.d.ts +37 -0
- package/dist/core/import/load/prepare.js +10 -0
- package/dist/core/import/load/rewrite.d.ts +39 -0
- package/dist/core/import/load/rewrite.js +9 -0
- package/dist/core/import/load/rollback.d.ts +3 -0
- package/dist/core/import/load/rollback.js +6 -0
- package/dist/core/import/load/run.d.ts +22 -0
- package/dist/core/import/load/run.js +3 -0
- package/dist/core/import/load/stage.d.ts +15 -0
- package/dist/core/import/load/stage.js +1 -0
- package/dist/core/import/load/stores.d.ts +24 -0
- package/dist/core/import/load/stores.js +2 -0
- package/dist/core/import/load/verify.d.ts +4 -0
- package/dist/core/import/load/verify.js +11 -0
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/revive.d.ts +5 -2
- package/dist/core/runtime/revive.js +2 -2
- package/dist/core/runtime/runner/blaxel/runner.js +3 -3
- package/dist/core/runtime/runner/index.d.ts +1 -0
- package/dist/core/runtime/runner/index.js +1 -1
- package/dist/core/runtime/runner/machine/call.d.ts +8 -0
- package/dist/core/runtime/runner/machine/call.js +3 -0
- package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
- package/dist/core/runtime/runner/machine/environment.js +1 -0
- package/dist/core/runtime/runner/machine/executor.d.ts +23 -0
- package/dist/core/runtime/runner/machine/executor.js +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
- package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
- package/dist/core/runtime/runner/machine/ops.js +1 -0
- package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
- package/dist/core/runtime/runner/machine/runner.js +1 -0
- package/dist/core/runtime/runner/types.d.ts +7 -1
- package/dist/core/runtime/warm-pool.d.ts +2 -0
- package/dist/core/runtime/warm-pool.js +1 -1
- package/dist/core/secrets/env-rows.d.ts +1 -0
- package/dist/core/secrets/env-rows.js +1 -1
- package/dist/core/storage/purge.d.ts +6 -0
- package/dist/core/storage/purge.js +4 -0
- package/dist/core/subscription-state.d.ts +1 -0
- package/dist/core/subscription-state.js +1 -1
- package/dist/daemon/api/bridge.js +3 -3
- package/dist/daemon/api/handlers/attach.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/daemon.js +1 -1
- package/dist/daemon/api/handlers/execution-start.js +2 -2
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/imports.d.ts +2 -0
- package/dist/daemon/api/handlers/imports.js +1 -0
- package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
- package/dist/daemon/api/handlers/machine-credential.js +1 -0
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/import-auth.d.ts +6 -0
- package/dist/daemon/api/import-auth.js +1 -0
- package/dist/daemon/api/operations.d.ts +2 -2
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +7 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/broker-signals/bus.d.ts +3 -2
- package/dist/daemon/broker-signals/bus.js +1 -1
- package/dist/daemon/cron/armed-context.d.ts +1 -0
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/closed.d.ts +31 -0
- package/dist/daemon/cron/closed.js +1 -0
- package/dist/daemon/cron/ended.js +2 -2
- package/dist/daemon/cron/lease-recovery.js +1 -1
- package/dist/daemon/cron/machine-exec.d.ts +5 -0
- package/dist/daemon/cron/machine-exec.js +1 -0
- package/dist/daemon/cron/machine-fire.d.ts +26 -0
- package/dist/daemon/cron/machine-fire.js +1 -0
- package/dist/daemon/cron/recurrence.d.ts +14 -0
- package/dist/daemon/cron/recurrence.js +1 -0
- package/dist/daemon/cron/run-token.d.ts +7 -0
- package/dist/daemon/cron/run-token.js +1 -0
- package/dist/daemon/cron-run.js +2 -2
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/crtrd.d.ts +2 -2
- package/dist/daemon/crtrd.js +10 -10
- package/dist/daemon/drain.d.ts +25 -0
- package/dist/daemon/drain.js +9 -0
- package/dist/daemon/due-sources.d.ts +1 -1
- package/dist/daemon/due-sources.js +1 -1
- package/dist/daemon/due.js +8 -8
- package/dist/daemon/fleet.js +5 -5
- package/dist/daemon/import-dump-run.d.ts +1 -0
- package/dist/daemon/import-dump-run.js +5 -0
- package/dist/daemon/machine-daemon.d.ts +7 -0
- package/dist/daemon/machine-daemon.js +1 -0
- package/dist/daemon/machine-link/client.d.ts +29 -0
- package/dist/daemon/machine-link/client.js +1 -0
- package/dist/daemon/machine-link/config.d.ts +14 -0
- package/dist/daemon/machine-link/config.js +3 -0
- package/dist/daemon/machine-link/credential.d.ts +7 -0
- package/dist/daemon/machine-link/credential.js +2 -0
- package/dist/daemon/machine-link/forward.d.ts +16 -0
- package/dist/daemon/machine-link/forward.js +1 -0
- package/dist/daemon/machine-link/frames.d.ts +49 -0
- package/dist/daemon/machine-link/frames.js +1 -0
- package/dist/daemon/machine-link/holder.d.ts +73 -0
- package/dist/daemon/machine-link/holder.js +9 -0
- package/dist/daemon/manage.d.ts +19 -3
- package/dist/daemon/manage.js +3 -3
- package/dist/daemon/reconcilers/cron-lane.js +1 -1
- package/dist/daemon/reconcilers/delivery-lane.js +2 -2
- package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
- package/dist/daemon/reconcilers/import-lane.js +1 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
- package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
- package/dist/daemon/reconcilers/purge-due-work.js +1 -0
- package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
- package/dist/daemon/reconcilers/row-claim.js +2 -2
- package/dist/daemon/runs/observer.js +3 -3
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/scripts/sandbox-start.mjs +185 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Cron, CronRunRecord } from '../../core/canvas/crons.js';
|
|
2
|
+
/** Everything a CLOSED child process leaves behind — the flat record the
|
|
3
|
+
* settle decision is made over, instead of five levels of live closure. */
|
|
4
|
+
export interface ClosedRun {
|
|
5
|
+
runId: string;
|
|
6
|
+
outOfBand: boolean;
|
|
7
|
+
startedAtIso: string;
|
|
8
|
+
startedAtMs: number;
|
|
9
|
+
finishedAtMs: number;
|
|
10
|
+
/** The raw close code; `timedOut` overrides it to -1 in the record. */
|
|
11
|
+
code: number | null;
|
|
12
|
+
timedOut: boolean;
|
|
13
|
+
processError: Error | null;
|
|
14
|
+
stdoutHead: string;
|
|
15
|
+
stderrHead: string;
|
|
16
|
+
stdoutSha256: string;
|
|
17
|
+
/** overlap=replace killed this run so the next fire could start fresh. */
|
|
18
|
+
wasReplaced: boolean;
|
|
19
|
+
/** The run canceled its own row — the documented self-cancel pattern. */
|
|
20
|
+
wasSelfCanceled: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Settle one closed child: choose the outcome (replaced short-circuit, owed
|
|
23
|
+
* gate, or the full disposition), then hand it to the one settle tail. */
|
|
24
|
+
export declare function settleClosedRun(c: Cron, run: ClosedRun): Promise<CronRunRecord>;
|
|
25
|
+
/** Settle a claimed run that failed before its launched child could be observed. */
|
|
26
|
+
export declare function settleUnspawnedRun(c: Cron, run: {
|
|
27
|
+
runId: string;
|
|
28
|
+
outOfBand: boolean;
|
|
29
|
+
startedAtIso: string;
|
|
30
|
+
startedAtMs: number;
|
|
31
|
+
}, lead: string, err: Error): Promise<CronRunRecord>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var p=Object.defineProperty;var c=(t,e)=>p(t,"name",{value:e,configurable:!0});import{CRON_OUTPUT_HEAD_BYTES as m,EMPTY_STDOUT_SHA256 as E}from"./capture.js";import{decideHeldSettle as I,EXIT_HELD as A,HELD_DELIVERED as S}from"./held.js";import{settleRun as H}from"./settle.js";async function x(t,e){const{runId:r,outOfBand:s,startedAtIso:d,finishedAtMs:o,code:u,timedOut:l,processError:a}=e,f=!s&&!l&&a==null&&u===A&&!e.wasReplaced&&!e.wasSelfCanceled;let i,n;return e.wasReplaced?(i={kind:"stated",delivered:"replaced (overlap=replace)"},n="release"):f?(i={kind:"stated",delivered:S},n=await I(t,d,o)??"release"):(i={kind:"disposed",ctx:{runId:r,startedAtIso:d,exitCode:l?-1:u,timedOut:l,runTimeoutS:t.run_timeout_s,processErrorMessage:a!=null?a.message:null,stdoutHead:e.stdoutHead,stdoutSha256:e.stdoutSha256,stderrHead:e.stderrHead},selfCanceled:e.wasSelfCanceled},n=s?"release":{kind:"clear"}),H(t,{outOfBand:s,outcome:i,record:{runId:r,startedAtIso:d,finishedAtMs:o,durationMs:o-e.startedAtMs,exitCode:l?-1:u,stdoutHead:e.stdoutHead===""?null:e.stdoutHead,stderrHead:a!=null?`run process error: ${a.message}`.slice(0,m):e.stderrHead===""?null:e.stderrHead},lease:n})}c(x,"settleClosedRun");function D(t,e,r,s){const d=Date.now(),o=`${r}: ${s.message}`.slice(0,m);return H(t,{outOfBand:e.outOfBand,outcome:{kind:"disposed",ctx:{runId:e.runId,startedAtIso:e.startedAtIso,exitCode:null,timedOut:!1,runTimeoutS:t.run_timeout_s,processErrorMessage:s.message,stdoutHead:"",stdoutSha256:E,stderrHead:o}},record:{runId:e.runId,startedAtIso:e.startedAtIso,finishedAtMs:d,durationMs:d-e.startedAtMs,exitCode:null,stdoutHead:null,stderrHead:o},lease:"release"})}c(D,"settleUnspawnedRun");export{x as settleClosedRun,D as settleUnspawnedRun};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}}r(
|
|
1
|
+
var h=Object.defineProperty;var r=(e,n)=>h(e,"name",{value:n,configurable:!0});import{getNode as c}from"../../core/canvas/canvas.js";import{withCanvasWrite as m}from"../../core/canvas/db.js";import{CRON_RUN_RING as f,cancelCron as p,listCronRuns as y,parseCronSink as _}from"../../core/canvas/crons.js";import{hasNoNaturalCycle as g}from"../../core/runtime/revive-all.js";import{isParked as w}from"../../core/runtime/lifecycle.js";import{enqueueNodeMail as x}from"../../core/feed/messages.js";import{cadenceDisplay as $}from"../../core/wake.js";import{machineEnvironmentOfCron as v,queueCronCancel as k}from"./machine-fire.js";const b=/^delivered: /,C=r(e=>e.exit_code===0&&(e.stdout_head??"")==="","EMPTY_OK");function N(e){return e.filter(n=>n.exit_code!==null||!(n.delivered??"").startsWith("skipped"))}r(N,"realRuns");function R(e,n,s){const t=N(s),o=s.length>=f,l=t.find(u=>b.test(u.delivered??"")),a=n.kind==="expired"?`expired at ${e.expires_at} and was deleted${e.held?" while held by exit 75; the pending occurrence never fired":""}`:`was canceled by ${n.by} and deleted`,d=[`Cron "${e.name}" (${e.cron_id}, ${e.recur!=null?$(e.recur):"one-shot"}) ${a}. It was delivering to you, so nothing will wake you from it any more.`,"",`- runs made: ${o?`at least ${t.length} (the run log keeps the last ${f} entries)`:t.length}`,l!==void 0?`- last delivery to you: ${l.finished}`:`- last delivery to you: ${o?`none in its last ${t.length} runs`:"never delivered"}`],i=t[0];if(i!==void 0){const u=i.exit_code===null?"no exit observed":i.exit_code===-1?"timed out":`exit ${i.exit_code}`;d.push(`- last run: ${i.finished} \u2014 ${u} \u2014 ${i.delivered??""}`)}return t.length>0&&t.every(C)&&d.push("","Every recorded run exited 0 with empty stdout, so the check may have been failing without saying so. A pipeline exits with its last stage, so `probe 2>&1 | grep \u2026 | jq \u2026` hides a failed probe unless the command starts with `set -o pipefail`."),{label:`\u23F0 cron ${e.name} ${n.kind==="expired"?"expired":"canceled"}`,body:d.join(`
|
|
2
|
+
`)}}r(R,"endedMessage");async function E(e){return e.deleting_at!=null||e.run_id!=null&&(await c(e.run_id))?.deleting_at!=null}r(E,"runIsDeleting");async function I(e){return!await E(e)&&(!g(e)||w(e))}r(I,"canTakeMail");async function W(e,n,s){const t=_(e.sink),o=t?.kind==="node"?await c(t.node):null,l=n!==null&&o!==null&&await I(o);if(e.run_state==="running"&&e.run_execution_id!==null&&s?.preserveRunningProcess!==!0){const a=await v(e);a!==null&&await k(a,e.run_execution_id)}return await m(async()=>{const a=l?await y(e.cron_id):[];if(!await p(e.cron_id,s))return!1;if(l){const{label:d,body:i}=R(e,n,a);await x(o.node_id,{kind:"message",label:d,senderNodeId:null,bodyJson:{body:i},urgency:"normal",wake:1,idempotencyKey:`cron-ended:${e.cron_id}`})}return!0})}r(W,"endCron");export{I as canTakeMail,W as endCron};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var R=Object.defineProperty;var s=(e,t)=>R(e,"name",{value:t,configurable:!0});import{randomUUID as w}from"node:crypto";import{setTimeout as A}from"node:timers/promises";import{CRON_DEATH_POLL_MS as O,CRON_KILL_GRACE_MS as C,EMPTY_STDOUT_SHA256 as h}from"./capture.js";import{cronDaemonInstanceId as v,cronOwnerAlive as p,ensureCronOwnerLease as L}from"./live-runs.js";import{crashedOneShotSummary as x,escalateFailure as D}from"./escalation.js";import{settleRun as I}from"./settle.js";import{adoptCronRunId as E,getCron as g,runningCrons as M,stopLeasedCronRun as S,setCronState as T,verifyLeasedCronRun as _}from"../../core/canvas/crons.js";import{claimReconcilerLease as H,releaseReconcilerLease as b,renewReconcilerLease as N}from"../../core/canvas/reconciler-leases.js";import{emitEvent as l}from"../../core/events/emit.js";import{openDb as $}from"../../core/canvas/db.js";import{machineEnvironmentOfCron as k,resumeMachineFire as G}from"./machine-fire.js";const c="cron-recovery",f=3e4;async function U(e){const t=w();if(await H(c,e,t,f)===void 0)return;let n=!1;const r=s(async()=>{if(n)return!1;try{n=!await N(c,e,t,f)}catch{n=!0}return!n},"holds"),a=setInterval(()=>{r()},f/3);return a.unref(),{holds:r,release:s(async()=>{clearInterval(a),await b(c,e,t)},"release")}}s(U,"claimRecovery");async function F(e){const t=await g(e.cron_id);if(t===null||t.run_state!=="running"||t.run_execution_id!==e.run_execution_id||t.run_started_at!==e.run_started_at||t.run_lease_owner!==e.run_lease_owner||t.run_lease_owner!==null&&await p(t.run_lease_owner))return null;if(t.run_execution_id!==null)return t;const n=w();return await E(t,n)?{...t,run_execution_id:n}:null}s(F,"reverifyAbandoned");async function ae(e,t={}){await L();for(const n of await M()){if(n.run_lease_owner===v||n.run_lease_owner!==null&&await p(n.run_lease_owner))continue;const r=n.run_pid===null&&n.run_execution_id!==null?await k(n):null,a=r===null&&(n.run_pid===null||!_(n));if(r===null&&!a&&e-(n.run_started_at!=null?new Date(n.run_started_at).getTime():e)<n.run_timeout_s*1e3)continue;const o=await U(n.cron_id);if(o===void 0)continue;let i;try{i=await F(n)}catch(u){throw await o.release(),u}if(i===null){await o.release();continue}if(r!==null){P(i,r,t,o);continue}if(a){V(i,e,t,o);continue}K(i,e,t,o)}}s(ae,"recoverStaleCronLeases");function K(e,t,n,r){const a=e.run_started_at!=null?new Date(e.run_started_at).getTime():t,o={runId:e.run_execution_id,startedAtIso:e.run_started_at??new Date(t).toISOString(),exitCode:-1,timedOut:!0,runTimeoutS:e.run_timeout_s,processErrorMessage:null,stdoutHead:"",stdoutSha256:h,stderrHead:`run timed out after ${e.run_timeout_s}s (recovered stale lease from a prior daemon instance)`},i=(async()=>{await r.holds()&&(await S(e),await B(e),await r.holds()&&await I(e,{outOfBand:!1,outcome:{kind:"disposed",ctx:o},record:{runId:o.runId,startedAtIso:o.startedAtIso,finishedAtMs:t,durationMs:t-a,exitCode:-1,stdoutHead:null,stderrHead:o.stderrHead},lease:{kind:"clear"}}))})().finally(()=>r.release());n.register?.(i),i.catch(u=>{l({level:"error",event:"cron.stale_lease.settle_failed",error:u,fields:{cron_id:e.cron_id}})})}s(K,"recoverTimedOutRun");function P(e,t,n,r){const a=(async()=>{await r.holds()&&await Y(e)&&await G(e,t)})().finally(()=>r.release());n.register?.(a),a.catch(o=>{l({level:"error",event:"cron.machine_resume.failed",error:o,fields:{cron_id:e.cron_id,run_id:e.run_execution_id}})})}s(P,"resumeMachineRun");async function Y(e){const t=await(await $()).prepare("UPDATE crons SET run_lease_owner = ?, updated = ? WHERE cron_id = ? AND run_state = 'running' AND run_execution_id = ? AND run_lease_owner IS NOT DISTINCT FROM ?").run(v,new Date().toISOString(),e.cron_id,e.run_execution_id,e.run_lease_owner);return Number(t.changes)>0}s(Y,"takeOverLease");async function B(e){if(!await y(e)&&(await S(e,"SIGKILL"),!await y(e)))throw new Error(`cron ${e.cron_id} run ${e.run_pid} survived SIGKILL`)}s(B,"awaitLeasedRunDeath");async function y(e){const t=Date.now()+C;for(;Date.now()<t;){if(!_(e))return!0;await A(O)}return!_(e)}s(y,"pollLeasedRunGone");function V(e,t,n,r){const a=e.recur==null,o=e.run_started_at!=null?new Date(e.run_started_at).getTime():t,i=e.run_pid===null?"the daemon life that claimed this run ended before its launch pid was recorded \u2014 the command may have started and its outcome is unknown":"the daemon life that owned this run ended before it settled \u2014 the process is gone and its outcome was never observed",u={runId:e.run_execution_id,startedAtIso:e.run_started_at??new Date(t).toISOString(),exitCode:null,timedOut:!1,runTimeoutS:e.run_timeout_s,processErrorMessage:i,stdoutHead:"",stdoutSha256:h,stderrHead:`run outcome unobserved: ${i}`},m=(async()=>{let d=`crashed: ${i}${a?"":" \u2014 the next occurrence is unaffected"}`;if(a){if(!await r.holds())return;if(await T(e.cron_id,"paused"),e.on_output==="silent")d=`crashed: ${i} \u2014 one-shot paused, not re-fired (silent: not escalated)`;else{if(!await r.holds())return;d=`crashed one-shot: ${await D(e,u,x(i))}`}}await r.holds()&&(await I(e,{outOfBand:!1,outcome:{kind:"stated",delivered:d,paused:a},record:{runId:u.runId,startedAtIso:u.startedAtIso,finishedAtMs:t,durationMs:Math.max(0,t-o),exitCode:null,stdoutHead:null,stderrHead:u.stderrHead},lease:"release"}),l({level:"warn",event:"cron.run.unobserved",fields:{cron_id:e.cron_id,name:e.name,one_shot:a}}))})().finally(()=>r.release());n.register?.(m),m.catch(d=>{l({level:"error",event:"cron.crashed_run.settle_failed",error:d,fields:{cron_id:e.cron_id}})})}s(V,"settleCrashedRun");export{ae as recoverStaleCronLeases};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MachineCronCancelArgs, MachineCronCancelResult, MachineCronRunArgs, MachineCronRunResult } from '../../core/runtime/runner/machine/ops.js';
|
|
2
|
+
export declare const machineCronMarkerDir: () => string;
|
|
3
|
+
export declare function runMachineCron(args: MachineCronRunArgs): Promise<MachineCronRunResult>;
|
|
4
|
+
/** Stop a run. A run that has not started is canceled for good: a marker holding a canceled result is left, so a `cron.run` that arrives later returns it without spawning. */
|
|
5
|
+
export declare function cancelMachineCron(args: MachineCronCancelArgs): Promise<MachineCronCancelResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var x=Object.defineProperty;var i=(e,t)=>x(e,"name",{value:t,configurable:!0});import{closeSync as f,fsyncSync as h,mkdirSync as k,openSync as p,readFileSync as D,readdirSync as b,renameSync as v,rmSync as A,statSync as N,writeSync as w}from"node:fs";import{join as y}from"node:path";import{contextDir as P,crtrHome as H}from"../../core/canvas/paths.js";import{runCommandOnce as $}from"../../core/runtime/run-command.js";import{ADMITTED_HOST_PATH_ENV as T,buildOperationalEnvBase as U}from"../../core/runtime/spawn-env.js";import{readMachineConfig as F}from"../machine-link/config.js";import{CRON_OUTPUT_HEAD_BYTES as E,EMPTY_STDOUT_SHA256 as M,formatCronOutputHead as _}from"./capture.js";const j=168*36e5,l=new Map,a=i(()=>y(H(),"runtime","machine-cron"),"machineCronMarkerDir"),m=i(e=>y(a(),`${encodeURIComponent(e)}.json`),"markerPath");function C(e){const t=p(e,"r");try{h(t)}finally{f(t)}}i(C,"fsyncDirectory");function R(e,t){const n=p(e,"wx");try{w(n,t),h(n)}finally{f(n)}C(a())}i(R,"createMarkerFile");function J(e,t){const n=`${e}.${process.pid}.tmp`,o=p(n,"w");try{w(o,t),h(o)}finally{f(o)}v(n,e),C(a())}i(J,"replaceMarkerFile");function S(e){let t;try{t=D(m(e),"utf8")}catch(n){return n.code==="ENOENT"?{kind:"none"}:{kind:"unreadable"}}try{const n=JSON.parse(t);return n===null||typeof n!="object"||n.runId!==e?{kind:"unreadable"}:{kind:"marker",marker:n}}catch{return{kind:"unreadable"}}}i(S,"readMarker");function B(e){try{for(const t of b(a())){const n=y(a(),t);e-N(n).mtimeMs>j&&A(n,{force:!0})}}catch{}}i(B,"sweepMarkers");const c=i((e,t)=>({exitCode:null,stdoutHead:null,stderrHead:e.slice(0,E),durationMs:Date.now()-t,stdoutSha256:M}),"failed");function O(e,t,n){return e.kind==="unreadable"?c(`run ${t} has a marker on this machine that cannot be read, so whether it ran is unknown; it is not run again`,n):e.marker.result??c(`run ${t} was started on this machine and its outcome was not recorded (the daemon that ran it ended); it is not run again`,n)}i(O,"answerFromMarker");function X(e){const t=e.grantee==="app:terminal",n=U({targetCwd:e.cwd,targetProfileId:t?e.profileId:null,skipSpawnAllow:!t}),o={...n,...e.env,CRTR_RUN_ID:e.runId,CRTR_CRON_ID:e.cronId},u=F();return u!==void 0&&(o.CRTR_API_URL=u.apiUrl,o.CRTR_ENVIRONMENT_ID=u.environmentId),e.contextAnchor!==null&&(o.CRTR_CONTEXT_DIR=P(e.contextAnchor.node,e.contextAnchor.grantee)),o[T]=n[T],o}i(X,"machineRunEnv");function W(e){const t=l.get(e.runId);if(t!==void 0)return t.promise;const n=Date.now();k(a(),{recursive:!0});const o=S(e.runId);if(o.kind!=="none")return Promise.resolve(O(o,e.runId,n));if(Date.parse(e.leaseExpiresAt)<=n)return Promise.resolve({...c(`lease expired at ${e.leaseExpiresAt} before the run started; nothing ran`,n),notStarted:!0});B(n);let u;try{u=X(e)}catch(d){return Promise.resolve({...c(`run could not be armed on this machine: ${d.message}`,n),notStarted:!0})}const I={runId:e.runId,startedAt:new Date(n).toISOString(),result:null};try{R(m(e.runId),JSON.stringify(I))}catch(d){if(d.code==="EEXIST"){const r=S(e.runId);if(r.kind!=="none")return Promise.resolve(O(r,e.runId,n))}return Promise.resolve({...c(`run ${e.runId} could not be recorded before it started: ${d.message}; nothing ran`,n),notStarted:!0})}const s={promise:Promise.resolve(c("unreachable",n)),command:null};return s.promise=(async()=>{let d;try{s.command=$({command:e.command,cwd:e.cwd,env:u,timeoutMs:e.timeoutS*1e3,headBytes:E});const r=await s.command.completion;d=r.processError!==null?c(r.processError.message,n):{exitCode:r.timedOut?-1:r.exitCode,stdoutHead:r.stdout===""?null:_(r.stdout,r.stdoutTruncated),stderrHead:r.stderr===""?null:_(r.stderr,r.stderrTruncated),durationMs:r.durationMs,stdoutSha256:r.stdoutSha256}}catch(r){d=c(`run failed to spawn: ${r.message}`,n)}return J(m(e.runId),JSON.stringify({...I,result:d})),d})().finally(()=>{l.delete(e.runId)}),l.set(e.runId,s),s.promise}i(W,"runMachineCron");async function Z(e){const t=l.get(e.runId);if(t!==void 0)return t.command===null?{canceled:!1}:(t.command.terminate(),{canceled:!0});if(k(a(),{recursive:!0}),S(e.runId).kind!=="none")return{canceled:!1};const n={runId:e.runId,startedAt:new Date().toISOString(),result:{exitCode:null,stdoutHead:null,stderrHead:"canceled before it started; nothing ran",durationMs:0,stdoutSha256:M}};try{R(m(e.runId),JSON.stringify(n))}catch(o){if(o.code==="EEXIST")return{canceled:!1};throw o}return{canceled:!0}}i(Z,"cancelMachineCron");export{Z as cancelMachineCron,a as machineCronMarkerDir,W as runMachineCron};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type MachineCronRunArgs, type MachineCronRunResult } from '../../core/runtime/runner/machine/ops.js';
|
|
2
|
+
import { type Cron, type CronRunRecord } from '../../core/canvas/crons.js';
|
|
3
|
+
/** The machine environment a cron runs on, or null when it runs here (a Mac's own daemon, a cron with no profile, a Blaxel profile). */
|
|
4
|
+
export declare function machineEnvironmentOfCron(c: Cron): Promise<string | null>;
|
|
5
|
+
export declare function machineLinkLive(environmentId: string): Promise<boolean>;
|
|
6
|
+
/** What a machine cron's call carries beyond the command: the stored variables and armed env, and who the Mac rebuilds its run context for. */
|
|
7
|
+
type PreparedRun = Pick<MachineCronRunArgs, 'env' | 'grantee' | 'profileId' | 'contextAnchor'>;
|
|
8
|
+
/** One send of the fire: a leased `cron.run` call. Throws MachineOfflineError, MachineLostError or MachineCallFailedError. */
|
|
9
|
+
export declare function sendCronRun(c: Cron, environmentId: string, runId: string, prepared: PreparedRun): Promise<MachineCronRunResult>;
|
|
10
|
+
/** Owe the machine a cancel of this run. The call is queued without a link check: an offline machine takes it when its link returns
|
|
11
|
+
* (the machine's own record of the run id decides whether that stops a running command or keeps a late `cron.run` from starting). */
|
|
12
|
+
export declare function queueCronCancel(environmentId: string, runId: string): Promise<void>;
|
|
13
|
+
export interface MachineFire {
|
|
14
|
+
environmentId: string;
|
|
15
|
+
runId: string;
|
|
16
|
+
outOfBand: boolean;
|
|
17
|
+
startedAtIso: string;
|
|
18
|
+
startedAtMs: number;
|
|
19
|
+
}
|
|
20
|
+
/** Fire `c` on its machine and settle the run. Resolves with the settled record, or null when the cron ended while the run was owed.
|
|
21
|
+
* The caller holds the cron's run lease and has already passed the authority and start checks; `admit` runs once the run is registered. */
|
|
22
|
+
export declare function fireOnMachine(c: Cron, fire: MachineFire, admit: () => void): Promise<CronRunRecord | null>;
|
|
23
|
+
/** A control plane that took a cron's run lease over from one that ended mid-fire: the same run id goes to the machine again, so the machine
|
|
24
|
+
* returns the stored result or joins the live run. A fresh id is never minted for this occurrence. Resolves with the settled record. */
|
|
25
|
+
export declare function resumeMachineFire(c: Cron, environmentId: string): Promise<CronRunRecord | null>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var D=Object.defineProperty;var d=(e,t)=>D(e,"name",{value:t,configurable:!0});import{randomUUID as N}from"node:crypto";import{setTimeout as x}from"node:timers/promises";import{openDb as R}from"../../core/canvas/db.js";import{isCloudRole as L}from"../../core/canvas/person.js";import{withCanvasWrite as H}from"../../core/canvas/db.js";import{getNode as k}from"../../core/canvas/canvas.js";import{callMachine as F}from"../../core/runtime/runner/machine/call.js";import{machineEnvironmentOfProfile as U}from"../../core/runtime/runner/machine/environment.js";import{MachineCallFailedError as b,MachineLostError as P,MachineOfflineError as C}from"../../core/runtime/runner/machine/ops.js";import{environmentSecrets as B}from"../../core/secrets/environment-secrets.js";import{profileInApp as W}from"../../core/secrets/environment-owner.js";import{getCron as $}from"../../core/canvas/crons.js";import{emitEvent as _}from"../../core/events/emit.js";import{runTokenFor as j}from"./run-token.js";import{parseArmedEnv as K}from"./armed-context.js";import{CRON_OUTPUT_HEAD_BYTES as V}from"./capture.js";import{settleClosedRun as Y,settleUnspawnedRun as w}from"./closed.js";import{clearLiveRun as q,registerLiveRun as J}from"./live-runs.js";import{advanceRecurrence as v}from"./recurrence.js";const z=15,G=2e3;async function Re(e){return!L()||e.profile==null?null:await U(e.profile)}d(Re,"machineEnvironmentOfCron");async function Ce(e){return await(await R()).prepare("SELECT 1 AS live FROM machine_links WHERE environment = ? AND lease_expires_at > ?").get(e,new Date().toISOString())!==void 0}d(Ce,"machineLinkLive");async function S(e,t){const n=e.profile,l=await(await R()).prepare("SELECT person FROM profiles WHERE id = ?").get(n);if(l===void 0)throw new Error(`profile ${n} of cron ${e.cron_id} is not in the control plane`);const o=await B({person:l.person,app:e.grantee,ownerProfile:n,ownerProfileInApp:await W(n,e.grantee),includeDeployment:!1}),r=e.grantee==="app:terminal",a=e.anchor_node!=null?await k(e.anchor_node):null;return{env:{...o,...K(e.env_json),...r?{CRTR_PROFILE_ID:n}:{},CRTR_CRON_ID:e.cron_id,CRTR_CRON_NAME:e.name,CRTR_RUN_ID:t,CRTR_API_TOKEN:j(t),...e.anchor_node!=null?{CRTR_ANCHOR:e.anchor_node}:{}},grantee:e.grantee,profileId:n,contextAnchor:a===null?null:{node:a.node_id,grantee:a.grantee??e.grantee}}}d(S,"prepareRun");async function Q(e,t,n,l){const o=(e.run_timeout_s+z)*1e3;return await F(t,"cron.run",{runId:n,cronId:e.cron_id,command:e.command,cwd:e.cwd,...l,timeoutS:e.run_timeout_s,leaseExpiresAt:new Date(Date.now()+o).toISOString()},{timeoutMs:o})}d(Q,"sendCronRun");async function X(e,t){await H(async n=>{await n.prepare("INSERT INTO machine_calls (call_id, environment, op, args_json, created_at) VALUES (?, ?, ?, ?, ?)").run(N(),e,"cron.cancel",JSON.stringify({runId:t}),new Date().toISOString()),await n.prepare("SELECT pg_notify(?, ?)").get("crtr_signal",`machine:${e}`)})}d(X,"queueCronCancel");async function ve(e,t,n){const{runId:l,outOfBand:o,startedAtIso:r,startedAtMs:a}=t;let c;try{if(e.isolated)throw new Error("an isolated cron needs a root launcher, which a machine does not have");if(c=await S(e,l),!o&&e.recur!=null){const i=await v(e,a);if(i!==null)throw i}}catch(i){return await w(e,{runId:l,outOfBand:o,startedAtIso:r,startedAtMs:a},"run could not be armed",i)}return await g(e,t,c,n)}d(ve,"fireOnMachine");async function Se(e,t){const n=e.run_execution_id,l=e.run_started_at,o=Date.parse(l),r=e.run_out_of_band,a={environmentId:t,runId:n,outOfBand:r,startedAtIso:l,startedAtMs:o};let c;try{if(c=await S(e,n),!r&&e.recur!=null&&Date.parse(e.fire_at)<=o){const i=await v(e,o);if(i!==null)throw i}}catch(i){return await w(e,a,"run could not be armed",i)}return await g(e,a,c,()=>{})}d(Se,"resumeMachineFire");async function g(e,t,n,l){const{environmentId:o,runId:r,outOfBand:a,startedAtIso:c,startedAtMs:i}=t,m={runId:r,replaced:!1,selfCanceled:!1,kill:d(()=>{X(o,r).catch(u=>{_({level:"error",event:"cron.machine.cancel_failed",error:u,fields:{cron_id:e.cron_id,run_id:r}})})},"kill")};J(e.cron_id,m),l();try{let u=null,f=null;for(;u===null&&f===null;){try{const s=await Q(e,o,r,n);if(s.notStarted!==!0){u=s;break}_({level:"warn",event:"cron.machine.resend",fields:{cron_id:e.cron_id,run_id:r,why:s.stderrHead}})}catch(s){if(s instanceof b){f=s;break}if(!(s instanceof C)&&!(s instanceof P))throw s;if(a&&s instanceof C){f=s;break}_({level:"warn",event:"cron.machine.resend",fields:{cron_id:e.cron_id,run_id:r,why:s.message}})}if(!m.selfCanceled&&await $(e.cron_id)===null)return null;await x(G)}if(f!==null)return await w(e,{runId:r,outOfBand:a,startedAtIso:c,startedAtMs:i},"run failed on the machine",f);const{exitCode:p,stdoutHead:O,stderrHead:h,durationMs:A,stdoutSha256:y}=u,E=p===-1,I=p===null?new Error((h??"the machine reported no exit").slice(0,V)):null,T=m.replaced,M=m.selfCanceled;return await Y(e,{runId:r,outOfBand:a,startedAtIso:c,startedAtMs:i,finishedAtMs:i+A,code:E?null:p,timedOut:E,processError:I,stdoutHead:O??"",stderrHead:h??"",stdoutSha256:y,wasReplaced:T,wasSelfCanceled:M})}finally{q(e.cron_id,r)}}d(g,"driveFire");export{ve as fireOnMachine,Re as machineEnvironmentOfCron,Ce as machineLinkLive,X as queueCronCancel,Se as resumeMachineFire,Q as sendCronRun};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Cron } from '../../core/canvas/crons.js';
|
|
2
|
+
/** Settle the schedule BEFORE the run: a recurrence advances to its next slot
|
|
3
|
+
* now, so a crash mid-run never re-fires this slot (the lease guards the
|
|
4
|
+
* intra-life overlap; the advanced fire_at guards the crash window).
|
|
5
|
+
*
|
|
6
|
+
* Returns the process error to hand `close` when the recurrence cannot be
|
|
7
|
+
* parsed, else null. That row can never be scheduled again as written — but
|
|
8
|
+
* DELETING it would destroy the user's config AND its whole run history with
|
|
9
|
+
* no record and no escalation, the one outcome the spec has no vocabulary for.
|
|
10
|
+
* It takes the failure path instead, so the row PAUSES and an escalation node
|
|
11
|
+
* arrives holding the parse error. `fire_at` is deliberately left past-due: a
|
|
12
|
+
* paused row is not due, and once someone fixes the recurrence a `cron resume`
|
|
13
|
+
* fires immediately. */
|
|
14
|
+
export declare function advanceRecurrence(c: Cron, startedAtMs: number): Promise<Error | null>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var c=Object.defineProperty;var a=(r,e)=>c(r,"name",{value:e,configurable:!0});import{advanceCron as o,setCronState as i}from"../../core/canvas/crons.js";import{emitEvent as u}from"../../core/events/emit.js";import{nextSlotAfter as s}from"../../core/wake.js";async function p(r,e){let n;try{n=s(r.recur,new Date(e))}catch(t){return u({level:"error",event:"cron.recur.unparseable",error:t,fields:{cron_id:r.cron_id,name:r.name,recur:r.recur}}),await i(r.cron_id,"paused"),new Error(`unparseable recurrence ${JSON.stringify(r.recur)}: ${t.message} \u2014 this cron cannot be rescheduled until its recurrence is fixed (\`crtr cron cancel\` and re-arm it), so it has been paused.`)}return await o(r.cron_id,n),null}a(p,"advanceRecurrence");export{p as advanceRecurrence};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CronPrincipalClass } from '../api/router.js';
|
|
2
|
+
export declare const RUN_TOKEN_PREFIX = "crtr_run_";
|
|
3
|
+
export declare const runTokenFor: (runId: string) => string;
|
|
4
|
+
/** The run id a well-formed token with a valid MAC names, else null. */
|
|
5
|
+
export declare function runIdOfToken(token: string): string | null;
|
|
6
|
+
/** The cron principal of the run `token` names, or null when the token is invalid or the run is no longer the cron's running one. */
|
|
7
|
+
export declare function cronPrincipalForRunToken(token: string): Promise<CronPrincipalClass | null>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var a=Object.defineProperty;var t=(r,e)=>a(r,"name",{value:e,configurable:!0});import{createHmac as l,timingSafeEqual as d}from"node:crypto";import{openDb as f}from"../../core/canvas/db.js";import{getCron as _}from"../../core/canvas/crons.js";import{loadSecretsKey as p}from"../../core/secrets/key.js";const c="crtr_run_",s=t(r=>l("sha256",p().key).update(`cron-run:${r}`).digest("hex"),"mac"),T=t(r=>`${c}${r}.${s(r)}`,"runTokenFor");function m(r){if(!r.startsWith(c))return null;const e=r.slice(c.length),o=e.lastIndexOf(".");if(o<=0)return null;const n=e.slice(0,o),i=Buffer.from(e.slice(o+1)),u=Buffer.from(s(n));return i.length===u.length&&d(i,u)?n:null}t(m,"runIdOfToken");async function b(r){const e=m(r);if(e===null)return null;const o=await(await f()).prepare("SELECT cron_id FROM crons WHERE run_execution_id = ? AND run_state = 'running'").get(e),n=o===void 0?null:await _(o.cron_id);return n===null||n.run_execution_id!==e||n.run_state!=="running"?null:{class:"cron",kind:"cron",cron:n.cron_id,app:n.grantee,sub:n.sub,grant:n.grant_id,scopes:n.scopes,execution:e,from_exec:n.armed_from_exec,origin_run:n.origin_run_id,isolated:n.isolated,anchor:n.anchor_node,uid:-1}}t(b,"cronPrincipalForRunToken");export{c as RUN_TOKEN_PREFIX,b as cronPrincipalForRunToken,m as runIdOfToken,T as runTokenFor};
|
package/dist/daemon/cron-run.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
${F}`.trim().slice(0,
|
|
1
|
+
var q=Object.defineProperty;var i=(e,f)=>q(e,"name",{value:f,configurable:!0});import{randomUUID as N}from"node:crypto";import{CRON_OUTPUT_HEAD_BYTES as H,formatCronOutputHead as O}from"./cron/capture.js";import{clearLiveRun as p,cronDaemonInstanceId as M,ensureCronOwnerLease as Y,registerLiveRun as z}from"./cron/live-runs.js";import{closeCronExecution as G,openCronExecution as J}from"./cron/run-socket.js";import{appProcessSandboxed as K}from"../core/spaces/process.js";import{nodeAgentDir as Q}from"../core/canvas/paths.js";import{uidForApp as V}from"../core/spaces/users.js";import{buildArmedRunContext as W}from"./cron/armed-context.js";import{ensureCronFolder as X}from"./cron/folders.js";import{fireOnMachine as Z,machineEnvironmentOfCron as j,machineLinkLive as ee}from"./cron/machine-fire.js";import{advanceRecurrence as P}from"./cron/recurrence.js";import{checkCronFire as ne,cronPrincipalOf as te}from"./cron/authority.js";import{refusalError as re}from"../core/ledger/index.js";import{startRefusal as oe}from"../core/runtime/start-check.js";import{settleClosedRun as ae,settleUnspawnedRun as D}from"./cron/closed.js";import{settleRun as U}from"./cron/settle.js";import{applyOverlapPolicies as ie,expireDueCrons as se}from"./cron/passes.js";import{dueClockCrons as de,acquireCronRunLease as le,releaseCronRunLease as ue,attachCronRunPid as fe}from"../core/canvas/crons.js";import{emitEvent as me}from"../core/events/emit.js";import{runAppCommand as ce}from"../core/runtime/run-command.js";import{CRON_OUTPUT_HEAD_BYTES as Fe}from"./cron/capture.js";import{cronDaemonInstanceId as Ne,markSelfCanceledCronRun as Ye}from"./cron/live-runs.js";import{recoverStaleCronLeases as Ge}from"./cron/lease-recovery.js";function Pe(e,f){return B(e,f).settled}i(Pe,"executeCron");function B(e,f){const{outOfBand:s}=f;let l=i(()=>{},"admit");const h=new Promise(o=>{l=o}),g=new Promise((o,r)=>{let b=!1;const t=N();let x=i(()=>{},"undo");const S=i(d=>{if(x(),!b){r(d);return}b=!1,ue(e.cron_id,t).then(()=>r(d),u=>{me({level:"error",event:"cron.lease.release_failed",error:u,fields:{cron_id:e.cron_id}}),r(d)})},"fail");(async()=>{const d=Date.now(),u=new Date(d).toISOString(),E={runId:t,outOfBand:s,startedAtIso:u,startedAtMs:d},I=await j(e);if(I!==null&&!s&&!await ee(I)){o(null);return}if(await Y(),!await le(e.cron_id,M,u,s,t)){o(null);return}b=!0;let m=await ne(te(e));if(m.allowed){const n=await oe({grantee:e.grantee,sub:e.sub,grant_id:e.grant_id});n!==null&&(m={allowed:!1,reason:"limit_reached",message:re(n).message})}if(!m.allowed){try{if(!s&&e.recur!=null){const c=await P(e,d);if(c!==null)throw c}}catch(c){D(e,E,"run could not be armed",c).then(o,r);return}const n=Date.now();U(e,{outOfBand:s,outcome:{kind:"refused",reason:m.reason,message:m.message},record:{runId:t,startedAtIso:u,finishedAtMs:n,durationMs:n-d,exitCode:null,stdoutHead:null,stderrHead:`${m.reason}: ${m.message}`.slice(0,H)},lease:"release"}).then(o,r);return}if(I!==null){Z(e,{environmentId:I,runId:t,outOfBand:s,startedAtIso:u,startedAtMs:d},l).then(o,S);return}let v;try{if(e.isolated&&X(e.grantee,e.cron_id),{env:v}=await W(e,t),!s&&e.recur!=null){const n=await P(e,d);if(n!==null)throw n}}catch(n){D(e,E,"run could not be armed",n).then(o,r);return}let A=null,T=!1;const R={runId:t,replaced:!1,selfCanceled:!1,kill:i(()=>{A!==null?A():T=!0},"kill")};z(e.cron_id,R),x=i(()=>p(e.cron_id,t),"undo"),l();const w=K()?{executionId:t,kind:"cron",cronId:e.cron_id,fromExec:e.armed_from_exec,grantee:e.grantee,sub:e.sub,grantId:e.grant_id,scopes:e.scopes,originRunId:e.origin_run_id,isolated:e.isolated,anchor:e.anchor_node,uid:await V(e.grantee),rebuilt:!1,refusal:null}:null,_=i(()=>{w!==null&&G(t)},"closeExecution");x=i(()=>{p(e.cron_id,t),_()},"undo"),i(async()=>(w!==null&&await J(w,e.cwd,e.profile),ce({command:e.command,cwd:e.cwd,env:v,app:e.grantee,isolated:e.isolated,principal:{sub:e.sub,grantId:e.grant_id,scopes:e.scopes},...e.isolated?{folder:e.cwd}:{},...w!==null?{cron:{executionId:t,...e.anchor_node===null||e.isolated?{}:{anchorDir:Q(e.anchor_node,e.grantee)}}}:e.anchor_node===null||e.isolated?{}:{nodeId:e.anchor_node},timeoutMs:e.run_timeout_s*1e3,headBytes:H})),"launch")().then(async n=>{if(n.pid===null){p(e.cron_id,t),_(),n.completion.then(a=>{D(e,E,"run failed to spawn",a.processError??new Error("bash did not provide a process id")).then(o,r)},r);return}x=i(()=>{p(e.cron_id,t),_(),n.terminate()},"undo");let c=!1;const k=i(()=>{n.completion.then(a=>{const y=R.replaced,$=R.selfCanceled,C=w?.refusal??null;if(p(e.cron_id,t),_(),c){o(null);return}if(C!==null&&!y){const L=Date.now(),F=O(a.stderr,a.stderrTruncated);U(e,{outOfBand:s,outcome:{kind:"refused",reason:C.reason,message:C.message},record:{runId:t,startedAtIso:u,finishedAtMs:L,durationMs:L-d,exitCode:a.exitCode,stdoutHead:O(a.stdout,a.stdoutTruncated)||null,stderrHead:`${C.reason}: ${C.message}
|
|
2
|
+
${F}`.trim().slice(0,H)},lease:"release"}).then(o,r);return}ae(e,{runId:t,outOfBand:s,startedAtIso:u,startedAtMs:d,finishedAtMs:Date.now(),code:a.exitCode,timedOut:a.timedOut,processError:a.processError,stdoutHead:O(a.stdout,a.stdoutTruncated),stderrHead:O(a.stderr,a.stderrTruncated),stdoutSha256:a.stdoutSha256,wasReplaced:y,wasSelfCanceled:$}).then(o,r)},r)},"settleCommand");if(!await fe(e.cron_id,M,u,n.pid)&&!R.selfCanceled){c=!0,n.terminate(),k();return}A=n.terminate,T&&n.terminate(),k()},n=>{p(e.cron_id,t),_(),D(e,E,"run failed to spawn",n).then(o,r)}).catch(S)})().catch(S)});return g.then(l,l),{admitted:h,settled:g}}i(B,"startCron");async function Ue(e,f={}){const s=new Date(e).toISOString();await se(s),await ie(e,s);const l=[];for(const h of await de(s)){const{admitted:g,settled:o}=B(h,{outOfBand:!1}),r=o.then(()=>{});r.catch(()=>{}),await g,l.push(r),f.register?.(r)}for(const h of l)h.catch(()=>{});return l}i(Ue,"runDueCrons");export{Fe as CRON_OUTPUT_HEAD_BYTES,Ne as cronDaemonInstanceId,Pe as executeCron,Ye as markSelfCanceledCronRun,Ge as recoverStaleCronLeases,Ue as runDueCrons,B as startCron};
|
package/dist/daemon/crtrd-cli.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var
|
|
1
|
+
var c=Object.defineProperty;var o=(e,r)=>c(e,"name",{value:r,configurable:!0});import"../suppress-experimental-warnings.js";import{runDaemon as p}from"./crtrd.js";import{configureDaemonHttpDispatcher as a}from"./http-dispatcher.js";import{readMachineConfig as s}from"./machine-link/config.js";function u(e){for(let r=0;r<e.length;r++){const t=e[r];if(t==="--tcp")return e[r+1];if(t!==void 0&&t.startsWith("--tcp="))return t.slice(6)}}o(u,"parseTcpArg");function l(e){for(let r=0;r<e.length;r++){const t=e[r],n=t==="--role"?e[r+1]:t?.startsWith("--role=")?t.slice(7):void 0;if(n!==void 0){if(n!=="api"&&n!=="reconcile"&&n!=="machine")throw new Error(`--role must be api, reconcile or machine, got ${n}`);return n}}return s()===void 0?void 0:"machine"}o(l,"parseRoleArg");function f(e){return e.includes("--prepare")}o(f,"parsePrepareArg");const i=process.argv.slice(2);if(i[0]==="export-sqlite"){const{runExportSqlite:e}=await import("./export-sqlite-run.js");process.exitCode=await e()}else if(i[0]==="import-dump"){const{runImportDump:e}=await import("./import-dump-run.js");process.exitCode=await e(i[1],i[2])}else a(),await p({tcp:u(i),prepare:f(i),role:l(i)});
|
package/dist/daemon/crtrd.d.ts
CHANGED
|
@@ -18,10 +18,10 @@ export interface DaemonOpts {
|
|
|
18
18
|
/** Hold after storage preparation until `POST /v1/daemon/admit`. */
|
|
19
19
|
prepare?: boolean;
|
|
20
20
|
/** `api`: serves requests, runs no reconciler lane. `reconcile`: runs the lanes and serves only the probe API.
|
|
21
|
-
* Absent (a Mac): one process doing both, holding the `daemon_owner` claim. */
|
|
21
|
+
* `machine`: a Mac after import; holds the machine link and executes runner calls, no database. Absent (a Mac before import): one process doing both, holding the `daemon_owner` claim. */
|
|
22
22
|
role?: DaemonRole;
|
|
23
23
|
}
|
|
24
|
-
export type DaemonRole = 'api' | 'reconcile';
|
|
24
|
+
export type DaemonRole = 'api' | 'reconcile' | 'machine';
|
|
25
25
|
/** Whether a process of this role runs the reconciler lanes and their boot sweeps (an api process does not). */
|
|
26
26
|
export declare function roleRunsLanes(role: DaemonRole | undefined): boolean;
|
|
27
27
|
/** Whether a process of this role serves conversations, documents and the app listener (a reconcile process does not). */
|
package/dist/daemon/crtrd.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
var er=Object.defineProperty;var i=(e,l)=>er(e,"name",{value:l,configurable:!0});import{applyBootSettings as rr}from"./boot-settings.js";import{startPeerOutbox as tr}from"./peer/outbox.js";import{reportMachineStatus as nr}from"../core/peer/directory.js";import{createPeerRelay as or}from"./peer/relay-service.js";import{writeFileSync as ir,readFileSync as ar,rmSync as sr,mkdirSync as ye,existsSync as b,unlinkSync as _e,chmodSync as be}from"node:fs";import{createServer as J}from"node:http";import{dirname as cr}from"node:path";import{envCrtrdTcp as ke,envDocumentPort as lr}from"../shared/env.js";import{openCanvasConversationStore as dr,serveCanvasConversationStore as pr}from"../core/canvas/sessions.js";import{installConversationStore as mr}from"../core/conversation-store/index.js";import{pollRunObservers as fr}from"./runs/observer.js";import{randomBytes as Re}from"node:crypto";import{apiSocketPath as Pe,crtrHome as ur}from"../core/canvas/paths.js";import{activeAppUsers as wr}from"../core/spaces/users.js";import{ensureAppSpace as hr,ensureUserFiles as vr}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as x}from"../launcher/spec.js";import{launcherRequest as L}from"../launcher/client.js";import{installLauncherEventSink as gr}from"./launcher-events.js";import{initializeEmptyLayout as Sr,layoutNeedsMigration as yr,socketDir as Ee}from"../core/layout.js";import{migrateLooseKeys as _r}from"../core/layout-migrate/loose-keys.js";import{relocateProfileFolders as br}from"../core/profiles/relocate.js";import{getNode as kr,listNodesForSupervision as Ie,listParkedResidentsWithWakeMail as Rr,setFrozen as xe}from"../core/canvas/index.js";import{createApiServer as Pr,closeServer as Te}from"./api/server.js";import{Router as Er}from"./api/router.js";import{healthRoutes as Ir}from"./api/handlers/health.js";import{createDocumentListener as xr}from"./api/document-listener.js";import{documentSecret as Tr}from"./api/document-secret.js";import{createAppListener as Cr}from"./api/app-listener.js";import{registerMachineEnvironment as Dr}from"../core/environments.js";import{registerBlaxelRunnerFromEnv as Lr}from"../core/secrets/blaxel-register.js";import{markCloudRole as $r,resolvePerson as Ar,retireLocalPerson as Nr,setPersonId as Mr}from"../core/canvas/person.js";import{backfillNodeApps as Gr,runPermissionsBoot as Or}from"../core/permissions/boot.js";import{startRunNotices as Fr}from"./run-notices.js";import{startGrantEffects as Br}from"./reconcilers/grant-effects.js";import{createGrantSync as jr,GRANT_SYNC_INTERVAL_S as zr}from"../core/grants/sync.js";import{sweepSandboxes as Hr}from"../core/grants/on-sync.js";import{rebuildCronExecutions as Ur}from"./cron/run-socket.js";import{hasSyncedGrants as qr}from"../core/grants/mirror.js";import{readConfig as se}from"../core/config.js";import{reconcileUndeliveredTickets as Wr}from"./human/sweep.js";import{resetHandedOffDeliveries as Jr}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as Kr}from"../core/canvas/daemon-actions.js";import{sweepReviews as Vr}from"./review/sweep.js";import{pidfilePath as ce}from"./pidfile.js";import{rendererWarning as Yr}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as Qr}from"./supervise-cadence.js";import{createResumeDetector as Xr}from"./resume-detector.js";import{setFatalFaultAnnouncer as Zr}from"../core/canvas/recovery.js";import{BrokerSupervisionReconciler as et}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as rt}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as tt}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as nt}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as ot}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as it}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as at}from"./reconcilers/action-lane.js";import{endLiveExecution as st,migrateLegacyExecutionIdentities as ct}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as lt}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as dt}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as pt}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as mt}from"./reconcilers/storage-due-work.js";import{assertLedgerEnv as ft}from"../core/ledger/rates.js";import{LedgerSandboxReconciler as ut}from"./reconcilers/ledger-sandbox.js";import{LedgerStorageReconciler as wt}from"./reconcilers/ledger-storage.js";import{LedgerR2Reconciler as ht}from"./reconcilers/ledger-r2.js";import{BashDeadlineReconciler as vt}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as gt}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as St}from"./reconcilers/structured-request-lane.js";import{RecipeImageReconciler as yt,subscribeImageBuilt as _t}from"./reconcilers/recipe-image.js";import{reconcileStructuredRequestSidecars as bt}from"./structured-requests/boot.js";import{captureLivenessSnapshot as Ce,capturePidCommand as kt,captureTeardownSnapshot as De,isPidAlive as Rt,isAnyPidAlive as Le,killProcessTreePids as $e}from"../core/canvas/pid.js";import{bindFleet as Pt,boundFleet as Et}from"../core/runtime/fleet.js";import{bindDaemonControl as It,clearDaemonControl as xt}from"./control.js";import{execDaemon as Tt,spawnDaemon as Ct}from"./manage.js";import{reconcileAndMigrateBoot as Dt}from"../core/canvas/boot.js";import{resolveInstallId as Lt}from"../core/canvas/install-id.js";import{setInstallId as $t}from"../core/runtime/nodes.js";import{claimDaemonOwnership as At,daemonOwnershipApplies as Nt,daemonOwnershipClaimed as Mt,releaseDaemonOwnership as Ae}from"../core/canvas/daemon-owner.js";import{canvasDatabaseUrl as Gt,closeAllDbs as le,openDb as Ne,withCanvasWrite as Ot}from"../core/canvas/db.js";import{importSqliteCanvasIfPresent as Ft}from"../core/canvas/import-sqlite.js";import{ensurePostgres as Bt,ensurePostgresHealthy as jt,stopPostgres as zt,withPostgresStartLock as Ht}from"./postgres/index.js";import{brokerSignalBus as F}from"./broker-signals/bus.js";import{brokerSignalStreams as Ut}from"./api/handlers/broker-signals.js";import{setRequestsStartRunObservers as qt}from"./api/handlers/broker-ops.js";import{reconcilePackages as Wt}from"../core/graph/package-reconcile.js";import{startRepoSync as Jt}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as Kt,reapStaleSpares as Vt}from"../core/runtime/warm-pool.js";import{DaemonFleet as Yt}from"./fleet.js";import{copyNodeFilesToRows as Qt}from"../core/runtime/first-boot-rows.js";import{BOOT_MIGRATIONS as Xt}from"./boot-migrations.js";import{emitEvent as u}from"../core/events/emit.js";import{operationIdContext as _}from"../core/events/operation-id.js";import{bindDaemonEventSource as Zt}from"../core/events/source.js";import{activateCanvasImport as en,MigrationBlockedError as rn,runForcedCanvasImport as tn}from"../migrations/activation.js";import{adoptInstallPerson as nn,convertLegacyScopedStore as on,personStoresCopied as an}from"../core/scoped-state/migrate.js";import{readLegacyUserSnapshot as sn}from"../core/scoped-state/legacy-profiles.js";import{heldPersons as Me,installPreludeSnapshot as cn}from"../core/scoped-state/person-store.js";import{loadPersonSettings as Ge}from"../core/scoped-state/person-rows.js";import{userScopeRoot as Oe}from"../core/scope.js";function de(e,l){return e.includes(` --canvas-home ${l} `)}i(de,"brokerCommandDeclaresCanvasHome");function pe(e){return/ --epoch (\S+) /.exec(e)?.[1]??null}i(pe,"brokerCommandEpoch");const ln=5e3,Fe=150;async function dn(e){const l=Ce();if(l===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 f=ur(),m=[];for(const[n,d]of l.processes){const y=d.command;if(d.zombie||!y.includes("broker-cli.js")||!de(y,f)||pe(y)===e)continue;const k=y.trim().split(/\s+/).at(-1)??"";k===""||k.endsWith("broker-cli.js")||m.push({pid:n,nodeId:k})}let v=0;const a=[];for(const n of m){const d=kt(n.pid);if(d===null||!d.includes("broker-cli.js")||!de(d,f)||pe(d)===e||d.trim().split(/\s+/).at(-1)!==n.nodeId)continue;const y=De(n.pid,null);if(!y.reused){try{process.platform==="linux"&&b(x)?await L({operation:"stop",pid:n.pid}):process.kill(n.pid,"SIGTERM")}catch{continue}v++,a.push({pid:n.pid,snap:y})}}const w=Date.now()+ln;for(;Date.now()<w&&a.some(n=>Le(n.snap.tree.length>0?n.snap.tree:[n.pid]));)await new Promise(n=>setTimeout(n,Fe));let g=0;for(const n of a){const d=n.snap.tree.length>0?n.snap.tree:[n.pid];Le(d)&&(process.platform==="linux"&&b(x)?await L({operation:"stop",pid:n.pid}):$e(d,"SIGKILL",n.snap.identities),g++)}g>0&&await new Promise(n=>setTimeout(n,Fe));const p=Ce();if(p===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 c=[];for(const[n,d]of p.processes){const y=d.command;d.zombie||!y.includes("broker-cli.js")||de(y,f)&&pe(y)!==e&&c.push(n)}if(c.length>0)throw new Error(`prior-epoch broker census could not prove termination of pid(s): ${c.join(", ")}`);u({level:"info",event:"daemon.census.completed",fields:{prior_epoch_brokers:m.length,sigtermed:v,sigkilled:g}})}i(dn,"reapPriorEpochBrokers");const pn=5e3;async function mn(e){const l=Array.from(e.entries());if(l.length===0)return;for(const[a]of l){const w=(await kr(a))?.status;(w==="active"||w==="idle")&&await xe(a,new Date().toISOString())}const f=l.map(([,a])=>({pid:a.pid,snap:De(a.pid,null),exited:a.handle.exited}));for(const a of f)if(!a.snap.reused)try{process.platform==="linux"&&b(x)?await L({operation:"stop",pid:a.pid}):process.kill(a.pid,"SIGTERM")}catch{}const m=new Promise(a=>{setTimeout(a,pn).unref()});await Promise.allSettled(f.map(a=>Promise.race([a.exited.then(()=>{}),m])));const v=[];for(const a of f)Rt(a.pid)&&(process.platform==="linux"&&b(x)?await L({operation:"stop",pid:a.pid}):$e(a.snap.tree.length>0?a.snap.tree:[a.pid],"SIGKILL",a.snap.identities),v.push(a));await Promise.all(v.map(a=>a.exited));for(const[a]of l)await st(a,{reason:"daemon-teardown"});u({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:f.length,sigkilled:v.length}})}i(mn,"teardownFleet");function Be(){ye(cr(ce()),{recursive:!0}),ir(ce(),String(process.pid),"utf8")}i(Be,"writePidfile");function je(e){return typeof e=="object"&&e!==null&&e.code==="ENOENT"}i(je,"isMissingPathError");function ze(e){const l=ce();let f;try{f=ar(l,"utf8")}catch(m){if(je(m))return;throw m}if(f.trim()!==String(e))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${e})`);try{sr(l)}catch(m){if(je(m))return;throw m}}i(ze,"removeOwnedPidfileMirror");const fn={acceptsDetachedWork:i(()=>!0,"acceptsDetachedWork"),registerDetached:i(e=>{e.catch(()=>{})},"registerDetached")},K=new et;Zr(e=>K.handleFatalFault(e,!1));const un=new rt,wn=new tt,hn=new nt,vn=new ot,gn=new it,Sn=new at,yn=new dt,_n=new pt,bn=new mt,kn=new ut,Rn=new wt,Pn=new ht,En=new vt,In=new gt,xn=new St,Tn=new yt;async function Cn(e=Date.now(),l=fn){const f=Et(),m=await K.runFleetPreamble(f);let v,a;try{const g=await Ie();v=g.filter(c=>c.status==="active"||c.status==="idle"),a=g.filter(c=>c.status==="done"||c.status==="canceled"||c.status==="dead");const p=new Set(a.map(c=>c.node_id));a.push(...(await Rr()).filter(c=>!p.has(c.node_id)))}catch(g){u({level:"error",event:"canvas.node_list.failed",error:g});return}await K.run(e,{rows:v,fleet:f,lifecycle:l}),await un.run(e,{rows:v});const w=await wn.run(e,{rows:[...v,...a],fleet:f});await hn.run(e),await xn.run(e),await Tn.run(e,{lifecycle:l}),await vn.run(e,{lifecycle:l}),await gn.run(e,{lifecycle:l}),await Sn.run(e,{lifecycle:l}),await yn.run(e,{lifecycle:l}),await _n.run(e,{lifecycle:l,fleet:f}),bn.run(e,{lifecycle:l}),kn.run(e,{lifecycle:l}),Rn.run(e,{lifecycle:l}),Pn.run(e,{lifecycle:l}),await fr(),In.run(e,{fleet:f,lifecycle:l}),await En.run(e,{rows:v}),K.warnIfSaturated({fleet:f,lifecycle:l,thresholds:m,frozen:w})}i(Cn,"superviseTick");function V(e){return e!=="api"}i(V,"roleRunsLanes");function $(e){return e!=="reconcile"}i($,"roleServesRequests");const Dn=66e4;function Ln(e){const l=new Er().registerAll(Ir(()=>null).filter(n=>n.pattern==="/healthz")),f=i((n,d)=>{const y=(n.url??"/").split("?")[0];if((n.method??"GET").toUpperCase()==="GET"&&y==="/healthz"){l.handle(n,d,null,{class:"person"});return}d.writeHead(404,{"content-type":"application/json; charset=utf-8"}),d.end(JSON.stringify({error:{code:"not_found",message:"this process serves only /healthz",origin:"runtime"}}))},"handler"),m=Pe();b(m)&&_e(m);const v=J(f),a=new Promise((n,d)=>{v.once("error",d),v.listen(m,()=>{v.off("error",d),be(m,384),n()})});let w,g=null;const p=e.tcp??ke()??se("user").api.tcp;if(p!==void 0&&p!==""){const n=/^(.*):(\d+)$/.exec(p.trim());if(n===null||Number(n[2])>65535)throw new Error(`invalid reconcile probe TCP address: ${p}`);const d=J(f);d.on("error",y=>u({event:"api.server.failed",level:"error",error:y})),d.listen(Number(n[2]),n[1]===""?"0.0.0.0":n[1],()=>{g=`${n[1]===""?"0.0.0.0":n[1]}:${n[2]}`}),w=d}let c=!1;return{ready:a,tcpAddress:i(()=>g,"tcpAddress"),async close(n={}){if(!c){c=!0,await Promise.all([Te(v,new Set,n.graceMs),w===void 0?Promise.resolve():Te(w,new Set,n.graceMs)]);try{b(m)&&_e(m)}catch{}}}}}i(Ln,"createProbeServer");const $n="daemon is draining for shutdown and admits no new work";async function An(e){ye(Ee(),{recursive:!0,mode:493}),process.platform==="linux"&&be(Ee(),493);const l=Pe();if(b(l))throw new Error(`layout refusal cannot bind occupied socket: ${l}`);const f=J((p,c)=>{if(p.method==="GET"&&p.url==="/healthz"){c.writeHead(200,{"content-type":"application/json; charset=utf-8"}),c.end(JSON.stringify({ok:!1,daemon_up:!0,store_accessible:!1,brokers_reconciled:!1,startup_phase:"blocked",startup_blocked:"layout_migration_required"}));return}c.writeHead(503,{"content-type":"application/json; charset=utf-8"}),c.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((p,c)=>{f.once("error",c),f.listen(l,()=>{f.off("error",c),p()})});let m;const v=e.tcp??ke();if(v){const p=/^(.*):(\d+)$/.exec(v);if(!p||Number(p[2])>65535)throw new Error(`invalid refusal TCP address: ${v}`);const c=J(f.listeners("request")[0]);await new Promise((n,d)=>{c.once("error",d),c.listen(Number(p[2]),p[1],()=>{c.off("error",d),n()})}),m=c}Be();let a;const w=i(()=>(a??=(async()=>{f.closeAllConnections(),m?.closeAllConnections(),await Promise.all([f,m].filter(p=>p!==void 0).map(p=>new Promise((c,n)=>p.close(d=>d?n(d):c())))),ze(process.pid),process.off("SIGINT",g),process.off("SIGTERM",g)})(),a),"cleanup"),g=i(()=>{w().then(()=>process.exit(0),p=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(p)}
|
|
2
|
-
`),process.exit(1)})},"onSignal");return process.on("SIGINT",g),process.on("SIGTERM",g),w}i(
|
|
3
|
-
`)},"logPostgres"),f=!!process.env.CRTR_TEST_PG_URL,
|
|
1
|
+
var er=Object.defineProperty;var i=(e,c)=>er(e,"name",{value:c,configurable:!0});import{applyBootSettings as rr}from"./boot-settings.js";import{startPeerOutbox as tr}from"./peer/outbox.js";import{reportMachineStatus as nr}from"../core/peer/directory.js";import{createPeerRelay as or}from"./peer/relay-service.js";import{writeFileSync as ir,readFileSync as ar,rmSync as sr,mkdirSync as ye,existsSync as b,unlinkSync as _e,chmodSync as be}from"node:fs";import{createServer as J}from"node:http";import{dirname as cr}from"node:path";import{envCrtrdTcp as ke,envDocumentPort as lr}from"../shared/env.js";import{openCanvasConversationStore as dr,serveCanvasConversationStore as mr}from"../core/canvas/sessions.js";import{installConversationStore as pr}from"../core/conversation-store/index.js";import{pollRunObservers as fr}from"./runs/observer.js";import{randomBytes as Re}from"node:crypto";import{apiSocketPath as Pe,crtrHome as ur}from"../core/canvas/paths.js";import{activeAppUsers as wr}from"../core/spaces/users.js";import{ensureAppSpace as hr,ensureUserFiles as vr}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as x}from"../launcher/spec.js";import{launcherRequest as L}from"../launcher/client.js";import{installLauncherEventSink as gr}from"./launcher-events.js";import{initializeEmptyLayout as Sr,layoutNeedsMigration as yr,socketDir as Ee}from"../core/layout.js";import{migrateLooseKeys as _r}from"../core/layout-migrate/loose-keys.js";import{relocateProfileFolders as br}from"../core/profiles/relocate.js";import{getNode as kr,listNodesForSupervision as Ie,listParkedResidentsWithWakeMail as Rr,setFrozen as xe}from"../core/canvas/index.js";import{createApiServer as Pr,closeServer as Te}from"./api/server.js";import{Router as Er}from"./api/router.js";import{healthRoutes as Ir}from"./api/handlers/health.js";import{createDocumentListener as xr}from"./api/document-listener.js";import{documentSecret as Tr}from"./api/document-secret.js";import{createAppListener as Dr}from"./api/app-listener.js";import{registerMachineEnvironment as Cr}from"../core/environments.js";import{registerBlaxelRunnerFromEnv as Lr}from"../core/secrets/blaxel-register.js";import{markCloudRole as $r,resolvePerson as Ar,retireLocalPerson as Nr,setPersonId as Mr}from"../core/canvas/person.js";import{backfillNodeApps as Gr,runPermissionsBoot as Or}from"../core/permissions/boot.js";import{startRunNotices as Fr}from"./run-notices.js";import{startGrantEffects as Br}from"./reconcilers/grant-effects.js";import{createGrantSync as jr,GRANT_SYNC_INTERVAL_S as zr}from"../core/grants/sync.js";import{sweepSandboxes as Hr}from"../core/grants/on-sync.js";import{rebuildCronExecutions as Ur}from"./cron/run-socket.js";import{hasSyncedGrants as qr}from"../core/grants/mirror.js";import{readConfig as se}from"../core/config.js";import{reconcileUndeliveredTickets as Wr}from"./human/sweep.js";import{resetHandedOffDeliveries as Jr}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as Kr}from"../core/canvas/daemon-actions.js";import{sweepReviews as Vr}from"./review/sweep.js";import{pidfilePath as ce}from"./pidfile.js";import{rendererWarning as Yr}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as Qr}from"./supervise-cadence.js";import{createResumeDetector as Xr}from"./resume-detector.js";import{setFatalFaultAnnouncer as Zr}from"../core/canvas/recovery.js";import{BrokerSupervisionReconciler as et}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as rt}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as tt}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as nt}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as ot}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as it}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as at}from"./reconcilers/action-lane.js";import{endLiveExecution as st,migrateLegacyExecutionIdentities as ct}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as lt}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as dt}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as mt}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as pt}from"./reconcilers/storage-due-work.js";import{PurgeDueWorkReconciler as ft}from"./reconcilers/purge-due-work.js";import{assertLedgerEnv as ut}from"../core/ledger/rates.js";import{LedgerSandboxReconciler as wt}from"./reconcilers/ledger-sandbox.js";import{LedgerStorageReconciler as ht}from"./reconcilers/ledger-storage.js";import{LedgerR2Reconciler as vt}from"./reconcilers/ledger-r2.js";import{ImportLaneReconciler as gt}from"./reconcilers/import-lane.js";import{BashDeadlineReconciler as St}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as yt}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as _t}from"./reconcilers/structured-request-lane.js";import{RecipeImageReconciler as bt,subscribeImageBuilt as kt}from"./reconcilers/recipe-image.js";import{reconcileStructuredRequestSidecars as Rt}from"./structured-requests/boot.js";import{captureLivenessSnapshot as De,capturePidCommand as Pt,captureTeardownSnapshot as Ce,isPidAlive as Et,isAnyPidAlive as Le,killProcessTreePids as $e}from"../core/canvas/pid.js";import{bindFleet as It,boundFleet as xt}from"../core/runtime/fleet.js";import{bindDaemonControl as Tt,clearDaemonControl as Dt}from"./control.js";import{execDaemon as Ct,spawnDaemon as Lt}from"./manage.js";import{reconcileAndMigrateBoot as $t}from"../core/canvas/boot.js";import{resolveInstallId as At}from"../core/canvas/install-id.js";import{setInstallId as Nt}from"../core/runtime/nodes.js";import{claimDaemonOwnership as Mt,daemonOwnershipApplies as Gt,daemonOwnershipClaimed as Ot,releaseDaemonOwnership as Ae}from"../core/canvas/daemon-owner.js";import{canvasDatabaseUrl as Ft,closeAllDbs as le,openDb as Ne,withCanvasWrite as Bt}from"../core/canvas/db.js";import{importSqliteCanvasIfPresent as jt}from"../core/canvas/import-sqlite.js";import{ensurePostgres as zt,ensurePostgresHealthy as Ht,stopPostgres as Ut,withPostgresStartLock as qt}from"./postgres/index.js";import{brokerSignalBus as F}from"./broker-signals/bus.js";import{brokerSignalStreams as Wt}from"./api/handlers/broker-signals.js";import{setRequestsStartRunObservers as Jt}from"./api/handlers/broker-ops.js";import{reconcilePackages as Kt}from"../core/graph/package-reconcile.js";import{startRepoSync as Vt}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as Yt,reapStaleSpares as Qt}from"../core/runtime/warm-pool.js";import{DaemonFleet as Xt}from"./fleet.js";import{copyNodeFilesToRows as Zt}from"../core/runtime/first-boot-rows.js";import{BOOT_MIGRATIONS as en}from"./boot-migrations.js";import{emitEvent as u}from"../core/events/emit.js";import{operationIdContext as _}from"../core/events/operation-id.js";import{bindDaemonEventSource as rn}from"../core/events/source.js";import{activateCanvasImport as tn,MigrationBlockedError as nn,runForcedCanvasImport as on}from"../migrations/activation.js";import{adoptInstallPerson as an,convertLegacyScopedStore as sn,personStoresCopied as cn}from"../core/scoped-state/migrate.js";import{readLegacyUserSnapshot as ln}from"../core/scoped-state/legacy-profiles.js";import{heldPersons as Me,installPreludeSnapshot as dn}from"../core/scoped-state/person-store.js";import{loadPersonSettings as Ge}from"../core/scoped-state/person-rows.js";import{userScopeRoot as Oe}from"../core/scope.js";function de(e,c){return e.includes(` --canvas-home ${c} `)}i(de,"brokerCommandDeclaresCanvasHome");function me(e){return/ --epoch (\S+) /.exec(e)?.[1]??null}i(me,"brokerCommandEpoch");const mn=5e3,Fe=150;async function pn(e){const c=De();if(c===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 f=ur(),p=[];for(const[n,d]of c.processes){const y=d.command;if(d.zombie||!y.includes("broker-cli.js")||!de(y,f)||me(y)===e)continue;const k=y.trim().split(/\s+/).at(-1)??"";k===""||k.endsWith("broker-cli.js")||p.push({pid:n,nodeId:k})}let v=0;const a=[];for(const n of p){const d=Pt(n.pid);if(d===null||!d.includes("broker-cli.js")||!de(d,f)||me(d)===e||d.trim().split(/\s+/).at(-1)!==n.nodeId)continue;const y=Ce(n.pid,null);if(!y.reused){try{process.platform==="linux"&&b(x)?await L({operation:"stop",pid:n.pid}):process.kill(n.pid,"SIGTERM")}catch{continue}v++,a.push({pid:n.pid,snap:y})}}const w=Date.now()+mn;for(;Date.now()<w&&a.some(n=>Le(n.snap.tree.length>0?n.snap.tree:[n.pid]));)await new Promise(n=>setTimeout(n,Fe));let g=0;for(const n of a){const d=n.snap.tree.length>0?n.snap.tree:[n.pid];Le(d)&&(process.platform==="linux"&&b(x)?await L({operation:"stop",pid:n.pid}):$e(d,"SIGKILL",n.snap.identities),g++)}g>0&&await new Promise(n=>setTimeout(n,Fe));const m=De();if(m===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[n,d]of m.processes){const y=d.command;d.zombie||!y.includes("broker-cli.js")||de(y,f)&&me(y)!==e&&l.push(n)}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:p.length,sigtermed:v,sigkilled:g}})}i(pn,"reapPriorEpochBrokers");const fn=5e3;async function un(e){const c=Array.from(e.entries());if(c.length===0)return;for(const[a]of c){const w=(await kr(a))?.status;(w==="active"||w==="idle")&&await xe(a,new Date().toISOString())}const f=c.map(([,a])=>({pid:a.pid,snap:Ce(a.pid,null),exited:a.handle.exited}));for(const a of f)if(!a.snap.reused)try{process.platform==="linux"&&b(x)?await L({operation:"stop",pid:a.pid}):process.kill(a.pid,"SIGTERM")}catch{}const p=new Promise(a=>{setTimeout(a,fn).unref()});await Promise.allSettled(f.map(a=>Promise.race([a.exited.then(()=>{}),p])));const v=[];for(const a of f)Et(a.pid)&&(process.platform==="linux"&&b(x)?await L({operation:"stop",pid:a.pid}):$e(a.snap.tree.length>0?a.snap.tree:[a.pid],"SIGKILL",a.snap.identities),v.push(a));await Promise.all(v.map(a=>a.exited));for(const[a]of c)await st(a,{reason:"daemon-teardown"});u({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:f.length,sigkilled:v.length}})}i(un,"teardownFleet");function Be(){ye(cr(ce()),{recursive:!0}),ir(ce(),String(process.pid),"utf8")}i(Be,"writePidfile");function je(e){return typeof e=="object"&&e!==null&&e.code==="ENOENT"}i(je,"isMissingPathError");function ze(e){const c=ce();let f;try{f=ar(c,"utf8")}catch(p){if(je(p))return;throw p}if(f.trim()!==String(e))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${e})`);try{sr(c)}catch(p){if(je(p))return;throw p}}i(ze,"removeOwnedPidfileMirror");const wn={acceptsDetachedWork:i(()=>!0,"acceptsDetachedWork"),registerDetached:i(e=>{e.catch(()=>{})},"registerDetached")},K=new et;Zr(e=>K.handleFatalFault(e,!1));const hn=new rt,vn=new tt,gn=new nt,Sn=new ot,yn=new it,_n=new at,bn=new dt,kn=new mt,Rn=new pt,Pn=new ft,En=new wt,In=new ht,xn=new vt,Tn=new gt,Dn=new St,Cn=new yt,Ln=new _t,$n=new bt;async function An(e=Date.now(),c=wn){const f=xt(),p=await K.runFleetPreamble(f);let v,a;try{const g=await Ie();v=g.filter(l=>l.status==="active"||l.status==="idle"),a=g.filter(l=>l.status==="done"||l.status==="canceled"||l.status==="dead");const m=new Set(a.map(l=>l.node_id));a.push(...(await Rr()).filter(l=>!m.has(l.node_id)))}catch(g){u({level:"error",event:"canvas.node_list.failed",error:g});return}await K.run(e,{rows:v,fleet:f,lifecycle:c}),await hn.run(e,{rows:v});const w=await vn.run(e,{rows:[...v,...a],fleet:f});await gn.run(e),await Ln.run(e),await $n.run(e,{lifecycle:c}),await Sn.run(e,{lifecycle:c}),await yn.run(e,{lifecycle:c}),await _n.run(e,{lifecycle:c}),await bn.run(e,{lifecycle:c}),await kn.run(e,{lifecycle:c,fleet:f}),Rn.run(e,{lifecycle:c}),Pn.run(e,{lifecycle:c}),En.run(e,{lifecycle:c}),In.run(e,{lifecycle:c}),xn.run(e,{lifecycle:c}),Tn.run(e,{lifecycle:c}),await fr(),Cn.run(e,{fleet:f,lifecycle:c}),await Dn.run(e,{rows:v}),K.warnIfSaturated({fleet:f,lifecycle:c,thresholds:p,frozen:w})}i(An,"superviseTick");function V(e){return e===void 0||e==="reconcile"}i(V,"roleRunsLanes");function $(e){return e===void 0||e==="api"}i($,"roleServesRequests");const Nn=66e4;function Mn(e){const c=new Er().registerAll(Ir(()=>null).filter(n=>n.pattern==="/healthz")),f=i((n,d)=>{const y=(n.url??"/").split("?")[0];if((n.method??"GET").toUpperCase()==="GET"&&y==="/healthz"){c.handle(n,d,null,{class:"person"});return}d.writeHead(404,{"content-type":"application/json; charset=utf-8"}),d.end(JSON.stringify({error:{code:"not_found",message:"this process serves only /healthz",origin:"runtime"}}))},"handler"),p=Pe();b(p)&&_e(p);const v=J(f),a=new Promise((n,d)=>{v.once("error",d),v.listen(p,()=>{v.off("error",d),be(p,384),n()})});let w,g=null;const m=e.tcp??ke()??se("user").api.tcp;if(m!==void 0&&m!==""){const n=/^(.*):(\d+)$/.exec(m.trim());if(n===null||Number(n[2])>65535)throw new Error(`invalid reconcile probe TCP address: ${m}`);const d=J(f);d.on("error",y=>u({event:"api.server.failed",level:"error",error:y})),d.listen(Number(n[2]),n[1]===""?"0.0.0.0":n[1],()=>{g=`${n[1]===""?"0.0.0.0":n[1]}:${n[2]}`}),w=d}let l=!1;return{ready:a,tcpAddress:i(()=>g,"tcpAddress"),async close(n={}){if(!l){l=!0,await Promise.all([Te(v,new Set,n.graceMs),w===void 0?Promise.resolve():Te(w,new Set,n.graceMs)]);try{b(p)&&_e(p)}catch{}}}}}i(Mn,"createProbeServer");const Gn="daemon is draining for shutdown and admits no new work";async function On(e){ye(Ee(),{recursive:!0,mode:493}),process.platform==="linux"&&be(Ee(),493);const c=Pe();if(b(c))throw new Error(`layout refusal cannot bind occupied socket: ${c}`);const f=J((m,l)=>{if(m.method==="GET"&&m.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((m,l)=>{f.once("error",l),f.listen(c,()=>{f.off("error",l),m()})});let p;const v=e.tcp??ke();if(v){const m=/^(.*):(\d+)$/.exec(v);if(!m||Number(m[2])>65535)throw new Error(`invalid refusal TCP address: ${v}`);const l=J(f.listeners("request")[0]);await new Promise((n,d)=>{l.once("error",d),l.listen(Number(m[2]),m[1],()=>{l.off("error",d),n()})}),p=l}Be();let a;const w=i(()=>(a??=(async()=>{f.closeAllConnections(),p?.closeAllConnections(),await Promise.all([f,p].filter(m=>m!==void 0).map(m=>new Promise((l,n)=>m.close(d=>d?n(d):l())))),ze(process.pid),process.off("SIGINT",g),process.off("SIGTERM",g)})(),a),"cleanup"),g=i(()=>{w().then(()=>process.exit(0),m=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(m)}
|
|
2
|
+
`),process.exit(1)})},"onSignal");return process.on("SIGINT",g),process.on("SIGTERM",g),w}i(On,"serveLayoutRefusal");async function Fn(e={}){if(e.role==="machine")return(await import("./machine-daemon.js")).runMachineDaemon();if(yr())return On(e);Sr();const c=i(r=>{process.stderr.write(`[crtrd] ${r}
|
|
3
|
+
`)},"logPostgres"),f=!!process.env.CRTR_TEST_PG_URL,p=Gt(e.role);Jt(V(e.role));const v=i(async()=>p?await Mt(Re(16).toString("hex")):{claimed:!0,claim:{pid:process.pid,pid_identity:"",token:Re(16).toString("hex")}},"claimOwnership"),{database:a,ownership:w}=f?{database:{url:await Ft()},ownership:await v()}:await qt(async()=>({database:await zt({log:c,startLockHeld:!0}),ownership:await v()}));if(!w.claimed){if(w.reason==="incumbent_alive")process.stderr.write(`[crtrd] already running (pid ${w.incumbentPid??"?"})
|
|
4
4
|
`);else{const r=[w.incumbentPid!==void 0?`pid ${w.incumbentPid}`:void 0,w.verdict!==void 0?`verdict ${w.verdict}`:void 0].filter(h=>h!==void 0),o=r.length>0?` (${r.join(", ")})`:"";process.stderr.write(`[crtrd] claim lost: ${w.reason}${o}
|
|
5
|
-
`),w.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"),(w.reason==="own_identity_indeterminate"||w.reason==="incumbent_malformed")&&(process.exitCode=1)}return async()=>{}}const g=w.claim;let
|
|
5
|
+
`),w.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"),(w.reason==="own_identity_indeterminate"||w.reason==="incumbent_malformed")&&(process.exitCode=1)}return async()=>{}}const g=w.claim;let m=null,l=null,n=null,d,y="";try{p&&await pn(g.token);for(const t of br())process.stderr.write(`[crtrd] ${t}
|
|
6
6
|
`);for(const t of await _r())process.stderr.write(`[crtrd] ${t}
|
|
7
|
-
`);await Ne(),await
|
|
7
|
+
`);await Ne(),await jt(a.url,c);const r=e.role===void 0&&!await cn();let o=null;r&&(sn({kind:"user",root:Oe()}),o=ln(Oe())),e.role===void 0&&dn(o??{profiles:[],defaults:{},settings:{},state:{}}),await F.listen(a.url);const h=i(t=>{Ge(t).catch(O=>u({level:"error",event:"daemon.settings.reload_failed",error:O}))},"reloadPerson");d=await F.subscribe(t=>{Me().includes(t)&&h(t)},{kinds:["settings"],onListenStart:i(()=>{for(const t of Me())h(t)},"onListenStart")}),e.role!==void 0&&$r();const s=await Bt(async t=>(await Gr(t),await Kt(t)));for(const t of s.conflicts)process.stderr.write(`[crtrd] plugin documents not reconciled: ${t.message}
|
|
8
8
|
`);for(const t of s.invalid)process.stderr.write(`[crtrd] package file is not a document: ${t.path}: ${t.error}
|
|
9
9
|
`);for(const t of s.quarantined)process.stderr.write(`[crtrd] plugin "${t.name}" has a damaged ${t.file} (${t.reason}); ${t.movedTo!==void 0?`moved to ${t.movedTo}`:"left in place and skipped"}
|
|
10
10
|
`);for(const t of s.kinds.refused)process.stderr.write(`[crtrd] plugin "${t.plugin}" kind ${t.kind} not registered: ${t.message}
|
|
11
11
|
`);for(const t of s.kinds.invalid)process.stderr.write(`[crtrd] plugin "${t.plugin}" declares invalid object kinds or scopes; no kinds registered and no app token: ${t.error}
|
|
12
|
-
`);if(y=await
|
|
13
|
-
`)}}catch(r){await F.close();try{if(
|
|
12
|
+
`);if(y=await At(),Nt(y),e.role===void 0&&(m=await Ar(),m!==null)){if(Mr(m.id),await an(m.id),process.platform==="darwin"){const{readConfig:t,updateConfig:O}=await import("../core/config.js");if(t("user").paths?.user_files===void 0){const{homedir:Ze}=await import("node:os");await O("user",Se=>{Se.paths??={},Se.paths.user_files??=Ze()})}}l=await Nr(),l.joinedFrom!==null&&await Ge(l.person),await rr(),n=await Or()}if(process.platform==="linux"&&b(x)&&(gr(),await L({operation:"users.sync",users:await wr()})),p){await lt(g.token);const t=await Zt();Object.values(t).some(O=>O>0)&&process.stderr.write(`[crtrd] node files copied into rows: ${JSON.stringify(t)}
|
|
13
|
+
`)}}catch(r){await F.close();try{if(p){const{released:o}=await Ae(g);if(!o)throw new Error("daemon ownership claim was not released")}await le()}catch(o){throw new AggregateError([r,o],"daemon startup failed and cleanup also failed")}throw r}let k=!0,Y,B=Promise.resolve(),Q=!1,j,X,Z,T;const z=new Set;let ee=!1,pe=!1,P,D,re,H=!1,U,A,N,M,te,ne,oe;const He=Xr();let E,S={phase:"initializing",message:"daemon startup is running the canvas import and normal services are unavailable",blockerPaths:[]},C="initial startup activation",Ue=null;function qe(){return S.phase==="ready"?null:{phase:S.phase,message:S.message,blockerPaths:S.blockerPaths}}i(qe,"startupBlock");function R(r){z.add(r),r.finally(()=>{z.delete(r)}).catch(()=>{})}i(R,"registerDetached");const fe={acceptsDetachedWork:i(()=>k,"acceptsDetachedWork"),registerDetached:R};V(e.role)&&await Vt(fe,Ne);async function We(){try{k=!1,Wt.close(),X?.(),X=void 0,Z?.(),d?.(),Z=void 0,ne?.(),ne=void 0,await F.close(),j!==void 0&&(clearInterval(j),j=void 0),N!==void 0&&(clearInterval(N),N=void 0),oe?.(),oe=void 0;let r;do r=B,await r;while(r!==B);for(;z.size>0;)await Promise.allSettled([...z]);if(T!==void 0&&(p&&await un(T),await T.exitsSettled(),T.dispose()),re!==void 0&&await re.close(),D!==void 0&&!H&&(H=!0,await D.close()),te?.(),te=void 0,M!==void 0){const o=M;M=void 0,await o.stop()}if(A!==void 0){const o=A;A=void 0,await o.close()}if(U!==void 0){const o=U;U=void 0,await o.close()}if(ee||(ze(process.pid),ee=!0),ee&&!pe){if(p){const{released:o}=await Ae(g);if(!o)throw new Error("daemon ownership claim was not released")}pe=!0,await le()}}finally{Dt(),process.off("SIGINT",he),process.off("SIGTERM",ve),process.off("exit",ge)}}i(We,"cleanupAttempt");const q=1e3;let ue=!1;function Je(){return ue?{graceMs:q}:(ue=!0,_.fresh(()=>{u({level:"info",event:"daemon.handover.armed",fields:{pid:process.pid,grace_ms:q}})}),setTimeout(()=>{Ke()},q),{graceMs:q})}i(Je,"requestHandover");async function Ke(){try{await I()}catch(r){const o=r instanceof Error?r.stack??r.message:String(r);process.stderr.write(`[crtrd] handover cleanup failed:
|
|
14
14
|
${o}
|
|
15
|
-
`),process.exit(1)}try{process.platform==="linux"&&b(x)&&(await L({operation:"daemon.handover"}),process.exit(0)),process.pid===1&&await
|
|
16
|
-
`),process.exit(1)}process.exit(0)}i(Ke,"performHandover");function I(){if(P!==void 0)return P;const r=We();return P=r,r.then(()=>{P===r&&(P=void 0)},()=>{P===r&&(P=void 0)}),r}i(I,"requestCleanup");async function Ve(){S={phase:"initializing",message
|
|
15
|
+
`),process.exit(1)}try{process.platform==="linux"&&b(x)&&(await L({operation:"daemon.handover"}),process.exit(0)),process.pid===1&&await Ct();const r=await Lt({waitForJoinLock:!0});_.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){_.fresh(()=>{u({level:"error",event:"daemon.handover.successor_failed",error:r})});const o=r instanceof Error?r.message:String(r);process.stderr.write(`[crtrd] handover successor failed to start: ${o}
|
|
16
|
+
`),process.exit(1)}process.exit(0)}i(Ke,"performHandover");function I(){if(P!==void 0)return P;const r=We();return P=r,r.then(()=>{P===r&&(P=void 0)},()=>{P===r&&(P=void 0)}),r}i(I,"requestCleanup");async function Ve(){S={phase:"initializing",message:Gn,blockerPaths:[]},D!==void 0&&!H&&(H=!0,await D.close({graceMs:Nn}))}i(Ve,"drainApi");async function we(r){try{r==="SIGTERM"&&e.role==="api"&&await Ve(),await I(),await Ut({claimed:i(async()=>{try{return p&&await Ot()}finally{await le()}},"claimed")}),process.exit(0)}catch(o){process.exitCode=1;const h=o instanceof Error?o.stack??`${o.name}: ${o.message}`:String(o);process.stderr.write(`[crtrd] ${r} cleanup failed:
|
|
17
17
|
${h}
|
|
18
|
-
`),process.exit(1)}}i(we,"handleSignal");function he(){we("SIGINT")}i(he,"onSigint");function ve(){we("SIGTERM")}i(ve,"onSigterm");function ge(){I().catch(()=>{})}i(ge,"onExit");async function ie(r){k&&(B=B.then(r).then(()=>{},o=>{_.fresh(()=>{u({level:"error",event:"daemon.lane_job.failed",error:o})})}))}i(ie,"enqueue");async function W(){!k||Q||(He.check()&&E&&(u({level:"info",event:"daemon.resume.detected"}),R(E().catch(()=>{}))),Q=!0,await ie(async()=>{if(Q=!1,!!k)try{f||await
|
|
18
|
+
`),process.exit(1)}}i(we,"handleSignal");function he(){we("SIGINT")}i(he,"onSigint");function ve(){we("SIGTERM")}i(ve,"onSigterm");function ge(){I().catch(()=>{})}i(ge,"onExit");async function ie(r){k&&(B=B.then(r).then(()=>{},o=>{_.fresh(()=>{u({level:"error",event:"daemon.lane_job.failed",error:o})})}))}i(ie,"enqueue");async function W(){!k||Q||(He.check()&&E&&(u({level:"info",event:"daemon.resume.detected"}),R(E().catch(()=>{}))),Q=!0,await ie(async()=>{if(Q=!1,!!k)try{f||await Ht({log:c}),await An(Date.now(),fe)}catch(r){_.fresh(()=>{u({level:"error",event:"daemon.tick.failed",error:r})})}}))}i(W,"enqueueTick");let G=null;async function Ye(){if(G!==null)throw{status:500,code:"admission_failed",message:`daemon admission previously failed: ${G instanceof Error?G.message:String(G)}`};if(S.phase==="ready")return{admitted:!1,already_admitted:!0,startup_phase:"ready"};if(S.phase!=="prepared"&&S.phase!=="recovering")throw{status:409,code:"not_prepared",message:S.message};const r=S.phase==="prepared";try{return await ae(),S={phase:"ready",message:"",blockerPaths:[]},r?{admitted:!0,already_admitted:!1,startup_phase:"ready"}:{admitted:!1,already_admitted:!0,startup_phase:"ready"}}catch(o){throw G=o,{status:500,code:"admission_failed",message:`daemon admission failed: ${o instanceof Error?o.message:String(o)}`}}}i(Ye,"admitStartup");async function Qe(r){if(C!==null)throw{status:409,code:"migration_in_progress",message:`the canvas import is already running (${C})`};C="repair";try{const o=await on({dryRun:r.dryRun});if(r.dryRun||o.pending||S.phase!=="blocked")return{result:o,resumed:!1};if(e.prepare)return S={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]},{result:o,resumed:!1};try{return await ae(),S={phase:"ready",message:"",blockerPaths:[]},{result:o,resumed:!0}}catch(h){const s=h instanceof Error?h.message:String(h);return setTimeout(()=>{I().catch(()=>{})},1e3),{result:o,resumed:!1,resumeError:s}}}finally{C=null}}i(Qe,"repairMigrations");async function Xe(){Y=e.intervalMs??Qr;const r=g.token;T=new Xt({epoch:r,enqueue:ie}),It(T),S={phase:"recovering",message:"daemon startup recovery is running and normal services are unavailable",blockerPaths:[]},_.fresh(()=>{u({level:"info",event:"daemon.started",fields:{pid:process.pid,interval_ms:Y}})});const o=y;_.fresh(()=>{u({level:"info",event:"daemon.install_id.resolved",fields:{install_id:o}})});const h=await Cr(o);if(_.fresh(()=>{u({level:"info",event:"daemon.environment.registered",fields:{environment_id:h.environmentId,created:h.created}})}),_.fresh(()=>{let s;try{s=Yr()}catch{return}s!==null&&u({level:"warn",event:"daemon.startup.warning",fields:{stage:"termrender",warning:s.slice(0,2048)}})}),ut(),e.role!==void 0&&!process.env.CRTR_DIRECTORY_URL)throw new Error("CRTR_DIRECTORY_URL must be set on a cloud role (--role api|reconcile): it names the directory this deployment mints tokens against");if(e.role!==void 0&&Lr(),!V(e.role)){S={phase:"ready",message:"",blockerPaths:[]};return}if(await _.fresh(async()=>{await Vr(),await Wr(),await Jr(Date.now()),await Kr(Date.now())}),await _.fresh(async()=>{const{reconcile:s,migrated:t}=await $t(ct);s.changed&&u({level:"info",event:"daemon.boot_change.reconciled",fields:{cleared_count:s.cleared.length,same_boot_proven:s.sameBootProven}}),t.length>0&&u({level:"info",event:"daemon.pid_identity.migrated",fields:{migrated_count:t.length}})}),await _.fresh(async()=>{for(const s of await Ie())s.status!=="active"||s.pi_pid!=null||s.frozen_at!=null||s.respawn_not_before!=null||(await xe(s.node_id,new Date().toISOString()),u({level:"warn",event:"daemon.startup.launch_debt_recorded",node_id:s.node_id}))}),await _.fresh(async()=>{await Rt();for(const s of en){const t=await s.apply();t.changed&&u({level:"info",event:"daemon.boot_migration.applied",fields:{migration:s.id,...t.summary}})}}),R(Ur()),E){const s=i(()=>{R(E().catch(()=>{})),R(nr().then(t=>M?.observe(t)).then(()=>{},t=>{u({level:"warn",event:"daemon.peer.status_failed",error:t})}))},"refresh");R(Hr().then(({completion:t})=>t).catch(t=>{u({level:"error",event:"daemon.grants.sweep_failed",error:t})})),s(),N=setInterval(s,zr*1e3),N.unref(),oe=tr({joined:!0,register:R})}await ie(async()=>{await Qt(),await Yt()}),j=setInterval(()=>{W().catch(s=>u({level:"error",event:"daemon.tick.failed",error:s}))},Y),X=await kt(()=>{W().catch(s=>u({level:"error",event:"daemon.tick.failed",error:s}))}),Z=await F.subscribe(()=>{W().catch(s=>u({level:"error",event:"daemon.tick.failed",error:s}))},{kinds:["spend_limits"]}),await W()}i(Xe,"completeStartup");function ae(){return Ue??=Xe().catch(r=>{throw S={phase:"initializing",message:"daemon startup recovery failed and normal services are unavailable",blockerPaths:[]},r})}i(ae,"resumeStartup");try{process.on("SIGINT",he),process.on("SIGTERM",ve),process.on("exit",ge),rn(),m!==null&&u({level:"info",event:"daemon.person.resolved",fields:{person_id:m.id,source:m.source}}),n!==null&&u({level:"info",event:"daemon.permissions.boot",fields:{...n}}),l!==null&&u({level:"info",event:"daemon.person.retired",fields:{person_id:l.person,joined_from:l.joinedFrom,retired:l.retired,columns:l.columns}}),$(e.role)?U=await mr():pr(dr());const r=g.token;Tt({epoch:r,requestHandover:Je,registerDetached:R,startupBlock:qe,admitStartup:Ye,repairMigrations:Qe}),Be(),D=$(e.role)?Pr({tcp:e.tcp}):Mn(e),await D.ready;const o=lr()??se("user").document_port;if($(e.role)){const s=xr(o,Tr());try{await s.ready,re=s}catch(t){u({level:"warn",event:"daemon.document_listener.bind_failed",fields:{port:o,error:t instanceof Error?t.message:String(t)}})}}const h=se("user").runtime;if(h?.issuer&&(E=jr(h.issuer,R),h.unsandboxed===!0&&(M=or({syncGrants:E}))),$(e.role)&&(te=await Fr()),ne=await Br({serves:$(e.role),runsLanes:V(e.role)}),h?.unsandboxed===!0&&h.listener!==void 0&&u({level:"warn",event:"daemon.app_listener.refused_unsandboxed",fields:{reason:"runtime.unsandboxed is set; runtime.listener is ignored"}}),$(e.role)&&h!==void 0&&h.listener!==void 0&&h.unsandboxed!==!0&&(A=await Dr(h,async()=>await qr(h.issuer.replace(/\/$/,"")),E),await A.ready),e.role===void 0)try{await tn()}catch(s){if(C=null,s instanceof nn)return S={phase:"blocked",message:s.message,blockerPaths:s.blockerPaths},I;throw s}process.platform==="linux"&&b(x)&&(vr(),await hr("app:terminal"));try{e.prepare?S={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]}:(await ae(),S={phase:"ready",message:"",blockerPaths:[]})}finally{C=null}return I}catch(r){try{await I()}catch(o){throw new AggregateError([r,o],"daemon startup failed and cleanup also failed")}throw r}}i(Fn,"runDaemon");var Ai=Fn;export{Nn as API_DRAIN_GRACE_MS,de as brokerCommandDeclaresCanvasHome,me as brokerCommandEpoch,Ai as default,V as roleRunsLanes,$ as roleServesRequests,Fn as runDaemon,An as superviseTick,un as teardownFleet};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** How long turns in flight get to settle before their broker is stopped. */
|
|
2
|
+
export declare const DRAIN_SETTLE_LIMIT_MS: number;
|
|
3
|
+
export interface DrainStateDTO {
|
|
4
|
+
drained: boolean;
|
|
5
|
+
/** When the drain began (ISO), null when not drained. */
|
|
6
|
+
since: string | null;
|
|
7
|
+
/** Nodes with a turn in flight and a live broker right now. */
|
|
8
|
+
busy_nodes: string[];
|
|
9
|
+
/** Brokers stopped as a dormant exit because their turn outlived the limit (set by the call that waited). */
|
|
10
|
+
stopped_nodes: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function drainedSince(): Promise<string | null>;
|
|
13
|
+
export declare function isDrained(): Promise<boolean>;
|
|
14
|
+
/** Nodes mid-turn with a live broker: an open turn and a recorded pid that answers (the roster's own definition of busy). */
|
|
15
|
+
export declare function busyNodes(): Promise<string[]>;
|
|
16
|
+
/** Begin the drain. Idempotent: a second call keeps the first call's start time. */
|
|
17
|
+
export declare function startDrain(now?: Date): Promise<DrainStateDTO>;
|
|
18
|
+
export declare function endDrain(): Promise<DrainStateDTO>;
|
|
19
|
+
export declare function drainState(stopped?: string[]): Promise<DrainStateDTO>;
|
|
20
|
+
/** Wait for turns in flight to settle; past `limitMs`, stop each remaining broker as a dormant exit (the node is released, so
|
|
21
|
+
* the lifecycle lane does not respawn it as a crash, and its mail stays pending). */
|
|
22
|
+
export declare function settleDrain(opts?: {
|
|
23
|
+
limitMs?: number;
|
|
24
|
+
pollMs?: number;
|
|
25
|
+
}): Promise<DrainStateDTO>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
var m=Object.defineProperty;var n=(t,e)=>m(t,"name",{value:e,configurable:!0});import{openDb as o}from"../core/canvas/db.js";import{isPidPresent as _}from"../core/canvas/pid.js";import{emptyWarmPool as f}from"../core/runtime/warm-pool.js";import{requestBrokerTeardown as E}from"../core/runtime/host.js";import{transition as N}from"../core/runtime/lifecycle.js";import{emitEvent as i}from"../core/events/emit.js";const s="drain",D=10*6e4,L=1e3;async function l(){return(await(await o()).prepare("SELECT value FROM runtime_state WHERE key = ?").get(s))?.value??null}n(l,"drainedSince");async function T(){return await l()!==null}n(T,"isDrained");async function d(){return(await(await o()).prepare(`
|
|
2
|
+
SELECT node_id, pi_pid
|
|
3
|
+
FROM node_rows
|
|
4
|
+
WHERE node_id NOT IN (SELECT node_id FROM warm_pool)
|
|
5
|
+
AND kind != 'human'
|
|
6
|
+
AND pi_pid IS NOT NULL
|
|
7
|
+
AND turn_open_at IS NOT NULL
|
|
8
|
+
AND turn_closed_at IS NULL
|
|
9
|
+
`).all()).filter(e=>_(e.pi_pid)).map(e=>e.node_id)}n(d,"busyNodes");async function k(t=new Date){return await(await o()).prepare("INSERT INTO runtime_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO NOTHING").run(s,t.toISOString()),await f(),i({level:"info",event:"drain.started"}),await p()}n(k,"startDrain");async function R(){return await(await o()).prepare("DELETE FROM runtime_state WHERE key = ?").run(s),i({level:"info",event:"drain.ended"}),await p()}n(R,"endDrain");async function p(t=[]){const e=await l();return{drained:e!==null,since:e,busy_nodes:await d(),stopped_nodes:t}}n(p,"drainState");async function x(t={}){const e=t.limitMs??D,u=t.pollMs??L;if(!await T())throw new Error("settleDrain: the daemon is not drained");const c=Date.now()+e;let a=await d();for(;a.length>0&&Date.now()<c;)await new Promise(r=>setTimeout(r,u)),a=await d();for(const r of a){await N(r,"release");const w=await E(r,{reason:"import-drain"});if(!w.confirmed)throw new Error(`drain: the broker of ${r} could not be confirmed stopped: ${w.error?.message??"unknown"}`);i({level:"warn",event:"drain.broker_stopped",node_id:r})}return await p(a)}n(x,"settleDrain");export{D as DRAIN_SETTLE_LIMIT_MS,d as busyNodes,p as drainState,l as drainedSince,R as endDrain,T as isDrained,x as settleDrain,k as startDrain};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Every lane `superviseTick` runs, named as in crtrd.ts. Keep in tick order. */
|
|
2
|
-
export declare const TICK_LANES: readonly ["brokerSupervision", "controllerDeath", "nodeLifecycle", "nodeDeadline", "structuredRequests", "recipeImage", "cronLane", "deliveryLane", "actionLane", "pendingReviewSubmit", "storageMaintenance", "storageDueWork", "pollRunObservers", "faultRetryBackstop", "bashDeadline", "ledgerSandbox", "ledgerStorage", "ledgerR2"];
|
|
2
|
+
export declare const TICK_LANES: readonly ["brokerSupervision", "controllerDeath", "nodeLifecycle", "nodeDeadline", "structuredRequests", "recipeImage", "cronLane", "deliveryLane", "actionLane", "pendingReviewSubmit", "storageMaintenance", "storageDueWork", "purgeDueWork", "pollRunObservers", "faultRetryBackstop", "bashDeadline", "ledgerSandbox", "ledgerStorage", "ledgerR2", "importLane"];
|
|
3
3
|
export interface DueSource {
|
|
4
4
|
/** Epoch ms of the earliest timed work this lane owes; `now` when work is due already; null when none. */
|
|
5
5
|
nextDue(now: number): Promise<number | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var d=Object.defineProperty;var r=(e,n)=>d(e,"name",{value:n,configurable:!0});const
|
|
1
|
+
var d=Object.defineProperty;var r=(e,n)=>d(e,"name",{value:n,configurable:!0});const f=["brokerSupervision","controllerDeath","nodeLifecycle","nodeDeadline","structuredRequests","recipeImage","cronLane","deliveryLane","actionLane","pendingReviewSubmit","storageMaintenance","storageDueWork","purgeDueWork","pollRunObservers","faultRetryBackstop","bashDeadline","ledgerSandbox","ledgerStorage","ledgerR2","importLane"],u=new Map,a=new Map;function y(e,n){u.set(e,n)}r(y,"registerDueSource");function g(e,n){a.set(e,n)}r(g,"declareBusyOnly");function x(){return[...u.keys()]}r(x,"dueSourceLanes");function D(){return[...a.keys()]}r(D,"busyOnlyLanes");async function b(e,n=()=>{}){let o=null,s=!1;for(const[c,l]of u){let t;try{t=await l.nextDue(e)}catch(i){n(c,i),t=e}t===null||!Number.isFinite(t)||(t<=e&&(s=!0),(o===null||t<o)&&(o=t))}return{dueNow:s,nextDueMs:o}}r(b,"collectDue");export{f as TICK_LANES,D as busyOnlyLanes,b as collectDue,g as declareBusyOnly,x as dueSourceLanes,y as registerDueSource};
|
package/dist/daemon/due.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
var D=Object.defineProperty;var a=(n,e)=>D(n,"name",{value:e,configurable:!0});import{openDb as E}from"../core/canvas/db.js";import{isPidPresent as R}from"../core/canvas/pid.js";import{isProfilePaused as p}from"../core/profiles/manifest.js";import{hasUnseenWakeMail as f}from"../core/canvas/deliveries.js";import{listPendingProviderRetries as
|
|
2
|
-
WHERE status IN ('active', 'idle') AND kind != 'human' AND ${
|
|
3
|
-
AND pi_pid IS NULL AND respawn_not_before IS NOT NULL`),async e=>!p(e.profile_id)&&await m({node_id:e.node_id,status:e.status,intent:e.intent}))).map(e=>
|
|
4
|
-
WHERE deadline_at IS NOT NULL AND outcome_at IS NULL AND status IN ('active', 'idle') AND kind != 'human'`)).filter(e=>!p(e.profile_id)).map(e=>
|
|
5
|
-
WHERE kind = 'blaxel' AND recipe_revision > COALESCE(image_revision, 0) AND ${
|
|
1
|
+
var D=Object.defineProperty;var a=(n,e)=>D(n,"name",{value:e,configurable:!0});import{openDb as E}from"../core/canvas/db.js";import{isPidPresent as R}from"../core/canvas/pid.js";import{isProfilePaused as p}from"../core/profiles/manifest.js";import{hasUnseenWakeMail as f}from"../core/canvas/deliveries.js";import{listPendingProviderRetries as g}from"../core/canvas/recovery.js";import{listSubmitRequestedRequests as x}from"../core/human/requests.js";import{storageConfig as I}from"../core/storage/client.js";import{emitEvent as O}from"../core/events/emit.js";import{wantsExecution as m}from"./reconcilers/node-lifecycle/wants-execution.js";import{dueAtMs as y}from"./reconcilers/fault-retry-backstop.js";import{UNUSED_UPLOAD_TTL_MS as T}from"./reconcilers/storage-due-work.js";import{RECIPE_HAS_CONTENT_SQL as A}from"./api/environment-of.js";import{busyOnlyLanes as b,collectDue as h,declareBusyOnly as o,dueSourceLanes as M,registerDueSource as i}from"./due-sources.js";import{filterAsync as v}from"../core/async-array.js";const _=a(async(n,...e)=>await(await E()).prepare(n).all(...e),"rows"),r=a(async n=>await(await E()).prepare(n).get(),"first"),u=a(n=>{if(n==null)return null;const e=Date.parse(n);return Number.isFinite(e)?e:null},"iso"),c=a(n=>{let e=null;for(const t of n)t!==null&&(e===null||t<e)&&(e=t);return e},"earliest"),L="node_id NOT IN (SELECT node_id FROM warm_pool)";let N=!1;async function S(){if(N)return;N=!0,o("brokerSupervision","acts only on live broker processes, which already make busy true"),o("controllerDeath","acts only on live controller processes"),o("pollRunObservers","follows live pids only"),o("bashDeadline","acts only on running bash jobs, which already make busy true"),o("storageMaintenance","retention and placement housekeeping: never time-critical, and it must not wake or hold a sleeping runtime"),o("purgeDueWork","physical purge of objects soft-deleted over 30 days ago: housekeeping, and a tombstone a live object references stays due forever, so it must not wake or hold a sleeping runtime"),o("ledgerSandbox","bills awake time of executions that are busy or just exited; a sleeping runtime owes nothing until it wakes, and the lane must not wake it"),o("ledgerStorage","monthly storage billing: never time-critical, and it must not wake or hold a sleeping runtime"),o("ledgerR2","moves closed months to R2: housekeeping that must not wake or hold a sleeping runtime"),i("nodeLifecycle",{nextDue:a(async()=>c((await v(await _(`SELECT node_id, status, intent, profile_id, respawn_not_before FROM node_rows
|
|
2
|
+
WHERE status IN ('active', 'idle') AND kind != 'human' AND ${L}
|
|
3
|
+
AND pi_pid IS NULL AND respawn_not_before IS NOT NULL`),async e=>!p(e.profile_id)&&await m({node_id:e.node_id,status:e.status,intent:e.intent}))).map(e=>u(e.respawn_not_before))),"nextDue")}),i("nodeDeadline",{nextDue:a(async()=>c((await _(`SELECT deadline_at, profile_id FROM node_rows
|
|
4
|
+
WHERE deadline_at IS NOT NULL AND outcome_at IS NULL AND status IN ('active', 'idle') AND kind != 'human'`)).filter(e=>!p(e.profile_id)).map(e=>u(e.deadline_at))),"nextDue")}),i("structuredRequests",{nextDue:a(async e=>await r("SELECT 1 FROM structured_requests WHERE state != 'pending' AND effects_done_at IS NULL LIMIT 1")!==void 0?e:(await r("SELECT MIN(deadline_at) AS at FROM structured_requests WHERE state = 'pending' AND deadline_at IS NOT NULL"))?.at??null,"nextDue")}),i("recipeImage",{nextDue:a(async e=>c((await _(`SELECT image_building_at FROM environments
|
|
5
|
+
WHERE kind = 'blaxel' AND recipe_revision > COALESCE(image_revision, 0) AND ${A}`)).map(t=>t.image_building_at===null?e:Math.max(e,u(t.image_building_at)??e))),"nextDue")}),i("cronLane",{nextDue:a(async()=>{const e=(await _(`SELECT fire_at, profile FROM crons
|
|
6
6
|
WHERE state = 'active' AND run_state = 'idle'
|
|
7
|
-
AND NOT (held = 1 AND recur IS NULL AND expires_at IS NULL)`)).filter(
|
|
8
|
-
AND NOT EXISTS (SELECT 1 FROM upload_uses x WHERE x.upload_id = u.upload_id)`))?.at,l=
|
|
7
|
+
AND NOT (held = 1 AND recur IS NULL AND expires_at IS NULL)`)).filter(s=>!p(s.profile)).map(s=>u(s.fire_at)),t=(await _("SELECT expires_at FROM crons WHERE expires_at IS NOT NULL AND run_state = 'idle'")).map(s=>u(s.expires_at));return c([...e,...t])},"nextDue")});const n=a(async e=>async()=>(await r(`SELECT MIN(next_attempt_at) AS at FROM ${e} WHERE state = 'pending' AND canceled_at IS NULL AND next_attempt_at IS NOT NULL`))?.at??null,"attempts");i("deliveryLane",{nextDue:await n("deliveries")}),i("actionLane",{nextDue:await n("daemon_actions")}),i("pendingReviewSubmit",{nextDue:a(async e=>(await x()).length>0?e:null,"nextDue")}),i("storageDueWork",{nextDue:a(async e=>{if(I()===null)return null;if(await r("SELECT 1 FROM uploads WHERE delete_pending_since IS NOT NULL LIMIT 1")!==void 0||await r("SELECT 1 FROM shares WHERE status = 'off' AND object_deleted_at IS NULL LIMIT 1")!==void 0)return e;const t=(await r("SELECT MIN(expires_at) AS at FROM shares WHERE status = 'on' AND expires_at IS NOT NULL"))?.at,s=(await r(`SELECT MIN(created_at) AS at FROM uploads u WHERE delete_pending_since IS NULL
|
|
8
|
+
AND NOT EXISTS (SELECT 1 FROM upload_uses x WHERE x.upload_id = u.upload_id)`))?.at,l=u(s);return c([u(t),l===null?null:l+T])},"nextDue")}),i("importLane",{nextDue:a(async e=>await r(`SELECT 1 FROM import_runs WHERE state IN ('loading', 'committed') AND (claimed_by IS NULL OR lease_expires_at IS NULL OR lease_expires_at <= '${new Date(e).toISOString()}')`)!==void 0?e:null,"nextDue")}),i("faultRetryBackstop",{nextDue:a(async()=>c((await g()).map(e=>y(e))),"nextDue")})}a(S,"installDueSources");const w=12e4;async function C(n){const e=await _(`SELECT n.node_id, n.status, n.intent, n.profile_id, n.pi_pid, n.frozen_at, n.respawn_not_before, n.created, n.launched_at,
|
|
9
9
|
n.turn_open_at, n.turn_closed_at,
|
|
10
10
|
(SELECT x.started_at FROM executions x WHERE x.node_id = n.node_id AND x.state != 'exited') AS exec_started_at
|
|
11
11
|
FROM node_rows n
|
|
12
|
-
WHERE n.status IN ('active', 'idle') AND n.kind != 'human' AND n.${
|
|
12
|
+
WHERE n.status IN ('active', 'idle') AND n.kind != 'human' AND n.${L}`);for(const t of e){if(p(t.profile_id)||!await m({node_id:t.node_id,status:t.status,intent:t.intent}))continue;if(t.pi_pid!==null&&R(t.pi_pid)){if(t.turn_open_at!==null&&t.turn_closed_at===null)return!0;const d=u(t.exec_started_at);if(t.turn_open_at===null&&d!==null&&n-d<w||await f(t.node_id))return!0;continue}if(t.respawn_not_before!==null)continue;if(t.pi_pid!==null||t.frozen_at!==null||t.status==="active")return!0;const l=Math.max(...[t.created,t.launched_at,t.exec_started_at].map(d=>u(d)??0));if(l>0&&n-l<w||t.status==="idle"&&await f(t.node_id))return!0}return!1}a(C,"nodesOweWork");async function K(n=Date.now()){await S();const{dueNow:e,nextDueMs:t}=await h(n,(l,d)=>{O({level:"warn",event:"daemon.due.source_failed",error:d,fields:{lane:l}})});return{busy:e||await C(n)||await r("SELECT 1 FROM crons WHERE run_state = 'running' LIMIT 1")!==void 0||await r("SELECT 1 FROM jobs j JOIN objects o ON o.id = j.object WHERE j.status = 'running' AND o.deleted_at IS NULL LIMIT 1")!==void 0,next_due_at:t===null?null:new Date(t).toISOString()}}a(K,"dueReport");async function V(){return await S(),[...M(),...b()]}a(V,"coveredLanes");export{w as START_GRACE_MS,V as coveredLanes,K as dueReport,S as installDueSources};
|