@north-light/cli 0.3.421 → 0.3.423
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/contract.d.ts +45 -0
- package/dist/api/contract.js +1 -0
- package/dist/api/error-codes.d.ts +10 -0
- package/dist/api/error-codes.js +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +1 -1
- package/dist/builtin-memory/crouter-sdk/errors.md +1 -1
- package/dist/builtin-memory/crouter-sdk/getting-started.md +2 -0
- package/dist/builtin-memory/crouter-sdk/release-notes.md +10 -0
- package/dist/clients/attach/viewer.js +480 -480
- package/dist/commands/sys/join.js +1 -1
- package/dist/core/directory/join.d.ts +4 -2
- package/dist/core/directory/join.js +1 -1
- package/dist/core/grants/sync.d.ts +6 -0
- package/dist/core/grants/sync.js +1 -1
- package/dist/core/providers/errors.js +1 -1
- package/dist/core/providers/mint.js +1 -1
- package/dist/core/runtime/host.js +3 -3
- package/dist/core/runtime/nodes.js +1 -1
- package/dist/core/runtime/revive.js +2 -2
- package/dist/core/runtime/run-command.js +1 -1
- package/dist/core/runtime/unsandboxed.d.ts +3 -0
- package/dist/core/runtime/unsandboxed.js +1 -0
- package/dist/core/spaces/process.js +1 -1
- package/dist/daemon/api/app-listener.js +13 -13
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/cron/authority.d.ts +1 -1
- package/dist/daemon/cron/authority.js +1 -1
- package/dist/daemon/crtrd.js +7 -7
- package/dist/types.d.ts +3 -1
- package/docs/release-local.md +4 -3
- package/docs/sdk/errors.md +1 -1
- package/docs/sdk/getting-started.md +2 -0
- package/docs/sdk/release-notes.md +10 -0
- package/package.json +5 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
var q=Object.defineProperty;var u=(t,n)=>q(t,"name",{value:n,configurable:!0});import{createServer as x}from"node:http";import{chmodSync as ee,existsSync as C,unlinkSync as B}from"node:fs";import{MINIMUM_RUNTIME_VERSION_HEADER as j,RUNTIME_VERSION_HEADER as O,minimumRuntimeRefusal as re}from"../../api/runtime-version.js";import{crtrVersion as M}from"../../shared/crtr-version.js";import{apiSocketPath as te}from"../../core/canvas/paths.js";import{resolveNodeToken as G}from"../../core/canvas/node-tokens.js";import{grantFor as oe,hasSyncedGrants as ne}from"../../core/grants/mirror.js";import{readConfig as U}from"../../core/config.js";import{peerUid as ie}from"../../native/linux.js";import{getApiToken as se,listScopedApiTokens as le}from"../../core/secrets.js";import{emitEvent as g}from"../../core/events/emit.js";import{envCrtrdTcp as ae,envCrtrdToken as ce}from"../../shared/env.js";import{Router as L}from"./router.js";import{handleAttachUpgrade as ue,refuseUpgrade as R}from"./bridge.js";import{boundDaemonControl as pe}from"../control.js";import{attachRoutes as de}from"./handlers/attach.js";import{bashJobRoutes as me}from"./handlers/bash-jobs.js";import{bashRoutes as fe}from"./handlers/bash.js";import{hookExecRoutes as ge}from"./handlers/hook-exec.js";import{brokerOperationRoutes as W}from"./handlers/broker-ops.js";import{brokerSignalRoutes as he,brokerSignalStreams as Ae}from"./handlers/broker-signals.js";import{brokerRecoveryRoutes as $,nodeFaultRoutes as Re}from"./handlers/broker-recovery.js";import{canvasRoutes as ye}from"./handlers/canvas.js";import{chatInventoryRoutes as ve}from"./handlers/chat-inventory.js";import{daemonRoutes as be}from"./handlers/daemon.js";import{fileRoutes as we}from"./handlers/files.js";import{focusRoutes as Se}from"./handlers/focus.js";import{analyticsRoutes as Ee}from"./handlers/analytics.js";import{memoryReadsRoutes as ke}from"./handlers/memory-reads.js";import{memoryRoutes as Te}from"./handlers/memory.js";import{healthRoutes as _e}from"./handlers/health.js";import{humanRequestRoutes as Ce}from"./handlers/human-requests.js";import{inboxRoutes as Oe}from"./handlers/inbox.js";import{feedbackCommentRoutes as Ue}from"./handlers/feedback-comments.js";import{objectRoutes as He}from"./handlers/objects.js";import{customObjectRoutes as Ne}from"./handlers/custom-objects.js";import{docRoutes as ze}from"./handlers/docs.js";import{deliveryRoutes as D}from"./handlers/delivery.js";import{deliveryRetryRoutes as Pe}from"./handlers/delivery-retry.js";import{packageDocRoutes as Ie}from"./handlers/package-docs.js";import{brokerMailRoutes as J,messageRoutes as xe}from"./handlers/messages.js";import{modelAuthRoutes as Be}from"./handlers/modelauth.js";import{llmRoutes as je}from"./handlers/llm.js";import{providerRoutes as Me}from"./handlers/providers.js";import{modelConfigRoutes as Ge}from"./handlers/model-config.js";import{nodeRoutes as Le}from"./handlers/nodes.js";import{nodeRecordRoutes as We}from"./handlers/node-records.js";import{nodeOutcomeRoutes as $e}from"./handlers/node-outcomes.js";import{nodeEventRoutes as De}from"./handlers/node-events.js";import{profileRoutes as Je}from"./handlers/profiles.js";import{prospectiveChatInventoryRoutes as Fe}from"./handlers/prospective-chat-inventory.js";import{reportRoutes as Ve}from"./handlers/reports.js";import{runRoutes as Ye}from"./handlers/run-routes.js";import{reviewCommentRoutes as Ze}from"./handlers/review-comments.js";import{reviewRoutes as Ke}from"./handlers/reviews.js";import{subscriptionRoutes as Qe}from"./handlers/subscriptions.js";import{structuredRequestRoutes as Xe}from"./handlers/structured-requests.js";import{cronRoutes as qe}from"./handlers/crons.js";import{setCronRequestHandler as F}from"../cron/run-socket.js";import{worktreeRoutes as er}from"./handlers/worktree.js";import{limitsRoutes as rr}from"./handlers/limits.js";function tr(t){return new L().registerAll(_e(t)).registerAll(be).registerAll(Le).registerAll(We).registerAll($e).registerAll(Pe).registerAll(De).registerAll(me).registerAll(fe).registerAll(ge).registerAll(W).registerAll(he).registerAll(D).registerAll($).registerAll(Re).registerAll(er).registerAll(rr).registerAll(J).registerAll(xe).registerAll(Ve).registerAll(Ye).registerAll(Xe).registerAll(de).registerAll(ye).registerAll(Ee).registerAll(ke).registerAll(Te).registerAll(we).registerAll(He).registerAll(Ne).registerAll(ze).registerAll(Ie).registerAll(Se).registerAll(Qe).registerAll(qe).registerAll(Je).registerAll(Be).registerAll(je).registerAll(Me).registerAll(Ge).registerAll(Ke).registerAll(Ze).registerAll(Oe).registerAll(Ce).registerAll(ve).registerAll(Fe).registerAll(Ue)}u(tr,"buildRouter");function or(t,n){if(t.toUpperCase()!=="GET")return null;const i=n.split("/").filter(m=>m.length>0);if(i.length!==4||i[0]!=="v1"||i[1]!=="nodes"||i[3]!=="attach")return null;const p=i[2];return p===void 0||p===""?null:decodeURIComponent(p)}u(or,"attachNodeId");function nr(t){const n=t.trim();if(n==="")return null;const i=n.lastIndexOf(":");if(i<0)return null;const p=n.slice(0,i),m=n.slice(i+1),d=Number(m);return!Number.isInteger(d)||d<0||d>65535?null:{host:p===""?"0.0.0.0":p,port:d}}u(nr,"parseTcp");function ir(t,n){return`${t.includes(":")?`[${t}]`:t}:${n}`}u(ir,"formatTcpAddress");const w={code:"unauthorized",message:"missing or invalid bearer token"},k={code:"owner_only",message:"attach is owner-only; a scoped token does not reach it"},H={class:"anonymous",app:"",node:"",run:null,uid:-1,sub:"",grant:null,client_id:null,scopes:[]},A=new WeakMap;function V(t){return A.get(t)}u(V,"socketPrincipal");function Y(t,n){const i=process.platform==="linux"?ie(t.socket):null,p=t.headers.authorization;if(p===void 0)return i===null||n.has(i)?{class:"person"}:"unauthorized";if(typeof p!="string"||!p.startsWith("Bearer "))return"unauthorized";if(p.slice(7).startsWith("crtr_pub_"))return H;const m=G(p.slice(7));if(!m||i!==null&&i!==m.uid)return"unauthorized";const d=U("user").runtime?.issuer;if(d&&!ne(d.replace(/\/$/,"")))return"runtime_starting";const S=m.grant??oe("app:terminal",m.sub)?.id??null;return{class:"broker",...m,grant:S}}u(Y,"resolveSocketPrincipal");function sr(t){const n=new Map;for(const i of le("user"))n.set(i.token,{principal:{class:"app",app:i.grantee,scopes:i.scopes},ceiling:i.scopes});return n.set(t,{principal:{class:"person"},ceiling:null}),n}u(sr,"resolveBearers");function Z(t,n){const i=t.headers.authorization;if(!(typeof i!="string"||!i.startsWith("Bearer ")))return n.get(i.slice(7))}u(Z,"authorize");function T(t){t.writeHead(401,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:w}))}u(T,"writeUnauthorized");function lr(t,n){t.writeHead(503,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:{code:"startup_blocked",message:n}}))}u(lr,"writeStartupBlocked");function K(t){return new Promise(n=>{try{t.closeAllConnections()}catch{}t.close(()=>n())})}u(K,"closeServer");function At(t={}){let n=null;const i=tr(()=>n),p=new L().registerAll(W).registerAll(D).registerAll($).registerAll(J),m=u((e,r)=>{const o=pe()?.startupBlock()??null;return o===null||e==="GET"&&(r==="/healthz"||r==="/v1/status")||e==="POST"&&(r==="/v1/daemon/migrate"||r==="/v1/daemon/admit")||o.phase==="recovering"&&p.match(e,r)!==null?null:o},"startupBlockFor"),d=te(),S=new Set([process.getuid?.()??0,...U("user").runtime?.person_uids??[]]),y=u((e,r,o)=>{r.setHeader(O,M());const a=e.headers[j.toLowerCase()],f=re(Array.isArray(a)?a[0]:a,M());if(f!==null){r.writeHead(f.status,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:f.code,message:f.message,type:"invalid_request_error",origin:"runtime",retryable:!1,..."param"in f?{param:f.param}:{details:f.details}}}));return}const s=u(l=>{const b=V(e);g({event:"api.request.failed",level:"error",...b?.class==="broker"?{node_id:b.node}:{},error:l instanceof Error?l:new Error(String(l))});try{r.headersSent||r.writeHead(500,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"internal",message:"internal error"}}))}catch{}},"internalFailure");let c;try{c=new URL(e.url??"/","http://127.0.0.1")}catch{try{r.writeHead(400,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"invalid_request",message:"request URL is invalid"}}))}catch{}return}try{const l=m((e.method??"GET").toUpperCase(),c.pathname);if(l!==null){lr(r,l.message);return}i.handle(e,r,o,V(e)??{class:"person"}).catch(s)}catch(l){s(l)}},"onRequest"),_=u((e,r,o)=>{r.on("error",()=>{});try{const a=new URL(e.url??"/","http://127.0.0.1"),f=m((e.method??"GET").toUpperCase(),a.pathname);if(f!==null){R(r,503,"Service Unavailable","startup_blocked",f.message);return}const s=or(e.method??"GET",a.pathname);if(s===null){r.destroy();return}ue(e,r,o,s).catch(()=>{try{r.destroy()}catch{}})}catch{try{r.destroy()}catch{}}},"onUpgrade"),Q=u(e=>{e.on("error",r=>{g({event:"api.server.failed",level:"error",error:r})})},"bindErrorHandler"),v=x((e,r)=>{try{const o=Y(e,S);if(o==="unauthorized"){g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),T(r);return}if(o==="runtime_starting"){r.writeHead(503,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"runtime_starting",message:"grant sync is not ready",type:"server_error",origin:"runtime",retryable:!0}}));return}A.set(e,o),o.class==="broker"&&g({event:"api.request.principal",level:o.app==="app:terminal"?"debug":"info",node_id:o.node,fields:{principal:`${o.app} node:${o.node}`,method:e.method,path:e.url}}),y(e,r,null)}catch(o){g({event:"api.principal.failed",level:"error",error:o instanceof Error?o:new Error(String(o))}),T(r)}});v.on("upgrade",(e,r,o)=>{try{const a=Y(e,S);if(a==="unauthorized"||a==="runtime_starting"){a==="unauthorized"&&g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),R(r,a==="unauthorized"?401:503,"Unauthorized",a,"socket principal unavailable");return}if(a.class!=="person"&&a.class!=="broker"){R(r,403,"Forbidden",k.code,k.message);return}A.set(e,a),_(e,r,o)}catch(a){g({event:"api.principal.failed",level:"error",error:a instanceof Error?a:new Error(String(a))}),R(r,401,"Unauthorized",w.code,w.message)}}),F((e,r,o)=>{A.set(e,o),y(e,r,null)});let N=!1,z,P;const X=new Promise((e,r)=>{z=e,P=r});v.once("listening",()=>{N=!0,z()}),v.on("error",e=>{g({event:"api.server.failed",level:"error",error:e}),N||P(e)});try{C(d)&&B(d)}catch{}v.listen(d,()=>{try{ee(d,process.platform==="linux"&&C("/run/crtr/launcher.sock")?438:384)}catch(e){g({event:"api.socket.chmod_failed",level:"error",error:e instanceof Error?e:new Error(String(e))})}});let h;const E=t.tcp??ae()??U("user").api.tcp;if(E!==void 0&&E!==""){const e=nr(E);if(e===null)g({event:"api.tcp.invalid",level:"error",error:new Error(`invalid CRTRD_TCP/--tcp spec: ${E}`)});else{const r=t.token??ce()??se("user"),o=r===void 0?void 0:sr(r),a=o===void 0?(s,c)=>{const l=s.headers.authorization;if(typeof l=="string"&&l.startsWith("Bearer crtr_pub_"))A.set(s,H);else if(typeof l=="string"&&l.startsWith("Bearer ")&&G(l.slice(7))){T(c);return}y(s,c,null)}:(s,c)=>{if((s.method??"GET").toUpperCase()==="OPTIONS"){c.writeHead(204,{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":`authorization, content-type, ${j.toLowerCase()}`,"Access-Control-Allow-Methods":"GET, POST, PATCH, PUT, DELETE, OPTIONS","Access-Control-Max-Age":"600"}),c.end();return}const l=Z(s,o);if(l===void 0&&s.headers.authorization?.startsWith("Bearer crtr_pub_")){A.set(s,H),c.setHeader("Access-Control-Allow-Origin","*"),c.setHeader("Access-Control-Expose-Headers",O),y(s,c,null);return}if(l===void 0){T(c);return}A.set(s,l.principal),c.setHeader("Access-Control-Allow-Origin","*"),c.setHeader("Access-Control-Expose-Headers",O),y(s,c,l.ceiling)},f=o===void 0?_:(s,c,l)=>{const b=Z(s,o);if(b===void 0){R(c,401,"Unauthorized",w.code,w.message);return}if(b.ceiling!==null){R(c,403,"Forbidden",k.code,k.message);return}_(s,c,l)};h=x(a),h.on("upgrade",f),Q(h),h.once("listening",()=>{const s=h?.address();if(s!==null&&typeof s=="object"){const{address:c,port:l}=s;n=ir(c,l)}}),h.listen(e.port,e.host)}}let I=!1;return{ready:X,tcpAddress:u(()=>n,"tcpAddress"),close:u(async()=>{if(!I){I=!0,F(null),Ae.close(),await Promise.all([K(v),h!==void 0?K(h):Promise.resolve()]);try{C(d)&&B(d)}catch{}}},"close")}}u(At,"createApiServer");export{tr as buildRouter,At as createApiServer,V as socketPrincipal};
|
|
1
|
+
var se=Object.defineProperty;var a=(t,n)=>se(t,"name",{value:n,configurable:!0});import{createServer as j}from"node:http";import{chmodSync as le,existsSync as U,unlinkSync as D}from"node:fs";import{MINIMUM_RUNTIME_VERSION_HEADER as G,RUNTIME_VERSION_HEADER as H,minimumRuntimeRefusal as ae}from"../../api/runtime-version.js";import{CONTRACT_HEADER as N,CONTRACT_LEVEL as W,REQUIRED_CONTRACT_HEADER as J,contractRefusal as ue}from"../../api/contract.js";import{crtrVersion as z}from"../../shared/crtr-version.js";import{apiSocketPath as ce}from"../../core/canvas/paths.js";import{resolveNodeToken as F}from"../../core/canvas/node-tokens.js";import{grantFor as pe,hasSyncedGrants as de}from"../../core/grants/mirror.js";import{readConfig as P}from"../../core/config.js";import{peerUid as me}from"../../native/linux.js";import{getApiToken as fe,listScopedApiTokens as ge}from"../../core/secrets.js";import{emitEvent as g}from"../../core/events/emit.js";import{envCrtrdTcp as he,envCrtrdToken as Re}from"../../shared/env.js";import{Router as V}from"./router.js";import{handleAttachUpgrade as Ae,refuseUpgrade as A}from"./bridge.js";import{boundDaemonControl as ye}from"../control.js";import{attachRoutes as ve}from"./handlers/attach.js";import{bashJobRoutes as be}from"./handlers/bash-jobs.js";import{bashRoutes as Ee}from"./handlers/bash.js";import{hookExecRoutes as we}from"./handlers/hook-exec.js";import{brokerOperationRoutes as Q}from"./handlers/broker-ops.js";import{brokerSignalRoutes as Te,brokerSignalStreams as Ce}from"./handlers/broker-signals.js";import{brokerRecoveryRoutes as Y,nodeFaultRoutes as Se}from"./handlers/broker-recovery.js";import{canvasRoutes as ke}from"./handlers/canvas.js";import{chatInventoryRoutes as _e}from"./handlers/chat-inventory.js";import{daemonRoutes as Oe}from"./handlers/daemon.js";import{fileRoutes as Ue}from"./handlers/files.js";import{focusRoutes as He}from"./handlers/focus.js";import{analyticsRoutes as Ne}from"./handlers/analytics.js";import{memoryReadsRoutes as ze}from"./handlers/memory-reads.js";import{memoryRoutes as Pe}from"./handlers/memory.js";import{healthRoutes as Ie}from"./handlers/health.js";import{humanRequestRoutes as xe}from"./handlers/human-requests.js";import{inboxRoutes as $e}from"./handlers/inbox.js";import{feedbackCommentRoutes as Le}from"./handlers/feedback-comments.js";import{objectRoutes as Be}from"./handlers/objects.js";import{customObjectRoutes as Me}from"./handlers/custom-objects.js";import{docRoutes as je}from"./handlers/docs.js";import{deliveryRoutes as Z}from"./handlers/delivery.js";import{deliveryRetryRoutes as De}from"./handlers/delivery-retry.js";import{packageDocRoutes as Ge}from"./handlers/package-docs.js";import{brokerMailRoutes as K,messageRoutes as We}from"./handlers/messages.js";import{modelAuthRoutes as Je}from"./handlers/modelauth.js";import{llmRoutes as Fe}from"./handlers/llm.js";import{providerRoutes as Ve}from"./handlers/providers.js";import{modelConfigRoutes as Qe}from"./handlers/model-config.js";import{nodeRoutes as Ye}from"./handlers/nodes.js";import{nodeRecordRoutes as Ze}from"./handlers/node-records.js";import{nodeOutcomeRoutes as Ke}from"./handlers/node-outcomes.js";import{nodeEventRoutes as Xe}from"./handlers/node-events.js";import{profileRoutes as qe}from"./handlers/profiles.js";import{prospectiveChatInventoryRoutes as er}from"./handlers/prospective-chat-inventory.js";import{reportRoutes as rr}from"./handlers/reports.js";import{runRoutes as tr}from"./handlers/run-routes.js";import{reviewCommentRoutes as or}from"./handlers/review-comments.js";import{reviewRoutes as nr}from"./handlers/reviews.js";import{subscriptionRoutes as ir}from"./handlers/subscriptions.js";import{structuredRequestRoutes as sr}from"./handlers/structured-requests.js";import{cronRoutes as lr}from"./handlers/crons.js";import{setCronRequestHandler as X}from"../cron/run-socket.js";import{worktreeRoutes as ar}from"./handlers/worktree.js";import{limitsRoutes as ur}from"./handlers/limits.js";function cr(t){return new V().registerAll(Ie(t)).registerAll(Oe).registerAll(Ye).registerAll(Ze).registerAll(Ke).registerAll(De).registerAll(Xe).registerAll(be).registerAll(Ee).registerAll(we).registerAll(Q).registerAll(Te).registerAll(Z).registerAll(Y).registerAll(Se).registerAll(ar).registerAll(ur).registerAll(K).registerAll(We).registerAll(rr).registerAll(tr).registerAll(sr).registerAll(ve).registerAll(ke).registerAll(Ne).registerAll(ze).registerAll(Pe).registerAll(Ue).registerAll(Be).registerAll(Me).registerAll(je).registerAll(Ge).registerAll(He).registerAll(ir).registerAll(lr).registerAll(qe).registerAll(Je).registerAll(Fe).registerAll(Ve).registerAll(Qe).registerAll(nr).registerAll(or).registerAll($e).registerAll(xe).registerAll(_e).registerAll(er).registerAll(Le)}a(cr,"buildRouter");function pr(t,n){if(t.toUpperCase()!=="GET")return null;const s=n.split("/").filter(f=>f.length>0);if(s.length!==4||s[0]!=="v1"||s[1]!=="nodes"||s[3]!=="attach")return null;const d=s[2];return d===void 0||d===""?null:decodeURIComponent(d)}a(pr,"attachNodeId");function dr(t){const n=t.trim();if(n==="")return null;const s=n.lastIndexOf(":");if(s<0)return null;const d=n.slice(0,s),f=n.slice(s+1),m=Number(f);return!Number.isInteger(m)||m<0||m>65535?null:{host:d===""?"0.0.0.0":d,port:m}}a(dr,"parseTcp");function mr(t,n){return`${t.includes(":")?`[${t}]`:t}:${n}`}a(mr,"formatTcpAddress");const y={code:"unauthorized",message:"missing or invalid bearer token"},S={code:"owner_only",message:"attach is owner-only; a scoped token does not reach it"},I={class:"anonymous",app:"",node:"",run:null,uid:-1,sub:"",grant:null,client_id:null,scopes:[]},v=new WeakMap;function q(t){return v.get(t)}a(q,"socketPrincipal");function ee(t,n){const s=process.platform==="linux"?me(t.socket):null,d=t.headers.authorization;if(d===void 0)return s===null||n.has(s)?{class:"person"}:"unauthorized";if(typeof d!="string"||!d.startsWith("Bearer "))return"unauthorized";if(d.slice(7).startsWith("crtr_pub_"))return I;const f=F(d.slice(7));if(!f||s!==null&&s!==f.uid)return"unauthorized";const m=P("user").runtime?.issuer;if(m&&!de(m.replace(/\/$/,"")))return"runtime_starting";const T=f.grant??pe("app:terminal",f.sub)?.id??null;return{class:"broker",...f,grant:T}}a(ee,"resolveSocketPrincipal");function fr(t){const n=new Map;for(const s of ge("user"))n.set(s.token,{principal:{class:"app",app:s.grantee,scopes:s.scopes},ceiling:s.scopes});return n.set(t,{principal:{class:"person"},ceiling:null}),n}a(fr,"resolveBearers");function re(t,n){const s=t.headers.authorization;if(!(typeof s!="string"||!s.startsWith("Bearer ")))return n.get(s.slice(7))}a(re,"authorize");function k(t){t.setHeader(H,z()),t.setHeader(N,W)}a(k,"advertiseRuntime");function _(t){t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Expose-Headers",`${H}, ${N}`)}a(_,"exposeRuntimeHeaders");function te(t,n){return(t.method??"GET").toUpperCase()!=="OPTIONS"?!1:(k(n),n.writeHead(204,{"Access-Control-Allow-Origin":"*","Access-Control-Expose-Headers":`${H}, ${N}`,"Access-Control-Allow-Headers":`authorization, content-type, ${G.toLowerCase()}, ${J.toLowerCase()}`,"Access-Control-Allow-Methods":"GET, POST, PATCH, PUT, DELETE, OPTIONS","Access-Control-Max-Age":"600"}),n.end(),!0)}a(te,"answerPreflight");function w(t,n=!1){k(t),n&&_(t),t.writeHead(401,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:y}))}a(w,"writeUnauthorized");function gr(t,n){t.writeHead(503,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:{code:"startup_blocked",message:n}}))}a(gr,"writeStartupBlocked");function oe(t){return new Promise(n=>{try{t.closeAllConnections()}catch{}t.close(()=>n())})}a(oe,"closeServer");function St(t={}){let n=null;const s=cr(()=>n),d=new V().registerAll(Q).registerAll(Z).registerAll(Y).registerAll(K),f=a((e,r)=>{const i=ye()?.startupBlock()??null;return i===null||e==="GET"&&(r==="/healthz"||r==="/v1/status")||e==="POST"&&(r==="/v1/daemon/migrate"||r==="/v1/daemon/admit")||i.phase==="recovering"&&d.match(e,r)!==null?null:i},"startupBlockFor"),m=ce(),T=new Set([process.getuid?.()??0,...P("user").runtime?.person_uids??[]]),b=a((e,r,i)=>{k(r);const u=a(p=>Array.isArray(p)?p[0]:p,"first"),R=u(e.headers[G.toLowerCase()]),o=ae(R,z())??ue(u(e.headers[J.toLowerCase()]),W,{legacyMinimum:R,runtimeVersion:z()});if(o!==null){r.writeHead(o.status,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:o.code,message:o.message,type:"invalid_request_error",origin:"runtime",retryable:!1,..."param"in o?{param:o.param}:{details:o.details}}}));return}const l=a(p=>{const M=q(e);g({event:"api.request.failed",level:"error",...M?.class==="broker"?{node_id:M.node}:{},error:p instanceof Error?p:new Error(String(p))});try{r.headersSent||r.writeHead(500,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"internal",message:"internal error"}}))}catch{}},"internalFailure");let c;try{c=new URL(e.url??"/","http://127.0.0.1")}catch{try{r.writeHead(400,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"invalid_request",message:"request URL is invalid"}}))}catch{}return}try{const p=f((e.method??"GET").toUpperCase(),c.pathname);if(p!==null){gr(r,p.message);return}s.handle(e,r,i,q(e)??{class:"person"}).catch(l)}catch(p){l(p)}},"onRequest"),O=a((e,r,i)=>{r.on("error",()=>{});try{const u=new URL(e.url??"/","http://127.0.0.1"),R=f((e.method??"GET").toUpperCase(),u.pathname);if(R!==null){A(r,503,"Service Unavailable","startup_blocked",R.message);return}const o=pr(e.method??"GET",u.pathname);if(o===null){r.destroy();return}Ae(e,r,i,o).catch(()=>{try{r.destroy()}catch{}})}catch{try{r.destroy()}catch{}}},"onUpgrade"),ne=a(e=>{e.on("error",r=>{g({event:"api.server.failed",level:"error",error:r})})},"bindErrorHandler"),E=j((e,r)=>{try{const i=ee(e,T);if(i==="unauthorized"){g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),w(r);return}if(i==="runtime_starting"){k(r),r.writeHead(503,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"runtime_starting",message:"grant sync is not ready",type:"server_error",origin:"runtime",retryable:!0}}));return}v.set(e,i),i.class==="broker"&&g({event:"api.request.principal",level:i.app==="app:terminal"?"debug":"info",node_id:i.node,fields:{principal:`${i.app} node:${i.node}`,method:e.method,path:e.url}}),b(e,r,null)}catch(i){g({event:"api.principal.failed",level:"error",error:i instanceof Error?i:new Error(String(i))}),w(r)}});E.on("upgrade",(e,r,i)=>{try{const u=ee(e,T);if(u==="unauthorized"||u==="runtime_starting"){u==="unauthorized"&&g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),A(r,u==="unauthorized"?401:503,"Unauthorized",u,"socket principal unavailable");return}if(u.class!=="person"&&u.class!=="broker"){A(r,403,"Forbidden",S.code,S.message);return}v.set(e,u),O(e,r,i)}catch(u){g({event:"api.principal.failed",level:"error",error:u instanceof Error?u:new Error(String(u))}),A(r,401,"Unauthorized",y.code,y.message)}}),X((e,r,i)=>{v.set(e,i),b(e,r,null)});let x=!1,$,L;const ie=new Promise((e,r)=>{$=e,L=r});E.once("listening",()=>{x=!0,$()}),E.on("error",e=>{g({event:"api.server.failed",level:"error",error:e}),x||L(e)});try{U(m)&&D(m)}catch{}E.listen(m,()=>{try{le(m,process.platform==="linux"&&U("/run/crtr/launcher.sock")?438:384)}catch(e){g({event:"api.socket.chmod_failed",level:"error",error:e instanceof Error?e:new Error(String(e))})}});let h;const C=t.tcp??he()??P("user").api.tcp;if(C!==void 0&&C!==""){const e=dr(C);if(e===null)g({event:"api.tcp.invalid",level:"error",error:new Error(`invalid CRTRD_TCP/--tcp spec: ${C}`)});else{const r=t.token??Re()??fe("user"),i=r===void 0?void 0:fr(r),u=i===void 0?(o,l)=>{if(te(o,l))return;const c=o.headers.authorization;if(typeof c=="string"&&c.startsWith("Bearer crtr_pub_"))v.set(o,I),_(l);else if(o.headers.origin!==void 0){w(l,!0);return}else if(typeof c=="string"&&c.startsWith("Bearer ")&&F(c.slice(7))){w(l,!0);return}b(o,l,null)}:(o,l)=>{if(te(o,l))return;const c=re(o,i);if(c===void 0&&o.headers.authorization?.startsWith("Bearer crtr_pub_")){v.set(o,I),_(l),b(o,l,null);return}if(c===void 0){w(l,!0);return}v.set(o,c.principal),_(l),b(o,l,c.ceiling)},R=i===void 0?(o,l,c)=>{if(o.headers.origin!==void 0){A(l,401,"Unauthorized",y.code,y.message);return}O(o,l,c)}:(o,l,c)=>{const p=re(o,i);if(p===void 0){A(l,401,"Unauthorized",y.code,y.message);return}if(p.ceiling!==null){A(l,403,"Forbidden",S.code,S.message);return}O(o,l,c)};h=j(u),h.on("upgrade",R),ne(h),h.once("listening",()=>{const o=h?.address();if(o!==null&&typeof o=="object"){const{address:l,port:c}=o;n=mr(l,c)}}),h.listen(e.port,e.host)}}let B=!1;return{ready:ie,tcpAddress:a(()=>n,"tcpAddress"),close:a(async()=>{if(!B){B=!0,X(null),Ce.close(),await Promise.all([oe(E),h!==void 0?oe(h):Promise.resolve()]);try{U(m)&&D(m)}catch{}}},"close")}}a(St,"createApiServer");export{cr as buildRouter,St as createApiServer,q as socketPrincipal};
|
|
@@ -6,7 +6,7 @@ export interface CronPrincipal {
|
|
|
6
6
|
/** Null = the grant's whole current set (person-armed terminal crons only). */
|
|
7
7
|
scopes: readonly string[] | null;
|
|
8
8
|
}
|
|
9
|
-
export type CronRefusalReason = 'grant_missing' | 'grant_revoked' | 'scope_narrowed' | 'scope_missing';
|
|
9
|
+
export type CronRefusalReason = 'unsandboxed_machine' | 'grant_missing' | 'grant_revoked' | 'scope_narrowed' | 'scope_missing';
|
|
10
10
|
export type CronAuthority =
|
|
11
11
|
/** `effectiveScopes` is the grant's current resolved set. A stored scope list is a second ceiling the caller still applies (as `router.ts` does for a broker). */
|
|
12
12
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var o=(r,e)=>c(r,"name",{value:e,configurable:!0});import{covers as
|
|
1
|
+
var c=Object.defineProperty;var o=(r,e)=>c(r,"name",{value:e,configurable:!0});import{covers as u}from"@north-light/identity";import{activeRecord as g,grantRecord as d}from"../../core/grants/mirror.js";import{readConfig as l}from"../../core/config.js";import{isUnsandboxedMachine as f}from"../../core/runtime/unsandboxed.js";import{SCOPE_VOCABULARY as m,scopeAllowed as i}from"../../core/scopes.js";function t(r,e){return{allowed:!1,reason:r,message:e}}o(t,"refuse");function _(r){if(r.grantee!=="app:terminal"&&f())return t("unsandboxed_machine",`This computer runs only your own terminal agents; ${r.grantee} runs on your hosted machine.`);let e;if(r.grant_id===null){if(r.grantee!=="app:terminal"||l("user").runtime?.issuer)return t("grant_missing",`cron has no grant for ${r.grantee}; re-arm it from a node of that app`);e=m}else{const n=d(r.grant_id);if(n===null)return t("grant_missing",`grant ${r.grant_id} is not in this runtime's grant mirror`);if(n.status!=="active")return t("grant_revoked",`grant ${r.grant_id} is ${n.status}`);if(n.expires_at!==null&&Date.parse(n.expires_at)<=Date.now())return t("grant_revoked",`grant ${r.grant_id} expired at ${n.expires_at}`);if(n.grantee!==r.grantee||n.sub!==r.sub)return t("grant_revoked",`grant ${r.grant_id} no longer belongs to ${r.grantee} for ${r.sub}`);const s=g(n);if(s===null)return t("grant_revoked",`grant ${r.grant_id} cannot be read`);e=s.resolved}if(r.scopes!==null){const n=r.scopes.find(s=>!e.some(a=>u(a,s)));if(n!==void 0)return t("scope_narrowed",`scope ${n} is no longer covered by the grant`)}return!i(e,"crtr:schedule")||!i(r.scopes,"crtr:schedule")?t("scope_missing","the cron no longer holds crtr:schedule"):{allowed:!0,effectiveScopes:e}}o(_,"checkCronFire");function w(r){const e=_(r);return e.allowed&&(!i(e.effectiveScopes,"crtr:act")||!i(r.scopes,"crtr:act"))?t("scope_missing","the cron no longer holds crtr:act"):e}o(w,"checkCronBirth");function k(r){return{grantee:r.grantee,sub:r.sub,grant_id:r.grant_id,scopes:r.scopes}}o(k,"cronPrincipalOf");export{w as checkCronBirth,_ as checkCronFire,k as cronPrincipalOf};
|
package/dist/daemon/crtrd.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
var Se=Object.defineProperty;var s=(t,o)=>Se(t,"name",{value:o,configurable:!0});import{writeFileSync as _e,readFileSync as ke,rmSync as be,mkdirSync as Y,existsSync as b,chmodSync as Ee}from"node:fs";import{createServer as X}from"node:http";import{dirname as Re}from"node:path";import{envCrtrdTcp as Ie}from"../shared/env.js";import{serveCanvasConversationStore as Pe}from"../core/canvas/sessions.js";import{pollRunObservers as xe}from"./runs/observer.js";import{randomBytes as Te}from"node:crypto";import{apiSocketPath as De,crtrHome as Ce}from"../core/canvas/paths.js";import{activeAppUsers as Le}from"../core/spaces/users.js";import{ensureAppSpace as $e,ensureUserFiles as Ae}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as R}from"../launcher/spec.js";import{launcherRequest as x}from"../launcher/client.js";import{installLauncherEventSink as Ne}from"./launcher-events.js";import{initializeEmptyLayout as Me,layoutNeedsMigration as Ge,socketDir as Z}from"../core/layout.js";import{migrateLooseKeys as Oe}from"../core/layout-migrate/loose-keys.js";import{getNode as Fe,listNodesForSupervision as He,setFrozen as Be}from"../core/canvas/index.js";import{createApiServer as qe}from"./api/server.js";import{createAppListener as je}from"./api/app-listener.js";import{installLocalTerminalGrant as ze}from"../core/grants/local.js";import{onRunEventAppended as We}from"../core/runs/events.js";import{runNotices as Ue}from"./run-notices.js";import{createGrantSync as Ke,GRANT_SYNC_INTERVAL_S as Je}from"../core/grants/sync.js";import{runRemovalCascades as Ve}from"../core/grants/remove.js";import{sweepSandboxes as Qe}from"../core/grants/on-sync.js";import{rebuildCronExecutions as Ye}from"./cron/run-socket.js";import{hasSyncedGrants as Xe}from"../core/grants/mirror.js";import{readConfig as Ze}from"../core/config.js";import{reconcileUndeliveredTickets as er}from"./human/sweep.js";import{resetHandedOffDeliveries as rr}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as tr}from"../core/canvas/daemon-actions.js";import{sweepReviews as or}from"./review/sweep.js";import{pidfilePath as G}from"./pidfile.js";import{rendererWarning as nr}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as ir}from"./supervise-cadence.js";import{createResumeDetector as sr}from"./resume-detector.js";import{BrokerSupervisionReconciler as ar}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as cr}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as lr}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as dr}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as pr}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as mr}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as fr}from"./reconcilers/action-lane.js";import{endLiveExecution as ur,migrateLegacyExecutionIdentities as hr}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as vr}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as wr}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as gr}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as yr}from"./reconcilers/storage-due-work.js";import{BashDeadlineReconciler as Sr}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as _r}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as kr}from"./reconcilers/structured-request-lane.js";import{reconcileStructuredRequestSidecars as br}from"./structured-requests/boot.js";import{captureLivenessSnapshot as ee,capturePidCommand as Er,captureTeardownSnapshot as re,isPidAlive as Rr,isAnyPidAlive as te,killProcessTreePids as oe}from"../core/canvas/pid.js";import{bindFleet as Ir,boundFleet as Pr}from"../core/runtime/fleet.js";import{bindDaemonControl as xr,clearDaemonControl as Tr}from"./control.js";import{execDaemon as Dr,spawnDaemon as Cr}from"./manage.js";import{reconcileAndMigrateBoot as Lr}from"../core/canvas/boot.js";import{resolveInstallId as $r}from"../core/canvas/install-id.js";import{setInstallId as Ar}from"../core/runtime/nodes.js";import{claimDaemonOwnership as Nr,releaseDaemonOwnership as ne}from"../core/canvas/daemon-owner.js";import{migrate as Mr,openDb as ie,withCanvasWrite as Gr,bindDaemonCanvasOpenHook as Or,clearDaemonCanvasOpenHook as se}from"../core/canvas/db.js";import{brokerSignalBus as O}from"./broker-signals/bus.js";import{brokerSignalStreams as Fr}from"./api/handlers/broker-signals.js";import{reconcilePackageDocuments as Hr}from"../core/graph/package-docs.js";import{startRepoSync as Br}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as qr,reapStaleSpares as jr}from"../core/runtime/warm-pool.js";import{DaemonFleet as zr}from"./fleet.js";import{BOOT_MIGRATIONS as Wr}from"./boot-migrations.js";import{applyJoinedOwner as Ur}from"./joined-owner.js";import{emitEvent as u}from"../core/events/emit.js";import{operationIdContext as _}from"../core/events/operation-id.js";import{bindDaemonEventSource as Kr}from"../core/events/source.js";import{activateCanvasImport as Jr,MigrationBlockedError as Vr,runForcedCanvasImport as Qr}from"../migrations/activation.js";import{convertLegacyProviderStore as Yr,convertLegacyScopedStore as Xr}from"../core/scoped-state/migrate.js";import{userScopeRoot as Zr}from"../core/scope.js";import{secretsDir as et}from"../core/layout.js";function F(t,o){return t.includes(` --canvas-home ${o} `)}s(F,"brokerCommandDeclaresCanvasHome");function H(t){return/ --epoch (\S+) /.exec(t)?.[1]??null}s(H,"brokerCommandEpoch");const rt=5e3,ae=150;async function tt(t){const o=ee();if(o===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness"}}),new Error("prior-epoch broker census could not capture the process table");const a=Ce(),m=[];for(const[i,p]of o.processes){const g=p.command;if(p.zombie||!g.includes("broker-cli.js")||!F(g,a)||H(g)===t)continue;const S=g.trim().split(/\s+/).at(-1)??"";S===""||S.endsWith("broker-cli.js")||m.push({pid:i,nodeId:S})}let f=0;const n=[];for(const i of m){const p=Er(i.pid);if(p===null||!p.includes("broker-cli.js")||!F(p,a)||H(p)===t||p.trim().split(/\s+/).at(-1)!==i.nodeId)continue;const g=re(i.pid,null);if(!g.reused){try{process.platform==="linux"&&b(R)?await x({operation:"stop",pid:i.pid}):process.kill(i.pid,"SIGTERM")}catch{continue}f++,n.push({pid:i.pid,snap:g})}}const w=Date.now()+rt;for(;Date.now()<w&&n.some(i=>te(i.snap.tree.length>0?i.snap.tree:[i.pid]));)await new Promise(i=>setTimeout(i,ae));let v=0;for(const i of n){const p=i.snap.tree.length>0?i.snap.tree:[i.pid];te(p)&&(process.platform==="linux"&&b(R)?await x({operation:"stop",pid:i.pid}):oe(p,"SIGKILL",i.snap.identities),v++)}v>0&&await new Promise(i=>setTimeout(i,ae));const l=ee();if(l===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness",stage:"final"}}),new Error("prior-epoch broker census could not capture the final process table");const d=[];for(const[i,p]of l.processes){const g=p.command;p.zombie||!g.includes("broker-cli.js")||F(g,a)&&H(g)!==t&&d.push(i)}if(d.length>0)throw new Error(`prior-epoch broker census could not prove termination of pid(s): ${d.join(", ")}`);u({level:"info",event:"daemon.census.completed",fields:{prior_epoch_brokers:m.length,sigtermed:f,sigkilled:v}})}s(tt,"reapPriorEpochBrokers");const ot=5e3;async function nt(t){const o=Array.from(t.entries());if(o.length===0)return;for(const[n]of o){const w=Fe(n)?.status;(w==="active"||w==="idle")&&Be(n,new Date().toISOString())}const a=o.map(([,n])=>({pid:n.pid,snap:re(n.pid,null),exited:n.handle.exited}));for(const n of a)if(!n.snap.reused)try{process.platform==="linux"&&b(R)?await x({operation:"stop",pid:n.pid}):process.kill(n.pid,"SIGTERM")}catch{}const m=new Promise(n=>{setTimeout(n,ot).unref()});await Promise.allSettled(a.map(n=>Promise.race([n.exited.then(()=>{}),m])));const f=[];for(const n of a)Rr(n.pid)&&(process.platform==="linux"&&b(R)?await x({operation:"stop",pid:n.pid}):oe(n.snap.tree.length>0?n.snap.tree:[n.pid],"SIGKILL",n.snap.identities),f.push(n));await Promise.all(f.map(n=>n.exited));for(const[n]of o)ur(n,{reason:"daemon-teardown"});u({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:a.length,sigkilled:f.length}})}s(nt,"teardownFleet");function ce(){Y(Re(G()),{recursive:!0}),_e(G(),String(process.pid),"utf8")}s(ce,"writePidfile");function le(t){return typeof t=="object"&&t!==null&&t.code==="ENOENT"}s(le,"isMissingPathError");function de(t){const o=G();let a;try{a=ke(o,"utf8")}catch(m){if(le(m))return;throw m}if(a.trim()!==String(t))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${t})`);try{be(o)}catch(m){if(le(m))return;throw m}}s(de,"removeOwnedPidfileMirror");const it={acceptsDetachedWork:s(()=>!0,"acceptsDetachedWork"),registerDetached:s(t=>{t.catch(()=>{})},"registerDetached")},B=new ar,st=new cr,at=new lr,ct=new dr,lt=new pr,dt=new mr,pt=new fr,mt=new wr,ft=new gr,ut=new yr,ht=new Sr,vt=new _r,wt=new kr;async function gt(t=Date.now(),o=it){const a=Pr(),m=await B.runFleetPreamble(a);let f,n;try{const v=He();f=v.filter(l=>l.status==="active"||l.status==="idle"),n=v.filter(l=>l.status==="done"||l.status==="canceled"||l.status==="dead")}catch(v){u({level:"error",event:"canvas.node_list.failed",error:v});return}await B.run(t,{rows:f,fleet:a,lifecycle:o}),st.run(t,{rows:f});const w=await at.run(t,{rows:[...f,...n],fleet:a});ct.run(t),wt.run(t),lt.run(t,{lifecycle:o}),dt.run(t,{lifecycle:o}),pt.run(t,{lifecycle:o}),mt.run(t,{lifecycle:o}),ft.run(t,{lifecycle:o,fleet:a}),ut.run(t,{lifecycle:o}),xe(),vt.run(t,{fleet:a,lifecycle:o}),await ht.run(t,{rows:f}),B.warnIfSaturated({fleet:a,lifecycle:o,thresholds:m,frozen:w})}s(gt,"superviseTick");async function yt(t){Y(Z(),{recursive:!0,mode:493}),process.platform==="linux"&&Ee(Z(),493);const o=De();if(b(o))throw new Error(`layout refusal cannot bind occupied socket: ${o}`);const a=X((l,d)=>{if(l.method==="GET"&&l.url==="/healthz"){d.writeHead(200,{"content-type":"application/json; charset=utf-8"}),d.end(JSON.stringify({ok:!1,daemon_up:!0,store_accessible:!1,brokers_reconciled:!1,startup_phase:"blocked",startup_blocked:"layout_migration_required"}));return}d.writeHead(503,{"content-type":"application/json; charset=utf-8"}),d.end(JSON.stringify({error:{code:"layout_migration_required",message:"run crtr sys migrate while the runtime is offline",type:"server_error",origin:"runtime",retryable:!1}}))});await new Promise((l,d)=>{a.once("error",d),a.listen(o,()=>{a.off("error",d),l()})});let m;const f=t.tcp??Ie();if(f){const l=/^(.*):(\d+)$/.exec(f);if(!l||Number(l[2])>65535)throw new Error(`invalid refusal TCP address: ${f}`);const d=X(a.listeners("request")[0]);await new Promise((i,p)=>{d.once("error",p),d.listen(Number(l[2]),l[1],()=>{d.off("error",p),i()})}),m=d}ce();let n;const w=s(()=>(n??=(async()=>{a.closeAllConnections(),m?.closeAllConnections(),await Promise.all([a,m].filter(l=>l!==void 0).map(l=>new Promise((d,i)=>l.close(p=>p?i(p):d())))),de(process.pid),process.off("SIGINT",v),process.off("SIGTERM",v)})(),n),"cleanup"),v=s(()=>{w().then(()=>process.exit(0),l=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(l)}
|
|
2
|
-
`),process.exit(1)})},"onSignal");return process.on("SIGINT",v),process.on("SIGTERM",v),w}s(yt,"serveLayoutRefusal");async function St(t={}){if(Ge())return yt(t);Me();const
|
|
3
|
-
`);else{const r=[
|
|
4
|
-
`),
|
|
1
|
+
var Se=Object.defineProperty;var s=(t,n)=>Se(t,"name",{value:n,configurable:!0});import{writeFileSync as _e,readFileSync as be,rmSync as ke,mkdirSync as Y,existsSync as k,chmodSync as Ee}from"node:fs";import{createServer as X}from"node:http";import{dirname as Re}from"node:path";import{envCrtrdTcp as Ie}from"../shared/env.js";import{serveCanvasConversationStore as xe}from"../core/canvas/sessions.js";import{pollRunObservers as Pe}from"./runs/observer.js";import{randomBytes as Te}from"node:crypto";import{apiSocketPath as De,crtrHome as Ce}from"../core/canvas/paths.js";import{activeAppUsers as Le}from"../core/spaces/users.js";import{ensureAppSpace as $e,ensureUserFiles as Ae}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as R}from"../launcher/spec.js";import{launcherRequest as P}from"../launcher/client.js";import{installLauncherEventSink as Ne}from"./launcher-events.js";import{initializeEmptyLayout as Me,layoutNeedsMigration as Ge,socketDir as Z}from"../core/layout.js";import{migrateLooseKeys as Oe}from"../core/layout-migrate/loose-keys.js";import{getNode as Fe,listNodesForSupervision as He,setFrozen as Be}from"../core/canvas/index.js";import{createApiServer as qe}from"./api/server.js";import{createAppListener as je}from"./api/app-listener.js";import{installLocalTerminalGrant as ze}from"../core/grants/local.js";import{onRunEventAppended as We}from"../core/runs/events.js";import{runNotices as Ue}from"./run-notices.js";import{createGrantSync as Ke,GRANT_SYNC_INTERVAL_S as Je}from"../core/grants/sync.js";import{runRemovalCascades as Ve}from"../core/grants/remove.js";import{sweepSandboxes as Qe}from"../core/grants/on-sync.js";import{rebuildCronExecutions as Ye}from"./cron/run-socket.js";import{hasSyncedGrants as Xe}from"../core/grants/mirror.js";import{readConfig as Ze}from"../core/config.js";import{reconcileUndeliveredTickets as er}from"./human/sweep.js";import{resetHandedOffDeliveries as rr}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as tr}from"../core/canvas/daemon-actions.js";import{sweepReviews as nr}from"./review/sweep.js";import{pidfilePath as G}from"./pidfile.js";import{rendererWarning as or}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as ir}from"./supervise-cadence.js";import{createResumeDetector as sr}from"./resume-detector.js";import{BrokerSupervisionReconciler as ar}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as cr}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as dr}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as lr}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as pr}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as mr}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as fr}from"./reconcilers/action-lane.js";import{endLiveExecution as ur,migrateLegacyExecutionIdentities as hr}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as vr}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as wr}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as gr}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as yr}from"./reconcilers/storage-due-work.js";import{BashDeadlineReconciler as Sr}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as _r}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as br}from"./reconcilers/structured-request-lane.js";import{reconcileStructuredRequestSidecars as kr}from"./structured-requests/boot.js";import{captureLivenessSnapshot as ee,capturePidCommand as Er,captureTeardownSnapshot as re,isPidAlive as Rr,isAnyPidAlive as te,killProcessTreePids as ne}from"../core/canvas/pid.js";import{bindFleet as Ir,boundFleet as xr}from"../core/runtime/fleet.js";import{bindDaemonControl as Pr,clearDaemonControl as Tr}from"./control.js";import{execDaemon as Dr,spawnDaemon as Cr}from"./manage.js";import{reconcileAndMigrateBoot as Lr}from"../core/canvas/boot.js";import{resolveInstallId as $r}from"../core/canvas/install-id.js";import{setInstallId as Ar}from"../core/runtime/nodes.js";import{claimDaemonOwnership as Nr,releaseDaemonOwnership as oe}from"../core/canvas/daemon-owner.js";import{migrate as Mr,openDb as ie,withCanvasWrite as Gr,bindDaemonCanvasOpenHook as Or,clearDaemonCanvasOpenHook as se}from"../core/canvas/db.js";import{brokerSignalBus as O}from"./broker-signals/bus.js";import{brokerSignalStreams as Fr}from"./api/handlers/broker-signals.js";import{reconcilePackageDocuments as Hr}from"../core/graph/package-docs.js";import{startRepoSync as Br}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as qr,reapStaleSpares as jr}from"../core/runtime/warm-pool.js";import{DaemonFleet as zr}from"./fleet.js";import{BOOT_MIGRATIONS as Wr}from"./boot-migrations.js";import{applyJoinedOwner as Ur}from"./joined-owner.js";import{emitEvent as u}from"../core/events/emit.js";import{operationIdContext as _}from"../core/events/operation-id.js";import{bindDaemonEventSource as Kr}from"../core/events/source.js";import{activateCanvasImport as Jr,MigrationBlockedError as Vr,runForcedCanvasImport as Qr}from"../migrations/activation.js";import{convertLegacyProviderStore as Yr,convertLegacyScopedStore as Xr}from"../core/scoped-state/migrate.js";import{userScopeRoot as Zr}from"../core/scope.js";import{secretsDir as et}from"../core/layout.js";function F(t,n){return t.includes(` --canvas-home ${n} `)}s(F,"brokerCommandDeclaresCanvasHome");function H(t){return/ --epoch (\S+) /.exec(t)?.[1]??null}s(H,"brokerCommandEpoch");const rt=5e3,ae=150;async function tt(t){const n=ee();if(n===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness"}}),new Error("prior-epoch broker census could not capture the process table");const a=Ce(),m=[];for(const[i,p]of n.processes){const g=p.command;if(p.zombie||!g.includes("broker-cli.js")||!F(g,a)||H(g)===t)continue;const S=g.trim().split(/\s+/).at(-1)??"";S===""||S.endsWith("broker-cli.js")||m.push({pid:i,nodeId:S})}let f=0;const o=[];for(const i of m){const p=Er(i.pid);if(p===null||!p.includes("broker-cli.js")||!F(p,a)||H(p)===t||p.trim().split(/\s+/).at(-1)!==i.nodeId)continue;const g=re(i.pid,null);if(!g.reused){try{process.platform==="linux"&&k(R)?await P({operation:"stop",pid:i.pid}):process.kill(i.pid,"SIGTERM")}catch{continue}f++,o.push({pid:i.pid,snap:g})}}const w=Date.now()+rt;for(;Date.now()<w&&o.some(i=>te(i.snap.tree.length>0?i.snap.tree:[i.pid]));)await new Promise(i=>setTimeout(i,ae));let v=0;for(const i of o){const p=i.snap.tree.length>0?i.snap.tree:[i.pid];te(p)&&(process.platform==="linux"&&k(R)?await P({operation:"stop",pid:i.pid}):ne(p,"SIGKILL",i.snap.identities),v++)}v>0&&await new Promise(i=>setTimeout(i,ae));const d=ee();if(d===null)throw u({level:"error",event:"daemon.census.snapshot_failed",fields:{invariant:"prior-epoch brokers are reaped before readiness",stage:"final"}}),new Error("prior-epoch broker census could not capture the final process table");const l=[];for(const[i,p]of d.processes){const g=p.command;p.zombie||!g.includes("broker-cli.js")||F(g,a)&&H(g)!==t&&l.push(i)}if(l.length>0)throw new Error(`prior-epoch broker census could not prove termination of pid(s): ${l.join(", ")}`);u({level:"info",event:"daemon.census.completed",fields:{prior_epoch_brokers:m.length,sigtermed:f,sigkilled:v}})}s(tt,"reapPriorEpochBrokers");const nt=5e3;async function ot(t){const n=Array.from(t.entries());if(n.length===0)return;for(const[o]of n){const w=Fe(o)?.status;(w==="active"||w==="idle")&&Be(o,new Date().toISOString())}const a=n.map(([,o])=>({pid:o.pid,snap:re(o.pid,null),exited:o.handle.exited}));for(const o of a)if(!o.snap.reused)try{process.platform==="linux"&&k(R)?await P({operation:"stop",pid:o.pid}):process.kill(o.pid,"SIGTERM")}catch{}const m=new Promise(o=>{setTimeout(o,nt).unref()});await Promise.allSettled(a.map(o=>Promise.race([o.exited.then(()=>{}),m])));const f=[];for(const o of a)Rr(o.pid)&&(process.platform==="linux"&&k(R)?await P({operation:"stop",pid:o.pid}):ne(o.snap.tree.length>0?o.snap.tree:[o.pid],"SIGKILL",o.snap.identities),f.push(o));await Promise.all(f.map(o=>o.exited));for(const[o]of n)ur(o,{reason:"daemon-teardown"});u({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:a.length,sigkilled:f.length}})}s(ot,"teardownFleet");function ce(){Y(Re(G()),{recursive:!0}),_e(G(),String(process.pid),"utf8")}s(ce,"writePidfile");function de(t){return typeof t=="object"&&t!==null&&t.code==="ENOENT"}s(de,"isMissingPathError");function le(t){const n=G();let a;try{a=be(n,"utf8")}catch(m){if(de(m))return;throw m}if(a.trim()!==String(t))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${t})`);try{ke(n)}catch(m){if(de(m))return;throw m}}s(le,"removeOwnedPidfileMirror");const it={acceptsDetachedWork:s(()=>!0,"acceptsDetachedWork"),registerDetached:s(t=>{t.catch(()=>{})},"registerDetached")},B=new ar,st=new cr,at=new dr,ct=new lr,dt=new pr,lt=new mr,pt=new fr,mt=new wr,ft=new gr,ut=new yr,ht=new Sr,vt=new _r,wt=new br;async function gt(t=Date.now(),n=it){const a=xr(),m=await B.runFleetPreamble(a);let f,o;try{const v=He();f=v.filter(d=>d.status==="active"||d.status==="idle"),o=v.filter(d=>d.status==="done"||d.status==="canceled"||d.status==="dead")}catch(v){u({level:"error",event:"canvas.node_list.failed",error:v});return}await B.run(t,{rows:f,fleet:a,lifecycle:n}),st.run(t,{rows:f});const w=await at.run(t,{rows:[...f,...o],fleet:a});ct.run(t),wt.run(t),dt.run(t,{lifecycle:n}),lt.run(t,{lifecycle:n}),pt.run(t,{lifecycle:n}),mt.run(t,{lifecycle:n}),ft.run(t,{lifecycle:n,fleet:a}),ut.run(t,{lifecycle:n}),Pe(),vt.run(t,{fleet:a,lifecycle:n}),await ht.run(t,{rows:f}),B.warnIfSaturated({fleet:a,lifecycle:n,thresholds:m,frozen:w})}s(gt,"superviseTick");async function yt(t){Y(Z(),{recursive:!0,mode:493}),process.platform==="linux"&&Ee(Z(),493);const n=De();if(k(n))throw new Error(`layout refusal cannot bind occupied socket: ${n}`);const a=X((d,l)=>{if(d.method==="GET"&&d.url==="/healthz"){l.writeHead(200,{"content-type":"application/json; charset=utf-8"}),l.end(JSON.stringify({ok:!1,daemon_up:!0,store_accessible:!1,brokers_reconciled:!1,startup_phase:"blocked",startup_blocked:"layout_migration_required"}));return}l.writeHead(503,{"content-type":"application/json; charset=utf-8"}),l.end(JSON.stringify({error:{code:"layout_migration_required",message:"run crtr sys migrate while the runtime is offline",type:"server_error",origin:"runtime",retryable:!1}}))});await new Promise((d,l)=>{a.once("error",l),a.listen(n,()=>{a.off("error",l),d()})});let m;const f=t.tcp??Ie();if(f){const d=/^(.*):(\d+)$/.exec(f);if(!d||Number(d[2])>65535)throw new Error(`invalid refusal TCP address: ${f}`);const l=X(a.listeners("request")[0]);await new Promise((i,p)=>{l.once("error",p),l.listen(Number(d[2]),d[1],()=>{l.off("error",p),i()})}),m=l}ce();let o;const w=s(()=>(o??=(async()=>{a.closeAllConnections(),m?.closeAllConnections(),await Promise.all([a,m].filter(d=>d!==void 0).map(d=>new Promise((l,i)=>d.close(p=>p?i(p):l())))),le(process.pid),process.off("SIGINT",v),process.off("SIGTERM",v)})(),o),"cleanup"),v=s(()=>{w().then(()=>process.exit(0),d=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(d)}
|
|
2
|
+
`),process.exit(1)})},"onSignal");return process.on("SIGINT",v),process.on("SIGTERM",v),w}s(yt,"serveLayoutRefusal");async function St(t={}){if(Ge())return yt(t);Me();const n=Nr(Te(16).toString("hex"));if(!n.claimed){if(n.reason==="incumbent_alive")process.stderr.write(`[crtrd] already running (pid ${n.incumbentPid??"?"})
|
|
3
|
+
`);else{const r=[n.incumbentPid!==void 0?`pid ${n.incumbentPid}`:void 0,n.verdict!==void 0?`verdict ${n.verdict}`:void 0].filter(c=>c!==void 0),e=r.length>0?` (${r.join(", ")})`:"";process.stderr.write(`[crtrd] claim lost: ${n.reason}${e}
|
|
4
|
+
`),n.reason==="own_identity_indeterminate"&&process.stderr.write("[crtrd] cannot read its own process identity. crouter needs /proc on Linux, or the `ps` command elsewhere (Debian/Ubuntu: apt-get install procps).\n"),(n.reason==="own_identity_indeterminate"||n.reason==="incumbent_malformed")&&(process.exitCode=1)}return async()=>{}}const a=n.claim;let m=null;try{if(await tt(a.token),Xr({kind:"user",root:Zr()}),process.platform==="darwin"){const{readConfig:e,updateConfig:c}=await import("../core/config.js");if(e("user").paths?.user_files===void 0){const{homedir:y}=await import("node:os");c("user",Q=>{Q.paths??={},Q.paths.user_files??=y()})}}await Yr(et());for(const e of Oe())process.stderr.write(`[crtrd] ${e}
|
|
5
5
|
`);Mr(ie()),Or(e=>O.install(e));const r=Gr(e=>Hr(e));for(const e of r.conflicts)process.stderr.write(`[crtrd] plugin documents not reconciled: ${e.message}
|
|
6
6
|
`);for(const e of r.invalid)process.stderr.write(`[crtrd] package file is not a document: ${e.path}: ${e.error}
|
|
7
7
|
`);for(const e of r.quarantined)process.stderr.write(`[crtrd] plugin "${e.name}" has a damaged ${e.file} (${e.reason}); ${e.movedTo!==void 0?`moved to ${e.movedTo}`:"left in place and skipped"}
|
|
8
|
-
`);m=Ur(),process.platform==="linux"&&
|
|
8
|
+
`);m=Ur(),process.platform==="linux"&&k(R)&&(Ne(),await P({operation:"users.sync",users:Le()})),vr(a.token)}catch(r){se(),O.close();try{const{released:e}=oe(a);if(!e)throw new Error("daemon ownership claim was not released")}catch(e){throw new AggregateError([r,e],"daemon startup failed and cleanup also failed")}throw r}let f=!0,o,w=Promise.resolve(),v=!1,d,l;const i=new Set;let p=!1,g=!1,S,L,q=!1,$,T,D;const pe=sr();let I,h={phase:"initializing",message:"daemon startup is running the canvas import and normal services are unavailable",blockerPaths:[]},x="initial startup activation",me=null;function fe(){return h.phase==="ready"?null:{phase:h.phase,message:h.message,blockerPaths:h.blockerPaths}}s(fe,"startupBlock");function b(r){i.add(r),r.finally(()=>{i.delete(r)}).catch(()=>{})}s(b,"registerDetached");const j={acceptsDetachedWork:s(()=>f,"acceptsDetachedWork"),registerDetached:b};Br(j,ie);async function ue(){try{f=!1,Fr.close(),se(),O.close(),d!==void 0&&(clearInterval(d),d=void 0),D!==void 0&&(clearInterval(D),D=void 0);let r;do r=w,await r;while(r!==w);for(;i.size>0;)await Promise.allSettled([...i]);if(l!==void 0&&(await ot(l),l.dispose()),L!==void 0&&!q&&(q=!0,await L.close()),T!==void 0){const e=T;T=void 0,await e.close()}if($!==void 0){const e=$;$=void 0,await e.close()}if(p||(le(process.pid),p=!0),p&&!g){const{released:e}=oe(a);if(!e)throw new Error("daemon ownership claim was not released");g=!0}}finally{Tr(),process.off("SIGINT",U),process.off("SIGTERM",K),process.off("exit",J)}}s(ue,"cleanupAttempt");const A=1e3;let z=!1;function he(){return z?{graceMs:A}:(z=!0,_.fresh(()=>{u({level:"info",event:"daemon.handover.armed",fields:{pid:process.pid,grace_ms:A}})}),setTimeout(()=>{ve()},A),{graceMs:A})}s(he,"requestHandover");async function ve(){try{await E()}catch(r){const e=r instanceof Error?r.stack??r.message:String(r);process.stderr.write(`[crtrd] handover cleanup failed:
|
|
9
9
|
${e}
|
|
10
|
-
`),process.exit(1)}try{process.platform==="linux"&&
|
|
10
|
+
`),process.exit(1)}try{process.platform==="linux"&&k(R)&&(await P({operation:"daemon.handover"}),process.exit(0)),process.pid===1&&await Dr();const r=await Cr();_.fresh(()=>{u({level:"info",event:"daemon.handover.completed",fields:{predecessor_pid:process.pid,successor_pid:r.pid??r.existing_pid??null,spawned:r.started}})})}catch(r){_.fresh(()=>{u({level:"error",event:"daemon.handover.successor_failed",error:r})});const e=r instanceof Error?r.message:String(r);process.stderr.write(`[crtrd] handover successor failed to start: ${e}
|
|
11
11
|
`),process.exit(1)}process.exit(0)}s(ve,"performHandover");function E(){if(S!==void 0)return S;const r=ue();return S=r,r.then(()=>{S===r&&(S=void 0)},()=>{S===r&&(S=void 0)}),r}s(E,"requestCleanup");async function W(r){try{await E(),process.exit(0)}catch(e){process.exitCode=1;const c=e instanceof Error?e.stack??`${e.name}: ${e.message}`:String(e);process.stderr.write(`[crtrd] ${r} cleanup failed:
|
|
12
12
|
${c}
|
|
13
|
-
`),process.exit(1)}}s(W,"handleSignal");function U(){W("SIGINT")}s(U,"onSigint");function K(){W("SIGTERM")}s(K,"onSigterm");function J(){E().catch(()=>{})}s(J,"onExit");function N(r){f&&(w=w.then(r).then(()=>{},e=>{_.fresh(()=>{u({level:"error",event:"daemon.lane_job.failed",error:e})})}))}s(N,"enqueue");function V(){!f||v||(pe.check()&&I&&(u({level:"info",event:"daemon.resume.detected"}),
|
|
13
|
+
`),process.exit(1)}}s(W,"handleSignal");function U(){W("SIGINT")}s(U,"onSigint");function K(){W("SIGTERM")}s(K,"onSigterm");function J(){E().catch(()=>{})}s(J,"onExit");function N(r){f&&(w=w.then(r).then(()=>{},e=>{_.fresh(()=>{u({level:"error",event:"daemon.lane_job.failed",error:e})})}))}s(N,"enqueue");function V(){!f||v||(pe.check()&&I&&(u({level:"info",event:"daemon.resume.detected"}),b(I().catch(()=>{}))),v=!0,N(async()=>{if(v=!1,!!f)try{await gt(Date.now(),j)}catch(r){_.fresh(()=>{u({level:"error",event:"daemon.tick.failed",error:r})})}}))}s(V,"enqueueTick");let C=null;async function we(){if(C!==null)throw{status:500,code:"admission_failed",message:`daemon admission previously failed: ${C instanceof Error?C.message:String(C)}`};if(h.phase==="ready")return{admitted:!1,already_admitted:!0,startup_phase:"ready"};if(h.phase!=="prepared"&&h.phase!=="recovering")throw{status:409,code:"not_prepared",message:h.message};const r=h.phase==="prepared";try{return await M(),h={phase:"ready",message:"",blockerPaths:[]},r?{admitted:!0,already_admitted:!1,startup_phase:"ready"}:{admitted:!1,already_admitted:!0,startup_phase:"ready"}}catch(e){throw C=e,{status:500,code:"admission_failed",message:`daemon admission failed: ${e instanceof Error?e.message:String(e)}`}}}s(we,"admitStartup");async function ge(r){if(x!==null)throw{status:409,code:"migration_in_progress",message:`the canvas import is already running (${x})`};x="repair";try{const e=await Qr({dryRun:r.dryRun});if(r.dryRun||e.pending||h.phase!=="blocked")return{result:e,resumed:!1};if(t.prepare)return h={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]},{result:e,resumed:!1};try{return await M(),h={phase:"ready",message:"",blockerPaths:[]},{result:e,resumed:!0}}catch(c){const y=c instanceof Error?c.message:String(c);return setTimeout(()=>{E().catch(()=>{})},1e3),{result:e,resumed:!1,resumeError:y}}}finally{x=null}}s(ge,"repairMigrations");async function ye(){o=t.intervalMs??ir;const r=a.token;l=new zr({epoch:r,enqueue:N}),Ir(l),h={phase:"recovering",message:"daemon startup recovery is running and normal services are unavailable",blockerPaths:[]},_.fresh(()=>{u({level:"info",event:"daemon.started",fields:{pid:process.pid,interval_ms:o}})});const e=$r();if(Ar(e),_.fresh(()=>{u({level:"info",event:"daemon.install_id.resolved",fields:{install_id:e}})}),_.fresh(()=>{let c;try{c=or()}catch{return}c!==null&&u({level:"warn",event:"daemon.startup.warning",fields:{stage:"termrender",warning:c.slice(0,2048)}})}),await _.fresh(async()=>{await nr(),await er(),rr(Date.now()),tr(Date.now())}),_.fresh(()=>{const{reconcile:c,migrated:y}=Lr(hr);c.changed&&u({level:"info",event:"daemon.boot_change.reconciled",fields:{cleared_count:c.cleared.length,same_boot_proven:c.sameBootProven}}),y.length>0&&u({level:"info",event:"daemon.pid_identity.migrated",fields:{migrated_count:y.length}})}),_.fresh(()=>{kr();for(const c of Wr){const y=c.apply();y.changed&&u({level:"info",event:"daemon.boot_migration.applied",fields:{migration:c.id,...y.summary}})}}),b(Ye()),I){const c=s(()=>{b(I().catch(()=>{}))},"refresh");b(Ve().catch(()=>{})),b(Qe().then(({completion:y})=>y).catch(y=>{u({level:"error",event:"daemon.grants.sweep_failed",error:y})})),c(),D=setInterval(c,Je*1e3),D.unref()}N(()=>{jr(),qr()}),d=setInterval(V,o),V()}s(ye,"completeStartup");function M(){return me??=ye().catch(r=>{throw h={phase:"initializing",message:"daemon startup recovery failed and normal services are unavailable",blockerPaths:[]},r})}s(M,"resumeStartup");try{process.on("SIGINT",U),process.on("SIGTERM",K),process.on("exit",J),Kr(),m!==null&&u({level:"info",event:"daemon.joined_owner.applied",fields:{owner:m.owner,rows:m.rows}}),$=await xe();const r=a.token;Pr({epoch:r,requestHandover:he,registerDetached:b,startupBlock:fe,admitStartup:we,repairMigrations:ge}),ce(),L=qe({tcp:t.tcp}),await L.ready;const e=Ze("user").runtime;e?.issuer?(I=Ke(e.issuer,b),We(c=>Ue().appended(c))):ze(),e?.unsandboxed===!0&&e.listener!==void 0&&u({level:"warn",event:"daemon.app_listener.refused_unsandboxed",fields:{reason:"runtime.unsandboxed is set; runtime.listener is ignored"}}),e!==void 0&&e.listener!==void 0&&e.unsandboxed!==!0&&(T=je(e,()=>Xe(e.issuer.replace(/\/$/,"")),I),await T.ready);try{await Jr()}catch(c){if(x=null,c instanceof Vr)return h={phase:"blocked",message:c.message,blockerPaths:c.blockerPaths},E;throw c}process.platform==="linux"&&k(R)&&(Ae(),await $e("app:terminal"));try{t.prepare?h={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]}:(await M(),h={phase:"ready",message:"",blockerPaths:[]})}finally{x=null}return E}catch(r){try{await E()}catch(e){throw new AggregateError([r,e],"daemon startup failed and cleanup also failed")}throw r}}s(St,"runDaemon");var zn=St;export{F as brokerCommandDeclaresCanvasHome,H as brokerCommandEpoch,zn as default,St as runDaemon,gt as superviseTick,ot as teardownFleet};
|
package/dist/types.d.ts
CHANGED
|
@@ -388,10 +388,12 @@ export interface ScopeConfig {
|
|
|
388
388
|
runtime?: {
|
|
389
389
|
issuer: string;
|
|
390
390
|
audience: string;
|
|
391
|
-
listener
|
|
391
|
+
listener?: {
|
|
392
392
|
address: string;
|
|
393
393
|
port: number;
|
|
394
394
|
};
|
|
395
|
+
/** Set by joining as a non-default machine: no app listener; only `app:terminal` nodes run. */
|
|
396
|
+
unsandboxed?: boolean;
|
|
395
397
|
jwksUrl?: string;
|
|
396
398
|
person_uids?: number[];
|
|
397
399
|
/** Hosting file storage: `service_url` is the storage token audience and API
|
package/docs/release-local.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Use this only for a release explicitly authorized by Silas. `.github/workflows/publish.yml` is the source of truth; this is its manual path when Actions cannot run. Work on clean, current `main`, with `npm whoami` showing the publishing account. Verify the code first (`npm run build:api`, `npm run build`, `npx tsc --noEmit`, relevant `npm run test:isolated -- …`). Do not start a release while another agent owns uncommitted changes in this shared checkout.
|
|
4
4
|
|
|
5
5
|
1. Check the six latest registry versions with `npm view <name> version` for each of `crouter`, `@north-light/api`, `@north-light/env-docker`, `@north-light/identity`, `@north-light/sdk` and `@north-light/plugin`. Fetch `origin/main`, ensure the release commit is current, and choose a patch version greater than every published version. Do **not** use the workflow's `git reset --hard` in a shared checkout.
|
|
6
|
-
2. Run `node scripts/release-version.mjs "$VERSION"`. It sets all six manifests to `$VERSION`, pins every dependency between them (root → identity, SDK → api and identity, plugin → api and identity) to exactly `$VERSION` with no `^`, writes `SDK_VERSION` in `packages/crouter-sdk/src/version.ts
|
|
7
|
-
3. From the tagged release commit run `npm ci`. Publish in dependency order, checking `npm view "$PACKAGE@$VERSION" version` before each and skipping only on a positive sighting: `npm run build:identity` then `npm publish` in `packages/crouter-identity`; `npm run build` then `CRTR_PACK_MINIFY=1 npm publish` at root (Docker must be running: the pack cross-builds the Linux x64 and arm64 native addons into `dist/native/linux/<arch>/addon.node` and fails the release if it cannot); `npm run build:api` then publish in `packages/crouter-api`; `npm run build:env-docker` then publish in `packages/crouter-env-docker`; `npm run build:api && npm run build:identity && npm run build:sdk` then publish in `packages/crouter-sdk`; `npm run build:api && npm run build:identity && npm run build:plugin` then `CRTR_PACK_MINIFY=1 npm publish` in `packages/crouter-plugin`. Use npm's interactive authentication/2FA where required (see `personal-operations/npm-2fa-cli`); a read-only token cannot publish. Provenance remains off because the source repository is private. Do not build or publish the Docker image in this no-Actions path.
|
|
8
|
-
4. Confirm all six exact versions with `npm view`. In a fresh temporary directory, `npm init -y && npm install @north-light/sdk@$VERSION`, then prove both `@north-light/sdk/errors` (`errorCodes`) and `@north-light/sdk/stores/postgres` resolve via `node --input-type=module -e '…'`. Record any partial publish and
|
|
6
|
+
2. Run `node scripts/release-version.mjs "$VERSION"`. It sets all six manifests to `$VERSION`, pins every dependency between them (root → identity, SDK → api and identity, plugin → api and identity) to exactly `$VERSION` with no `^`, writes `SDK_VERSION` in `packages/crouter-sdk/src/version.ts` (informational: compatibility is the contract level), and renames the `## Next release` section of `docs/sdk/release-notes.md` to `## $VERSION`. One release is one version set: an app pinning `@north-light/sdk@$VERSION` gets exactly this release's api and identity. Run `npm install --package-lock-only --ignore-scripts`, then `node scripts/contract.mjs release`: it fails on an unacknowledged breaking `/v1` change or an undeclared watched file (the same check CI runs), then writes `contract/surface.txt`, `CONTRACT_LEVEL` in `src/api/contract.ts` (a minor bump only when the surface changed or a declaration says `bump`), `crouterContract` in the SDK manifest, and resets `contract/next.json`. Run `node scripts/release-version.mjs --check` (prints `one version set`). Check `git diff` and commit only the six manifests, `packages/crouter-sdk/src/version.ts`, `docs/sdk/release-notes.md`, `package-lock.json`, `contract/surface.txt`, `contract/next.json` and `src/api/contract.ts` as `chore: release v$VERSION`. Create `git tag -a "v$VERSION" -m "chore: release v$VERSION"`, then `git push --atomic origin HEAD:main "refs/tags/v$VERSION"`. If rejected due to a concurrent push, remove the *local* release tag, integrate fresh main, then recheck versions and retry; never push a tag separately.
|
|
7
|
+
3. From the tagged release commit run `npm ci`. Publish in dependency order, checking `npm view "$PACKAGE@$VERSION" version` before each and skipping only on a positive sighting: `npm run build:identity` then `npm publish` in `packages/crouter-identity`; `npm run build` then `CRTR_PACK_MINIFY=1 npm publish` at root (Docker must be running: the pack cross-builds the Linux x64 and arm64 native addons into `dist/native/linux/<arch>/addon.node` and fails the release if it cannot); `npm run build:api` then publish in `packages/crouter-api`; `npm run build:env-docker` then publish in `packages/crouter-env-docker`; `npm run build:api && npm run build:identity && npm run build:sdk` then `npm publish --tag next` in `packages/crouter-sdk` (its `prepublishOnly` guard refuses any other form, including `--tag latest`; every SDK release lands on `next`); `npm run build:api && npm run build:identity && npm run build:plugin` then `CRTR_PACK_MINIFY=1 npm publish` in `packages/crouter-plugin`. Use npm's interactive authentication/2FA where required (see `personal-operations/npm-2fa-cli`); a read-only token cannot publish. Provenance remains off because the source repository is private. Do not build or publish the Docker image in this no-Actions path.
|
|
8
|
+
4. Confirm all six exact versions with `npm view`. In a fresh temporary directory, `npm init -y && npm install @north-light/sdk@$VERSION`, then prove both `@north-light/sdk/errors` (`errorCodes`) and `@north-light/sdk/stores/postgres` resolve via `node --input-type=module -e '…'`. Record any partial publish and finish it from the exact release tag rather than cutting another version: `gh workflow run publish.yml -f release_tag=v$VERSION` runs `recover-release`, which verifies the tag is a release commit on main, checks it out, and publishes only the packages and image the registry does not yet show (SDK to `next`; it never writes the image `:latest`, so if the recovered tag is the newest release, `:latest` follows the next normal release).
|
|
9
|
+
5. Promote `latest` last, through CI only: `gh workflow run publish.yml -f promote=true` runs the `promote-sdk` job, the one writer of `latest`. It shares the `sdk-dist-tag` concurrency group with the automatic runs (they queue, never overlap), reads the router's `/contract` itself, re-reads `latest` right before the write and moves it only if unchanged. It also runs after each SDK publish and hourly, once the npm trusted-publisher entry for `@north-light/sdk` has "Allow npm dist-tag" ticked. `PROD_CONTRACT_URL=<the router's /contract URL> node scripts/promote-sdk.mjs` is a read-only dry run; its `--apply` refuses outside Actions. **Never `npm dist-tag add` the SDK by hand.** The one exception is Actions fully down: a person then runs the printed target by hand through the npm 2FA wrapper only after re-reading `npm view @north-light/sdk dist-tags` and confirming no `promote-sdk` run is active (`gh run list --workflow publish.yml`) and that the target is above the current `latest`.
|
package/docs/sdk/errors.md
CHANGED
|
@@ -24,7 +24,7 @@ Every class below extends `APIError`, which carries `status`, `code`, `message`,
|
|
|
24
24
|
| Condition | Class |
|
|
25
25
|
|---|---|
|
|
26
26
|
| SDK configuration error | `CrouterError` with `status: 0`, `code: 'crouter_error'` |
|
|
27
|
-
| The runtime
|
|
27
|
+
| The runtime does not serve the contract level the SDK requires, or names none. The hosting router or the runtime refuses this *before* handling (the SDK sends `Crouter-Required-Contract` and a frozen `Crouter-Minimum-Runtime-Version`): nothing was accepted and no idempotency key was recorded. A runtime that predates the contract level handles the request first, and the SDK then raises after | `RuntimeVersionError` with `status: 426`, `code: 'runtime_version_unsupported'`; `requiredContract` and `runtimeContract` (or `null`) name both levels, `sdkVersion` and `runtimeVersion` stay for logging, and it is never retryable. A level is `MAJOR.MINOR`; the runtime satisfies it when it serves the same major and a minor at or above. See [Release notes](./release-notes.md) |
|
|
28
28
|
| HTTP 400 | `BadRequestError` |
|
|
29
29
|
| HTTP 401 | `AuthenticationError` |
|
|
30
30
|
| HTTP 403 | `PermissionDeniedError` |
|
|
@@ -25,6 +25,8 @@ npm i @north-light/sdk
|
|
|
25
25
|
|
|
26
26
|
One dependency. It re-exports every data type you need, so you do not also install `@north-light/api`.
|
|
27
27
|
|
|
28
|
+
`@latest` always works against production: a release reaches `latest` only once production serves its contract level. The `next` tag carries every release the moment it is published and may need a newer runtime than production runs (see [Errors](./errors.md) for `RuntimeVersionError`). Pin an exact version in your app.
|
|
29
|
+
|
|
28
30
|
## 3. Node, on the owner's own daemon
|
|
29
31
|
|
|
30
32
|
Pass nothing. The client resolves the same socket path the `crtr` CLI does, and starts the daemon if it is not already up.
|
|
@@ -7,6 +7,16 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
|
|
|
7
7
|
|
|
8
8
|
Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
|
|
9
9
|
|
|
10
|
+
## 0.3.423
|
|
11
|
+
|
|
12
|
+
### Compatibility is a contract level, not the package version (additive)
|
|
13
|
+
|
|
14
|
+
The SDK no longer refuses a runtime because its release number is lower than the SDK's. It sends `Crouter-Required-Contract: <CONTRACT_LEVEL>` and refuses a runtime that does not serve that level (`MAJOR.MINOR`, same major, minor at or above). The level changes only when the `/v1` surface changes, so an ordinary release no longer makes production answer 426. `RuntimeVersionError` keeps `status` 426 and code `runtime_version_unsupported`, and gains `requiredContract` and `runtimeContract`; `sdkVersion` and `runtimeVersion` stay. Apps that catch the error need no change.
|
|
15
|
+
|
|
16
|
+
- Every SDK release is published to the `next` dist-tag; `latest` moves only once production serves its level. `@latest` therefore always works against production.
|
|
17
|
+
- The SDK now tolerates values it does not know: an unknown run event `type` is passed to your listener as-is, and an unknown run `status` ends `runs.wait` and the run watcher, reporting the raw status (`RunEnded.ended` can be `'unknown'`). Handle a status or event type you do not recognize rather than assuming the union is closed.
|
|
18
|
+
- `FakeCloud` takes `contract` and `routedContract` options and refuses an incompatible call at ingress, before any interceptor runs.
|
|
19
|
+
|
|
10
20
|
## 0.3.419
|
|
11
21
|
|
|
12
22
|
### Testing mode: `app_in_testing` (additive)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.423",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -97,6 +97,9 @@
|
|
|
97
97
|
"build:plugin": "tsc -p packages/crouter-plugin/tsconfig.json",
|
|
98
98
|
"typecheck:pi-packages": "npx tsc -p src/builtin-pi-packages/pi-crtr-extensions/tsconfig.json",
|
|
99
99
|
"test:runtime-packaging": "node --test scripts/runtime-smoke.test.mjs scripts/pack-args.test.mjs scripts/ensure-native-linux.test.mjs scripts/release-version.test.mjs",
|
|
100
|
+
"contract:check": "node scripts/contract.mjs check",
|
|
101
|
+
"contract:surface": "node scripts/contract.mjs generate",
|
|
102
|
+
"test:contract": "node --conditions=crtr-src --import tsx/esm --test scripts/contract.test.mjs scripts/promote-sdk.test.mjs scripts/release-version.test.mjs",
|
|
100
103
|
"test:build-output": "node scripts/build-concurrency-test.mjs",
|
|
101
104
|
"check:cli-no-canvas": "node scripts/check-cli-no-canvas.mjs",
|
|
102
105
|
"check:cli-cold-start": "node scripts/check-cli-cold-start.mjs",
|
|
@@ -134,7 +137,7 @@
|
|
|
134
137
|
},
|
|
135
138
|
"license": "UNLICENSED",
|
|
136
139
|
"dependencies": {
|
|
137
|
-
"@north-light/identity": "0.3.
|
|
140
|
+
"@north-light/identity": "0.3.423",
|
|
138
141
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
139
142
|
"@earendil-works/pi-ai": "1.1.0",
|
|
140
143
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.423",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.423",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"workspaces": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@earendil-works/pi-ai": "1.1.0",
|
|
19
19
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
20
20
|
"@earendil-works/pi-tui": "1.1.0",
|
|
21
|
-
"@north-light/identity": "0.3.
|
|
21
|
+
"@north-light/identity": "0.3.423",
|
|
22
22
|
"cron-parser": "^5.6.0",
|
|
23
23
|
"esbuild": "^0.27.7",
|
|
24
24
|
"jose": "^6.2.12",
|
|
@@ -12095,17 +12095,17 @@
|
|
|
12095
12095
|
},
|
|
12096
12096
|
"packages/crouter-api": {
|
|
12097
12097
|
"name": "@north-light/api",
|
|
12098
|
-
"version": "0.3.
|
|
12098
|
+
"version": "0.3.423",
|
|
12099
12099
|
"license": "UNLICENSED"
|
|
12100
12100
|
},
|
|
12101
12101
|
"packages/crouter-env-docker": {
|
|
12102
12102
|
"name": "@north-light/env-docker",
|
|
12103
|
-
"version": "0.3.
|
|
12103
|
+
"version": "0.3.423",
|
|
12104
12104
|
"license": "UNLICENSED"
|
|
12105
12105
|
},
|
|
12106
12106
|
"packages/crouter-identity": {
|
|
12107
12107
|
"name": "@north-light/identity",
|
|
12108
|
-
"version": "0.3.
|
|
12108
|
+
"version": "0.3.423",
|
|
12109
12109
|
"license": "UNLICENSED",
|
|
12110
12110
|
"dependencies": {
|
|
12111
12111
|
"jose": "^6.2.1"
|
|
@@ -12113,20 +12113,20 @@
|
|
|
12113
12113
|
},
|
|
12114
12114
|
"packages/crouter-plugin": {
|
|
12115
12115
|
"name": "@north-light/plugin",
|
|
12116
|
-
"version": "0.3.
|
|
12116
|
+
"version": "0.3.423",
|
|
12117
12117
|
"license": "UNLICENSED",
|
|
12118
12118
|
"dependencies": {
|
|
12119
|
-
"@north-light/api": "0.3.
|
|
12120
|
-
"@north-light/identity": "0.3.
|
|
12119
|
+
"@north-light/api": "0.3.423",
|
|
12120
|
+
"@north-light/identity": "0.3.423"
|
|
12121
12121
|
}
|
|
12122
12122
|
},
|
|
12123
12123
|
"packages/crouter-sdk": {
|
|
12124
12124
|
"name": "@north-light/sdk",
|
|
12125
|
-
"version": "0.3.
|
|
12125
|
+
"version": "0.3.423",
|
|
12126
12126
|
"license": "UNLICENSED",
|
|
12127
12127
|
"dependencies": {
|
|
12128
|
-
"@north-light/api": "0.3.
|
|
12129
|
-
"@north-light/identity": "0.3.
|
|
12128
|
+
"@north-light/api": "0.3.423",
|
|
12129
|
+
"@north-light/identity": "0.3.423",
|
|
12130
12130
|
"jose": "^6.2.1"
|
|
12131
12131
|
},
|
|
12132
12132
|
"bin": {
|