@north-light/cli 0.3.570 → 0.3.572

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/bin/runtime-selector.mjs +15 -0
  2. package/dist/api/contract.d.ts +1 -1
  3. package/dist/api/contract.js +1 -1
  4. package/dist/api/dto/broker-ops.d.ts +3 -0
  5. package/dist/api/dto/recovery.d.ts +7 -0
  6. package/dist/api/errors.d.ts +3 -0
  7. package/dist/api/errors.js +1 -1
  8. package/dist/builtin-memory/crouter-sdk/release-notes.md +7 -0
  9. package/dist/commands/dashboard.js +3 -1
  10. package/dist/core/auth-file.d.ts +2 -0
  11. package/dist/core/auth-file.js +1 -1
  12. package/dist/core/canvas/recovery.d.ts +3 -0
  13. package/dist/core/canvas/recovery.js +2 -2
  14. package/dist/core/canvas/schema-pg/008-import.sql +126 -0
  15. package/dist/core/canvas/schema-version.d.ts +1 -1
  16. package/dist/core/canvas/schema-version.js +1 -1
  17. package/dist/core/command.d.ts +2 -3
  18. package/dist/core/command.js +6 -6
  19. package/dist/core/llm/run.js +2 -2
  20. package/dist/core/model-auth/readiness.js +1 -1
  21. package/dist/core/providers/files.js +1 -1
  22. package/dist/core/runs/operations.js +6 -6
  23. package/dist/core/runtime/blaxel-environment.d.ts +2 -0
  24. package/dist/core/runtime/blaxel-environment.js +1 -0
  25. package/dist/core/runtime/broker/fault-retry.d.ts +4 -0
  26. package/dist/core/runtime/broker/fault-retry.js +1 -1
  27. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  28. package/dist/core/runtime/broker/rebind.js +1 -1
  29. package/dist/core/runtime/nodes.js +1 -1
  30. package/dist/core/runtime/revive.js +2 -2
  31. package/dist/core/runtime/spawn.js +2 -2
  32. package/dist/core/runtime/warm-pool.js +1 -1
  33. package/dist/core/spaces/process.js +1 -1
  34. package/dist/core/spaces/space.d.ts +2 -1
  35. package/dist/core/spaces/space.js +2 -2
  36. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  37. package/dist/daemon/api/handlers/modelauth.js +1 -1
  38. package/dist/daemon/reconcilers/action-lane.js +1 -1
  39. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  40. package/dist/daemon/reconcilers/node-lifecycle/launch-failure.d.ts +1 -0
  41. package/dist/daemon/reconcilers/node-lifecycle/launch-failure.js +1 -0
  42. package/dist/daemon/reconcilers/node-lifecycle/node-person.d.ts +9 -0
  43. package/dist/daemon/reconcilers/node-lifecycle/node-person.js +1 -0
  44. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +0 -2
  45. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
  46. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  47. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -2
  48. package/docs/sdk/release-notes.md +7 -0
  49. package/package.json +8 -3
  50. package/packages/crouter-identity/package.json +1 -1
  51. package/runtime.lock.json +17 -13
@@ -258,11 +258,26 @@ function findCompatibleSibling(entryName, excludeId) {
258
258
  return best;
259
259
  }
260
260
 
261
+ const RELEASE_ENTRIES = { broker: 'dist/core/runtime/broker-cli.js', cli: 'dist/cli.js', daemon: 'dist/daemon/crtrd-cli.js', launcher: 'dist/launcher/main.js' };
262
+
263
+ /** A fleet sandbox runs its installed release tree itself. `scripts/sandbox-bootstrap.sh` marks a finished release with `.complete`, so a
264
+ * `crtr` loaded from that tree needs no environment variable to know it (an agent's shell may not carry the broker's env). Staging a
265
+ * generation there would repeat a ~20 s `npm pack` and `npm ci` in every sandbox, and its safety check refuses the sandbox's layout. */
266
+ function releaseRuntime(entryName) {
267
+ if (!existsSync(join(PACKAGE_ROOT, '.complete'))) return null;
268
+ const entry = RELEASE_ENTRIES[entryName];
269
+ const entryPath = join(PACKAGE_ROOT, entry ?? '');
270
+ if (entry === undefined || !existsSync(entryPath)) return failure(`release ${PACKAGE_ROOT} has no ${entryName} entry`);
271
+ return { ok: true, runtime: { manifest: null, entryPath, generationId: 'release' } };
272
+ }
273
+
261
274
  /** Resolve a runtime without changing process state or writing output. Falls
262
275
  * back to the newest ABI-compatible sibling generation when `selected` is
263
276
  * incompatible with the running Node; only fails outright when no compatible
264
277
  * generation exists at all. */
265
278
  export function tryResolveRuntime(entryName) {
279
+ const release = releaseRuntime(entryName);
280
+ if (release !== null) return release;
266
281
  const selected = readSelection();
267
282
  if (!selected.ok) return selected;
268
283
 
@@ -1,5 +1,5 @@
1
1
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
- export declare const CONTRACT_LEVEL = "1.36";
2
+ export declare const CONTRACT_LEVEL = "1.37";
3
3
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
4
  export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
5
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -1 +1 @@
1
- var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.36",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
1
+ var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.37",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
@@ -27,6 +27,9 @@ export interface BrokerExecutionRequest {
27
27
  export interface BrokerSettleRequest extends BrokerExecutionRequest {
28
28
  stopReason: string;
29
29
  pushedFinal: boolean;
30
+ /** Set only by the broker once it has recorded the settled turn's fatal provider fault and armed no retry or model fallback:
31
+ * the daemon may then idle a resident Blaxel broker. A settle without it never idles on an error. */
32
+ faultSettled?: boolean;
30
33
  }
31
34
  /** `POST /v1/nodes/{id}/broker/telemetry` body. Tokens are cumulative within
32
35
  * the broker's current Pi session; null context/activity values preserve the
@@ -48,6 +48,13 @@ export interface ExecutionExitDiagnosticDTO {
48
48
  routes: string[];
49
49
  };
50
50
  }
51
+ /** The diagnostic the daemon itself writes when a launch fails before the broker reports; it never crosses `/v1` as a request. */
52
+ export interface ExecutionLaunchFailureDiagnostic {
53
+ schema: 'crtr.execution-exit-diagnostic/v1';
54
+ launch_failure: {
55
+ message: string;
56
+ };
57
+ }
51
58
  export interface BrokerTurnResultDTO {
52
59
  applied: boolean;
53
60
  fault_link: FaultLink | null;
@@ -43,5 +43,8 @@ export { APIError as ApiError };
43
43
  /** A local API transport failure has stable status/code identity, independent
44
44
  * of message prose. */
45
45
  export declare function isDaemonTransportApiError(error: unknown): error is APIError;
46
+ /** The daemon refused the caller's node token. A teardown revokes the broker's token before its process stops, so a broker
47
+ * that sees this on a poll is being shut down, not hitting a corrupt state. */
48
+ export declare function isTokenRevokedApiError(error: unknown): error is APIError;
46
49
  /** Type guard for an `ErrorBody`-shaped parsed payload. */
47
50
  export declare function isErrorBody(value: unknown): value is ErrorBody;
@@ -1 +1 @@
1
- var u=Object.defineProperty;var r=(t,e)=>u(t,"name",{value:e,configurable:!0});class n extends Error{static{r(this,"APIError")}status;code;details;headers;type;param;origin;retryable;retryAfterS;resetAt;requestId;runId;questionId;scope;userAction;constructor(e,i,a,d,o,s){super(a),this.name="APIError",this.status=e,this.code=i,this.headers=new Headers(o),d!==void 0&&(this.details=d),this.type=s?.type,this.param=s?.param,this.origin=s?.origin,this.retryable=s?.retryable,this.retryAfterS=s?.retry_after_s,this.resetAt=s?.reset_at,this.requestId=s?.request_id??this.headers.get("Request-Id")??void 0,this.runId=s?.run_id,this.questionId=s?.question_id,this.scope=s?.scope,this.userAction=s?.user_action}}function h(t){return t instanceof n?t.status===503&&(t.code==="daemon_unavailable"||t.code==="transport_error"||t.code==="daemon_request_interrupted")||t.status===504&&(t.code==="request_timeout"||t.code==="deadline_exceeded")||t.status>=500&&t.code==="daemon_health_unavailable":!1}r(h,"isDaemonTransportApiError");function f(t){if(typeof t!="object"||t===null)return!1;const e=t.error;if(typeof e!="object"||e===null)return!1;const{code:i,message:a}=e;return typeof i=="string"&&typeof a=="string"}r(f,"isErrorBody");export{n as APIError,n as ApiError,h as isDaemonTransportApiError,f as isErrorBody};
1
+ var u=Object.defineProperty;var r=(t,e)=>u(t,"name",{value:e,configurable:!0});class n extends Error{static{r(this,"APIError")}status;code;details;headers;type;param;origin;retryable;retryAfterS;resetAt;requestId;runId;questionId;scope;userAction;constructor(e,i,a,o,d,s){super(a),this.name="APIError",this.status=e,this.code=i,this.headers=new Headers(d),o!==void 0&&(this.details=o),this.type=s?.type,this.param=s?.param,this.origin=s?.origin,this.retryable=s?.retryable,this.retryAfterS=s?.retry_after_s,this.resetAt=s?.reset_at,this.requestId=s?.request_id??this.headers.get("Request-Id")??void 0,this.runId=s?.run_id,this.questionId=s?.question_id,this.scope=s?.scope,this.userAction=s?.user_action}}function h(t){return t instanceof n?t.status===503&&(t.code==="daemon_unavailable"||t.code==="transport_error"||t.code==="daemon_request_interrupted")||t.status===504&&(t.code==="request_timeout"||t.code==="deadline_exceeded")||t.status>=500&&t.code==="daemon_health_unavailable":!1}r(h,"isDaemonTransportApiError");function f(t){return t instanceof n&&t.status===401}r(f,"isTokenRevokedApiError");function p(t){if(typeof t!="object"||t===null)return!1;const e=t.error;if(typeof e!="object"||e===null)return!1;const{code:i,message:a}=e;return typeof i=="string"&&typeof a=="string"}r(p,"isErrorBody");export{n as APIError,n as ApiError,h as isDaemonTransportApiError,p as isErrorBody,f as isTokenRevokedApiError};
@@ -9,6 +9,13 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
9
9
 
10
10
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
11
11
 
12
+ ## 0.3.572
13
+
14
+ `POST /v1/runs` works on a cloud role (a runtime started with `--role api` or `--role reconcile`). It answered `500 server_failure` because the role has no root launcher and the run start tried to prepare an app OS user and folder. Behavior on a person's Mac and on a hosted guest with a launcher is unchanged.
15
+
16
+ - A run whose profile points at a Blaxel environment is created dormant and started by the node-lifecycle lane through the Blaxel runner, with the prompt as its first turn, within about one lifecycle tick. `POST /v1/nodes` follows the same rule: it no longer fails `400 broker_launch_failed` for such a profile, and answers with the node frozen until the lane starts it.
17
+ - A provider call that returns a file result is refused `storage_unavailable` on a cloud role, which has no folder to put the file in.
18
+
12
19
  ## 0.3.562
13
20
 
14
21
  A cloud role's runtime (one with `CRTR_DIRECTORY_URL` and `CRTR_PUBLIC_API_URL` set) accepts a directory person bearer on its TCP listener: the caller acts as that person and reads only the nodes that person may read; schedules, charges, files and provider calls are refused `403 permission_error` to it. Any other listener still refuses a person bearer `401`, now with the code `person_bearer_not_supported` (was `person_bearer_not_supported_on_cloud_role`). A cloud role also answers `GET /v1/runtime/vendor-status` and `POST /v1/runtime/vendor-account-remove` for the person the directory's token names, and refuses a token that names no person `401`.
@@ -1 +1,3 @@
1
- var l=Object.defineProperty;var u=(s,e)=>l(s,"name",{value:e,configurable:!0});import{defineLeaf as h}from"../core/command.js";import{InputError as m}from"../core/io.js";import{cliCanvasSource as w}from"./api-client.js";import{renderTreeFromSource as f,renderForestFromSource as p,dashboardRowsFromSource as b,dashboardRowsAllFromSource as S,enrichRowsFromSource as g}from"../core/canvas/render-source.js";const C=h({name:"dashboard",description:"render the canvas as a subscription tree",whenToUse:"you want the whole graph at a glance as a rendered tree \u2014 the subscription forest drawn in ASCII so you can read its SHAPE: who reports to whom, how deep each branch runs, plus each node's status and context size. Scope to one root or show the full forest. Use `node inspect list` instead for a flat roster without the tree, `node inspect show` to drill into one node's neighbors, and `canvas attention` to find which nodes are blocked on the user",help:{name:"canvas dashboard",summary:"render the canvas as an ASCII subscription tree \u2014 scoped to a root or the full forest",params:[{kind:"flag",name:"root",type:"string",required:!1,constraint:"Node id to use as the tree root. Omit for the full forest (all canvas roots)."}],output:[{name:"tree",type:"string",required:!0,constraint:"Multi-line ASCII tree."},{name:"nodes",type:"integer",required:!0,constraint:"Total node count in this view."},{name:"rows",type:"object[]",required:!0,constraint:"One per node: {node_id,name,status,kind,mode,ctx_tokens,asks}."}],outputKind:"object",effects:["Read-only: queries canvas.db and node telemetry files."]},run:u(async s=>{const e=s.root,t=w(),o=await t.listNodes();if(e!==void 0){if(!o.some(n=>n.node_id===e))throw new m({error:"not_found",message:`no node: ${e}`,next:"List nodes with `crtr node inspect list` to find a valid id."});let r={},d;try{r=await t.ticketCountsForView(e)}catch(n){d=n instanceof Error?n.message:String(n)}const c=await b(t,e,o,r);return{tree:await f(t,e,o,r,d),nodes:c.length,rows:c}}const i=await S(t,new Set,!1,o);let a={};try{a=await t.ticketCountsForNodes(o.map(r=>r.node_id))}catch{}return await g(t,i,a),{tree:await p(t,o,a),nodes:o.length,rows:i}},"run")});export{C as dashboardLeaf};
1
+ var l=Object.defineProperty;var i=(r,e)=>l(r,"name",{value:e,configurable:!0});import{defineLeaf as h}from"../core/command.js";import{InputError as m}from"../core/io.js";import{cliCanvasSource as f}from"./api-client.js";import{renderTreeFromSource as p,renderForestFromSource as w,dashboardRowsFromSource as b,dashboardRowsAllFromSource as S,enrichRowsFromSource as g}from"../core/canvas/render-source.js";const C=h({name:"dashboard",description:"render the canvas as a subscription tree",whenToUse:"you want the whole graph at a glance as a rendered tree \u2014 the subscription forest drawn in ASCII so you can read its SHAPE: who reports to whom, how deep each branch runs, plus each node's status and context size. Scope to one root or show the full forest. Use `node inspect list` instead for a flat roster without the tree, `node inspect show` to drill into one node's neighbors, and `canvas attention` to find which nodes are blocked on the user",help:{name:"canvas dashboard",summary:"render the canvas as an ASCII subscription tree \u2014 scoped to a root or the full forest",params:[{kind:"flag",name:"root",type:"string",required:!1,constraint:"Node id to use as the tree root. Omit for the full forest (all canvas roots)."}],output:[{name:"tree",type:"string",required:!0,constraint:"Multi-line ASCII tree."},{name:"nodes",type:"integer",required:!0,constraint:"Total node count in this view."},{name:"rows",type:"object[]",required:!0,constraint:"One per node: {node_id,name,status,kind,mode,ctx_tokens,asks}. Emitted only under `--json`; the default prose output is the tree and node count."}],outputKind:"object",effects:["Read-only: queries canvas.db and node telemetry files."]},render:i(r=>`${String(r.tree)}
2
+
3
+ ${String(r.nodes)} nodes`,"render"),run:i(async r=>{const e=r.root,t=f(),o=await t.listNodes();if(e!==void 0){if(!o.some(s=>s.node_id===e))throw new m({error:"not_found",message:`no node: ${e}`,next:"List nodes with `crtr node inspect list` to find a valid id."});let n={},c;try{n=await t.ticketCountsForView(e)}catch(s){c=s instanceof Error?s.message:String(s)}const u=await b(t,e,o,n);return{tree:await p(t,e,o,n,c),nodes:u.length,rows:u}}const d=await S(t,new Set,!1,o);let a={};try{a=await t.ticketCountsForNodes(o.map(n=>n.node_id))}catch{}return await g(t,d,a),{tree:await w(t,o,a),nodes:o.length,rows:d}},"run")});export{C as dashboardLeaf};
@@ -10,6 +10,8 @@ type AuthFileData = Record<string, Credential>;
10
10
  * be sent to the provider verbatim.
11
11
  */
12
12
  export declare function resolveCredentialValue(config: string, env?: Record<string, string>): string | undefined;
13
+ /** The api_key a model call can send for `providerId`: the stored row with its `$VAR` / `!cmd` reference resolved. Undefined when no api_key is stored or its reference does not resolve. */
14
+ export declare function storedApiKey(providerId: string): Promise<string | undefined>;
13
15
  /** Read all stored credentials. */
14
16
  export declare function readAuthCredentials(): Promise<AuthFileData>;
15
17
  /** Read one provider's stored credential. */
@@ -1 +1 @@
1
- var O=Object.defineProperty;var a=(t,e)=>O(t,"name",{value:e,configurable:!0});import{execSync as A}from"node:child_process";import{openDb as _,withCanvasWrite as f}from"./canvas/db.js";import{personIdAsync as l}from"./canvas/person.js";import{loadSecretsKey as w,open as C,seal as v}from"./secrets/index.js";const m=a((t,e)=>`model_api_keys|${t}|${e}`,"aadOf");function y(t,e){return JSON.parse(C(e.credential_sealed,e.key_id,w(),m(t,e.provider_id)))}a(y,"openRow");async function k(t,e,n,i){const{sealed:r,keyId:d}=v(JSON.stringify(i),w(),m(e,n));await t.prepare("INSERT INTO model_api_keys(person,provider_id,credential_sealed,key_id,updated_at) VALUES(?,?,?,?,?) ON CONFLICT(person,provider_id) DO UPDATE SET credential_sealed=EXCLUDED.credential_sealed,key_id=EXCLUDED.key_id,updated_at=EXCLUDED.updated_at").run(e,n,r,d,new Date().toISOString())}a(k,"put");const D=/^[A-Za-z_][A-Za-z0-9_]*/,x=/^[A-Za-z_][A-Za-z0-9_]*$/,E=new Map;function R(t){if(E.has(t))return E.get(t);let e;try{e=A(t,{encoding:"utf-8",timeout:1e4,stdio:["ignore","pipe","ignore"]}).trim()||void 0}catch{e=void 0}return E.set(t,e),e}a(R,"runValueCommand");function S(t,e){if(t.startsWith("!"))return R(t.slice(1));let n="",i=0;for(;i<t.length;){const r=t.indexOf("$",i);if(r<0){n+=t.slice(i);break}n+=t.slice(i,r);const d=t[r+1];if(d==="$"||d==="!"){n+=d,i=r+2;continue}let s,o=r+1;if(d==="{"){const p=t.indexOf("}",r+2),u=p<0?void 0:t.slice(r+2,p);u!==void 0&&x.test(u)&&(s=u,o=p+1)}else{const p=D.exec(t.slice(r+1));p!==null&&(s=p[0],o=r+1+s.length)}if(s===void 0){n+="$",i=r+1;continue}const c=e?.[s]||process.env[s];if(c===void 0||c==="")return;n+=c,i=o}return n}a(S,"resolveCredentialValue");async function h(){const t=await l(),e=await(await _()).prepare("SELECT provider_id,credential_sealed,key_id FROM model_api_keys WHERE person=? ORDER BY provider_id").all(t);return Object.fromEntries(e.map(n=>[n.provider_id,y(t,n)]))}a(h,"readAuthCredentials");async function N(t){const e=await l(),n=await(await _()).prepare("SELECT provider_id,credential_sealed,key_id FROM model_api_keys WHERE person=? AND provider_id=?").get(e,t);return n?y(e,n):void 0}a(N,"readAuthCredential");async function W(t,e){const n=await l();await f(i=>k(i,n,t,e))}a(W,"setAuthCredential");async function L(t){const e=await l();return(await(await _()).prepare("DELETE FROM model_api_keys WHERE person=? AND provider_id=?").run(e,t)).changes>0}a(L,"deleteAuthCredential");class ${static{a(this,"CrouterCredentialStore")}async read(e){const n=await N(e);return n?.type!=="api_key"||n.key===void 0?n:{...n,key:S(n.key,n.env)}}async list(){return Object.entries(await h()).map(([e,n])=>({providerId:e,type:n.type}))}async modify(e,n){const i=await l();return f(async r=>{await r.exec("SET LOCAL lock_timeout = '15s'");const d=await r.prepare("SELECT provider_id,credential_sealed,key_id FROM model_api_keys WHERE person=? AND provider_id=? FOR UPDATE").get(i,e),s=d?y(i,d):void 0,o=await n(s);return o===void 0?s:(await k(r,i,e,o),o)})}async delete(e){await L(e)}}export{$ as CrouterCredentialStore,L as deleteAuthCredential,N as readAuthCredential,h as readAuthCredentials,S as resolveCredentialValue,W as setAuthCredential};
1
+ var A=Object.defineProperty;var a=(t,e)=>A(t,"name",{value:e,configurable:!0});import{execSync as O}from"node:child_process";import{openDb as _,withCanvasWrite as f}from"./canvas/db.js";import{personIdAsync as c}from"./canvas/person.js";import{loadSecretsKey as w,open as C,seal as v}from"./secrets/index.js";const k=a((t,e)=>`model_api_keys|${t}|${e}`,"aadOf");function y(t,e){return JSON.parse(C(e.credential_sealed,e.key_id,w(),k(t,e.provider_id)))}a(y,"openRow");async function m(t,e,n,i){const{sealed:r,keyId:d}=v(JSON.stringify(i),w(),k(e,n));await t.prepare("INSERT INTO model_api_keys(person,provider_id,credential_sealed,key_id,updated_at) VALUES(?,?,?,?,?) ON CONFLICT(person,provider_id) DO UPDATE SET credential_sealed=EXCLUDED.credential_sealed,key_id=EXCLUDED.key_id,updated_at=EXCLUDED.updated_at").run(e,n,r,d,new Date().toISOString())}a(m,"put");const D=/^[A-Za-z_][A-Za-z0-9_]*/,x=/^[A-Za-z_][A-Za-z0-9_]*$/,E=new Map;function R(t){if(E.has(t))return E.get(t);let e;try{e=O(t,{encoding:"utf-8",timeout:1e4,stdio:["ignore","pipe","ignore"]}).trim()||void 0}catch{e=void 0}return E.set(t,e),e}a(R,"runValueCommand");function S(t,e){if(t.startsWith("!"))return R(t.slice(1));let n="",i=0;for(;i<t.length;){const r=t.indexOf("$",i);if(r<0){n+=t.slice(i);break}n+=t.slice(i,r);const d=t[r+1];if(d==="$"||d==="!"){n+=d,i=r+2;continue}let s,o=r+1;if(d==="{"){const p=t.indexOf("}",r+2),u=p<0?void 0:t.slice(r+2,p);u!==void 0&&x.test(u)&&(s=u,o=p+1)}else{const p=D.exec(t.slice(r+1));p!==null&&(s=p[0],o=r+1+s.length)}if(s===void 0){n+="$",i=r+1;continue}const l=e?.[s]||process.env[s];if(l===void 0||l==="")return;n+=l,i=o}return n}a(S,"resolveCredentialValue");async function $(t){const e=await new T().read(t);return e?.type==="api_key"&&e.key?e.key:void 0}a($,"storedApiKey");async function h(){const t=await c(),e=await(await _()).prepare("SELECT provider_id,credential_sealed,key_id FROM model_api_keys WHERE person=? ORDER BY provider_id").all(t);return Object.fromEntries(e.map(n=>[n.provider_id,y(t,n)]))}a(h,"readAuthCredentials");async function N(t){const e=await c(),n=await(await _()).prepare("SELECT provider_id,credential_sealed,key_id FROM model_api_keys WHERE person=? AND provider_id=?").get(e,t);return n?y(e,n):void 0}a(N,"readAuthCredential");async function I(t,e){const n=await c();await f(i=>m(i,n,t,e))}a(I,"setAuthCredential");async function L(t){const e=await c();return(await(await _()).prepare("DELETE FROM model_api_keys WHERE person=? AND provider_id=?").run(e,t)).changes>0}a(L,"deleteAuthCredential");class T{static{a(this,"CrouterCredentialStore")}async read(e){const n=await N(e);return n?.type!=="api_key"||n.key===void 0?n:{...n,key:S(n.key,n.env)}}async list(){return Object.entries(await h()).map(([e,n])=>({providerId:e,type:n.type}))}async modify(e,n){const i=await c();return f(async r=>{await r.exec("SET LOCAL lock_timeout = '15s'");const d=await r.prepare("SELECT provider_id,credential_sealed,key_id FROM model_api_keys WHERE person=? AND provider_id=? FOR UPDATE").get(i,e),s=d?y(i,d):void 0,o=await n(s);return o===void 0?s:(await m(r,i,e,o),o)})}async delete(e){await L(e)}}export{T as CrouterCredentialStore,L as deleteAuthCredential,N as readAuthCredential,h as readAuthCredentials,S as resolveCredentialValue,I as setAuthCredential,$ as storedApiKey};
@@ -1,5 +1,6 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
2
  import { type Fault, type FaultKind, type FaultLink, type FaultProviderError, type FaultRetry } from '../fault-classifier.js';
3
+ import type { ExecutionExitDiagnosticDTO, ExecutionLaunchFailureDiagnostic } from '../../api/dto/recovery.js';
3
4
  export type { Fault, FaultKind, FaultLink, FaultProviderError, FaultRetry } from '../fault-classifier.js';
4
5
  export declare const FAULT_QUIET_MS: number;
5
6
  export interface FaultRecordInput {
@@ -44,6 +45,8 @@ export declare function closeTurn(executionId: string, opts: {
44
45
  }): Promise<boolean>;
45
46
  /** Stamps `read_at`: a viewer has just been shown this conversation. */
46
47
  export declare function markNodeRead(nodeId: string): Promise<boolean>;
48
+ /** The one writer of an `executions.exit_diagnostic_json` envelope: the schema tag plus one payload the DTOs declare. */
49
+ export declare function exitDiagnosticJson(payload: Omit<ExecutionExitDiagnosticDTO, 'schema'> | Omit<ExecutionLaunchFailureDiagnostic, 'schema'>): string;
47
50
  export declare function recordTransportPoisonVerdict(executionId: string, verdict: TransportPoisonVerdict): Promise<boolean>;
48
51
  export declare function readTransportPoisonVerdict(nodeId: string): Promise<TransportPoisonVerdict | null>;
49
52
  export declare function readLiveTurn(nodeId: string): Promise<{
@@ -1,4 +1,4 @@
1
- var ae=Object.defineProperty;var o=(e,t)=>ae(e,"name",{value:t,configurable:!0});import{prepared as _}from"../sqlite-statements.js";import{randomUUID as I}from"node:crypto";import{existsSync as f,readdirSync as se,readFileSync as de,realpathSync as ue,statSync as ce}from"node:fs";import{join as N,resolve as _e}from"node:path";import{withCanvasWrite as l,openDb as p}from"./db.js";import{jobDir as pe,nodesRoot as C}from"./paths.js";import{errorClassFromFault as B}from"../fault-classifier.js";import{parseProviderRateLimit as le}from"../provider-failure.js";import{emitEvent as J}from"../events/emit.js";import{operationIdContext as K,validateOperationId as Y}from"../events/operation-id.js";const Me=5*6e4,Ee=500,G=["pi\u2192provider","viewer\u2194broker","relay\u2194broker","viewer\u2194crtrd","daemon\u2192node","crtr\u2192pi"],fe=[...G,"broker\u2194crtrd"],ye=["rate-limit","overloaded","connection","auth","protocol","context-overflow","other","wedged","model-not-found"],Te=["auto","manual","fatal"],Ne=["sdk","daemon","client"],ge=["pending","admitted","invalidated"];function u(){return new Date().toISOString()}o(u,"now");function H(e,t){return new Set(e.prepare(`PRAGMA table_info(${t})`).all().map(({name:n})=>n))}o(H,"tableColumns");function Le(e){const t=H(e,"executions"),n=[["turn_open_at","TEXT"],["turn_progress_at","TEXT"],["turn_closed_at","TEXT"],["turn_end_reason","TEXT CHECK (turn_end_reason IS NULL OR turn_end_reason IN ('settled', 'teardown-aborted'))"],["exit_diagnostic_json","TEXT"]];for(const[r,i]of n)t.has(r)||e.exec(`ALTER TABLE executions ADD COLUMN ${r} ${i}`)}o(Le,"addExecutionColumns");function me(e){e.exec(`
1
+ var se=Object.defineProperty;var o=(e,t)=>se(e,"name",{value:t,configurable:!0});import{prepared as _}from"../sqlite-statements.js";import{randomUUID as I}from"node:crypto";import{existsSync as f,readdirSync as de,readFileSync as ue,realpathSync as ce,statSync as _e}from"node:fs";import{join as N,resolve as pe}from"node:path";import{withCanvasWrite as l,openDb as p}from"./db.js";import{jobDir as le,nodesRoot as C}from"./paths.js";import{errorClassFromFault as B}from"../fault-classifier.js";import{parseProviderRateLimit as Ee}from"../provider-failure.js";import{emitEvent as J}from"../events/emit.js";import{operationIdContext as K,validateOperationId as Y}from"../events/operation-id.js";const Xe=5*6e4,fe=500,G=["pi\u2192provider","viewer\u2194broker","relay\u2194broker","viewer\u2194crtrd","daemon\u2192node","crtr\u2192pi"],ye=[...G,"broker\u2194crtrd"],Te=["rate-limit","overloaded","connection","auth","protocol","context-overflow","other","wedged","model-not-found"],Ne=["auto","manual","fatal"],ge=["sdk","daemon","client"],Le=["pending","admitted","invalidated"];function u(){return new Date().toISOString()}o(u,"now");function H(e,t){return new Set(e.prepare(`PRAGMA table_info(${t})`).all().map(({name:n})=>n))}o(H,"tableColumns");function me(e){const t=H(e,"executions"),n=[["turn_open_at","TEXT"],["turn_progress_at","TEXT"],["turn_closed_at","TEXT"],["turn_end_reason","TEXT CHECK (turn_end_reason IS NULL OR turn_end_reason IN ('settled', 'teardown-aborted'))"],["exit_diagnostic_json","TEXT"]];for(const[r,i]of n)t.has(r)||e.exec(`ALTER TABLE executions ADD COLUMN ${r} ${i}`)}o(me,"addExecutionColumns");function Ae(e){e.exec(`
2
2
  CREATE TABLE IF NOT EXISTS node_faults (
3
3
  fault_id TEXT PRIMARY KEY,
4
4
  node_id TEXT NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,
@@ -23,4 +23,4 @@ CREATE TABLE IF NOT EXISTS node_faults (
23
23
  );
24
24
  CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_active ON node_faults(node_id) WHERE active = 1;
25
25
  CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_episode ON node_faults(node_id) WHERE episode_state IS NOT NULL;
26
- `)}o(me,"createFaultTable");function g(e,t){return typeof t=="string"&&e.includes(t)}o(g,"contains");function W(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(W,"isRecord");function q(e){if(!W(e)||typeof e.provider!="string"||e.provider===""||typeof e.model!="string"||e.model==="")return;const t=typeof e.status=="number"&&Number.isFinite(e.status)?e.status:void 0,n=typeof e.code=="string"||typeof e.code=="number"?e.code:void 0,r=typeof e.errno=="string"?e.errno:void 0,i=typeof e.body=="string"?e.body:void 0,a=le(e.ratelimit),d=Array.isArray(e.causes)?e.causes.flatMap(s=>{if(!W(s))return[];const E={...typeof s.message=="string"?{message:s.message}:{},...typeof s.status=="number"&&Number.isFinite(s.status)?{status:s.status}:{},...typeof s.code=="string"||typeof s.code=="number"?{code:s.code}:{},...typeof s.errno=="string"?{errno:s.errno}:{},...typeof s.body=="string"?{body:s.body}:{}};return Object.keys(E).length===0?[]:[E]}):void 0;return{provider:e.provider,model:e.model,...t===void 0?{}:{status:t},...n===void 0?{}:{code:n},...r===void 0?{}:{errno:r},...i===void 0?{}:{body:i},...d===void 0||d.length===0?{}:{causes:d},...a===void 0?{}:{ratelimit:a}}}o(q,"parseProviderError");function V(e){try{const t=JSON.parse(e);if(!g(fe,t.link)||typeof t.op!="string"||t.op===""||!g(ye,t.kind)||typeof t.message!="string"||typeof t.since!="string")return null;const n=Y(t.operation_id);if(!W(t.retry)||!g(Te,t.retry.disposition)||t.retry.by!==void 0&&!g(Ne,t.retry.by)||t.retry.attempt!==void 0&&typeof t.retry.attempt!="number"||t.retry.max!==void 0&&typeof t.retry.max!="number"||t.retry.nextAt!==void 0&&typeof t.retry.nextAt!="string"||t.anchorEntryId!==void 0&&(typeof t.anchorEntryId!="string"||t.anchorEntryId===""))return null;const r=t.providerError===void 0?void 0:q(t.providerError);return t.providerError!==void 0&&r===void 0?null:{link:t.link,op:t.op,kind:t.kind,retry:{disposition:t.retry.disposition,...t.retry.by===void 0?{}:{by:t.retry.by},...t.retry.attempt===void 0?{}:{attempt:t.retry.attempt},...t.retry.max===void 0?{}:{max:t.retry.max},...t.retry.nextAt===void 0?{}:{nextAt:t.retry.nextAt}},message:t.message,since:t.since,operation_id:n,...t.anchorEntryId===void 0?{}:{anchorEntryId:t.anchorEntryId},...r===void 0?{}:{providerError:r}}}catch{return null}}o(V,"parseLegacyFault");function Ae(e){const t=_e(e);try{return ue(t)}catch{return t}}o(Ae,"canonicalPath");function Se(e){try{const t=JSON.parse(e);if(!g(ge,t.state)||typeof t.sessionFile!="string"||t.sessionFile==="")return null;const n=JSON.stringify(t.fault),r=V(n);if(r===null||r.link!=="pi\u2192provider"||r.retry.disposition!=="auto"||r.retry.by!=="daemon")return null;const i=t.anchorEntryId;return i!==null&&typeof i!="string"||(r.anchorEntryId??null)!==i?null:{state:t.state,sessionFile:Ae(t.sessionFile),fault:r}}catch{return null}}o(Se,"parseLegacyEpisode");function Q(e){return e!==void 0?Y(e):K.current()??K.fresh(n=>n)}o(Q,"resolveOperationId");function Re(e){return e.slice(0,Ee)}o(Re,"clampMessage");function w(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="auto"&&e.retry.by==="daemon"?3:e.retry.disposition==="fatal"?2:1}o(w,"faultPriority");function z(e){return{link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operationId:e.operation_id,anchorEntryId:e.anchorEntryId??null,retryDisposition:e.retry.disposition,retryBy:e.retry.by??null,retryAttempt:e.retry.attempt??null,retryMax:e.retry.max??null,retryNextAt:e.retry.nextAt??null,providerError:e.providerError===void 0?null:JSON.stringify(e.providerError)}}o(z,"encodeFault");function b(e){const t=e.provider_error_json,n=typeof t=="string"?q(JSON.parse(t)):void 0;if(typeof t=="string"&&n===void 0)throw new Error(`invalid provider error on stored fault ${e.fault_id}`);const r={disposition:e.retry_disposition,...e.retry_by===null?{}:{by:e.retry_by},...e.retry_attempt===null?{}:{attempt:e.retry_attempt},...e.retry_max===null?{}:{max:e.retry_max},...e.retry_next_at===null?{}:{nextAt:e.retry_next_at}};return{faultId:e.fault_id,link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operation_id:e.operation_id,retry:r,...e.anchor_entry_id===null?{}:{anchorEntryId:e.anchor_entry_id},...n===void 0?{}:{providerError:n},...typeof e.notified_since=="string"?{notifiedSince:e.notified_since}:{}}}o(b,"hydrateFault");async function L(e,t){const n=await e.prepare("SELECT * FROM node_faults WHERE node_id = ? AND active = 1").get(t);return n===void 0?null:b(n)}o(L,"activeFault");function P(e){if(!g(G,e.link))throw new Error(`fault link is not durable: ${e.link}`);return{link:e.link,op:e.op,kind:e.kind,retry:e.retry,message:Re(e.message),since:e.since??u(),operation_id:Q(e.operation_id),...typeof e.anchorEntryId=="string"&&e.anchorEntryId!==""?{anchorEntryId:e.anchorEntryId}:{},...e.providerError===void 0?{}:{providerError:e.providerError}}}o(P,"storedFault");function M(e,t){J({level:t.retry.disposition==="fatal"?"error":"warn",event:"fault.recorded",operation_id:t.operation_id,node_id:e,error_class:B(t),fields:{link:t.link,op:t.op,kind:t.kind,retry:t.retry,message:t.message,...t.anchorEntryId===void 0?{}:{anchor_entry_id:t.anchorEntryId},...t.providerError===void 0?{}:{provider_error:t.providerError}}})}o(M,"emitRecordedFault");const Z="INSERT INTO node_faults (fault_id, node_id, link, op, kind, message, since, operation_id, anchor_entry_id, retry_disposition, retry_by, retry_attempt, retry_max, retry_next_at, provider_error_json, active, episode_state, episode_binding_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";function ee(e,t,n,r,i){const a=z(n),d=u();return[e,t,a.link,a.op,a.kind,a.message,a.since,a.operationId,a.anchorEntryId,a.retryDisposition,a.retryBy,a.retryAttempt,a.retryMax,a.retryNextAt,a.providerError,r?1:0,i?.state??null,i?.bindingId??null,d,d]}o(ee,"faultInsertParams");async function te(e,t,n,r,i){const a=I();return await e.prepare(Z).run(...ee(a,t,n,r,i)),{...n,faultId:a}}o(te,"insertFault");async function D(e,t,n,r){const i=z(n);return await e.prepare("UPDATE node_faults SET link = ?, op = ?, kind = ?, message = ?, since = ?, operation_id = ?, anchor_entry_id = ?, retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, provider_error_json = ?, episode_state = COALESCE(?, episode_state), episode_binding_id = COALESCE(?, episode_binding_id), updated_at = ? WHERE fault_id = ?").run(i.link,i.op,i.kind,i.message,i.since,i.operationId,i.anchorEntryId,i.retryDisposition,i.retryBy,i.retryAttempt,i.retryMax,i.retryNextAt,i.providerError,r?.state??null,r?.bindingId??null,u(),t),{...n,faultId:t}}o(D,"updateFault");async function U(e,t){await e.prepare("DELETE FROM node_faults WHERE fault_id = ? AND active = 0 AND episode_state IS NULL").run(t)}o(U,"deleteIfUnretained");async function ne(e,t,n,r){const i=await L(e,t);if(i===null){const a=await e.prepare("SELECT fault_id, link, kind, since, operation_id FROM node_faults WHERE node_id = ? AND active = 0 AND episode_state IS NOT NULL").get(t);return a!==void 0&&a.link===n.link&&a.kind===n.kind&&a.since===n.since&&a.operation_id===n.operation_id?(await e.prepare("UPDATE node_faults SET active = 1, updated_at = ? WHERE fault_id = ?").run(u(),a.fault_id),await D(e,a.fault_id,n)):await te(e,t,n,!0,r)}if(i.link===n.link&&i.kind===n.kind){const a=n.retry.disposition==="auto"?{...i.retry,...n.retry}:n.retry,d=i.retry.disposition===n.retry.disposition&&(n.retry.disposition!=="fatal"||i.message===n.message),s=d?i.since:n.since,E=await D(e,i.faultId,{...n,since:s,anchorEntryId:n.anchorEntryId??i.anchorEntryId,retry:a},r);return d||await e.prepare("UPDATE node_faults SET notified_since = NULL WHERE fault_id = ?").run(i.faultId),E}return i.link!==n.link&&w(i)>w(n)?null:(await e.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(u(),i.faultId),await U(e,i.faultId),await te(e,t,n,!0,r))}o(ne,"replaceActiveFault");async function m(e,t,n){return(await e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? AND unbound_at IS NULL").get(t,n))?.binding_id??null}o(m,"liveBinding");async function Xe(e,t){return await l(async n=>{const r=u();return(await n.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(r,e)).changes>0?!0:(await n.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ? AND state != 'exited'").run(r,r,e)).changes>0})}o(Xe,"openTurn");async function $e(e){return(await(await p()).prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(u(),e)).changes>0}o($e,"recordTurnProgress");async function je(e,t){return await l(async n=>{const r=u(),i=(await n.prepare("UPDATE executions SET turn_closed_at = ?, turn_end_reason = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(r,t.endReason,e)).changes>0;if(i&&t.endReason==="settled"){const a="(SELECT node_id FROM executions WHERE execution_id = ?)";t.viewerPresent===!0?await n.prepare(`UPDATE nodes SET reply_at = ?, read_at = ? WHERE node_id = ${a}`).run(r,r,e):await n.prepare(`UPDATE nodes SET reply_at = ? WHERE node_id = ${a}`).run(r,e)}return i})}o(je,"closeTurn");async function Be(e){return(await(await p()).prepare("UPDATE nodes SET read_at = ? WHERE node_id = ?").run(u(),e)).changes>0}o(Be,"markNodeRead");async function Je(e,t){if(!Number.isInteger(t.consecutivePasses)||t.consecutivePasses<1||t.routes.length===0||!t.routes.every(n=>n!==""))throw new Error("invalid transport-poison verdict");return(await(await p()).prepare("UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ? AND state != 'exited'").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:t.consecutivePasses,routes:t.routes}}),e)).changes>0}o(Je,"recordTransportPoisonVerdict");async function Ke(e){const t=await(await p()).prepare("SELECT exit_diagnostic_json FROM executions WHERE node_id = ? ORDER BY seq DESC LIMIT 1").get(e);if(t===void 0||t.exit_diagnostic_json===null)return null;try{const n=JSON.parse(t.exit_diagnostic_json),r=n.transport_poison;return n.schema!=="crtr.execution-exit-diagnostic/v1"||r===void 0||!Number.isInteger(r.consecutive_passes)||r.consecutive_passes<1||!Array.isArray(r.routes)||r.routes.length===0||!r.routes.every(i=>typeof i=="string"&&i!=="")?null:{consecutivePasses:r.consecutive_passes,routes:r.routes}}catch{return null}}o(Ke,"readTransportPoisonVerdict");async function Ye(e){const t=await(await p()).prepare("SELECT turn_open_at, turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND state != 'exited'").get(e);return t===void 0?null:{busy:t.turn_open_at!==null&&t.turn_closed_at===null,progressAt:t.turn_progress_at}}o(Ye,"readLiveTurn");async function Ge(e){const t=await p(),n=await t.prepare("SELECT turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL ORDER BY seq DESC LIMIT 1").get(e);if(n===void 0)return null;const r=await t.prepare("SELECT turn_end_reason FROM executions WHERE node_id = ? ORDER BY seq DESC LIMIT 1").get(e);return{interrupted:n.turn_closed_at===null||r?.turn_end_reason==="teardown-aborted",progressAt:n.turn_progress_at}}o(Ge,"readInterruptedTurn");async function qe(e){return await(await p()).prepare("SELECT 1 FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL LIMIT 1").get(e)!==void 0}o(qe,"hasAdmittedTurn");async function Ve(e,t){const n=P(t);return M(e,n),await l(async r=>await ne(r,e,n))}o(Ve,"recordFault");async function Qe(e){return await l(async t=>(await t.prepare("UPDATE node_faults SET notified_since = since, updated_at = ? WHERE fault_id = ?").run(u(),e)).changes>0)}o(Qe,"markFaultNotified");function xe(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="fatal"&&e.notifiedSince!==e.since}o(xe,"isUnnotifiedFatalProviderFault");async function ze(){return(await(await p()).prepare("SELECT f.node_id FROM node_faults f JOIN nodes n ON n.node_id = f.node_id WHERE f.active = 1 AND f.link = 'pi\u2192provider' AND f.retry_disposition = 'fatal' AND (f.notified_since IS NULL OR f.notified_since != f.since) AND n.status IN ('active', 'idle')").all()).map(e=>e.node_id)}o(ze,"listUnnotifiedFatalFaultNodes");let X=null;function Ze(e){X=e}o(Ze,"setFatalFaultAnnouncer");async function et(e,t){const n=await L(await p(),e);return n===null||t?.link!==void 0&&n.link!==t.link?!1:await l(async r=>{const i=await L(r,e);if(i===null||t?.link!==void 0&&i.link!==t.link)return!1;X!==null&&xe(i)&&await X(e);const a=await r.prepare("SELECT episode_state FROM node_faults WHERE fault_id = ?").get(i.faultId);await r.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(u(),i.faultId),a.episode_state==="pending"&&t?.preserveEpisode!==!0&&await r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(u(),i.faultId),await U(r,i.faultId);const d=Q();return J({level:"info",event:"fault.cleared",operation_id:d,node_id:e,error_class:B(i),fields:{cleared_fault_operation_id:i.operation_id,link:i.link,op:i.op,kind:i.kind,retry:i.retry,message:i.message,since:i.since,...i.anchorEntryId===void 0?{}:{anchor_entry_id:i.anchorEntryId}}}),!0})}o(et,"clearFault");async function re(e){return await L(await p(),e)}o(re,"readFault");async function tt(e,t){const n=P(t.input);if(n.link!=="pi\u2192provider"||n.retry.disposition!=="auto"||n.retry.by!=="daemon")throw new Error("provider retry episode requires an automatic daemon provider fault");return M(e,n),await l(async r=>{const i=await m(r,e,t.sessionId);if(i===null)return null;const a=await L(r,e);if(a!==null&&(a.link!==n.link||a.kind!==n.kind)&&w(a)>w(n))return null;const d=await r.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_state IS NOT NULL").all(e);await r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_state IS NOT NULL").run(u(),e);for(const E of d)await U(r,E.fault_id);const s=await ne(r,e,n,{state:"pending",bindingId:i});return s===null?null:(await r.prepare("UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run("pending",i,u(),s.faultId),s)})}o(tt,"recordPendingEpisode");async function nt(e,t){return await l(async n=>{if(await n.prepare("SELECT 1 FROM nodes WHERE node_id = ? AND intent = 'refresh'").get(e)!==void 0)return!1;const r=await m(n,e,t.sessionId);return r===null?!1:(await n.prepare("UPDATE node_faults SET episode_state = 'admitted', retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, updated_at = ? WHERE node_id = ? AND fault_id = ? AND episode_state = 'pending' AND episode_binding_id = ?").run(t.retry.disposition,t.retry.by??null,t.retry.attempt??null,t.retry.max??null,t.retry.nextAt??null,u(),e,t.faultId,r)).changes>0})}o(nt,"admitEpisode");async function rt(e,t){const n=P(t.fault);return await l(async r=>{const i=await m(r,e,t.sessionId);return i===null||(await L(r,e))?.faultId!==t.faultId||await r.prepare("SELECT 1 FROM node_faults WHERE node_id = ? AND fault_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").get(e,t.faultId,i)===void 0?!1:(M(e,n),t.reopen?await D(r,t.faultId,n,{state:"pending",bindingId:i}):(await D(r,t.faultId,n),await r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(u(),t.faultId),await r.prepare("UPDATE node_faults SET notified_since = NULL WHERE fault_id = ?").run(t.faultId)),!0)})}o(rt,"settleRejectedEpisode");async function it(e,t){return await l(async n=>{const r=await m(n,e,t.sessionId);return r===null?!1:(await n.prepare("UPDATE node_faults SET episode_state = 'pending', updated_at = ? WHERE node_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").run(u(),e,r)).changes>0})}o(it,"reopenEpisode");async function ot(e,t){return await l(async n=>{const r=await m(n,e,t.sessionId);return r===null?!1:(await n.prepare("UPDATE node_faults SET episode_state = 'invalidated', updated_at = ? WHERE node_id = ? AND episode_state != 'invalidated' AND episode_state IS NOT NULL AND episode_binding_id = ?").run(u(),e,r)).changes>0})}o(ot,"invalidateEpisode");async function at(e,t){return await l(async n=>{const r=await m(n,e,t.sessionId);if(r===null)return!1;const i=await n.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").all(e,r);if(i.length===0)return!1;await n.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").run(u(),e,r);for(const a of i)await U(n,a.fault_id);return!0})}o(at,"clearEpisode");async function st(e){const t=await(await p()).prepare("SELECT f.*, s.session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.node_id = ? AND f.episode_state IS NOT NULL").get(e);return t===void 0?null:{state:t.episode_state,sessionId:t.session_id,fault:b(t)}}o(st,"readEpisode");async function dt(){return(await(await p()).prepare("SELECT f.*, s.session_id AS bound_session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.active = 1 AND f.episode_state = 'pending' AND s.unbound_at IS NULL AND s.session_id IS NOT NULL AND f.link = 'pi\u2192provider' AND f.retry_disposition = 'auto' AND f.retry_by = 'daemon' AND f.kind IN ('connection', 'rate-limit', 'overloaded')").all()).map(t=>({nodeId:t.node_id,sessionId:t.bound_session_id,fault:b(t),updatedAt:t.updated_at,nudgedAt:t.nudged_at??null}))}o(dt,"listPendingProviderRetries");async function ut(e,t,n){return(await(await p()).prepare("UPDATE node_faults SET nudged_at = ? WHERE fault_id = ? AND active = 1 AND episode_state = 'pending' AND (nudged_at IS NULL OR nudged_at <= ?)").run(new Date(t).toISOString(),e,new Date(t-n).toISOString())).changes===1}o(ut,"claimFaultNudge");async function ct(e){const t=await re(e)===null;return{async explanation(){if(!t)return null;const n=await re(e);if(n===null||n.link!=="crtr\u2192pi"||n.retry.disposition!=="fatal")return null;const r=n.op.trim(),i=n.message.trim();return r===""||i===""?null:`${r}: ${i}`}}}o(ct,"beginBootFaultAttempt");function ie(e,t){try{return ce(e).mtime.toISOString()}catch{return t}}o(ie,"markerMtime");function $(e,t){if(!f(e))return null;try{return de(e,"utf8")}catch(n){if(!t)return null;throw new Error(`cannot import recovery input ${e}: ${n.message}`)}}o($,"legacyFile");function Ie(e,t){return _(e,"SELECT execution_id, started_at FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(t)??null}o(Ie,"latestExecution");function we(e,t){const r=H(e,"nodes").has("launched_at")?_(e,"SELECT COALESCE(launched_at, created) AS started_at FROM nodes WHERE node_id = ?").get(t):_(e,"SELECT created AS started_at FROM nodes WHERE node_id = ?").get(t);if(r===void 0)throw new Error(`cannot import recovery witness for unknown node ${t}`);const i=I();return _(e,"INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, NULL, NULL, 'exited', ?, ?, 'pre-consolidation')").run(i,t,r.started_at,r.started_at),{execution_id:i,started_at:r.started_at}}o(we,"syntheticExecution");function oe(e,t,n,r,i){const a=I();return _(e,Z).run(...ee(a,t,n,r,i)),{...n,faultId:a}}o(oe,"insertMigratedFault");function De(e,t,n,r){const i=_(e,"SELECT binding_id, node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(n);if(i!==void 0){if(i.node_id!==t)throw new Error(`cannot import provider retry ${r}: transcript is live-owned by ${i.node_id}`);return i.binding_id}const a=_(e,"SELECT binding_id FROM sessions WHERE node_id = ? AND transcript_path = ? ORDER BY rowid DESC LIMIT 1").get(t,n);if(a!==void 0)return a.binding_id;const d=_(e,"SELECT created FROM nodes WHERE node_id = ?").get(t);if(d===void 0)throw new Error(`cannot import provider retry ${r}: unknown node ${t}`);const s=I();return _(e,"INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, NULL, ?, ?, NULL, NULL, ?, ?, ?)").run(s,t,n,d.created,d.created,u()),s}o(De,"migrationBinding");function _t(e){e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'node_faults'").get()!==void 0&&(H(e,"node_faults").has("notified_since")||e.exec("ALTER TABLE node_faults ADD COLUMN notified_since TEXT"))}o(_t,"addFaultNotifiedSince");function pt(e){if(Le(e),me(e),!f(C()))return;const t=se(C(),{withFileTypes:!0}).filter(r=>r.isDirectory()).sort((r,i)=>r.name.localeCompare(i.name)),n=_(e,"SELECT 1 FROM nodes WHERE node_id = ?");for(const r of t){const i=r.name,a=pe(i),d=N(a,"busy"),s=N(a,"clean-abort"),E=N(a,"transport-poison.json"),O=N(a,"fault"),A=N(a,"provider-retry"),h=f(d),v=f(s);if(!h&&!v&&!f(E)&&!f(O)&&!f(A)||!f(N(C(),i,"meta.json"))&&n.get(i)===void 0)continue;const k=$(E,!1),S=$(O,!0),R=$(A,!0);if(!h&&!v&&k===null&&S===null&&R===null)continue;let y=Ie(e,i);if(y===null&&(y=we(e,i)),h){const c=ie(d,y.started_at);_(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ?").run(c,c,y.execution_id)}else if(v){const c=ie(s,y.started_at);_(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = ?, turn_end_reason = 'teardown-aborted' WHERE execution_id = ?").run(c,c,c,y.execution_id)}if(k!==null)try{const c=JSON.parse(k);Number.isInteger(c.consecutivePasses)&&c.consecutivePasses>=1&&Array.isArray(c.routes)&&c.routes.length>0&&c.routes.every(j=>typeof j=="string"&&j!=="")&&_(e,"UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ?").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:c.consecutivePasses,routes:c.routes}}),y.execution_id)}catch{}const x=S===null?null:V(S);if(S!==null&&x===null)throw new Error(`cannot import recovery fault ${O}`);const T=R===null?null:Se(R);if(R!==null&&T===null)throw new Error(`cannot import provider retry ${A}`);let F=x===null||x.link==="broker\u2194crtrd"?null:oe(e,i,x,!0);if(T!==null){const c=De(e,i,T.sessionFile,A);F!==null&&F.since===T.fault.since?_(e,"UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run(T.state,c,u(),F.faultId):oe(e,i,T.fault,!1,{state:T.state,bindingId:c})}}}o(pt,"migrateRecoveryWitnesses");export{Me as FAULT_QUIET_MS,_t as addFaultNotifiedSince,nt as admitEpisode,ct as beginBootFaultAttempt,ut as claimFaultNudge,at as clearEpisode,et as clearFault,je as closeTurn,qe as hasAdmittedTurn,b as hydrateFault,ot as invalidateEpisode,dt as listPendingProviderRetries,ze as listUnnotifiedFatalFaultNodes,Qe as markFaultNotified,Be as markNodeRead,pt as migrateRecoveryWitnesses,Xe as openTurn,q as parseProviderError,st as readEpisode,re as readFault,Ge as readInterruptedTurn,Ye as readLiveTurn,Ke as readTransportPoisonVerdict,Ve as recordFault,tt as recordPendingEpisode,Je as recordTransportPoisonVerdict,$e as recordTurnProgress,it as reopenEpisode,Ze as setFatalFaultAnnouncer,rt as settleRejectedEpisode};
26
+ `)}o(Ae,"createFaultTable");function g(e,t){return typeof t=="string"&&e.includes(t)}o(g,"contains");function W(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(W,"isRecord");function q(e){if(!W(e)||typeof e.provider!="string"||e.provider===""||typeof e.model!="string"||e.model==="")return;const t=typeof e.status=="number"&&Number.isFinite(e.status)?e.status:void 0,n=typeof e.code=="string"||typeof e.code=="number"?e.code:void 0,r=typeof e.errno=="string"?e.errno:void 0,i=typeof e.body=="string"?e.body:void 0,a=Ee(e.ratelimit),d=Array.isArray(e.causes)?e.causes.flatMap(s=>{if(!W(s))return[];const E={...typeof s.message=="string"?{message:s.message}:{},...typeof s.status=="number"&&Number.isFinite(s.status)?{status:s.status}:{},...typeof s.code=="string"||typeof s.code=="number"?{code:s.code}:{},...typeof s.errno=="string"?{errno:s.errno}:{},...typeof s.body=="string"?{body:s.body}:{}};return Object.keys(E).length===0?[]:[E]}):void 0;return{provider:e.provider,model:e.model,...t===void 0?{}:{status:t},...n===void 0?{}:{code:n},...r===void 0?{}:{errno:r},...i===void 0?{}:{body:i},...d===void 0||d.length===0?{}:{causes:d},...a===void 0?{}:{ratelimit:a}}}o(q,"parseProviderError");function V(e){try{const t=JSON.parse(e);if(!g(ye,t.link)||typeof t.op!="string"||t.op===""||!g(Te,t.kind)||typeof t.message!="string"||typeof t.since!="string")return null;const n=Y(t.operation_id);if(!W(t.retry)||!g(Ne,t.retry.disposition)||t.retry.by!==void 0&&!g(ge,t.retry.by)||t.retry.attempt!==void 0&&typeof t.retry.attempt!="number"||t.retry.max!==void 0&&typeof t.retry.max!="number"||t.retry.nextAt!==void 0&&typeof t.retry.nextAt!="string"||t.anchorEntryId!==void 0&&(typeof t.anchorEntryId!="string"||t.anchorEntryId===""))return null;const r=t.providerError===void 0?void 0:q(t.providerError);return t.providerError!==void 0&&r===void 0?null:{link:t.link,op:t.op,kind:t.kind,retry:{disposition:t.retry.disposition,...t.retry.by===void 0?{}:{by:t.retry.by},...t.retry.attempt===void 0?{}:{attempt:t.retry.attempt},...t.retry.max===void 0?{}:{max:t.retry.max},...t.retry.nextAt===void 0?{}:{nextAt:t.retry.nextAt}},message:t.message,since:t.since,operation_id:n,...t.anchorEntryId===void 0?{}:{anchorEntryId:t.anchorEntryId},...r===void 0?{}:{providerError:r}}}catch{return null}}o(V,"parseLegacyFault");function Se(e){const t=pe(e);try{return ce(t)}catch{return t}}o(Se,"canonicalPath");function Re(e){try{const t=JSON.parse(e);if(!g(Le,t.state)||typeof t.sessionFile!="string"||t.sessionFile==="")return null;const n=JSON.stringify(t.fault),r=V(n);if(r===null||r.link!=="pi\u2192provider"||r.retry.disposition!=="auto"||r.retry.by!=="daemon")return null;const i=t.anchorEntryId;return i!==null&&typeof i!="string"||(r.anchorEntryId??null)!==i?null:{state:t.state,sessionFile:Se(t.sessionFile),fault:r}}catch{return null}}o(Re,"parseLegacyEpisode");function Q(e){return e!==void 0?Y(e):K.current()??K.fresh(n=>n)}o(Q,"resolveOperationId");function xe(e){return e.slice(0,fe)}o(xe,"clampMessage");function w(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="auto"&&e.retry.by==="daemon"?3:e.retry.disposition==="fatal"?2:1}o(w,"faultPriority");function z(e){return{link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operationId:e.operation_id,anchorEntryId:e.anchorEntryId??null,retryDisposition:e.retry.disposition,retryBy:e.retry.by??null,retryAttempt:e.retry.attempt??null,retryMax:e.retry.max??null,retryNextAt:e.retry.nextAt??null,providerError:e.providerError===void 0?null:JSON.stringify(e.providerError)}}o(z,"encodeFault");function b(e){const t=e.provider_error_json,n=typeof t=="string"?q(JSON.parse(t)):void 0;if(typeof t=="string"&&n===void 0)throw new Error(`invalid provider error on stored fault ${e.fault_id}`);const r={disposition:e.retry_disposition,...e.retry_by===null?{}:{by:e.retry_by},...e.retry_attempt===null?{}:{attempt:e.retry_attempt},...e.retry_max===null?{}:{max:e.retry_max},...e.retry_next_at===null?{}:{nextAt:e.retry_next_at}};return{faultId:e.fault_id,link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operation_id:e.operation_id,retry:r,...e.anchor_entry_id===null?{}:{anchorEntryId:e.anchor_entry_id},...n===void 0?{}:{providerError:n},...typeof e.notified_since=="string"?{notifiedSince:e.notified_since}:{}}}o(b,"hydrateFault");async function L(e,t){const n=await e.prepare("SELECT * FROM node_faults WHERE node_id = ? AND active = 1").get(t);return n===void 0?null:b(n)}o(L,"activeFault");function P(e){if(!g(G,e.link))throw new Error(`fault link is not durable: ${e.link}`);return{link:e.link,op:e.op,kind:e.kind,retry:e.retry,message:xe(e.message),since:e.since??u(),operation_id:Q(e.operation_id),...typeof e.anchorEntryId=="string"&&e.anchorEntryId!==""?{anchorEntryId:e.anchorEntryId}:{},...e.providerError===void 0?{}:{providerError:e.providerError}}}o(P,"storedFault");function M(e,t){J({level:t.retry.disposition==="fatal"?"error":"warn",event:"fault.recorded",operation_id:t.operation_id,node_id:e,error_class:B(t),fields:{link:t.link,op:t.op,kind:t.kind,retry:t.retry,message:t.message,...t.anchorEntryId===void 0?{}:{anchor_entry_id:t.anchorEntryId},...t.providerError===void 0?{}:{provider_error:t.providerError}}})}o(M,"emitRecordedFault");const Z="INSERT INTO node_faults (fault_id, node_id, link, op, kind, message, since, operation_id, anchor_entry_id, retry_disposition, retry_by, retry_attempt, retry_max, retry_next_at, provider_error_json, active, episode_state, episode_binding_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";function ee(e,t,n,r,i){const a=z(n),d=u();return[e,t,a.link,a.op,a.kind,a.message,a.since,a.operationId,a.anchorEntryId,a.retryDisposition,a.retryBy,a.retryAttempt,a.retryMax,a.retryNextAt,a.providerError,r?1:0,i?.state??null,i?.bindingId??null,d,d]}o(ee,"faultInsertParams");async function te(e,t,n,r,i){const a=I();return await e.prepare(Z).run(...ee(a,t,n,r,i)),{...n,faultId:a}}o(te,"insertFault");async function D(e,t,n,r){const i=z(n);return await e.prepare("UPDATE node_faults SET link = ?, op = ?, kind = ?, message = ?, since = ?, operation_id = ?, anchor_entry_id = ?, retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, provider_error_json = ?, episode_state = COALESCE(?, episode_state), episode_binding_id = COALESCE(?, episode_binding_id), updated_at = ? WHERE fault_id = ?").run(i.link,i.op,i.kind,i.message,i.since,i.operationId,i.anchorEntryId,i.retryDisposition,i.retryBy,i.retryAttempt,i.retryMax,i.retryNextAt,i.providerError,r?.state??null,r?.bindingId??null,u(),t),{...n,faultId:t}}o(D,"updateFault");async function U(e,t){await e.prepare("DELETE FROM node_faults WHERE fault_id = ? AND active = 0 AND episode_state IS NULL").run(t)}o(U,"deleteIfUnretained");async function ne(e,t,n,r){const i=await L(e,t);if(i===null){const a=await e.prepare("SELECT fault_id, link, kind, since, operation_id FROM node_faults WHERE node_id = ? AND active = 0 AND episode_state IS NOT NULL").get(t);return a!==void 0&&a.link===n.link&&a.kind===n.kind&&a.since===n.since&&a.operation_id===n.operation_id?(await e.prepare("UPDATE node_faults SET active = 1, updated_at = ? WHERE fault_id = ?").run(u(),a.fault_id),await D(e,a.fault_id,n)):await te(e,t,n,!0,r)}if(i.link===n.link&&i.kind===n.kind){const a=n.retry.disposition==="auto"?{...i.retry,...n.retry}:n.retry,d=i.retry.disposition===n.retry.disposition&&(n.retry.disposition!=="fatal"||i.message===n.message),s=d?i.since:n.since,E=await D(e,i.faultId,{...n,since:s,anchorEntryId:n.anchorEntryId??i.anchorEntryId,retry:a},r);return d||await e.prepare("UPDATE node_faults SET notified_since = NULL WHERE fault_id = ?").run(i.faultId),E}return i.link!==n.link&&w(i)>w(n)?null:(await e.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(u(),i.faultId),await U(e,i.faultId),await te(e,t,n,!0,r))}o(ne,"replaceActiveFault");async function m(e,t,n){return(await e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? AND unbound_at IS NULL").get(t,n))?.binding_id??null}o(m,"liveBinding");async function $e(e,t){return await l(async n=>{const r=u();return(await n.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(r,e)).changes>0?!0:(await n.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ? AND state != 'exited'").run(r,r,e)).changes>0})}o($e,"openTurn");async function je(e){return(await(await p()).prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(u(),e)).changes>0}o(je,"recordTurnProgress");async function Be(e,t){return await l(async n=>{const r=u(),i=(await n.prepare("UPDATE executions SET turn_closed_at = ?, turn_end_reason = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(r,t.endReason,e)).changes>0;if(i&&t.endReason==="settled"){const a="(SELECT node_id FROM executions WHERE execution_id = ?)";t.viewerPresent===!0?await n.prepare(`UPDATE nodes SET reply_at = ?, read_at = ? WHERE node_id = ${a}`).run(r,r,e):await n.prepare(`UPDATE nodes SET reply_at = ? WHERE node_id = ${a}`).run(r,e)}return i})}o(Be,"closeTurn");async function Je(e){return(await(await p()).prepare("UPDATE nodes SET read_at = ? WHERE node_id = ?").run(u(),e)).changes>0}o(Je,"markNodeRead");function re(e){return JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",...e})}o(re,"exitDiagnosticJson");async function Ke(e,t){if(!Number.isInteger(t.consecutivePasses)||t.consecutivePasses<1||t.routes.length===0||!t.routes.every(n=>n!==""))throw new Error("invalid transport-poison verdict");return(await(await p()).prepare("UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ? AND state != 'exited'").run(re({transport_poison:{consecutive_passes:t.consecutivePasses,routes:t.routes}}),e)).changes>0}o(Ke,"recordTransportPoisonVerdict");async function Ye(e){const t=await(await p()).prepare("SELECT exit_diagnostic_json FROM executions WHERE node_id = ? ORDER BY seq DESC LIMIT 1").get(e);if(t===void 0||t.exit_diagnostic_json===null)return null;try{const n=JSON.parse(t.exit_diagnostic_json),r=n.transport_poison;return n.schema!=="crtr.execution-exit-diagnostic/v1"||r===void 0||!Number.isInteger(r.consecutive_passes)||r.consecutive_passes<1||!Array.isArray(r.routes)||r.routes.length===0||!r.routes.every(i=>typeof i=="string"&&i!=="")?null:{consecutivePasses:r.consecutive_passes,routes:r.routes}}catch{return null}}o(Ye,"readTransportPoisonVerdict");async function Ge(e){const t=await(await p()).prepare("SELECT turn_open_at, turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND state != 'exited'").get(e);return t===void 0?null:{busy:t.turn_open_at!==null&&t.turn_closed_at===null,progressAt:t.turn_progress_at}}o(Ge,"readLiveTurn");async function qe(e){const t=await p(),n=await t.prepare("SELECT turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL ORDER BY seq DESC LIMIT 1").get(e);if(n===void 0)return null;const r=await t.prepare("SELECT turn_end_reason FROM executions WHERE node_id = ? ORDER BY seq DESC LIMIT 1").get(e);return{interrupted:n.turn_closed_at===null||r?.turn_end_reason==="teardown-aborted",progressAt:n.turn_progress_at}}o(qe,"readInterruptedTurn");async function Ve(e){return await(await p()).prepare("SELECT 1 FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL LIMIT 1").get(e)!==void 0}o(Ve,"hasAdmittedTurn");async function Qe(e,t){const n=P(t);return M(e,n),await l(async r=>await ne(r,e,n))}o(Qe,"recordFault");async function ze(e){return await l(async t=>(await t.prepare("UPDATE node_faults SET notified_since = since, updated_at = ? WHERE fault_id = ?").run(u(),e)).changes>0)}o(ze,"markFaultNotified");function Ie(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="fatal"&&e.notifiedSince!==e.since}o(Ie,"isUnnotifiedFatalProviderFault");async function Ze(){return(await(await p()).prepare("SELECT f.node_id FROM node_faults f JOIN nodes n ON n.node_id = f.node_id WHERE f.active = 1 AND f.link = 'pi\u2192provider' AND f.retry_disposition = 'fatal' AND (f.notified_since IS NULL OR f.notified_since != f.since) AND n.status IN ('active', 'idle')").all()).map(e=>e.node_id)}o(Ze,"listUnnotifiedFatalFaultNodes");let X=null;function et(e){X=e}o(et,"setFatalFaultAnnouncer");async function tt(e,t){const n=await L(await p(),e);return n===null||t?.link!==void 0&&n.link!==t.link?!1:await l(async r=>{const i=await L(r,e);if(i===null||t?.link!==void 0&&i.link!==t.link)return!1;X!==null&&Ie(i)&&await X(e);const a=await r.prepare("SELECT episode_state FROM node_faults WHERE fault_id = ?").get(i.faultId);await r.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(u(),i.faultId),a.episode_state==="pending"&&t?.preserveEpisode!==!0&&await r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(u(),i.faultId),await U(r,i.faultId);const d=Q();return J({level:"info",event:"fault.cleared",operation_id:d,node_id:e,error_class:B(i),fields:{cleared_fault_operation_id:i.operation_id,link:i.link,op:i.op,kind:i.kind,retry:i.retry,message:i.message,since:i.since,...i.anchorEntryId===void 0?{}:{anchor_entry_id:i.anchorEntryId}}}),!0})}o(tt,"clearFault");async function ie(e){return await L(await p(),e)}o(ie,"readFault");async function nt(e,t){const n=P(t.input);if(n.link!=="pi\u2192provider"||n.retry.disposition!=="auto"||n.retry.by!=="daemon")throw new Error("provider retry episode requires an automatic daemon provider fault");return M(e,n),await l(async r=>{const i=await m(r,e,t.sessionId);if(i===null)return null;const a=await L(r,e);if(a!==null&&(a.link!==n.link||a.kind!==n.kind)&&w(a)>w(n))return null;const d=await r.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_state IS NOT NULL").all(e);await r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_state IS NOT NULL").run(u(),e);for(const E of d)await U(r,E.fault_id);const s=await ne(r,e,n,{state:"pending",bindingId:i});return s===null?null:(await r.prepare("UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run("pending",i,u(),s.faultId),s)})}o(nt,"recordPendingEpisode");async function rt(e,t){return await l(async n=>{if(await n.prepare("SELECT 1 FROM nodes WHERE node_id = ? AND intent = 'refresh'").get(e)!==void 0)return!1;const r=await m(n,e,t.sessionId);return r===null?!1:(await n.prepare("UPDATE node_faults SET episode_state = 'admitted', retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, updated_at = ? WHERE node_id = ? AND fault_id = ? AND episode_state = 'pending' AND episode_binding_id = ?").run(t.retry.disposition,t.retry.by??null,t.retry.attempt??null,t.retry.max??null,t.retry.nextAt??null,u(),e,t.faultId,r)).changes>0})}o(rt,"admitEpisode");async function it(e,t){const n=P(t.fault);return await l(async r=>{const i=await m(r,e,t.sessionId);return i===null||(await L(r,e))?.faultId!==t.faultId||await r.prepare("SELECT 1 FROM node_faults WHERE node_id = ? AND fault_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").get(e,t.faultId,i)===void 0?!1:(M(e,n),t.reopen?await D(r,t.faultId,n,{state:"pending",bindingId:i}):(await D(r,t.faultId,n),await r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(u(),t.faultId),await r.prepare("UPDATE node_faults SET notified_since = NULL WHERE fault_id = ?").run(t.faultId)),!0)})}o(it,"settleRejectedEpisode");async function ot(e,t){return await l(async n=>{const r=await m(n,e,t.sessionId);return r===null?!1:(await n.prepare("UPDATE node_faults SET episode_state = 'pending', updated_at = ? WHERE node_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").run(u(),e,r)).changes>0})}o(ot,"reopenEpisode");async function at(e,t){return await l(async n=>{const r=await m(n,e,t.sessionId);return r===null?!1:(await n.prepare("UPDATE node_faults SET episode_state = 'invalidated', updated_at = ? WHERE node_id = ? AND episode_state != 'invalidated' AND episode_state IS NOT NULL AND episode_binding_id = ?").run(u(),e,r)).changes>0})}o(at,"invalidateEpisode");async function st(e,t){return await l(async n=>{const r=await m(n,e,t.sessionId);if(r===null)return!1;const i=await n.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").all(e,r);if(i.length===0)return!1;await n.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").run(u(),e,r);for(const a of i)await U(n,a.fault_id);return!0})}o(st,"clearEpisode");async function dt(e){const t=await(await p()).prepare("SELECT f.*, s.session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.node_id = ? AND f.episode_state IS NOT NULL").get(e);return t===void 0?null:{state:t.episode_state,sessionId:t.session_id,fault:b(t)}}o(dt,"readEpisode");async function ut(){return(await(await p()).prepare("SELECT f.*, s.session_id AS bound_session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.active = 1 AND f.episode_state = 'pending' AND s.unbound_at IS NULL AND s.session_id IS NOT NULL AND f.link = 'pi\u2192provider' AND f.retry_disposition = 'auto' AND f.retry_by = 'daemon' AND f.kind IN ('connection', 'rate-limit', 'overloaded')").all()).map(t=>({nodeId:t.node_id,sessionId:t.bound_session_id,fault:b(t),updatedAt:t.updated_at,nudgedAt:t.nudged_at??null}))}o(ut,"listPendingProviderRetries");async function ct(e,t,n){return(await(await p()).prepare("UPDATE node_faults SET nudged_at = ? WHERE fault_id = ? AND active = 1 AND episode_state = 'pending' AND (nudged_at IS NULL OR nudged_at <= ?)").run(new Date(t).toISOString(),e,new Date(t-n).toISOString())).changes===1}o(ct,"claimFaultNudge");async function _t(e){const t=await ie(e)===null;return{async explanation(){if(!t)return null;const n=await ie(e);if(n===null||n.link!=="crtr\u2192pi"||n.retry.disposition!=="fatal")return null;const r=n.op.trim(),i=n.message.trim();return r===""||i===""?null:`${r}: ${i}`}}}o(_t,"beginBootFaultAttempt");function oe(e,t){try{return _e(e).mtime.toISOString()}catch{return t}}o(oe,"markerMtime");function $(e,t){if(!f(e))return null;try{return ue(e,"utf8")}catch(n){if(!t)return null;throw new Error(`cannot import recovery input ${e}: ${n.message}`)}}o($,"legacyFile");function we(e,t){return _(e,"SELECT execution_id, started_at FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(t)??null}o(we,"latestExecution");function De(e,t){const r=H(e,"nodes").has("launched_at")?_(e,"SELECT COALESCE(launched_at, created) AS started_at FROM nodes WHERE node_id = ?").get(t):_(e,"SELECT created AS started_at FROM nodes WHERE node_id = ?").get(t);if(r===void 0)throw new Error(`cannot import recovery witness for unknown node ${t}`);const i=I();return _(e,"INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, NULL, NULL, 'exited', ?, ?, 'pre-consolidation')").run(i,t,r.started_at,r.started_at),{execution_id:i,started_at:r.started_at}}o(De,"syntheticExecution");function ae(e,t,n,r,i){const a=I();return _(e,Z).run(...ee(a,t,n,r,i)),{...n,faultId:a}}o(ae,"insertMigratedFault");function Ue(e,t,n,r){const i=_(e,"SELECT binding_id, node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(n);if(i!==void 0){if(i.node_id!==t)throw new Error(`cannot import provider retry ${r}: transcript is live-owned by ${i.node_id}`);return i.binding_id}const a=_(e,"SELECT binding_id FROM sessions WHERE node_id = ? AND transcript_path = ? ORDER BY rowid DESC LIMIT 1").get(t,n);if(a!==void 0)return a.binding_id;const d=_(e,"SELECT created FROM nodes WHERE node_id = ?").get(t);if(d===void 0)throw new Error(`cannot import provider retry ${r}: unknown node ${t}`);const s=I();return _(e,"INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, NULL, ?, ?, NULL, NULL, ?, ?, ?)").run(s,t,n,d.created,d.created,u()),s}o(Ue,"migrationBinding");function pt(e){e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'node_faults'").get()!==void 0&&(H(e,"node_faults").has("notified_since")||e.exec("ALTER TABLE node_faults ADD COLUMN notified_since TEXT"))}o(pt,"addFaultNotifiedSince");function lt(e){if(me(e),Ae(e),!f(C()))return;const t=de(C(),{withFileTypes:!0}).filter(r=>r.isDirectory()).sort((r,i)=>r.name.localeCompare(i.name)),n=_(e,"SELECT 1 FROM nodes WHERE node_id = ?");for(const r of t){const i=r.name,a=le(i),d=N(a,"busy"),s=N(a,"clean-abort"),E=N(a,"transport-poison.json"),O=N(a,"fault"),A=N(a,"provider-retry"),h=f(d),k=f(s);if(!h&&!k&&!f(E)&&!f(O)&&!f(A)||!f(N(C(),i,"meta.json"))&&n.get(i)===void 0)continue;const v=$(E,!1),S=$(O,!0),R=$(A,!0);if(!h&&!k&&v===null&&S===null&&R===null)continue;let y=we(e,i);if(y===null&&(y=De(e,i)),h){const c=oe(d,y.started_at);_(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ?").run(c,c,y.execution_id)}else if(k){const c=oe(s,y.started_at);_(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = ?, turn_end_reason = 'teardown-aborted' WHERE execution_id = ?").run(c,c,c,y.execution_id)}if(v!==null)try{const c=JSON.parse(v);Number.isInteger(c.consecutivePasses)&&c.consecutivePasses>=1&&Array.isArray(c.routes)&&c.routes.length>0&&c.routes.every(j=>typeof j=="string"&&j!=="")&&_(e,"UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ?").run(re({transport_poison:{consecutive_passes:c.consecutivePasses,routes:c.routes}}),y.execution_id)}catch{}const x=S===null?null:V(S);if(S!==null&&x===null)throw new Error(`cannot import recovery fault ${O}`);const T=R===null?null:Re(R);if(R!==null&&T===null)throw new Error(`cannot import provider retry ${A}`);let F=x===null||x.link==="broker\u2194crtrd"?null:ae(e,i,x,!0);if(T!==null){const c=Ue(e,i,T.sessionFile,A);F!==null&&F.since===T.fault.since?_(e,"UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run(T.state,c,u(),F.faultId):ae(e,i,T.fault,!1,{state:T.state,bindingId:c})}}}o(lt,"migrateRecoveryWitnesses");export{Xe as FAULT_QUIET_MS,pt as addFaultNotifiedSince,rt as admitEpisode,_t as beginBootFaultAttempt,ct as claimFaultNudge,st as clearEpisode,tt as clearFault,Be as closeTurn,re as exitDiagnosticJson,Ve as hasAdmittedTurn,b as hydrateFault,at as invalidateEpisode,ut as listPendingProviderRetries,Ze as listUnnotifiedFatalFaultNodes,ze as markFaultNotified,Je as markNodeRead,lt as migrateRecoveryWitnesses,$e as openTurn,q as parseProviderError,dt as readEpisode,ie as readFault,qe as readInterruptedTurn,Ge as readLiveTurn,Ye as readTransportPoisonVerdict,Qe as recordFault,nt as recordPendingEpisode,Ke as recordTransportPoisonVerdict,je as recordTurnProgress,ot as reopenEpisode,et as setFatalFaultAnnouncer,it as settleRejectedEpisode};
@@ -0,0 +1,126 @@
1
+ -- 008-import.sql — cycle 6 I-S (design-import "Data model"): the tables the import lane, the remote machine runner and the
2
+ -- sandbox import step share, and the third runner kind. One transaction, additions only: `schema_min_reader` is not touched
3
+ -- and stays 2, so the previous build (v0.3.569, schema 7) still opens a version-8 database. It never names the new tables.
4
+ -- Timestamps are text, as in 002; `*_json` columns are text holding JSON, as in 002; a lane's claim is `claimed_by` +
5
+ -- `lease_expires_at`, as on the cycle 5 lanes' rows.
6
+
7
+ -- ---------------------------------------------------------------- the remote machine runner is a third runner (design-import I13)
8
+ -- Widening a CHECK is rollback-safe (007 did the same for `limits_source_check`): the previous build never writes 'machine'.
9
+ ALTER TABLE executions DROP CONSTRAINT IF EXISTS executions_runner_check;
10
+ ALTER TABLE executions ADD CONSTRAINT executions_runner_check CHECK (runner IN ('process', 'blaxel', 'machine'));
11
+
12
+ -- ---------------------------------------------------------------- import runs
13
+
14
+ -- One row per attempt. A run is open in `uploading`, `loading` and `committed`; `done` closes it for good. At most one run per
15
+ -- source install id is open or `done` (a `failed` or `rolled_back` run does not count), which is the rule behind the API's
16
+ -- `already_imported` and `import_running` refusals.
17
+ CREATE TABLE import_runs (
18
+ run_id text PRIMARY KEY,
19
+ source_install_id text NOT NULL,
20
+ source_kind text NOT NULL CHECK (source_kind IN ('machine', 'hosted')),
21
+ person text NOT NULL,
22
+ release text NOT NULL,
23
+ state text NOT NULL CHECK (state IN ('uploading', 'loading', 'committed', 'done', 'failed', 'rolled_back')),
24
+ committed_at text,
25
+ step text,
26
+ counts_json text,
27
+ verify_json text,
28
+ failure_json text,
29
+ manifest_key text,
30
+ created_at text NOT NULL,
31
+ loaded_at text,
32
+ finished_at text,
33
+ claimed_by text,
34
+ lease_expires_at text
35
+ );
36
+ CREATE UNIQUE INDEX import_runs_one_per_install ON import_runs(source_install_id) WHERE state IN ('uploading', 'loading', 'committed', 'done');
37
+ -- The lane's SKIP LOCKED claim: `loading` rows, and `committed` rows whose lease expired (resume at verify).
38
+ CREATE INDEX import_runs_claim ON import_runs(state, lease_expires_at) WHERE state IN ('loading', 'committed');
39
+
40
+ -- The three identity-keyed tables' source keys → the keys the target assigned (`audit_rows.id`, `review_comment_events.seq`,
41
+ -- `app_users.uid`). Written in the copy transaction.
42
+ CREATE TABLE import_key_map (
43
+ source_install_id text NOT NULL,
44
+ table_name text NOT NULL,
45
+ source_key text NOT NULL,
46
+ target_key text NOT NULL,
47
+ PRIMARY KEY (source_install_id, table_name, source_key)
48
+ );
49
+
50
+ -- The primary keys each run inserted, per table (a compressed list): the authority for verify and rollback.
51
+ CREATE TABLE import_manifest (
52
+ run_id text NOT NULL REFERENCES import_runs(run_id),
53
+ table_name text NOT NULL,
54
+ inserted_keys bytea NOT NULL,
55
+ PRIMARY KEY (run_id, table_name)
56
+ );
57
+
58
+ -- The id map and path map a run persisted, so a later import that maps onto this run's rows is visible to rollback.
59
+ CREATE TABLE import_id_map (
60
+ run_id text NOT NULL REFERENCES import_runs(run_id),
61
+ kind text NOT NULL CHECK (kind IN ('object', 'grant', 'kind', 'path')),
62
+ source_value text NOT NULL,
63
+ target_value text NOT NULL,
64
+ PRIMARY KEY (run_id, kind, source_value)
65
+ );
66
+ CREATE INDEX import_id_map_target ON import_id_map(kind, target_value);
67
+
68
+ -- ---------------------------------------------------------------- the remote machine runner
69
+
70
+ -- One live link per machine environment; the API process holding it renews `lease_expires_at`, and a delete is fenced by `generation`.
71
+ CREATE TABLE machine_links (
72
+ environment text PRIMARY KEY REFERENCES environments(object) ON DELETE CASCADE,
73
+ generation bigint NOT NULL,
74
+ api_address text NOT NULL,
75
+ connected_at text NOT NULL,
76
+ lease_expires_at text NOT NULL
77
+ );
78
+
79
+ -- The machine credential (principal `machine:<environment id>`): one live credential per environment, stored as the sha256 hex of
80
+ -- the secret (as `node_tokens`). Issued once when a machine source's run is `done`; `crtr sys machine rotate` replaces the row;
81
+ -- rollback and the console set `revoked_at`, which closes the link within one heartbeat.
82
+ CREATE TABLE machine_credentials (
83
+ environment text PRIMARY KEY REFERENCES environments(object) ON DELETE CASCADE,
84
+ token_hash text NOT NULL UNIQUE,
85
+ issued_at text NOT NULL,
86
+ revoked_at text
87
+ );
88
+
89
+ -- A runner call to a machine: `pending` until the holder of the link claims it (`claimed_generation`, `lease_expires_at`), `sent`
90
+ -- once forwarded, then `done` or `failed` with `result_json`. Deleted a day after `finished_at`.
91
+ CREATE TABLE machine_calls (
92
+ call_id text PRIMARY KEY,
93
+ environment text NOT NULL REFERENCES environments(object) ON DELETE CASCADE,
94
+ op text NOT NULL CHECK (op IN ('start', 'wake', 'stop', 'status', 'delete', 'viewer', 'cron.run', 'cron.cancel')),
95
+ args_json text NOT NULL DEFAULT '{}',
96
+ state text NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'sent', 'done', 'failed')),
97
+ claimed_generation bigint,
98
+ lease_expires_at text,
99
+ result_json text,
100
+ created_at text NOT NULL,
101
+ finished_at text
102
+ );
103
+ -- The holder's SKIP LOCKED claim of an environment's open calls, and the lease sweep.
104
+ CREATE INDEX machine_calls_open ON machine_calls(environment, state, created_at) WHERE state IN ('pending', 'sent');
105
+ CREATE INDEX machine_calls_finished ON machine_calls(finished_at) WHERE finished_at IS NOT NULL;
106
+
107
+ -- ---------------------------------------------------------------- hosted running nodes' files
108
+
109
+ -- The archive a hosted running node's sandbox unpacks at its next start; `unpacked_at` is set when the start script reports it.
110
+ -- Storage maintenance deletes the blob 30 days after unpack.
111
+ CREATE TABLE import_node_files (
112
+ node text PRIMARY KEY REFERENCES nodes(node_id) ON DELETE CASCADE,
113
+ blob_key text NOT NULL,
114
+ unpacked_at text
115
+ );
116
+ CREATE INDEX import_node_files_unpacked ON import_node_files(unpacked_at) WHERE unpacked_at IS NOT NULL;
117
+
118
+ -- ---------------------------------------------------------------- the 30-day physical purge (I-8)
119
+
120
+ -- The purge's candidate scan (soft-deleted rows other than nodes, which 005's index and D100's pass cover) and its reference
121
+ -- checks on authorship and kind ownership.
122
+ CREATE INDEX objects_deleted ON objects(deleted_at) WHERE deleted_at IS NOT NULL AND type <> 'node';
123
+ CREATE INDEX revisions_author ON revisions(author) WHERE author IS NOT NULL;
124
+ CREATE INDEX object_kinds_owner ON object_kinds(owner);
125
+
126
+ INSERT INTO canvas_meta (key, value) VALUES ('schema_min_reader', '2') ON CONFLICT (key) DO NOTHING;
@@ -1,7 +1,7 @@
1
1
  /** The canvas schema version this build supports: the number of the highest delta in `schema-pg/` (the baseline
2
2
  * `schema-pg.sql` is version 1). `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse
3
3
  * one below the live database's `schema_min_reader`. */
4
- export declare const CANVAS_SCHEMA_VERSION = 7;
4
+ export declare const CANVAS_SCHEMA_VERSION = 8;
5
5
  /** The Postgres schema holding the canvas tables: `CRTR_DATABASE_SCHEMA` (`crtr` in the cloud deploy). Unset or empty
6
6
  * means the role's own search_path (`public` on a Mac and on a VM). A name that is not a plain identifier is an error:
7
7
  * it is put in a `search_path` connection option. */
@@ -1 +1 @@
1
- var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=7;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
1
+ var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=8;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
@@ -8,9 +8,8 @@ export interface LeafRunContext {
8
8
  * caller. For optional parameters, presence in this set means the value was
9
9
  * supplied, even if it is falsy (empty string, 0, false, etc.). Parameters
10
10
  * absent from this set were either defaulted or not provided. For stdin
11
- * specifically, presence means it was piped/redirected (non-TTY), supplied
12
- * as a positional argument, or explicitly passed; absence means TTY with no
13
- * pipe (truly absent). */
11
+ * specifically, presence means piped/redirected bytes or a positional argument
12
+ * supplied it; absence means a TTY, an empty stream, or nothing given. */
14
13
  readonly providedParams: ReadonlySet<string>;
15
14
  /** Absolute source paths for JSON values supplied through `--context-file`,
16
15
  * keyed by their camel-cased parameter name. The parsed JSON remains the
@@ -1,10 +1,10 @@
1
- var G=Object.defineProperty;var f=(e,i)=>G(e,"name",{value:i,configurable:!0});import{renderRoot as V,renderBranch as I,renderLeafArgv as W,withAsyncHelpState as L,collapseWideChildren as K,rootPromotedChildren as J}from"./help.js";import{beginPreview as D,publishPreviewResult as Y,readStdinRaw as B,peekStdinRaw as Q,emit as X,handle as Z,setJsonOutput as ee,isJsonOutput as ne}from"./io.js";import{renderResult as te}from"./render.js";import{CrtrError as H}from"./errors.js";import{validateValue as ie}from"../api/command-manifest/result.js";import{isManifestStaleError as re}from"./manifest-stale.js";import{operationIdContext as ae}from"./events/operation-id.js";import{ExitCode as C}from"../types.js";import{envNodeId as oe}from"../shared/env.js";import{readFileSync as se}from"node:fs";import{spawnSync as le}from"node:child_process";import{constants as de}from"node:os";import{isatty as fe}from"node:tty";import{resolve as ce}from"node:path";function q(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))q(i);return Object.freeze(e)}f(q,"freezeCommandMetadata");function Je(e){const i=q(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>L(i,r,n)),slash:e.slash===void 0?void 0:q(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(Je,"defineLeaf");function A(e){return e.kind==="branch"&&e.children.length>0&&M(e)===0?"hidden":e.tier??"normal"}f(A,"effectiveTier");function M(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(M,"visibleSubCount");function Be(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=M(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:A(i),kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:e.children}}f(Be,"defineBranch");function Me(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Me,"collectSlashSpecs");function ze(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0&&A(n)!=="hidden").map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),d=J(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:d.length>0?d:void 0,otherSubcommandCount:a.length-d.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(ze,"defineRoot");function ue(e){if(e.kind==="root")return e.subtrees.filter(i=>A(i)!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=K(e.children.filter(n=>A(n)!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(ue,"childNames");function he(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const d=i[a];if(d==="-h"||d==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(he,"walk");function pe(e){return e.kind==="root"?V(e.help):e.kind==="branch"?I(e.help):W(e.help)}f(pe,"renderNode");async function z(e,i,r){if(r!==void 0&&e.kind==="leaf")return W(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?W(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?I(await e.effectiveHelp()):pe(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:d},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:k}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),v=await d(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:k});return n+v.map(E=>`
1
+ var z=Object.defineProperty;var f=(e,i)=>z(e,"name",{value:i,configurable:!0});import{renderRoot as F,renderBranch as V,renderLeafArgv as O,withAsyncHelpState as G,collapseWideChildren as L,rootPromotedChildren as J}from"./help.js";import{beginPreview as K,publishPreviewResult as D,readStdinRaw as B,peekStdinRaw as Y,emit as Q,handle as X,setJsonOutput as Z,isJsonOutput as ee}from"./io.js";import{renderResult as ne}from"./render.js";import{CrtrError as H}from"./errors.js";import{validateValue as te}from"../api/command-manifest/result.js";import{isManifestStaleError as ie}from"./manifest-stale.js";import{operationIdContext as re}from"./events/operation-id.js";import{ExitCode as C}from"../types.js";import{envNodeId as ae}from"../shared/env.js";import{readFileSync as oe}from"node:fs";import{spawnSync as se}from"node:child_process";import{constants as le}from"node:os";import{resolve as de}from"node:path";function W(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))W(i);return Object.freeze(e)}f(W,"freezeCommandMetadata");function Fe(e){const i=W(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>G(i,r,n)),slash:e.slash===void 0?void 0:W(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(Fe,"defineLeaf");function A(e){return e.kind==="branch"&&e.children.length>0&&I(e)===0?"hidden":e.tier??"normal"}f(A,"effectiveTier");function I(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(I,"visibleSubCount");function Ve(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=I(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:A(i),kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:e.children}}f(Ve,"defineBranch");function Je(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Je,"collectSlashSpecs");function Be(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0&&A(n)!=="hidden").map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),d=J(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:d.length>0?d:void 0,otherSubcommandCount:a.length-d.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(Be,"defineRoot");function fe(e){if(e.kind==="root")return e.subtrees.filter(i=>A(i)!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=L(e.children.filter(n=>A(n)!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(fe,"childNames");function ce(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const d=i[a];if(d==="-h"||d==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(ce,"walk");function ue(e){return e.kind==="root"?F(e.help):e.kind==="branch"?V(e.help):O(e.help)}f(ue,"renderNode");async function M(e,i,r){if(r!==void 0&&e.kind==="leaf")return O(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?O(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?V(await e.effectiveHelp()):ue(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:d},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:k}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),v=await d(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:k});return n+v.map(E=>`
2
2
 
3
3
  <plugin-help plugin="${E.plugin}">
4
4
  ${E.text}
5
- </plugin-help>`).join("")}f(z,"renderNodeWithAddenda");function me(e){return e.some(i=>i==="-h"||i==="--help")}f(me,"helpRequested");function ge(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(ge,"focusedHelpFlag");function we(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(we,"rawArgsAfterPath");function ve(e,i){const r=le(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new H("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,C.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=de.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(ve,"runPassthrough");function be(e,i,r){const n=ue(e),a=n.length>0?n.join(", "):"(none)",d=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new H("unknown_path",`unknown subcommand: ${r}`,C.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${d}\` for how to find the rest.`:`Valid children: ${a}. Run \`${d}\` for the full list.`})}f(be,"unknownPathError");const xe="https://account.northlight.ai";function ke(e){const i=xe,r=`open ${i}/account/machines, choose Create join token, then run \`crtr sys join --directory ${i} --token <token>\``;return new H("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from Northlight, this machine isn't joined to your account yet: ${r}.`,C.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}f(ke,"notJoinedError");async function ye(){try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}f(ye,"runtimeUnjoined");function m(e,i,r,n,a){return new H(e,i,C.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(m,"parseArgvError");function $(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f($,"flagNameToKey");async function $e(e,i,r){const n={},a=new Set,d=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),k=e.find(t=>t.kind==="context-file"),v=e.filter(t=>t.kind==="flag"),E=new Map(v.map(t=>[t.name,t])),S=v.some(t=>t.sensitive===!0);for(const t of v)t.type==="bool"?n[$(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[$(t.name)]=t.default);const g=[];let b=0,j=!1,w=!1,u=0;for(;u<i.length;){const t=i[u];if(t==="--"&&!w){w=!0,u++;continue}if(!w&&t.startsWith("--")){const p=t.indexOf("=");let l,_;if(p!==-1?(l=t.slice(2,p),_=t.slice(p+1)):l=t.slice(2),l==="context-file"){if(k===void 0)throw m("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const c=_!==void 0?_:i[++u];if(c===void 0)throw m("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let x;try{x=se(c,"utf8")}catch{throw m("invalid_type",`--context-file: cannot read file: ${c}`,c,"context-file","Provide a readable file path.")}let F;try{F=JSON.parse(x)}catch{throw m("invalid_type",`--context-file: file is not valid JSON: ${c}`,c,"context-file","Ensure the file contains a valid JSON object.")}const O=$(k.name);n[O]=F,d.set(O,ce(c)),a.add(O),u++;continue}const P=E.get(l);if(P===void 0)throw m("unknown_flag",`unknown flag: --${l}`,`--${l}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const R=$(l);if(a.add(R),P.type==="bool"){if(_!==void 0)throw m("invalid_type",`boolean flag --${l} takes no value`,`--${l}=${_}`,l,`Use --${l} (presence = true) with no value.`);n[R]=!0,u++;continue}const y=_!==void 0?_:i[++u];if(y===void 0||_===void 0&&y.startsWith("--")){const c=P.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${l} -h\` and read the focused value contract before retrying.`:void 0;throw m("missing_parameter",`--${l} requires a value`,`--${l}`,l,c)}let U;if(P.type==="int"){const c=Number(y);if(!Number.isInteger(c))throw m("invalid_type",`--${l} must be an integer`,y,l,`Provide an integer value for --${l}.`);U=c}else if(P.type==="enum"){const c=P.choices;if(c!==void 0&&!c.includes(y))throw m("invalid_type",`--${l} must be one of: ${c.join(", ")}`,y,l,`Retry with one of: ${c.join(", ")}.`);U=y}else if(P.type==="json"){let c;try{c=JSON.parse(y)}catch{throw m("invalid_type",`--${l} is not valid JSON`,y,l,`Pass one JSON value for --${l}; run -h for its shape.`)}const x=P.shape===void 0?null:ie(P.shape,c,`--${l}`);if(x!==null)throw m("invalid_type",`${x.path} must be ${x.expected}, got ${x.receivedType}`,y,x.path,`Fix ${x.path}; run -h for the shape of --${l}.`);U=c}else U=y;if(P.repeatable===!0){const c=n[R],x=Array.isArray(c)?c:[];x.push(U),n[R]=x}else n[R]=U;u++;continue}const h=s[b];if(h!==void 0){const p=$(h.name);if(h.repeatable===!0){const l=n[p]??[];l.push(t),n[p]=l}else n[p]=t,b++;g.push(t),j=!0,a.add(p),u++;continue}if(g.length>0){if(o!==void 0&&s.length===0){g.push(t),j=!0,u++;continue}throw o!==void 0?m("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):m("bad_invocation",S?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,S?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw m("bad_invocation",S?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,S?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");g.push(t),j=!0,u++}let T=!1;if(o!==void 0){if(g.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?m("bad_invocation",t.message,void 0,t.field,t.next):m("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(g.length>0&&s.length===0&&o.allowPositional!==!1){const t=g[0];if(t==="-"){const h=await B();(h.trim()!==""||!o.required)&&(n[$(o.name)]=h),T=!0}else{if((await Q()).trim()!==""){const p=o.positionalStdinConflict?.(n);throw m("bad_invocation",p?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,p?.field??o.name,p?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[$(o.name)]=t,T=!0}}else{const t=fe(0),h=await B();(h.trim()!==""||!o.required)&&(n[$(o.name)]=h),T=!t||j}}T&&o!==void 0&&a.add($(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const h=$(t.name);if(a.has(h))continue;const p=process.env[t.defaultFromEnv];p===void 0||p===""||(n[h]=t.kind==="positional"&&t.repeatable===!0?[p]:p,a.add(h))}const N=[];for(const t of e){const h=$(t.name),p=t.kind==="flag"&&t.type==="bool"&&!a.has(h);if(t.required&&(p||n[h]===void 0||n[h]===null)){const l=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";N.push({display:l,name:t.name})}}if(N.length>0){const t=N.map(l=>l.display).join(", "),h=r?.leafPath,p=h!==void 0&&h.length>0?`\`crtr ${h.join(" ")} -h\``:"this command with -h";throw m("missing_parameter",N.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,N.map(l=>l.name).join(", "),`Run ${p} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(d),n}f($e,"parseArgv");function Se(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const d=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&d===void 0){i.push(a);continue}const s=d??e[n+1];if(s===void 0||s===""||d===void 0&&s.startsWith("-"))throw m("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");d===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f(Se,"takeMachineSelector");const Pe=new Set(["--json","--no-autostart"]);async function Ge(e,i){const r=i.slice(2);r.includes("--json")&&ee(!0);const n=r.filter(o=>!Pe.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=oe()?{...e.help,commands:await Promise.all(e.help.commands.map(async k=>{const v=e.subtrees.find(b=>b.name===k.name);if(v?.effectiveHelp===void 0)return k;const S=((await v.effectiveHelp()).listing??[]).filter(b=>b.tier!=="hidden"),g=J(S).map(b=>({path:`${v.name} ${b.name}`,desc:b.tier==="important"?b.description:void 0}));return{...k,subcommands:g.length?g:void 0,otherSubcommandCount:S.length-g.length}}))}:e.help;process.stdout.write(V(o)+`
5
+ </plugin-help>`).join("")}f(M,"renderNodeWithAddenda");function he(e){return e.some(i=>i==="-h"||i==="--help")}f(he,"helpRequested");function me(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(me,"focusedHelpFlag");function pe(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(pe,"rawArgsAfterPath");function ge(e,i){const r=se(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new H("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,C.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=le.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(ge,"runPassthrough");function we(e,i,r){const n=fe(e),a=n.length>0?n.join(", "):"(none)",d=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new H("unknown_path",`unknown subcommand: ${r}`,C.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${d}\` for how to find the rest.`:`Valid children: ${a}. Run \`${d}\` for the full list.`})}f(we,"unknownPathError");const ve="https://account.northlight.ai";function be(e){const i=ve,r=`open ${i}/account/machines, choose Create join token, then run \`crtr sys join --directory ${i} --token <token>\``;return new H("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from Northlight, this machine isn't joined to your account yet: ${r}.`,C.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}f(be,"notJoinedError");async function xe(){try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}f(xe,"runtimeUnjoined");function p(e,i,r,n,a){return new H(e,i,C.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(p,"parseArgvError");function y(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f(y,"flagNameToKey");async function ke(e,i,r){const n={},a=new Set,d=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),k=e.find(t=>t.kind==="context-file"),v=e.filter(t=>t.kind==="flag"),E=new Map(v.map(t=>[t.name,t])),S=v.some(t=>t.sensitive===!0);for(const t of v)t.type==="bool"?n[y(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[y(t.name)]=t.default);const w=[];let b=0,U=!1,u=0;for(;u<i.length;){const t=i[u];if(t==="--"&&!U){U=!0,u++;continue}if(!U&&t.startsWith("--")){const h=t.indexOf("=");let l,_;if(h!==-1?(l=t.slice(2,h),_=t.slice(h+1)):l=t.slice(2),l==="context-file"){if(k===void 0)throw p("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const c=_!==void 0?_:i[++u];if(c===void 0)throw p("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let x;try{x=oe(c,"utf8")}catch{throw p("invalid_type",`--context-file: cannot read file: ${c}`,c,"context-file","Provide a readable file path.")}let q;try{q=JSON.parse(x)}catch{throw p("invalid_type",`--context-file: file is not valid JSON: ${c}`,c,"context-file","Ensure the file contains a valid JSON object.")}const T=y(k.name);n[T]=q,d.set(T,de(c)),a.add(T),u++;continue}const P=E.get(l);if(P===void 0)throw p("unknown_flag",`unknown flag: --${l}`,`--${l}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const R=y(l);if(a.add(R),P.type==="bool"){if(_!==void 0)throw p("invalid_type",`boolean flag --${l} takes no value`,`--${l}=${_}`,l,`Use --${l} (presence = true) with no value.`);n[R]=!0,u++;continue}const $=_!==void 0?_:i[++u];if($===void 0||_===void 0&&$.startsWith("--")){const c=P.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${l} -h\` and read the focused value contract before retrying.`:void 0;throw p("missing_parameter",`--${l} requires a value`,`--${l}`,l,c)}let j;if(P.type==="int"){const c=Number($);if(!Number.isInteger(c))throw p("invalid_type",`--${l} must be an integer`,$,l,`Provide an integer value for --${l}.`);j=c}else if(P.type==="enum"){const c=P.choices;if(c!==void 0&&!c.includes($))throw p("invalid_type",`--${l} must be one of: ${c.join(", ")}`,$,l,`Retry with one of: ${c.join(", ")}.`);j=$}else if(P.type==="json"){let c;try{c=JSON.parse($)}catch{throw p("invalid_type",`--${l} is not valid JSON`,$,l,`Pass one JSON value for --${l}; run -h for its shape.`)}const x=P.shape===void 0?null:te(P.shape,c,`--${l}`);if(x!==null)throw p("invalid_type",`${x.path} must be ${x.expected}, got ${x.receivedType}`,$,x.path,`Fix ${x.path}; run -h for the shape of --${l}.`);j=c}else j=$;if(P.repeatable===!0){const c=n[R],x=Array.isArray(c)?c:[];x.push(j),n[R]=x}else n[R]=j;u++;continue}const m=s[b];if(m!==void 0){const h=y(m.name);if(m.repeatable===!0){const l=n[h]??[];l.push(t),n[h]=l}else n[h]=t,b++;w.push(t),a.add(h),u++;continue}if(w.length>0){if(o!==void 0&&s.length===0){w.push(t),u++;continue}throw o!==void 0?p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):p("bad_invocation",S?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,S?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw p("bad_invocation",S?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,S?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");w.push(t),u++}let g=!1;if(o!==void 0){if(w.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?p("bad_invocation",t.message,void 0,t.field,t.next):p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(w.length>0&&s.length===0&&o.allowPositional!==!1){const t=w[0];if(t==="-"){const m=await B();(m.trim()!==""||!o.required)&&(n[y(o.name)]=m),g=!0}else{if((await Y()).trim()!==""){const h=o.positionalStdinConflict?.(n);throw p("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,h?.field??o.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[y(o.name)]=t,g=!0}}else{const t=await B();t!==""&&(t.trim()!==""||!o.required)&&(n[y(o.name)]=t,g=!0)}}g&&o!==void 0&&a.add(y(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const m=y(t.name);if(a.has(m))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[m]=t.kind==="positional"&&t.repeatable===!0?[h]:h,a.add(m))}const N=[];for(const t of e){const m=y(t.name),h=t.kind==="flag"&&t.type==="bool"&&!a.has(m);if(t.required&&(h||n[m]===void 0||n[m]===null)){const l=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";N.push({display:l,name:t.name})}}if(N.length>0){const t=N.map(l=>l.display).join(", "),m=r?.leafPath,h=m!==void 0&&m.length>0?`\`crtr ${m.join(" ")} -h\``:"this command with -h";throw p("missing_parameter",N.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,N.map(l=>l.name).join(", "),`Run ${h} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(d),n}f(ke,"parseArgv");function $e(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const d=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&d===void 0){i.push(a);continue}const s=d??e[n+1];if(s===void 0||s===""||d===void 0&&s.startsWith("-"))throw p("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");d===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f($e,"takeMachineSelector");const ye=new Set(["--json","--no-autostart"]);async function Ie(e,i){const r=i.slice(2);r.includes("--json")&&Z(!0);const n=r.filter(o=>!ye.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=ae()?{...e.help,commands:await Promise.all(e.help.commands.map(async k=>{const v=e.subtrees.find(b=>b.name===k.name);if(v?.effectiveHelp===void 0)return k;const S=((await v.effectiveHelp()).listing??[]).filter(b=>b.tier!=="hidden"),w=J(S).map(b=>({path:`${v.name} ${b.name}`,desc:b.tier==="important"?b.description:void 0}));return{...k,subcommands:w.length?w:void 0,otherSubcommandCount:S.length-w.length}}))}:e.help;process.stdout.write(F(o)+`
6
6
  `),process.exitCode=C.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:o}=await import("../commands/sys/shared.js");process.stdout.write(o()+`
7
- `),process.exitCode=C.SUCCESS;return}let{node:a,path:d,remaining:s}=he(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&ve(a.passthrough,we(r,d)),a.kind==="branch"&&a.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new H("provider_unavailable",a.unavailable,C.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&await ye()?ke(s[0]):be(a,d,s[0]);if(me(s)){process.stdout.write(await z(a,d,ge(a,s))+`
8
- `),process.exitCode=C.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await z(a,d)+`
9
- `),process.exitCode=C.SUCCESS;return}const o=a;D(d.join(" "));const k=o.help.params!==void 0?o.help.params:[];let v=new Set,E=new Map,S=s,g;if(o.machineSelector===!0&&!k.some(u=>u.kind==="flag"&&u.name==="machine")){const u=Se(s);S=u.rest,g=u.machine}const b=await $e(k,S,{leafPath:d,onProvidedParams:f(u=>{v=u},"onProvidedParams"),onContextFilePaths:f(u=>{E=u},"onContextFilePaths")}),j={providedParams:v,contextFilePaths:E,...g===void 0?{}:{machine:g}},w=await ae.fresh(()=>o.run(b,j));if(Y(w==null?w:o.preview?.(w)??w),w!=null)if(ne())X(w);else{const u=o.render!==void 0?o.render(w):te(w,o.help);process.stdout.write(u+(o.stdoutExact?.(w)?"":`
10
- `))}}catch(o){if(re(o))throw o;Z(o)}}f(Ge,"runCli");export{xe as DEFAULT_DIRECTORY_ISSUER,Pe as GLOBAL_TOKENS,Me as collectSlashSpecs,Be as defineBranch,Je as defineLeaf,ze as defineRoot,ke as notJoinedError,$e as parseArgv,pe as renderNode,Ge as runCli,Se as takeMachineSelector,be as unknownPathError,he as walk};
7
+ `),process.exitCode=C.SUCCESS;return}let{node:a,path:d,remaining:s}=ce(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&ge(a.passthrough,pe(r,d)),a.kind==="branch"&&a.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new H("provider_unavailable",a.unavailable,C.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&await xe()?be(s[0]):we(a,d,s[0]);if(he(s)){process.stdout.write(await M(a,d,me(a,s))+`
8
+ `),process.exitCode=C.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await M(a,d)+`
9
+ `),process.exitCode=C.SUCCESS;return}const o=a;K(d.join(" "));const k=o.help.params!==void 0?o.help.params:[];let v=new Set,E=new Map,S=s,w;if(o.machineSelector===!0&&!k.some(g=>g.kind==="flag"&&g.name==="machine")){const g=$e(s);S=g.rest,w=g.machine}const b=await ke(k,S,{leafPath:d,onProvidedParams:f(g=>{v=g},"onProvidedParams"),onContextFilePaths:f(g=>{E=g},"onContextFilePaths")}),U={providedParams:v,contextFilePaths:E,...w===void 0?{}:{machine:w}},u=await re.fresh(()=>o.run(b,U));if(D(u==null?u:o.preview?.(u)??u),u!=null)if(ee())Q(u);else{const g=o.render!==void 0?o.render(u):ne(u,o.help);process.stdout.write(g+(o.stdoutExact?.(u)?"":`
10
+ `))}}catch(o){if(ie(o))throw o;X(o)}}f(Ie,"runCli");export{ve as DEFAULT_DIRECTORY_ISSUER,ye as GLOBAL_TOKENS,Je as collectSlashSpecs,Ve as defineBranch,Fe as defineLeaf,Be as defineRoot,be as notJoinedError,ke as parseArgv,ue as renderNode,Ie as runCli,$e as takeMachineSelector,we as unknownPathError,ce as walk};