@north-light/cli 0.3.538 → 0.3.539
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 +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/profiles.d.ts +2 -0
- package/dist/builtin-memory/crouter-sdk/release-notes.md +8 -0
- package/dist/clients/attach/viewer.js +454 -454
- package/dist/commands/profile/list.js +1 -1
- package/dist/commands/profile/show.js +1 -1
- package/dist/commands/sys/join.js +1 -1
- package/dist/commands/sys/sync-shared.d.ts +1 -1
- package/dist/commands/sys/sync-shared.js +1 -1
- package/dist/core/canvas/canvas.d.ts +0 -6
- package/dist/core/canvas/canvas.js +17 -22
- package/dist/core/canvas/crons.js +10 -10
- package/dist/core/canvas/db.js +4 -4
- package/dist/core/canvas/export-sqlite.js +1 -1
- package/dist/core/canvas/legacy-person.d.ts +4 -0
- package/dist/core/canvas/legacy-person.js +1 -0
- package/dist/core/canvas/migrations.js +58 -58
- package/dist/core/canvas/node-tokens.d.ts +0 -4
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/canvas/person.d.ts +39 -0
- package/dist/core/canvas/person.js +18 -0
- package/dist/core/canvas/schema-pg.sql +11 -0
- package/dist/core/directory/join.d.ts +2 -2
- package/dist/core/environments.d.ts +10 -0
- package/dist/core/environments.js +1 -0
- package/dist/core/grants/local.d.ts +4 -4
- package/dist/core/grants/local.js +1 -1
- package/dist/core/grants/mirror.d.ts +1 -1
- package/dist/core/grants/on-sync.js +1 -1
- package/dist/core/grants/sync.js +1 -1
- package/dist/core/graph/kinds/environment.d.ts +2 -0
- package/dist/core/graph/kinds/environment.js +2 -0
- package/dist/core/graph/kinds/registry.js +1 -1
- package/dist/core/graph/objects.d.ts +1 -1
- package/dist/core/graph/objects.js +7 -7
- package/dist/core/graph/reader.d.ts +0 -1
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/layout-migrate/index.js +3 -3
- package/dist/core/layout-migrate/stores.js +3 -3
- package/dist/core/peer/stubs.js +1 -1
- package/dist/core/runs/events.d.ts +0 -2
- package/dist/core/runs/events.js +4 -4
- package/dist/core/runs/operations.d.ts +0 -3
- package/dist/core/runs/operations.js +6 -6
- package/dist/core/runtime/close.js +1 -1
- package/dist/core/runtime/host.js +3 -3
- package/dist/core/runtime/launch-target.d.ts +10 -1
- package/dist/core/runtime/launch-target.js +1 -1
- package/dist/core/runtime/nodes.js +1 -1
- package/dist/core/scoped-state/db.js +2 -2
- package/dist/core/scoped-state/profiles.js +1 -1
- package/dist/core/scoped-state/schema.d.ts +4 -0
- package/dist/core/scoped-state/schema.js +3 -3
- package/dist/core/spaces/process.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/hook-exec.js +1 -1
- package/dist/daemon/api/handlers/memory.js +5 -5
- package/dist/daemon/api/handlers/messages.js +2 -2
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/reader.js +1 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/authority.d.ts +1 -2
- package/dist/daemon/cron/authority.js +1 -1
- package/dist/daemon/crtrd.js +12 -12
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +3 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +1 -1
- package/dist/migrations/004-canvas-documents/apply.js +1 -1
- package/dist/migrations/004-canvas-documents/index.js +2 -2
- package/dist/types.d.ts +2 -0
- package/docs/cli/join-northlight.md +1 -1
- package/docs/sdk/release-notes.md +8 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
- package/dist/daemon/joined-owner.d.ts +0 -5
- package/dist/daemon/joined-owner.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var $=Object.defineProperty;var c=(n,e)=>$(n,"name",{value:e,configurable:!0});import{ApiError as d}from"../../api/index.js";import{readerFor as g}from"../../core/graph/reader.js";import{canRead as E}from"../../core/graph/access.js";import{ownersByHandle as y,resolveName as F,viewOf as R}from"../../core/graph/names.js";import{ensureOwner as v,ownerForGrantee as x}from"../../core/graph/objects.js";import{checkoutsForCwd as N,reposForCwd as j}from"../../core/graph/repo-sync/repos.js";import{lookupProfile as L}from"../../core/profiles/manifest.js";import{filterAsync as _,mapAsync as A}from"../../core/async-array.js";import{personId as I}from"../../core/canvas/person.js";async function G(n,e){return await g(n,e.principal,async o=>await N(n,o))}c(G,"readerFor");async function P(n,e,o){const r=await F(n,e,o);if("object"in r)return r.object;if("deleted"in r||"remote"in r)return r;if("ambiguous"in r){const s=await A(r.ambiguous,async m=>`${await O(n,m)}/${m.name}`);throw new d(409,"ambiguous_name",`'${o}' names more than one object: ${s.join(", ")}`,{candidates:s,next:"Read one of them by its full `<type>:<owner>/<name>`."})}const t=o.replace(/^\s*\[\[(.*)\]\]\s*$/,"$1").trim(),a=/^(person|app|repo|profile):([^/]+)$/.exec(t),i=await _(a?await y(n,a[2],a[1]):(await y(n,t)).filter(s=>s.type!=="node"),async s=>await E(n,e,s));if(i.length>0){const s=await R(n,e),m=c(w=>{const f=s.findIndex(h=>h.id===w.id);return f<0?1/0:f},"rank"),l=[...i].sort((w,f)=>m(w)-m(f))[0];if(m(l)===1/0&&i.length>1){const w=i.map(f=>`${f.type}:${f.name}`);throw new d(409,"ambiguous_name",`'${o}' names more than one owner: ${w.join(", ")}`,{candidates:w,next:"Name one as `<type>:<handle>`."})}return l.deleted_at?{deleted:l}:l}const p=`'${o}' not found: no object by that name in your view`,u=`Search for it with \`crtr canvas search ${t}\`.`;throw new d(404,"not_found",p,{error:"not_found",message:p,next:u})}c(P,"resolveRef");async function O(n,e){if(e.owner===null)return"";const o=await n.prepare("SELECT type, id, name FROM objects WHERE id = ?").get(e.owner);return o.type==="node"?o.id:`${o.type}:${o.name}`}c(O,"typedOwnerHandle");async function q(n,e,o,r){const t=o??(e.class==="node"?"node":e.class==="person"?"user":"app"),a=c(i=>new d(400,"invalid_owner",i,{next:"Run `crtr doc write --owner -h`."}),"refuse");if(t==="node"){if(e.class!=="node")throw a("`--owner node` needs a node; you are not running as one");return e.node}if(t==="user")return await x(n,I());if(t==="app"){if(e.class==="person")throw a("`--owner app` needs an app; a person has none \u2014 use `--owner user`");return e.app}if(t==="profile"){if(e.class!=="node"||!e.profile)throw a("`--owner profile` needs a profile; you have none");return e.profile}if(t==="repo"){const i=r?await j(n,r):[],p=i[0]?i:e.class==="node"?e.repos:[];if(!p[0])throw a(`\`--owner repo\` needs a repo; ${r??(e.class==="node"?e.cwd:"your directory")} is not inside one`);return p[0]}if(t.startsWith("repo:"))return await S(n,"repo",t.slice(5),a);if(t.startsWith("profile:")){const i=t.slice(8),p=await n.prepare("SELECT * FROM objects WHERE type = 'profile' AND name = ? AND home_machine IS NULL").get(i);if(p)return p;const u=L(i);if(u.kind!=="resolved")throw a(`no profile named '${i}'`);const s=e.class==="person"?e.person:e.app;return await v(n,"profile","profile",u.entry.profileId,u.entry.manifest.name,s)}throw a(`unknown --owner value '${t}'`)}c(q,"ownerFromFlag");async function S(n,e,o,r){const t=await n.prepare("SELECT * FROM objects WHERE type = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(e,o);if(!t)throw r(`no ${e} named '${o}'`);return t}c(S,"namedOwner");export{q as ownerFromFlag,G as readerFor,P as resolveRef};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var W=Object.defineProperty;var u=(e,t)=>W(e,"name",{value:t,configurable:!0});import{toErrorBody as j}from"./map.js";import{emitEvent as p}from"../../core/events/emit.js";import{scopeAllowed as f,ScopeDeniedError as N}from"../../core/scopes.js";import{coveredByAny as J}from"../../shared/env.js";import{ApiError as c}from"../../api/errors.js";import{activeGrant as B,grantFor as x}from"../../core/grants/mirror.js";import{getNode as y}from"../../core/canvas/canvas.js";import{ownedRun as z,RunError as _}from"../../core/runs/operations.js";import{nodeMayReach as H,placeMayReach as S}from"../../core/runs/confinement.js";import{checkCronFire as M}from"../cron/authority.js";import{readConfig as P}from"../../core/config.js";import{SCOPE_VOCABULARY as I}from"../../core/scopes.js";import{operationFor as L}from"./operations.js";import{CONTROL_BODY_MAX_BYTES as F}from"../../core/llm/body-limits.js";import{getRequest as Y,getRequestByInboxTicket as V}from"../../core/human/requests.js";import{requireVisibleReview as R}from"../../core/review/store.js";import{getComment as O}from"../../core/review/comments.js";async function D(e,t,o,s,n){const r=e.path,a=n&&typeof n=="object"&&!Array.isArray(n)?n:{};if(r==="/v1/human/requests"||r==="/v1/human/reviews"){const d=r.endsWith("/reviews")?a.origin_node_id:a.requester_node_id,h=r==="/v1/human/requests"&&d==null;if(d!==t.node&&!h)throw new c(403,"other_app_run","a broker may create a human request only for its own node");const i=a.source;if(i&&typeof i=="object"&&"nodeId"in i&&i.nodeId!==t.node)throw new c(403,"other_app_run","request source must name the calling node")}if(r==="/v1/human/reviews/self/comments"&&e.method==="GET"&&s.get("actor_node_id")!==t.node)throw new c(403,"other_app_run","self review requires the calling node");if(r.includes("/comments")&&e.method!=="GET"&&a.actor_node_id!==t.node)throw new c(403,"other_app_run","comment author must be the calling node");if(o.comment_id&&!await O(o.comment_id))throw new c(404,"not_found","comment not found")}u(D,"authorizeHumanBody");function g(e){return e.class==="broker"||e.class==="cron"}u(g,"boundedByStoredScopes");function b(e){return e.class==="person"||e.class==="anonymous"||e.class==="machine"?null:e.class==="forwarded"?e.actor.kind==="app"?e.actor.app:null:e.app}u(b,"appOf");function q(e){return e.class==="person"||e.class==="forwarded"&&e.actor.kind==="person"}u(q,"actsAsPerson");async function $(e){const t=await x(e.actor.app,e.sub);if(!t)throw new c(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);return t}u($,"forwardedGrant");async function v(e){if(e.class==="person"||e.class==="anonymous"||e.class==="machine")return[];if(e.class==="forwarded"){if(e.actor.kind==="person")return[];const{resolved:o}=await $(e),s=e.actor.node_scopes;return s===null?o:s.filter(n=>J(o,n))}if(e.class==="app")return e.scopes;if(e.class==="cron"){const o=await M({grantee:e.app,sub:e.sub,grant_id:e.grant,scopes:e.scopes});if(o.allowed)return o.effectiveScopes;throw o.reason==="grant_missing"||o.reason==="grant_revoked"?new c(401,"grant_revoked",o.message):new c(403,"scope_missing",o.message)}const t=e.grant?await B(e.grant):await x(e.app,e.sub);if(t&&t.grantee===e.app&&t.sub===e.sub)return t.resolved;if(e.app==="app:terminal"&&!P("user").runtime?.issuer)return I;throw new c(401,"grant_revoked","the node grant is not active")}u(v,"resolvedScopes");function k(e){return"class"in e?b(e):e.grantee}u(k,"runCallerApp");async function l(e,t,o){return t===void 0||!("class"in e)?!0:e.class==="cron"?await S({run:e.origin_run??"",isolated:e.isolated},e.app,t,o):e.class==="forwarded"?e.actor.kind!=="app"||await S(e.actor.place,e.actor.app,t,o):e.class!=="broker"?!0:await H(e.node,e.app,t,o)}u(l,"brokerMayReach");async function X(e,t,o){const s=k(e);if(s===t)return await l(e,o,t);if(s===null)return!0;const n="class"in e?null:await B(e.grant),r="class"in e?await v(e):n?.sub===e.sub&&n.grantee===e.grantee?n.resolved:[];return f(r,`crtr:conversations:read:${t}`)&&(!("class"in e)||!g(e)||f(e.scopes,`crtr:conversations:read:${t}`))}u(X,"canReadRun");async function Be(e,t,o=!1,s=!1){const n=await y(t);if(!n||n.parent!==null||n.run_id!==t||!n.grantee||n.deleting_at&&!s)throw new _(404,"not_found","run not found");if(!await X(e,n.grantee,t))throw new _(404,"not_found","run not found");if(o&&k(e)!==null&&k(e)!==n.grantee)throw new _(403,"other_app_run","cannot change another app's run");return await z(t,n.grantee,s)}u(Be,"requireRun");async function C(e,t,o){return q(e)?!0:e.class==="anonymous"||e.class==="machine"?!1:b(e)===t?await l(e,o,t):f(await v(e),`crtr:objects:read:${t}`)&&(!g(e)||f(e.scopes,`crtr:objects:read:${t}`))}u(C,"canReadNode");const K=new Set(["messages.post.58","messages.post.59"]);async function Q(e,t,o){if(!e.classes.includes(t.class)&&!(t.class!=="anonymous"&&t.class!=="machine"&&t.class!=="forwarded"&&e.classes.includes("anonymous")))throw new c(403,"permission_error","this operation is not available to the caller");if(t.class==="forwarded"&&t.actor.kind==="app"&&await $(t),t.class==="person"||t.class==="anonymous"||t.class==="machine"||q(t))return;const s=await v(t);if(e.scope&&(!f(s,e.scope)||g(t)&&!f(t.scopes,e.scope)))throw new c(403,"scope_missing",`missing scope ${e.scope}`);if(t.class==="app")return;const n=b(t);t.class==="broker"&&await Z(e,t,o,s);const r=o.id;if((e.resource==="node-read"||e.resource==="node-write")&&r!==void 0){const a=await y(r);if(!a)return;if(e.resource==="node-write"&&a.grantee!==n&&!(K.has(e.name)&&f(s,`crtr:objects:message:${a.grantee??"app:terminal"}`)))throw new c(403,"other_app_run","cannot change another app's node");if(e.resource==="node-write"&&!await l(t,r,a.grantee??"app:terminal"))throw new c(404,"not_found","node not found");if(e.resource==="node-read"&&!await C(t,a.grantee??"app:terminal",r))throw new c(404,"not_found","node not found")}}u(Q,"authorizeOperation");async function Z(e,t,o,s){if(e.resource==="daemon-bash"&&(!f(s,"crtr:files:rw:user:.")||!f(t.scopes,"crtr:files:rw:user:.")))throw new c(403,"scope_missing","missing scope crtr:files:rw:user:.");if(e.resource==="human"){const r=e.path,a=e.method==="GET";if(!a&&(r.endsWith("/respond")||r.endsWith("/submit")||r.endsWith("/dismiss")||r.endsWith("/progress")||r.endsWith("/comment-ranges")||r.includes("/feedback-comments")))throw new c(403,"permission_error","only the person may answer or edit the human inbox");const d=o.request_id?await Y(o.request_id):o.ticket_id?await V(o.ticket_id):null,h=o.comment_id?await O(o.comment_id):null,i=o.review_id?await R(o.review_id):h?await R(h.review_id):null,w=d?.requester_node_id??i?.origin_node_id;if(w!=null){const m=await y(w);if(m&&(a?!await C(t,m.grantee??"app:terminal",w):m.grantee!==t.app))throw new c(a?404:403,a?"not_found":"other_app_run","human request belongs to another app");if(m&&!a&&!await l(t,w,m.grantee??"app:terminal"))throw new c(404,"not_found","human request not found")}else if(d||i)throw new c(403,"permission_error","only the person may access a request without a node")}const n=o.id;if(e.resource==="node-own"&&n!==void 0&&n!==t.node)throw new c(403,"other_app_run","the operation requires the calling node")}u(Z,"authorizeBrokerOnly");function ee(e,t){if(!f(e,t))throw new N("the bearer token",t)}u(ee,"assertCeiling");function te(){return new c(403,"owner_only","this operation is owner-only; a scoped token does not reach it")}u(te,"ownerOnlyError");function xe(e){return e.map(t=>({...t,ownerOnly:!0}))}u(xe,"ownerOnly");class Se{static{u(this,"Router")}routes=[];register(t){const o=L(t.method,t.pattern);if(!o)throw new Error(`route has no operation row: ${t.method} ${t.pattern}`);if(this.routes.some(s=>s.method===t.method.toUpperCase()&&s.operation.path===t.pattern))throw new Error(`duplicate route: ${t.method} ${t.pattern}`);return this.routes.push({operation:o,method:t.method.toUpperCase(),segments:A(t.pattern),handler:t.handler,maxBodyBytes:t.maxBodyBytes??F}),this}registerAll(t){for(const o of t)this.register(o);return this}match(t,o){const s=t.toUpperCase(),n=A(o);for(const r of this.routes){if(r.method!==s)continue;const a=oe(r.segments,n);if(a!==null)return{handler:r.handler,params:a,operation:r.operation,maxBodyBytes:r.maxBodyBytes}}return null}async handle(t,o,s,n={class:"person"}){try{const r=new URL(t.url??"/","http://127.0.0.1"),a=(t.method??"GET").toUpperCase(),d=this.match(a,r.pathname);if(d===null){U(o,404,"not_found",`no route for ${a} ${r.pathname}`);return}if(await Q(d.operation,n,d.params),s!==null){if(d.operation.classes.length===1&&d.operation.classes[0]==="person")throw te();d.operation.scope&&ee(s,d.operation.scope)}let h;try{h=await ne(t,d.maxBodyBytes)}catch(m){const E=m instanceof T?"payload_too_large":"invalid_request";U(o,E==="payload_too_large"?413:400,E,m.message);return}n.class==="broker"&&d.operation.resource==="human"&&await D(d.operation,n,d.params,r.searchParams,h);const i={method:a,path:r.pathname,params:d.params,query:r.searchParams,body:h,ceiling:s,principal:n,...n.class==="anonymous"&&typeof t.headers.authorization=="string"?{publishToken:t.headers.authorization.slice(7)}:{},req:t,res:o},w=await d.handler(i);o.writableEnded||re(o,w)}catch(r){const{status:a,body:d}=j(r);a>=500&&p({event:"api.request.failed",level:"error",error:r instanceof Error?r:new Error(String(r)),...n.class==="broker"?{node_id:n.node}:{},fields:{method:(t.method??"GET").toUpperCase(),path:t.url??""}}),G(o,a,d)}}}function A(e){return e.split("/").filter(t=>t.length>0)}u(A,"splitPath");function oe(e,t){if(e.length!==t.length)return null;const o={};for(let s=0;s<e.length;s++){const n=e[s],r=t[s];if(n.startsWith(":"))o[n.slice(1)]=decodeURIComponent(r);else if(n!==r)return null}return o}u(oe,"matchSegments");class T extends Error{static{u(this,"BodyTooLargeError")}}async function ne(e,t){const o=[];let s=0;for await(const r of e){const a=r;if(s+=a.length,s>t)throw new T(`request body exceeds ${t} bytes`);o.push(a)}if(s===0)return;const n=Buffer.concat(o).toString("utf8").trim();if(n!=="")try{return JSON.parse(n)}catch{throw new Error("request body is not valid JSON")}}u(ne,"readJsonBody");function re(e,t){if(e.headersSent)return;const o={...t.headers??{}};if(t.rawBody!==void 0){e.writeHead(t.status,o),e.end(t.rawBody);return}if(t.status===204||t.body===void 0){e.writeHead(t.status,o),e.end();return}o["content-type"]===void 0&&(o["content-type"]="application/json; charset=utf-8"),e.writeHead(t.status,o),e.end(JSON.stringify(t.body))}u(re,"writeResult");function G(e,t,o){if(e.headersSent){e.end();return}e.writeHead(t,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}u(G,"writeJson");function U(e,t,o,s){G(e,t,{error:{code:o,message:s,origin:"runtime"}})}u(U,"writeError");export{Se as Router,q as actsAsPerson,b as appOf,ee as assertCeiling,l as brokerMayReach,C as canReadNode,X as canReadRun,xe as ownerOnly,te as ownerOnlyError,Be as requireRun,v as resolvedScopes,k as runCallerApp};
|
|
1
|
+
var j=Object.defineProperty;var u=(e,t)=>j(e,"name",{value:t,configurable:!0});import{toErrorBody as N}from"./map.js";import{emitEvent as U}from"../../core/events/emit.js";import{scopeAllowed as f,ScopeDeniedError as J}from"../../core/scopes.js";import{coveredByAny as p}from"../../shared/env.js";import{ApiError as c}from"../../api/errors.js";import{activeGrant as B,grantFor as S}from"../../core/grants/mirror.js";import{getNode as y}from"../../core/canvas/canvas.js";import{ownedRun as z,RunError as g}from"../../core/runs/operations.js";import{nodeMayReach as H,placeMayReach as R}from"../../core/runs/confinement.js";import{checkCronFire as M}from"../cron/authority.js";import{operationFor as I}from"./operations.js";import{CONTROL_BODY_MAX_BYTES as P}from"../../core/llm/body-limits.js";import{getRequest as F,getRequestByInboxTicket as L}from"../../core/human/requests.js";import{requireVisibleReview as q}from"../../core/review/store.js";import{getComment as O}from"../../core/review/comments.js";async function Y(e,t,o,s,r){const n=e.path,a=r&&typeof r=="object"&&!Array.isArray(r)?r:{};if(n==="/v1/human/requests"||n==="/v1/human/reviews"){const d=n.endsWith("/reviews")?a.origin_node_id:a.requester_node_id,h=n==="/v1/human/requests"&&d==null;if(d!==t.node&&!h)throw new c(403,"other_app_run","a broker may create a human request only for its own node");const i=a.source;if(i&&typeof i=="object"&&"nodeId"in i&&i.nodeId!==t.node)throw new c(403,"other_app_run","request source must name the calling node")}if(n==="/v1/human/reviews/self/comments"&&e.method==="GET"&&s.get("actor_node_id")!==t.node)throw new c(403,"other_app_run","self review requires the calling node");if(n.includes("/comments")&&e.method!=="GET"&&a.actor_node_id!==t.node)throw new c(403,"other_app_run","comment author must be the calling node");if(o.comment_id&&!await O(o.comment_id))throw new c(404,"not_found","comment not found")}u(Y,"authorizeHumanBody");function _(e){return e.class==="broker"||e.class==="cron"}u(_,"boundedByStoredScopes");function b(e){return e.class==="person"||e.class==="anonymous"||e.class==="machine"?null:e.class==="forwarded"?e.actor.kind==="app"?e.actor.app:null:e.app}u(b,"appOf");function $(e){return e.class==="person"||e.class==="forwarded"&&e.actor.kind==="person"}u($,"actsAsPerson");async function A(e){const t=await S(e.actor.app,e.sub);if(!t)throw new c(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);return t}u(A,"forwardedGrant");async function v(e){if(e.class==="person"||e.class==="anonymous"||e.class==="machine")return[];if(e.class==="forwarded"){if(e.actor.kind==="person")return[];const{resolved:o}=await A(e),s=e.actor.node_scopes;return s===null?o:s.filter(r=>p(o,r))}if(e.class==="app")return e.scopes;if(e.class==="cron"){const o=await M({grantee:e.app,sub:e.sub,grant_id:e.grant,scopes:e.scopes});if(o.allowed)return o.effectiveScopes;throw o.reason==="grant_missing"||o.reason==="grant_revoked"?new c(401,"grant_revoked",o.message):new c(403,"scope_missing",o.message)}const t=e.grant?await B(e.grant):await S(e.app,e.sub);if(t&&t.grantee===e.app&&t.sub===e.sub)return t.resolved;throw new c(401,"grant_revoked","the node grant is not active")}u(v,"resolvedScopes");function k(e){return"class"in e?b(e):e.grantee}u(k,"runCallerApp");async function l(e,t,o){return t===void 0||!("class"in e)?!0:e.class==="cron"?await R({run:e.origin_run??"",isolated:e.isolated},e.app,t,o):e.class==="forwarded"?e.actor.kind!=="app"||await R(e.actor.place,e.actor.app,t,o):e.class!=="broker"?!0:await H(e.node,e.app,t,o)}u(l,"brokerMayReach");async function D(e,t,o){const s=k(e);if(s===t)return await l(e,o,t);if(s===null)return!0;const r="class"in e?null:await B(e.grant),n="class"in e?await v(e):r?.sub===e.sub&&r.grantee===e.grantee?r.resolved:[];return f(n,`crtr:conversations:read:${t}`)&&(!("class"in e)||!_(e)||f(e.scopes,`crtr:conversations:read:${t}`))}u(D,"canReadRun");async function be(e,t,o=!1,s=!1){const r=await y(t);if(!r||r.parent!==null||r.run_id!==t||!r.grantee||r.deleting_at&&!s)throw new g(404,"not_found","run not found");if(!await D(e,r.grantee,t))throw new g(404,"not_found","run not found");if(o&&k(e)!==null&&k(e)!==r.grantee)throw new g(403,"other_app_run","cannot change another app's run");return await z(t,r.grantee,s)}u(be,"requireRun");async function C(e,t,o){return $(e)?!0:e.class==="anonymous"||e.class==="machine"?!1:b(e)===t?await l(e,o,t):f(await v(e),`crtr:objects:read:${t}`)&&(!_(e)||f(e.scopes,`crtr:objects:read:${t}`))}u(C,"canReadNode");const V=new Set(["messages.post.58","messages.post.59"]);async function X(e,t,o){if(!e.classes.includes(t.class)&&!(t.class!=="anonymous"&&t.class!=="machine"&&t.class!=="forwarded"&&e.classes.includes("anonymous")))throw new c(403,"permission_error","this operation is not available to the caller");if(t.class==="forwarded"&&t.actor.kind==="app"&&await A(t),t.class==="person"||t.class==="anonymous"||t.class==="machine"||$(t))return;const s=await v(t);if(e.scope&&(!f(s,e.scope)||_(t)&&!f(t.scopes,e.scope)))throw new c(403,"scope_missing",`missing scope ${e.scope}`);if(t.class==="app")return;const r=b(t);t.class==="broker"&&await K(e,t,o,s);const n=o.id;if((e.resource==="node-read"||e.resource==="node-write")&&n!==void 0){const a=await y(n);if(!a)return;if(e.resource==="node-write"&&a.grantee!==r&&!(V.has(e.name)&&f(s,`crtr:objects:message:${a.grantee??"app:terminal"}`)))throw new c(403,"other_app_run","cannot change another app's node");if(e.resource==="node-write"&&!await l(t,n,a.grantee??"app:terminal"))throw new c(404,"not_found","node not found");if(e.resource==="node-read"&&!await C(t,a.grantee??"app:terminal",n))throw new c(404,"not_found","node not found")}}u(X,"authorizeOperation");async function K(e,t,o,s){if(e.resource==="daemon-bash"&&(!f(s,"crtr:files:rw:user:.")||!f(t.scopes,"crtr:files:rw:user:.")))throw new c(403,"scope_missing","missing scope crtr:files:rw:user:.");if(e.resource==="human"){const n=e.path,a=e.method==="GET";if(!a&&(n.endsWith("/respond")||n.endsWith("/submit")||n.endsWith("/dismiss")||n.endsWith("/progress")||n.endsWith("/comment-ranges")||n.includes("/feedback-comments")))throw new c(403,"permission_error","only the person may answer or edit the human inbox");const d=o.request_id?await F(o.request_id):o.ticket_id?await L(o.ticket_id):null,h=o.comment_id?await O(o.comment_id):null,i=o.review_id?await q(o.review_id):h?await q(h.review_id):null,w=d?.requester_node_id??i?.origin_node_id;if(w!=null){const m=await y(w);if(m&&(a?!await C(t,m.grantee??"app:terminal",w):m.grantee!==t.app))throw new c(a?404:403,a?"not_found":"other_app_run","human request belongs to another app");if(m&&!a&&!await l(t,w,m.grantee??"app:terminal"))throw new c(404,"not_found","human request not found")}else if(d||i)throw new c(403,"permission_error","only the person may access a request without a node")}const r=o.id;if(e.resource==="node-own"&&r!==void 0&&r!==t.node)throw new c(403,"other_app_run","the operation requires the calling node")}u(K,"authorizeBrokerOnly");function Q(e,t){if(!f(e,t))throw new J("the bearer token",t)}u(Q,"assertCeiling");function Z(){return new c(403,"owner_only","this operation is owner-only; a scoped token does not reach it")}u(Z,"ownerOnlyError");function ve(e){return e.map(t=>({...t,ownerOnly:!0}))}u(ve,"ownerOnly");class ke{static{u(this,"Router")}routes=[];register(t){const o=I(t.method,t.pattern);if(!o)throw new Error(`route has no operation row: ${t.method} ${t.pattern}`);if(this.routes.some(s=>s.method===t.method.toUpperCase()&&s.operation.path===t.pattern))throw new Error(`duplicate route: ${t.method} ${t.pattern}`);return this.routes.push({operation:o,method:t.method.toUpperCase(),segments:T(t.pattern),handler:t.handler,maxBodyBytes:t.maxBodyBytes??P}),this}registerAll(t){for(const o of t)this.register(o);return this}match(t,o){const s=t.toUpperCase(),r=T(o);for(const n of this.routes){if(n.method!==s)continue;const a=ee(n.segments,r);if(a!==null)return{handler:n.handler,params:a,operation:n.operation,maxBodyBytes:n.maxBodyBytes}}return null}async handle(t,o,s,r={class:"person"}){try{const n=new URL(t.url??"/","http://127.0.0.1"),a=(t.method??"GET").toUpperCase(),d=this.match(a,n.pathname);if(d===null){W(o,404,"not_found",`no route for ${a} ${n.pathname}`);return}if(await X(d.operation,r,d.params),s!==null){if(d.operation.classes.length===1&&d.operation.classes[0]==="person")throw Z();d.operation.scope&&Q(s,d.operation.scope)}let h;try{h=await te(t,d.maxBodyBytes)}catch(m){const x=m instanceof G?"payload_too_large":"invalid_request";W(o,x==="payload_too_large"?413:400,x,m.message);return}r.class==="broker"&&d.operation.resource==="human"&&await Y(d.operation,r,d.params,n.searchParams,h);const i={method:a,path:n.pathname,params:d.params,query:n.searchParams,body:h,ceiling:s,principal:r,...r.class==="anonymous"&&typeof t.headers.authorization=="string"?{publishToken:t.headers.authorization.slice(7)}:{},req:t,res:o},w=await d.handler(i);o.writableEnded||oe(o,w)}catch(n){if(n instanceof Error&&n.code==="person_unresolved"){E(o,503,{error:{code:"runtime_starting",message:"grant sync is not ready",type:"server_error",origin:"runtime",retryable:!0}});return}const{status:a,body:d}=N(n);a>=500&&U({event:"api.request.failed",level:"error",error:n instanceof Error?n:new Error(String(n)),...r.class==="broker"?{node_id:r.node}:{},fields:{method:(t.method??"GET").toUpperCase(),path:t.url??""}}),E(o,a,d)}}}function T(e){return e.split("/").filter(t=>t.length>0)}u(T,"splitPath");function ee(e,t){if(e.length!==t.length)return null;const o={};for(let s=0;s<e.length;s++){const r=e[s],n=t[s];if(r.startsWith(":"))o[r.slice(1)]=decodeURIComponent(n);else if(r!==n)return null}return o}u(ee,"matchSegments");class G extends Error{static{u(this,"BodyTooLargeError")}}async function te(e,t){const o=[];let s=0;for await(const n of e){const a=n;if(s+=a.length,s>t)throw new G(`request body exceeds ${t} bytes`);o.push(a)}if(s===0)return;const r=Buffer.concat(o).toString("utf8").trim();if(r!=="")try{return JSON.parse(r)}catch{throw new Error("request body is not valid JSON")}}u(te,"readJsonBody");function oe(e,t){if(e.headersSent)return;const o={...t.headers??{}};if(t.rawBody!==void 0){e.writeHead(t.status,o),e.end(t.rawBody);return}if(t.status===204||t.body===void 0){e.writeHead(t.status,o),e.end();return}o["content-type"]===void 0&&(o["content-type"]="application/json; charset=utf-8"),e.writeHead(t.status,o),e.end(JSON.stringify(t.body))}u(oe,"writeResult");function E(e,t,o){if(e.headersSent){e.end();return}e.writeHead(t,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}u(E,"writeJson");function W(e,t,o,s){E(e,t,{error:{code:o,message:s,origin:"runtime"}})}u(W,"writeError");export{ke as Router,$ as actsAsPerson,b as appOf,Q as assertCeiling,l as brokerMayReach,C as canReadNode,D as canReadRun,ve as ownerOnly,Z as ownerOnlyError,be as requireRun,v as resolvedScopes,k as runCallerApp};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var _=Object.defineProperty;var o=(r,e)=>_(r,"name",{value:e,configurable:!0});import{envHomeOverride as m}from"../../shared/env.js";import{loadProfileManifest as h,lookupProfile as v}from"../../core/profiles/manifest.js";import{readProfileEnvVars as g}from"../../core/profiles/env-store.js";import{readAppEnvVars as R}from"../../core/profiles/app-env-store.js";import{ADMITTED_HOST_PATH_ENV as f,buildOperationalEnvBase as w}from"../../core/runtime/spawn-env.js";import{getNode as c}from"../../core/canvas/canvas.js";import{contextDir as y}from"../../core/canvas/paths.js";import{appendRunEvent as O}from"../../core/runs/events.js";import{payerOfGrant as b}from"../../core/grants/mirror.js";import{scopeAllowed as E}from"../../core/scopes.js";import{cronPrincipalOf as C}from"./authority.js";import{personId as N}from"../../core/canvas/person.js";function p(r){if(r.profile==null)return null;let e;try{e=v(r.profile)}catch{return null}if(e.kind==="resolved")return e.entry.profileId;if(e.kind==="absent")return null;throw new l(`the armed profile "${r.profile}" is still on this host but its manifest could not be read (${e.manifestPath}) \u2014 running as root would silently give this cron a different identity, env store, and purview. Repair the manifest, or re-arm the cron under a profile that resolves.`)}o(p,"resolveLiveProfile");function B(r){return{...C(r),cron_id:r.cron_id,origin_run_id:r.origin_run_id,isolated:r.isolated,creator:r.created_by}}o(B,"cronBirthPrincipalOf");async function V(r,e){const n=r.creator===null?null:await c(r.creator);return n!==null&&r.origin_run_id!==null&&n.run_id===r.origin_run_id&&(await c(r.origin_run_id))?.deleting_at==null&&n.grantee===r.grantee&&(n.sub??N())===r.sub&&(n.grant_id??null)===r.grant_id&&(r.scopes===null?n.scopes==null:r.scopes.every(t=>E(n.scopes,t)))?{parent:n.node_id,...r.scopes===null?{}:{scopes:[...r.scopes]}}:{root:!0,rootLifecycle:"terminal",...r.scopes===null?{}:{scopes:[...r.scopes]},run:{grantee:r.grantee,sub:r.sub,grant_id:r.grant_id,client_id:null,metadata_json:JSON.stringify({cron_id:r.cron_id}),run_id:e,event_cursor:1,usage_json:JSON.stringify({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0})},withinCreate:o(async(t,a)=>{await O(t,e,e,"run.started",{scopes:a.scopes??null,payer:await b(r.grant_id),usage:JSON.parse(a.usage_json)})},"withinCreate")}}o(V,"cronBirthPlacement");function F(r){try{return p(r)}catch{return null}}o(F,"resolveLiveProfileOrRoot");function G(r){if(r.profile!=null)try{return h(r.profile).manifest.name}catch{return}}o(G,"resolveLiveProfileName");class l extends Error{static{o(this,"ArmedContextError")}constructor(e){super(e),this.name="ArmedContextError"}}function x(r){if(r==null)return{};let e;try{e=JSON.parse(r)}catch(i){throw new l(`the armed env snapshot is not valid JSON (${i.message}) \u2014 this cron's stored env_json is corrupt, so the run would silently drop every armed variable. Re-arm the cron with a valid --env.`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new l("the armed env snapshot is not a JSON object \u2014 this cron's stored env_json is corrupt. Re-arm the cron with a valid --env.");const n={};for(const[i,t]of Object.entries(e)){if(typeof t!="string")throw new l(`the armed env snapshot's value for "${i}" is not a string \u2014 this cron's stored env_json is corrupt, so the run would silently drop that variable. Re-arm the cron with a valid --env.`);n[i]=t}return n}o(x,"parseArmedEnv");async function U(r,e){const n=x(r.env_json),i=r.grantee==="app:terminal",t=i?p(r):null,a=w({targetCwd:r.cwd,targetProfileId:t,skipSpawnAllow:!i}),s={...a,...R(r.grantee),...i?g(t):{},...n,...t!==null?{CRTR_PROFILE_ID:t}:{},CRTR_CRON_ID:r.cron_id,CRTR_CRON_NAME:r.name,CRTR_RUN_ID:e},u=m();if(u!==void 0&&u!==""&&(s.CRTR_HOME=u),r.anchor_node!=null){s.CRTR_ANCHOR=r.anchor_node;const d=await c(r.anchor_node);d!==null&&(s.CRTR_CONTEXT_DIR=y(d.node_id,d.grantee))}return s[f]=a[f],{profileId:t,env:s}}o(U,"buildArmedRunContext");export{l as ArmedContextError,U as buildArmedRunContext,V as cronBirthPlacement,B as cronBirthPrincipalOf,p as resolveLiveProfile,G as resolveLiveProfileName,F as resolveLiveProfileOrRoot};
|
|
@@ -19,8 +19,7 @@ export type CronAuthority =
|
|
|
19
19
|
};
|
|
20
20
|
/** May this principal fire now? The grant row must exist, be active and unexpired for this
|
|
21
21
|
* grantee and sub; every stored scope must still be covered by it; `crtr:schedule` must be
|
|
22
|
-
* in the effective set. A null `grant_id`
|
|
23
|
-
* no issuer. */
|
|
22
|
+
* in the effective set. A null `grant_id` is `grant_missing`. */
|
|
24
23
|
export declare function checkCronFire(p: CronPrincipal): Promise<CronAuthority>;
|
|
25
24
|
/** May this principal start a node? The fire check plus `crtr:act`. */
|
|
26
25
|
export declare function checkCronBirth(p: CronPrincipal): Promise<CronAuthority>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var a=(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{
|
|
1
|
+
var c=Object.defineProperty;var a=(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{isUnsandboxedMachine as l}from"../../core/runtime/unsandboxed.js";import{isOwnMemoryScope as f,scopeAllowed as s}from"../../core/scopes.js";function t(r,e){return{allowed:!1,reason:r,message:e}}a(t,"refuse");async function m(r){if(r.grantee!=="app:terminal"&&l())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)return t("grant_missing",`cron has no grant for ${r.grantee}; re-arm it from a node of that app`);{const n=await 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 o=g(n);if(o===null)return t("grant_revoked",`grant ${r.grant_id} cannot be read`);e=o.resolved}if(r.scopes!==null){const n=r.scopes.find(o=>!f(o,r.grantee)&&!e.some(i=>u(i,o)));if(n!==void 0)return t("scope_narrowed",`scope ${n} is no longer covered by the grant`)}return!s(e,"crtr:schedule")||!s(r.scopes,"crtr:schedule")?t("scope_missing","the cron no longer holds crtr:schedule"):{allowed:!0,effectiveScopes:e}}a(m,"checkCronFire");async function w(r){const e=await m(r);return e.allowed&&(!s(e.effectiveScopes,"crtr:act")||!s(r.scopes,"crtr:act"))?t("scope_missing","the cron no longer holds crtr:act"):e}a(w,"checkCronBirth");function b(r){return{grantee:r.grantee,sub:r.sub,grant_id:r.grant_id,scopes:r.scopes}}a(b,"cronPrincipalOf");export{w as checkCronBirth,m as checkCronFire,b as cronPrincipalOf};
|
package/dist/daemon/crtrd.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
var Ce=Object.defineProperty;var i=(n,s)=>Ce(n,"name",{value:s,configurable:!0});import{startPeerOutbox as Le}from"./peer/outbox.js";import{reportMachineStatus as $e}from"../core/peer/directory.js";import{createPeerRelay as Ae}from"./peer/relay-service.js";import{writeFileSync as Me,readFileSync as Ne,rmSync as Ge,mkdirSync as oe,existsSync as E,chmodSync as Oe}from"node:fs";import{createServer as ie}from"node:http";import{dirname as Fe}from"node:path";import{envCrtrdTcp as Be,envDocumentPort as He}from"../shared/env.js";import{serveCanvasConversationStore as je}from"../core/canvas/sessions.js";import{pollRunObservers as qe}from"./runs/observer.js";import{randomBytes as ae}from"node:crypto";import{apiSocketPath as ze,crtrHome as We}from"../core/canvas/paths.js";import{activeAppUsers as Ue}from"../core/spaces/users.js";import{ensureAppSpace as Ke,ensureUserFiles as Je}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as I}from"../launcher/spec.js";import{launcherRequest as D}from"../launcher/client.js";import{installLauncherEventSink as Ve}from"./launcher-events.js";import{initializeEmptyLayout as Qe,layoutNeedsMigration as Ye,socketDir as se}from"../core/layout.js";import{migrateLooseKeys as Xe}from"../core/layout-migrate/loose-keys.js";import{relocateProfileFolders as Ze}from"../core/profiles/relocate.js";import{getNode as er,listNodesForSupervision as ce,listParkedResidentsWithWakeMail as rr,setFrozen as de}from"../core/canvas/index.js";import{createApiServer as tr}from"./api/server.js";import{createDocumentListener as nr}from"./api/document-listener.js";import{documentSecret as or}from"./api/document-secret.js";import{createAppListener as ir}from"./api/app-listener.js";import{installLocalTerminalGrant as ar}from"../core/grants/local.js";import{onRunEventAppended as sr}from"../core/runs/events.js";import{runNotices as cr}from"./run-notices.js";import{createGrantSync as dr,GRANT_SYNC_INTERVAL_S as lr}from"../core/grants/sync.js";import{runRemovalCascades as pr}from"../core/grants/remove.js";import{sweepSandboxes as mr}from"../core/grants/on-sync.js";import{rebuildCronExecutions as fr}from"./cron/run-socket.js";import{hasSyncedGrants as ur}from"../core/grants/mirror.js";import{readConfig as le}from"../core/config.js";import{reconcileUndeliveredTickets as wr}from"./human/sweep.js";import{resetHandedOffDeliveries as hr}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as vr}from"../core/canvas/daemon-actions.js";import{sweepReviews as gr}from"./review/sweep.js";import{pidfilePath as z}from"./pidfile.js";import{rendererWarning as yr}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as Sr}from"./supervise-cadence.js";import{createResumeDetector as _r}from"./resume-detector.js";import{setFatalFaultAnnouncer as br}from"../core/canvas/recovery.js";import{BrokerSupervisionReconciler as kr}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as Er}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as Pr}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as Rr}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as xr}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as Ir}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as Tr}from"./reconcilers/action-lane.js";import{endLiveExecution as Dr,migrateLegacyExecutionIdentities as Cr}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as Lr}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as $r}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as Ar}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as Mr}from"./reconcilers/storage-due-work.js";import{BashDeadlineReconciler as Nr}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as Gr}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as Or}from"./reconcilers/structured-request-lane.js";import{reconcileStructuredRequestSidecars as Fr}from"./structured-requests/boot.js";import{captureLivenessSnapshot as pe,capturePidCommand as Br,captureTeardownSnapshot as me,isPidAlive as Hr,isAnyPidAlive as fe,killProcessTreePids as ue}from"../core/canvas/pid.js";import{bindFleet as jr,boundFleet as qr}from"../core/runtime/fleet.js";import{bindDaemonControl as zr,clearDaemonControl as Wr}from"./control.js";import{execDaemon as Ur,spawnDaemon as Kr}from"./manage.js";import{reconcileAndMigrateBoot as Jr}from"../core/canvas/boot.js";import{resolveInstallId as Vr}from"../core/canvas/install-id.js";import{setInstallId as Qr}from"../core/runtime/nodes.js";import{claimDaemonOwnership as we,daemonOwnershipClaimed as Yr,releaseDaemonOwnership as he}from"../core/canvas/daemon-owner.js";import{canvasDatabaseUrl as Xr,closeAllDbs as W,openDb as ve,withCanvasWrite as Zr}from"../core/canvas/db.js";import{importSqliteCanvasIfPresent as et}from"../core/canvas/import-sqlite.js";import{ensurePostgres as rt,ensurePostgresHealthy as tt,stopPostgres as nt,withPostgresStartLock as ot}from"./postgres/index.js";import{brokerSignalBus as U}from"./broker-signals/bus.js";import{brokerSignalStreams as it}from"./api/handlers/broker-signals.js";import{reconcilePackages as at}from"../core/graph/package-reconcile.js";import{startRepoSync as st}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as ct,reapStaleSpares as dt}from"../core/runtime/warm-pool.js";import{DaemonFleet as lt}from"./fleet.js";import{BOOT_MIGRATIONS as pt}from"./boot-migrations.js";import{applyJoinedOwner as mt}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 ft}from"../core/events/source.js";import{activateCanvasImport as ut,MigrationBlockedError as wt,runForcedCanvasImport as ht}from"../migrations/activation.js";import{convertLegacyProviderStore as vt,convertLegacyScopedStore as gt}from"../core/scoped-state/migrate.js";import{userScopeRoot as yt}from"../core/scope.js";import{secretsDir as St}from"../core/layout.js";function K(n,s){return n.includes(` --canvas-home ${s} `)}i(K,"brokerCommandDeclaresCanvasHome");function J(n){return/ --epoch (\S+) /.exec(n)?.[1]??null}i(J,"brokerCommandEpoch");const _t=5e3,ge=150;async function bt(n){const s=pe();if(s===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 l=We(),w=[];for(const[c,m]of s.processes){const v=m.command;if(m.zombie||!v.includes("broker-cli.js")||!K(v,l)||J(v)===n)continue;const k=v.trim().split(/\s+/).at(-1)??"";k===""||k.endsWith("broker-cli.js")||w.push({pid:c,nodeId:k})}let d=0;const t=[];for(const c of w){const m=Br(c.pid);if(m===null||!m.includes("broker-cli.js")||!K(m,l)||J(m)===n||m.trim().split(/\s+/).at(-1)!==c.nodeId)continue;const v=me(c.pid,null);if(!v.reused){try{process.platform==="linux"&&E(I)?await D({operation:"stop",pid:c.pid}):process.kill(c.pid,"SIGTERM")}catch{continue}d++,t.push({pid:c.pid,snap:v})}}const y=Date.now()+_t;for(;Date.now()<y&&t.some(c=>fe(c.snap.tree.length>0?c.snap.tree:[c.pid]));)await new Promise(c=>setTimeout(c,ge));let h=0;for(const c of t){const m=c.snap.tree.length>0?c.snap.tree:[c.pid];fe(m)&&(process.platform==="linux"&&E(I)?await D({operation:"stop",pid:c.pid}):ue(m,"SIGKILL",c.snap.identities),h++)}h>0&&await new Promise(c=>setTimeout(c,ge));const f=pe();if(f===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 a=[];for(const[c,m]of f.processes){const v=m.command;m.zombie||!v.includes("broker-cli.js")||K(v,l)&&J(v)!==n&&a.push(c)}if(a.length>0)throw new Error(`prior-epoch broker census could not prove termination of pid(s): ${a.join(", ")}`);u({level:"info",event:"daemon.census.completed",fields:{prior_epoch_brokers:w.length,sigtermed:d,sigkilled:h}})}i(bt,"reapPriorEpochBrokers");const kt=5e3;async function Et(n){const s=Array.from(n.entries());if(s.length===0)return;for(const[t]of s){const y=(await er(t))?.status;(y==="active"||y==="idle")&&await de(t,new Date().toISOString())}const l=s.map(([,t])=>({pid:t.pid,snap:me(t.pid,null),exited:t.handle.exited}));for(const t of l)if(!t.snap.reused)try{process.platform==="linux"&&E(I)?await D({operation:"stop",pid:t.pid}):process.kill(t.pid,"SIGTERM")}catch{}const w=new Promise(t=>{setTimeout(t,kt).unref()});await Promise.allSettled(l.map(t=>Promise.race([t.exited.then(()=>{}),w])));const d=[];for(const t of l)Hr(t.pid)&&(process.platform==="linux"&&E(I)?await D({operation:"stop",pid:t.pid}):ue(t.snap.tree.length>0?t.snap.tree:[t.pid],"SIGKILL",t.snap.identities),d.push(t));await Promise.all(d.map(t=>t.exited));for(const[t]of s)await Dr(t,{reason:"daemon-teardown"});u({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:l.length,sigkilled:d.length}})}i(Et,"teardownFleet");function ye(){oe(Fe(z()),{recursive:!0}),Me(z(),String(process.pid),"utf8")}i(ye,"writePidfile");function Se(n){return typeof n=="object"&&n!==null&&n.code==="ENOENT"}i(Se,"isMissingPathError");function _e(n){const s=z();let l;try{l=Ne(s,"utf8")}catch(w){if(Se(w))return;throw w}if(l.trim()!==String(n))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${n})`);try{Ge(s)}catch(w){if(Se(w))return;throw w}}i(_e,"removeOwnedPidfileMirror");const Pt={acceptsDetachedWork:i(()=>!0,"acceptsDetachedWork"),registerDetached:i(n=>{n.catch(()=>{})},"registerDetached")},O=new kr;br(n=>O.handleFatalFault(n,!1));const Rt=new Er,xt=new Pr,It=new Rr,Tt=new xr,Dt=new Ir,Ct=new Tr,Lt=new $r,$t=new Ar,At=new Mr,Mt=new Nr,Nt=new Gr,Gt=new Or;async function Ot(n=Date.now(),s=Pt){const l=qr(),w=await O.runFleetPreamble(l);let d,t;try{const h=await ce();d=h.filter(a=>a.status==="active"||a.status==="idle"),t=h.filter(a=>a.status==="done"||a.status==="canceled"||a.status==="dead");const f=new Set(t.map(a=>a.node_id));t.push(...(await rr()).filter(a=>!f.has(a.node_id)))}catch(h){u({level:"error",event:"canvas.node_list.failed",error:h});return}await O.run(n,{rows:d,fleet:l,lifecycle:s}),await Rt.run(n,{rows:d});const y=await xt.run(n,{rows:[...d,...t],fleet:l});await It.run(n),await Gt.run(n),await Tt.run(n,{lifecycle:s}),await Dt.run(n,{lifecycle:s}),await Ct.run(n,{lifecycle:s}),await Lt.run(n,{lifecycle:s}),await $t.run(n,{lifecycle:s,fleet:l}),At.run(n,{lifecycle:s}),await qe(),Nt.run(n,{fleet:l,lifecycle:s}),await Mt.run(n,{rows:d}),O.warnIfSaturated({fleet:l,lifecycle:s,thresholds:w,frozen:y})}i(Ot,"superviseTick");async function Ft(n){oe(se(),{recursive:!0,mode:493}),process.platform==="linux"&&Oe(se(),493);const s=ze();if(E(s))throw new Error(`layout refusal cannot bind occupied socket: ${s}`);const l=ie((f,a)=>{if(f.method==="GET"&&f.url==="/healthz"){a.writeHead(200,{"content-type":"application/json; charset=utf-8"}),a.end(JSON.stringify({ok:!1,daemon_up:!0,store_accessible:!1,brokers_reconciled:!1,startup_phase:"blocked",startup_blocked:"layout_migration_required"}));return}a.writeHead(503,{"content-type":"application/json; charset=utf-8"}),a.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((f,a)=>{l.once("error",a),l.listen(s,()=>{l.off("error",a),f()})});let w;const d=n.tcp??Be();if(d){const f=/^(.*):(\d+)$/.exec(d);if(!f||Number(f[2])>65535)throw new Error(`invalid refusal TCP address: ${d}`);const a=ie(l.listeners("request")[0]);await new Promise((c,m)=>{a.once("error",m),a.listen(Number(f[2]),f[1],()=>{a.off("error",m),c()})}),w=a}ye();let t;const y=i(()=>(t??=(async()=>{l.closeAllConnections(),w?.closeAllConnections(),await Promise.all([l,w].filter(f=>f!==void 0).map(f=>new Promise((a,c)=>f.close(m=>m?c(m):a())))),_e(process.pid),process.off("SIGINT",h),process.off("SIGTERM",h)})(),t),"cleanup"),h=i(()=>{y().then(()=>process.exit(0),f=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(f)}
|
|
2
|
-
`),process.exit(1)})},"onSignal");return process.on("SIGINT",h),process.on("SIGTERM",h),
|
|
3
|
-
`)},"logPostgres"),l=!!process.env.CRTR_TEST_PG_URL,{database:w,ownership:d}=l?{database:{url:await
|
|
4
|
-
`);else{const r=[d.incumbentPid!==void 0?`pid ${d.incumbentPid}`:void 0,d.verdict!==void 0?`verdict ${d.verdict}`:void 0].filter(
|
|
5
|
-
`),d.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"),(d.reason==="own_identity_indeterminate"||d.reason==="incumbent_malformed")&&(process.exitCode=1)}return async()=>{}}const t=d.claim;let
|
|
6
|
-
`);if(process.platform==="darwin"){const{readConfig:e,updateConfig:
|
|
7
|
-
`);await
|
|
1
|
+
var $e=Object.defineProperty;var a=(n,s)=>$e(n,"name",{value:s,configurable:!0});import{startPeerOutbox as Ae}from"./peer/outbox.js";import{reportMachineStatus as Me}from"../core/peer/directory.js";import{createPeerRelay as Ne}from"./peer/relay-service.js";import{writeFileSync as Fe,readFileSync as Ge,rmSync as Oe,mkdirSync as ae,existsSync as P,chmodSync as Be}from"node:fs";import{createServer as se}from"node:http";import{dirname as He}from"node:path";import{envCrtrdTcp as je,envDocumentPort as qe}from"../shared/env.js";import{serveCanvasConversationStore as ze}from"../core/canvas/sessions.js";import{pollRunObservers as We}from"./runs/observer.js";import{randomBytes as ce}from"node:crypto";import{apiSocketPath as Ue,crtrHome as Ke}from"../core/canvas/paths.js";import{activeAppUsers as Je}from"../core/spaces/users.js";import{ensureAppSpace as Ve,ensureUserFiles as Qe}from"../core/spaces/space.js";import{LAUNCHER_SOCKET as x}from"../launcher/spec.js";import{launcherRequest as C}from"../launcher/client.js";import{installLauncherEventSink as Ye}from"./launcher-events.js";import{initializeEmptyLayout as Xe,layoutNeedsMigration as Ze,socketDir as de}from"../core/layout.js";import{migrateLooseKeys as er}from"../core/layout-migrate/loose-keys.js";import{relocateProfileFolders as rr}from"../core/profiles/relocate.js";import{getNode as tr,listNodesForSupervision as le,listParkedResidentsWithWakeMail as nr,setFrozen as pe}from"../core/canvas/index.js";import{createApiServer as or}from"./api/server.js";import{createDocumentListener as ir}from"./api/document-listener.js";import{documentSecret as ar}from"./api/document-secret.js";import{createAppListener as sr}from"./api/app-listener.js";import{registerMachineEnvironment as cr}from"../core/environments.js";import{resolvePerson as dr,retireLocalPerson as lr,setPersonId as pr}from"../core/canvas/person.js";import{onRunEventAppended as mr}from"../core/runs/events.js";import{runNotices as fr}from"./run-notices.js";import{createGrantSync as ur,GRANT_SYNC_INTERVAL_S as wr}from"../core/grants/sync.js";import{runRemovalCascades as hr}from"../core/grants/remove.js";import{sweepSandboxes as vr}from"../core/grants/on-sync.js";import{rebuildCronExecutions as gr}from"./cron/run-socket.js";import{hasSyncedGrants as yr}from"../core/grants/mirror.js";import{readConfig as me}from"../core/config.js";import{reconcileUndeliveredTickets as Sr}from"./human/sweep.js";import{resetHandedOffDeliveries as _r}from"../core/canvas/deliveries.js";import{resetHandedOffDaemonActions as br}from"../core/canvas/daemon-actions.js";import{sweepReviews as kr}from"./review/sweep.js";import{pidfilePath as U}from"./pidfile.js";import{rendererWarning as Pr}from"../core/runtime/package-health.js";import{DEFAULT_INTERVAL_MS as Er}from"./supervise-cadence.js";import{createResumeDetector as Rr}from"./resume-detector.js";import{setFatalFaultAnnouncer as Ir}from"../core/canvas/recovery.js";import{BrokerSupervisionReconciler as xr}from"./reconcilers/broker-supervision.js";import{ControllerDeathReconciler as Dr}from"./reconcilers/controller-death.js";import{NodeLifecycleReconciler as Tr}from"./reconcilers/node-lifecycle/tick.js";import{NodeDeadlineReconciler as Cr}from"./reconcilers/node-deadline.js";import{CronLaneReconciler as Lr}from"./reconcilers/cron-lane.js";import{DeliveryLaneReconciler as $r}from"./reconcilers/delivery-lane.js";import{ActionLaneReconciler as Ar}from"./reconcilers/action-lane.js";import{endLiveExecution as Mr,migrateLegacyExecutionIdentities as Nr}from"../core/canvas/executions.js";import{revokePriorEpochNodeTokens as Fr}from"../core/canvas/node-tokens.js";import{PendingReviewSubmitReconciler as Gr}from"./reconcilers/pending-review-submit.js";import{StorageMaintenanceReconciler as Or}from"./reconcilers/storage-maintenance.js";import{StorageDueWorkReconciler as Br}from"./reconcilers/storage-due-work.js";import{BashDeadlineReconciler as Hr}from"./reconcilers/bash-deadline.js";import{FaultRetryBackstopReconciler as jr}from"./reconcilers/fault-retry-backstop.js";import{StructuredRequestReconciler as qr}from"./reconcilers/structured-request-lane.js";import{reconcileStructuredRequestSidecars as zr}from"./structured-requests/boot.js";import{captureLivenessSnapshot as fe,capturePidCommand as Wr,captureTeardownSnapshot as ue,isPidAlive as Ur,isAnyPidAlive as we,killProcessTreePids as he}from"../core/canvas/pid.js";import{bindFleet as Kr,boundFleet as Jr}from"../core/runtime/fleet.js";import{bindDaemonControl as Vr,clearDaemonControl as Qr}from"./control.js";import{execDaemon as Yr,spawnDaemon as Xr}from"./manage.js";import{reconcileAndMigrateBoot as Zr}from"../core/canvas/boot.js";import{resolveInstallId as et}from"../core/canvas/install-id.js";import{setInstallId as rt}from"../core/runtime/nodes.js";import{claimDaemonOwnership as ve,daemonOwnershipClaimed as tt,releaseDaemonOwnership as ge}from"../core/canvas/daemon-owner.js";import{canvasDatabaseUrl as nt,closeAllDbs as K,openDb as ye,withCanvasWrite as ot}from"../core/canvas/db.js";import{importSqliteCanvasIfPresent as it}from"../core/canvas/import-sqlite.js";import{ensurePostgres as at,ensurePostgresHealthy as st,stopPostgres as ct,withPostgresStartLock as dt}from"./postgres/index.js";import{brokerSignalBus as J}from"./broker-signals/bus.js";import{brokerSignalStreams as lt}from"./api/handlers/broker-signals.js";import{reconcilePackages as pt}from"../core/graph/package-reconcile.js";import{startRepoSync as mt}from"../core/graph/repo-sync/index.js";import{prewarmRecentRecipes as ft,reapStaleSpares as ut}from"../core/runtime/warm-pool.js";import{DaemonFleet as wt}from"./fleet.js";import{BOOT_MIGRATIONS as ht}from"./boot-migrations.js";import{emitEvent as m}from"../core/events/emit.js";import{operationIdContext as _}from"../core/events/operation-id.js";import{bindDaemonEventSource as vt}from"../core/events/source.js";import{activateCanvasImport as gt,MigrationBlockedError as yt,runForcedCanvasImport as St}from"../migrations/activation.js";import{convertLegacyProviderStore as _t,convertLegacyScopedStore as bt}from"../core/scoped-state/migrate.js";import{userScopeRoot as kt}from"../core/scope.js";import{secretsDir as Pt}from"../core/layout.js";function V(n,s){return n.includes(` --canvas-home ${s} `)}a(V,"brokerCommandDeclaresCanvasHome");function Q(n){return/ --epoch (\S+) /.exec(n)?.[1]??null}a(Q,"brokerCommandEpoch");const Et=5e3,Se=150;async function Rt(n){const s=fe();if(s===null)throw m({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 l=Ke(),w=[];for(const[c,f]of s.processes){const S=f.command;if(f.zombie||!S.includes("broker-cli.js")||!V(S,l)||Q(S)===n)continue;const k=S.trim().split(/\s+/).at(-1)??"";k===""||k.endsWith("broker-cli.js")||w.push({pid:c,nodeId:k})}let d=0;const t=[];for(const c of w){const f=Wr(c.pid);if(f===null||!f.includes("broker-cli.js")||!V(f,l)||Q(f)===n||f.trim().split(/\s+/).at(-1)!==c.nodeId)continue;const S=ue(c.pid,null);if(!S.reused){try{process.platform==="linux"&&P(x)?await C({operation:"stop",pid:c.pid}):process.kill(c.pid,"SIGTERM")}catch{continue}d++,t.push({pid:c.pid,snap:S})}}const g=Date.now()+Et;for(;Date.now()<g&&t.some(c=>we(c.snap.tree.length>0?c.snap.tree:[c.pid]));)await new Promise(c=>setTimeout(c,Se));let h=0;for(const c of t){const f=c.snap.tree.length>0?c.snap.tree:[c.pid];we(f)&&(process.platform==="linux"&&P(x)?await C({operation:"stop",pid:c.pid}):he(f,"SIGKILL",c.snap.identities),h++)}h>0&&await new Promise(c=>setTimeout(c,Se));const u=fe();if(u===null)throw m({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 i=[];for(const[c,f]of u.processes){const S=f.command;f.zombie||!S.includes("broker-cli.js")||V(S,l)&&Q(S)!==n&&i.push(c)}if(i.length>0)throw new Error(`prior-epoch broker census could not prove termination of pid(s): ${i.join(", ")}`);m({level:"info",event:"daemon.census.completed",fields:{prior_epoch_brokers:w.length,sigtermed:d,sigkilled:h}})}a(Rt,"reapPriorEpochBrokers");const It=5e3;async function xt(n){const s=Array.from(n.entries());if(s.length===0)return;for(const[t]of s){const g=(await tr(t))?.status;(g==="active"||g==="idle")&&await pe(t,new Date().toISOString())}const l=s.map(([,t])=>({pid:t.pid,snap:ue(t.pid,null),exited:t.handle.exited}));for(const t of l)if(!t.snap.reused)try{process.platform==="linux"&&P(x)?await C({operation:"stop",pid:t.pid}):process.kill(t.pid,"SIGTERM")}catch{}const w=new Promise(t=>{setTimeout(t,It).unref()});await Promise.allSettled(l.map(t=>Promise.race([t.exited.then(()=>{}),w])));const d=[];for(const t of l)Ur(t.pid)&&(process.platform==="linux"&&P(x)?await C({operation:"stop",pid:t.pid}):he(t.snap.tree.length>0?t.snap.tree:[t.pid],"SIGKILL",t.snap.identities),d.push(t));await Promise.all(d.map(t=>t.exited));for(const[t]of s)await Mr(t,{reason:"daemon-teardown"});m({level:"info",event:"daemon.fleet_teardown.completed",fields:{brokers:l.length,sigkilled:d.length}})}a(xt,"teardownFleet");function _e(){ae(He(U()),{recursive:!0}),Fe(U(),String(process.pid),"utf8")}a(_e,"writePidfile");function be(n){return typeof n=="object"&&n!==null&&n.code==="ENOENT"}a(be,"isMissingPathError");function ke(n){const s=U();let l;try{l=Ge(s,"utf8")}catch(w){if(be(w))return;throw w}if(l.trim()!==String(n))throw new Error(`daemon readiness mirror does not name this invocation (expected pid ${n})`);try{Oe(s)}catch(w){if(be(w))return;throw w}}a(ke,"removeOwnedPidfileMirror");const Dt={acceptsDetachedWork:a(()=>!0,"acceptsDetachedWork"),registerDetached:a(n=>{n.catch(()=>{})},"registerDetached")},B=new xr;Ir(n=>B.handleFatalFault(n,!1));const Tt=new Dr,Ct=new Tr,Lt=new Cr,$t=new Lr,At=new $r,Mt=new Ar,Nt=new Gr,Ft=new Or,Gt=new Br,Ot=new Hr,Bt=new jr,Ht=new qr;async function jt(n=Date.now(),s=Dt){const l=Jr(),w=await B.runFleetPreamble(l);let d,t;try{const h=await le();d=h.filter(i=>i.status==="active"||i.status==="idle"),t=h.filter(i=>i.status==="done"||i.status==="canceled"||i.status==="dead");const u=new Set(t.map(i=>i.node_id));t.push(...(await nr()).filter(i=>!u.has(i.node_id)))}catch(h){m({level:"error",event:"canvas.node_list.failed",error:h});return}await B.run(n,{rows:d,fleet:l,lifecycle:s}),await Tt.run(n,{rows:d});const g=await Ct.run(n,{rows:[...d,...t],fleet:l});await Lt.run(n),await Ht.run(n),await $t.run(n,{lifecycle:s}),await At.run(n,{lifecycle:s}),await Mt.run(n,{lifecycle:s}),await Nt.run(n,{lifecycle:s}),await Ft.run(n,{lifecycle:s,fleet:l}),Gt.run(n,{lifecycle:s}),await We(),Bt.run(n,{fleet:l,lifecycle:s}),await Ot.run(n,{rows:d}),B.warnIfSaturated({fleet:l,lifecycle:s,thresholds:w,frozen:g})}a(jt,"superviseTick");async function qt(n){ae(de(),{recursive:!0,mode:493}),process.platform==="linux"&&Be(de(),493);const s=Ue();if(P(s))throw new Error(`layout refusal cannot bind occupied socket: ${s}`);const l=se((u,i)=>{if(u.method==="GET"&&u.url==="/healthz"){i.writeHead(200,{"content-type":"application/json; charset=utf-8"}),i.end(JSON.stringify({ok:!1,daemon_up:!0,store_accessible:!1,brokers_reconciled:!1,startup_phase:"blocked",startup_blocked:"layout_migration_required"}));return}i.writeHead(503,{"content-type":"application/json; charset=utf-8"}),i.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((u,i)=>{l.once("error",i),l.listen(s,()=>{l.off("error",i),u()})});let w;const d=n.tcp??je();if(d){const u=/^(.*):(\d+)$/.exec(d);if(!u||Number(u[2])>65535)throw new Error(`invalid refusal TCP address: ${d}`);const i=se(l.listeners("request")[0]);await new Promise((c,f)=>{i.once("error",f),i.listen(Number(u[2]),u[1],()=>{i.off("error",f),c()})}),w=i}_e();let t;const g=a(()=>(t??=(async()=>{l.closeAllConnections(),w?.closeAllConnections(),await Promise.all([l,w].filter(u=>u!==void 0).map(u=>new Promise((i,c)=>u.close(f=>f?c(f):i())))),ke(process.pid),process.off("SIGINT",h),process.off("SIGTERM",h)})(),t),"cleanup"),h=a(()=>{g().then(()=>process.exit(0),u=>{process.stderr.write(`[crtrd] layout refusal cleanup failed: ${String(u)}
|
|
2
|
+
`),process.exit(1)})},"onSignal");return process.on("SIGINT",h),process.on("SIGTERM",h),g}a(qt,"serveLayoutRefusal");async function zt(n={}){if(Ze())return qt(n);Xe();const s=a(r=>{process.stderr.write(`[crtrd] ${r}
|
|
3
|
+
`)},"logPostgres"),l=!!process.env.CRTR_TEST_PG_URL,{database:w,ownership:d}=l?{database:{url:await nt()},ownership:await ve(ce(16).toString("hex"))}:await dt(async()=>({database:await at({log:s,startLockHeld:!0}),ownership:await ve(ce(16).toString("hex"))}));if(!d.claimed){if(d.reason==="incumbent_alive")process.stderr.write(`[crtrd] already running (pid ${d.incumbentPid??"?"})
|
|
4
|
+
`);else{const r=[d.incumbentPid!==void 0?`pid ${d.incumbentPid}`:void 0,d.verdict!==void 0?`verdict ${d.verdict}`:void 0].filter(y=>y!==void 0),e=r.length>0?` (${r.join(", ")})`:"";process.stderr.write(`[crtrd] claim lost: ${d.reason}${e}
|
|
5
|
+
`),d.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"),(d.reason==="own_identity_indeterminate"||d.reason==="incumbent_malformed")&&(process.exitCode=1)}return async()=>{}}const t=d.claim;let g=null,h=null,u="";try{await Rt(t.token),bt({kind:"user",root:kt()});for(const e of rr())process.stderr.write(`[crtrd] ${e}
|
|
6
|
+
`);if(process.platform==="darwin"){const{readConfig:e,updateConfig:y}=await import("../core/config.js");if(e("user").paths?.user_files===void 0){const{homedir:o}=await import("node:os");y("user",p=>{p.paths??={},p.paths.user_files??=o()})}}await _t(Pt());for(const e of er())process.stderr.write(`[crtrd] ${e}
|
|
7
|
+
`);await ye(),await it(w.url,s),await J.listen(w.url);const r=await ot(async e=>await pt(e));for(const e of r.conflicts)process.stderr.write(`[crtrd] plugin documents not reconciled: ${e.message}
|
|
8
8
|
`);for(const e of r.invalid)process.stderr.write(`[crtrd] package file is not a document: ${e.path}: ${e.error}
|
|
9
9
|
`);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"}
|
|
10
10
|
`);for(const e of r.kinds.refused)process.stderr.write(`[crtrd] plugin "${e.plugin}" kind ${e.kind} not registered: ${e.message}
|
|
11
11
|
`);for(const e of r.kinds.invalid)process.stderr.write(`[crtrd] plugin "${e.plugin}" declares invalid object kinds or scopes; no kinds registered and no app token: ${e.error}
|
|
12
|
-
`);
|
|
12
|
+
`);u=await et(),rt(u),g=await dr(),g!==null&&(pr(g.id),h=await lr()),process.platform==="linux"&&P(x)&&(Ye(),await C({operation:"users.sync",users:await Je()})),await Fr(t.token)}catch(r){await J.close();try{const{released:e}=await ge(t);if(!e)throw new Error("daemon ownership claim was not released");await K()}catch(e){throw new AggregateError([r,e],"daemon startup failed and cleanup also failed")}throw r}let i=!0,c,f=Promise.resolve(),S=!1,k,D;const N=new Set;let H=!1,Y=!1,E,F,j,X=!1,G,L,$,A,q;const Pe=Rr();let R,v={phase:"initializing",message:"daemon startup is running the canvas import and normal services are unavailable",blockerPaths:[]},T="initial startup activation",Ee=null;function Re(){return v.phase==="ready"?null:{phase:v.phase,message:v.message,blockerPaths:v.blockerPaths}}a(Re,"startupBlock");function b(r){N.add(r),r.finally(()=>{N.delete(r)}).catch(()=>{})}a(b,"registerDetached");const Z={acceptsDetachedWork:a(()=>i,"acceptsDetachedWork"),registerDetached:b};await mt(Z,ye);async function Ie(){try{i=!1,lt.close(),await J.close(),k!==void 0&&(clearInterval(k),k=void 0),$!==void 0&&(clearInterval($),$=void 0),q?.(),q=void 0;let r;do r=f,await r;while(r!==f);for(;N.size>0;)await Promise.allSettled([...N]);if(D!==void 0&&(await xt(D),await D.exitsSettled(),D.dispose()),j!==void 0&&await j.close(),F!==void 0&&!X&&(X=!0,await F.close()),A!==void 0){const e=A;A=void 0,await e.stop()}if(L!==void 0){const e=L;L=void 0,await e.close()}if(G!==void 0){const e=G;G=void 0,await e.close()}if(H||(ke(process.pid),H=!0),H&&!Y){const{released:e}=await ge(t);if(!e)throw new Error("daemon ownership claim was not released");Y=!0,await K()}}finally{Qr(),process.off("SIGINT",te),process.off("SIGTERM",ne),process.off("exit",oe)}}a(Ie,"cleanupAttempt");const O=1e3;let ee=!1;function xe(){return ee?{graceMs:O}:(ee=!0,_.fresh(()=>{m({level:"info",event:"daemon.handover.armed",fields:{pid:process.pid,grace_ms:O}})}),setTimeout(()=>{De()},O),{graceMs:O})}a(xe,"requestHandover");async function De(){try{await I()}catch(r){const e=r instanceof Error?r.stack??r.message:String(r);process.stderr.write(`[crtrd] handover cleanup failed:
|
|
13
13
|
${e}
|
|
14
|
-
`),process.exit(1)}try{process.platform==="linux"&&
|
|
15
|
-
`),process.exit(1)}process.exit(0)}
|
|
16
|
-
${
|
|
17
|
-
`),process.exit(1)}}
|
|
14
|
+
`),process.exit(1)}try{process.platform==="linux"&&P(x)&&(await C({operation:"daemon.handover"}),process.exit(0)),process.pid===1&&await Yr();const r=await Xr({waitForJoinLock:!0});_.fresh(()=>{m({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(()=>{m({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}
|
|
15
|
+
`),process.exit(1)}process.exit(0)}a(De,"performHandover");function I(){if(E!==void 0)return E;const r=Ie();return E=r,r.then(()=>{E===r&&(E=void 0)},()=>{E===r&&(E=void 0)}),r}a(I,"requestCleanup");async function re(r){try{await I(),await ct({claimed:a(async()=>{try{return await tt()}finally{await K()}},"claimed")}),process.exit(0)}catch(e){process.exitCode=1;const y=e instanceof Error?e.stack??`${e.name}: ${e.message}`:String(e);process.stderr.write(`[crtrd] ${r} cleanup failed:
|
|
16
|
+
${y}
|
|
17
|
+
`),process.exit(1)}}a(re,"handleSignal");function te(){re("SIGINT")}a(te,"onSigint");function ne(){re("SIGTERM")}a(ne,"onSigterm");function oe(){I().catch(()=>{})}a(oe,"onExit");async function z(r){i&&(f=f.then(r).then(()=>{},e=>{_.fresh(()=>{m({level:"error",event:"daemon.lane_job.failed",error:e})})}))}a(z,"enqueue");async function ie(){!i||S||(Pe.check()&&R&&(m({level:"info",event:"daemon.resume.detected"}),b(R().catch(()=>{}))),S=!0,await z(async()=>{if(S=!1,!!i)try{l||await st({log:s}),await jt(Date.now(),Z)}catch(r){_.fresh(()=>{m({level:"error",event:"daemon.tick.failed",error:r})})}}))}a(ie,"enqueueTick");let M=null;async function Te(){if(M!==null)throw{status:500,code:"admission_failed",message:`daemon admission previously failed: ${M instanceof Error?M.message:String(M)}`};if(v.phase==="ready")return{admitted:!1,already_admitted:!0,startup_phase:"ready"};if(v.phase!=="prepared"&&v.phase!=="recovering")throw{status:409,code:"not_prepared",message:v.message};const r=v.phase==="prepared";try{return await W(),v={phase:"ready",message:"",blockerPaths:[]},r?{admitted:!0,already_admitted:!1,startup_phase:"ready"}:{admitted:!1,already_admitted:!0,startup_phase:"ready"}}catch(e){throw M=e,{status:500,code:"admission_failed",message:`daemon admission failed: ${e instanceof Error?e.message:String(e)}`}}}a(Te,"admitStartup");async function Ce(r){if(T!==null)throw{status:409,code:"migration_in_progress",message:`the canvas import is already running (${T})`};T="repair";try{const e=await St({dryRun:r.dryRun});if(r.dryRun||e.pending||v.phase!=="blocked")return{result:e,resumed:!1};if(n.prepare)return v={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]},{result:e,resumed:!1};try{return await W(),v={phase:"ready",message:"",blockerPaths:[]},{result:e,resumed:!0}}catch(y){const o=y instanceof Error?y.message:String(y);return setTimeout(()=>{I().catch(()=>{})},1e3),{result:e,resumed:!1,resumeError:o}}}finally{T=null}}a(Ce,"repairMigrations");async function Le(){c=n.intervalMs??Er;const r=t.token;D=new wt({epoch:r,enqueue:z}),Kr(D),v={phase:"recovering",message:"daemon startup recovery is running and normal services are unavailable",blockerPaths:[]},_.fresh(()=>{m({level:"info",event:"daemon.started",fields:{pid:process.pid,interval_ms:c}})});const e=u;_.fresh(()=>{m({level:"info",event:"daemon.install_id.resolved",fields:{install_id:e}})});const y=await cr(e);if(_.fresh(()=>{m({level:"info",event:"daemon.environment.registered",fields:{environment_id:y.environmentId,created:y.created}})}),_.fresh(()=>{let o;try{o=Pr()}catch{return}o!==null&&m({level:"warn",event:"daemon.startup.warning",fields:{stage:"termrender",warning:o.slice(0,2048)}})}),await _.fresh(async()=>{await kr(),await Sr(),await _r(Date.now()),await br(Date.now())}),await _.fresh(async()=>{const{reconcile:o,migrated:p}=await Zr(Nr);o.changed&&m({level:"info",event:"daemon.boot_change.reconciled",fields:{cleared_count:o.cleared.length,same_boot_proven:o.sameBootProven}}),p.length>0&&m({level:"info",event:"daemon.pid_identity.migrated",fields:{migrated_count:p.length}})}),await _.fresh(async()=>{for(const o of await le())o.status!=="active"||o.pi_pid!=null||o.frozen_at!=null||o.respawn_not_before!=null||(await pe(o.node_id,new Date().toISOString()),m({level:"warn",event:"daemon.startup.launch_debt_recorded",node_id:o.node_id}))}),await _.fresh(async()=>{await zr();for(const o of ht){const p=o.apply();p.changed&&m({level:"info",event:"daemon.boot_migration.applied",fields:{migration:o.id,...p.summary}})}}),b(gr()),R){const o=a(()=>{b(R().catch(()=>{})),b(Me().then(p=>A?.observe(p)).then(()=>{},p=>{m({level:"warn",event:"daemon.peer.status_failed",error:p})}))},"refresh");b(hr().catch(()=>{})),b(vr().then(({completion:p})=>p).catch(p=>{m({level:"error",event:"daemon.grants.sweep_failed",error:p})})),o(),$=setInterval(o,wr*1e3),$.unref(),q=Ae({joined:!0,register:b})}await z(async()=>{await ut(),await ft()}),k=setInterval(()=>{ie().catch(o=>m({level:"error",event:"daemon.tick.failed",error:o}))},c),await ie()}a(Le,"completeStartup");function W(){return Ee??=Le().catch(r=>{throw v={phase:"initializing",message:"daemon startup recovery failed and normal services are unavailable",blockerPaths:[]},r})}a(W,"resumeStartup");try{process.on("SIGINT",te),process.on("SIGTERM",ne),process.on("exit",oe),vt(),g!==null&&m({level:"info",event:"daemon.person.resolved",fields:{person_id:g.id,source:g.source}}),h!==null&&m({level:"info",event:"daemon.person.retired",fields:{person_id:h.person,joined_from:h.joinedFrom,retired:h.retired,columns:h.columns}}),G=await ze();const r=t.token;Vr({epoch:r,requestHandover:xe,registerDetached:b,startupBlock:Re,admitStartup:Te,repairMigrations:Ce}),_e(),F=or({tcp:n.tcp}),await F.ready;const e=qe()??me("user").document_port,y=ir(e,ar());try{await y.ready,j=y}catch(p){m({level:"warn",event:"daemon.document_listener.bind_failed",fields:{port:e,error:p instanceof Error?p.message:String(p)}})}const o=me("user").runtime;o?.issuer&&(R=ur(o.issuer,b),o.unsandboxed===!0&&(A=Ne({syncGrants:R})),mr(p=>fr().appended(p))),o?.unsandboxed===!0&&o.listener!==void 0&&m({level:"warn",event:"daemon.app_listener.refused_unsandboxed",fields:{reason:"runtime.unsandboxed is set; runtime.listener is ignored"}}),o!==void 0&&o.listener!==void 0&&o.unsandboxed!==!0&&(L=await sr(o,async()=>await yr(o.issuer.replace(/\/$/,"")),R),await L.ready);try{await gt()}catch(p){if(T=null,p instanceof yt)return v={phase:"blocked",message:p.message,blockerPaths:p.blockerPaths},I;throw p}process.platform==="linux"&&P(x)&&(Qe(),await Ve("app:terminal"));try{n.prepare?v={phase:"prepared",message:"daemon storage is prepared and normal services are unavailable until admission",blockerPaths:[]}:(await W(),v={phase:"ready",message:"",blockerPaths:[]})}finally{T=null}return I}catch(r){try{await I()}catch(e){throw new AggregateError([r,e],"daemon startup failed and cleanup also failed")}throw r}}a(zt,"runDaemon");var Po=zt;export{V as brokerCommandDeclaresCanvasHome,Q as brokerCommandEpoch,Po as default,zt as runDaemon,jt as superviseTick,xt as teardownFleet};
|
|
@@ -16,6 +16,7 @@ export interface StorageMaintenanceContext {
|
|
|
16
16
|
export declare class StorageMaintenanceReconciler {
|
|
17
17
|
private lastSpareSweepAt;
|
|
18
18
|
private lastCreateRequestSweepAt;
|
|
19
|
+
private lastEnvironmentTouchAt;
|
|
19
20
|
private lastRunExpiryAt;
|
|
20
21
|
private lastRunEventPassAt;
|
|
21
22
|
private runEventCursor;
|
|
@@ -58,6 +59,8 @@ export declare class StorageMaintenanceReconciler {
|
|
|
58
59
|
private gcStaleFocuses;
|
|
59
60
|
/** Delete create idempotency keys once their bounded recovery window ends. */
|
|
60
61
|
private gcCreateRequests;
|
|
62
|
+
/** Stamp the machine environment as seen. A failed stamp is an event, never an error out of the tick. */
|
|
63
|
+
private touchEnvironment;
|
|
61
64
|
/** Collect dead or expired hidden warm-pool spares. */
|
|
62
65
|
private gcWarmPool;
|
|
63
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var I=Object.defineProperty;var l=(f,t)=>I(f,"name",{value:t,configurable:!0});import{rm as
|
|
1
|
+
var I=Object.defineProperty;var l=(f,t)=>I(f,"name",{value:t,configurable:!0});import{rm as y}from"node:fs/promises";import{closeFocusRow as A,getFocusById as S,listFocuses as N,listNodes as w}from"../../core/canvas/index.js";import{pruneExpiredIdempotencyKeys as R}from"../../core/runs/operations.js";import{getNode as g,lastActivityMs as u,listTerminalPlacementResidue as T,pruneExpiredCreateRequests as C,pruneNodes as F}from"../../core/canvas/canvas.js";import{lastContextStripMs as x,stripContextDir as P}from"../../core/canvas/context-strip.js";import{isAppRun as D}from"../../core/canvas/app-run.js";import{isSafeNodeId as c}from"../../core/canvas/paths.js";import{emitEvent as i}from"../../core/events/emit.js";import{operationIdContext as s}from"../../core/events/operation-id.js";import{listLivePanesAsync as G,tearDownNode as V}from"../../core/runtime/placement.js";import{reapStaleSpares as b}from"../../core/runtime/warm-pool.js";import{GroveInstanceSweepReconciler as M}from"./grove-instance-sweep.js";import{ManagedWorktreeSweepReconciler as Y}from"./managed-worktree-sweep.js";import{compactRunEvents as W,expireRunEvents as k}from"../../core/runs/events.js";import{openDb as q}from"../../core/canvas/db.js";import{touchMachineEnvironment as L}from"../../core/environments.js";import{retentionHold as _}from"./live-obligation.js";import{pruneMemoryReads as O}from"../../core/canvas/memory-reads.js";import{existsSync as z}from"node:fs";import{launcherRequest as U}from"../../launcher/client.js";import{LAUNCHER_SOCKET as H}from"../../launcher/spec.js";import{cachePassDue as K}from"../../launcher/limits/policy.js";const $=10*6e4,m=60*1e3,B=60*1e3,v=5*6e4,Q=14,X=1440*6e4,Z=2e3;class Ae{static{l(this,"StorageMaintenanceReconciler")}lastSpareSweepAt=Number.NEGATIVE_INFINITY;lastCreateRequestSweepAt=Number.NEGATIVE_INFINITY;lastEnvironmentTouchAt=Number.NEGATIVE_INFINITY;lastRunExpiryAt=Number.NEGATIVE_INFINITY;lastRunEventPassAt=Number.NEGATIVE_INFINITY;runEventCursor=0;lastAnalyzeAt=Number.NEGATIVE_INFINITY;lastRetentionSweepAt=Number.NEGATIVE_INFINITY;lastContextStripAt=Number.NEGATIVE_INFINITY;contextStripInFlight=!1;focusGcInFlight=!1;lastCachePassAt=null;cachePassInFlight=!1;worktreeSweep=new Y;groveInstanceSweep=new M;async run(t,r){if(await this.reapDeadResidue(t),this.scheduleFocusGc(r),await this.gcWarmPool(t),await this.touchEnvironment(t),await this.gcCreateRequests(t),t-this.lastRunEventPassAt>=Z){this.lastRunEventPassAt=t;try{await k(t);const e=await W(t,this.runEventCursor);this.runEventCursor=e.cursor}catch(e){console.warn(`run event maintenance failed: ${String(e)}`)}}if(t-this.lastRunExpiryAt>=60*6e4){try{await O(t)}catch(e){console.warn(`memory-read log prune failed: ${String(e)}`)}this.lastRunExpiryAt=t}t-this.lastAnalyzeAt>=1440*6e4&&(await(await q()).exec("ANALYZE"),this.lastAnalyzeAt=t),await this.sweepRetention(t,r),this.scheduleCachePass(t,r),this.scheduleContextStrip(t,r),this.worktreeSweep.run(t,r),this.groveInstanceSweep.run(t,r)}scheduleCachePass(t,r){this.cachePassInFlight||!K(this.lastCachePassAt,t)||process.platform!=="linux"||!z(H)||r.lifecycle.acceptsDetachedWork()&&(this.lastCachePassAt=t,this.cachePassInFlight=!0,r.lifecycle.registerDetached(U({operation:"cache.purge"}).then(()=>{}).catch(e=>{s.fresh(()=>{i({level:"warn",event:"app.cache.pass_failed",error:e})})}).then(()=>{this.cachePassInFlight=!1})))}msSinceTerminalActivity(t,r){const e=u(t);return e===0?Number.POSITIVE_INFINITY:r-e}async sweepRetention(t,r){t-this.lastRetentionSweepAt<v||r.lifecycle.acceptsDetachedWork()&&(this.lastRetentionSweepAt=t,await s.fresh(async()=>{try{const e=await F({ttlDays:Q,held:await _(r.fleet),removeDirs:l(a=>{r.lifecycle.registerDetached(Promise.all(a.map(o=>y(o,{recursive:!0,force:!0}))).then(()=>{}).catch(o=>{s.fresh(()=>{i({level:"error",event:"canvas.retention.failed",error:o})})}))},"removeDirs")});e.pruned.length>0&&i({level:"info",event:"canvas.retention.pruned",outcome:"succeeded",fields:{pruned_count:e.pruned.length}})}catch(e){i({level:"error",event:"canvas.retention.failed",error:e})}}))}scheduleContextStrip(t,r){if(this.contextStripInFlight||t-this.lastContextStripAt<v||!r.lifecycle.acceptsDetachedWork())return;this.lastContextStripAt=t,this.contextStripInFlight=!0;const e=this.stripFinishedContexts(t,r).finally(()=>{this.contextStripInFlight=!1});r.lifecycle.registerDetached(e)}async stripFinishedContexts(t,r){const e=await _(r.fleet),a=l(async n=>n!==null&&["done","canceled","dead"].includes(n.status)&&(n.pi_pid??null)===null&&(n.frozen_at??null)===null&&!D(n)&&u(n)<t-X&&!await e(n.node_id),"eligible");let o=0,p=0;try{for(const n of await w({status:["done","canceled","dead"]})){if(!c(n.node_id)||!await a(n))continue;const h=await x(n.node_id);if(h!==null&&h>=u(n)||!await a(await g(n.node_id)))continue;let d;try{d=await P(n.node_id)}catch(E){s.fresh(()=>{i({level:"warn",event:"canvas.context_strip.node_failed",error:E,fields:{node_id:n.node_id}})});continue}d.removed.length>0&&(o+=1,p+=d.bytes)}}catch(n){s.fresh(()=>{i({level:"error",event:"canvas.context_strip.failed",error:n})})}o>0&&s.fresh(()=>{i({level:"info",event:"canvas.context_strip.completed",outcome:"succeeded",fields:{node_count:o,bytes:p}})})}async reapDeadResidue(t){let r;try{r=await T()}catch(e){s.fresh(()=>{i({level:"error",event:"residue.reap_scan.failed",error:e})});return}for(const e of r){try{if(this.msSinceTerminalActivity(e,t)<$)continue}catch(a){s.fresh(()=>{i({level:"error",event:"residue.reap.failed",...c(e.node_id)?{node_id:e.node_id}:{},error:a,fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}})});continue}await s.fresh(async()=>{try{i({level:"info",event:"residue.reaped",...c(e.node_id)?{node_id:e.node_id}:{},fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}}),await V(e.node_id)}catch(a){i({level:"error",event:"residue.reap.failed",...c(e.node_id)?{node_id:e.node_id}:{},error:a,fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}})}})}}scheduleFocusGc(t){if(this.focusGcInFlight||!t.lifecycle.acceptsDetachedWork())return;this.focusGcInFlight=!0;const r=this.gcStaleFocuses().finally(()=>{this.focusGcInFlight=!1});t.lifecycle.registerDetached(r)}async gcStaleFocuses(){let t;try{t=await N()}catch(e){s.fresh(()=>{i({level:"error",event:"focus.gc.failed",error:e})});return}if(t.length===0)return;let r;try{r=await G()}catch(e){s.fresh(()=>{i({level:"error",event:"focus.gc.failed",error:e})});return}if(r!==null)for(const e of t){if(e.pane===null||r.has(e.pane))continue;if(!await s.fresh(async()=>{try{return(await S(e.focus_id))?.pane!==e.pane||(i({level:"info",event:"focus.gc.removed",...c(e.node_id)?{node_id:e.node_id}:{},fields:{focus_id:e.focus_id,focus_node_id:e.node_id,pane:e.pane}}),await A(e.focus_id)),!0}catch(o){return i({level:"error",event:"focus.gc.failed",error:o}),!1}}))return}}async gcCreateRequests(t){t-this.lastCreateRequestSweepAt<B||(this.lastCreateRequestSweepAt=t,await s.fresh(async()=>{const r=await C(t),e=await R(t);e>0&&i({level:"info",event:"canvas.idempotency_keys.reaped",outcome:"succeeded",fields:{reaped_count:e}}),r>0&&i({level:"info",event:"canvas.create_requests.reaped",outcome:"succeeded",fields:{reaped_count:r}})}))}async touchEnvironment(t){t-this.lastEnvironmentTouchAt<m||(this.lastEnvironmentTouchAt=t,await s.fresh(async()=>{try{await L(new Date(t).toISOString())}catch(r){i({level:"warn",event:"environment.touch.failed",error:r})}}))}async gcWarmPool(t){t-this.lastSpareSweepAt<m||(this.lastSpareSweepAt=t,await s.fresh(async()=>{const r=await b();r.length>0&&i({level:"info",event:"warm_pool.spares_reaped",outcome:"succeeded",fields:{reaped_count:r.length}})}))}}export{X as CONTEXT_STRIP_GRACE_MS,$ as DEAD_REAP_GRACE_MS,v as RETENTION_SWEEP_INTERVAL_MS,Q as RETENTION_TTL_DAYS,Ae as StorageMaintenanceReconciler};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var x=Object.defineProperty;var d=(n,r)=>x(n,"name",{value:r,configurable:!0});import{put as N}from"../../core/graph/bodies.js";import{importDocument as O}from"../../core/graph/documents.js";import{linksIn as _}from"../../core/graph/documents.js";import{recordActedOn as M,setLinks as R}from"../../core/graph/edges.js";import{resolveName as $}from"../../core/graph/names.js";import{ensureOwner as y,nodeObject as k,ownerForGrantee as b}from"../../core/graph/objects.js";import{ensureRepo as S}from"../../core/graph/repo-sync/repos.js";import{recordWatch as T}from"../../core/graph/watches.js";import{mapAsync as A}from"../../core/async-array.js";const v="canvas-import",
|
|
1
|
+
var x=Object.defineProperty;var d=(n,r)=>x(n,"name",{value:r,configurable:!0});import{put as N}from"../../core/graph/bodies.js";import{importDocument as O}from"../../core/graph/documents.js";import{linksIn as _}from"../../core/graph/documents.js";import{recordActedOn as M,setLinks as R}from"../../core/graph/edges.js";import{resolveName as $}from"../../core/graph/names.js";import{ensureOwner as y,nodeObject as k,ownerForGrantee as b}from"../../core/graph/objects.js";import{ensureRepo as S}from"../../core/graph/repo-sync/repos.js";import{recordWatch as T}from"../../core/graph/watches.js";import{mapAsync as A}from"../../core/async-array.js";import{personId as K}from"../../core/canvas/person.js";const v="canvas-import",P="004-canvas-documents";async function Y(n){const r=await n.prepareUncached("SELECT value FROM canvas_meta WHERE key = ?").get(v);if(r===void 0)return null;const p=JSON.parse(r.value).report;return{...p,resolutions:p.resolutions??[]}}d(Y,"readImportRecord");function q(n){const r=new Map;for(const p of n.documents)for(const i of p.revisions)r.has(i.body)||r.set(i.body,N(i.body));return r}d(q,"storePlanBodies");async function z(n,r,p){const i=new Map(r.owners.map(e=>[e.key,e])),u=new Map,w=d(async e=>{const t=u.get(e.key);if(t)return t;let o;switch(e.type){case"person":o=await b(n,K());break;case"app":o=await y(n,"app","app",`app:${e.handle}`,e.handle);break;case"profile":{const c=i.get(e.ownerKey);if(!c)throw new Error(`import: profile ${e.key} names unplanned app ${e.ownerKey}`);o=await y(n,"profile","profile",e.key.slice(8),e.handle,await w(c));break}case"repo":o=await S(n,{scheme:e.scheme,externalId:e.externalId,handleHint:e.handle},e.handle);break;case"node":o=await k(n,e.handle);break}if(e.type!=="node"&&o.name!==e.handle)throw new Error(`import: owner ${e.key} holds handle ${o.name}, planned ${e.handle}`);return u.set(e.key,o),o},"ownerRow"),I={class:"person",person:await w(i.get("person")??{key:"person",type:"person",handle:"user"})},f=new Map,m=d(async e=>{let t=f.get(e);return t||f.set(e,t=await k(n,e)),t},"node"),h=[];for(const e of r.documents){const t=await w(i.get(e.ownerKey)),o=await O(n,{owner:t,name:e.name,kind:e.kind,preview:e.preview,privacy:e.privacy,summary:e.summary,whyItExists:e.whyItExists,unlisted:e.unlisted,rules:e.rules,lintIgnore:e.lintIgnore,extensions:e.extensions,revisions:await A(e.revisions,async a=>({body_hash:p.get(a.body),author:a.authorNodeId===null?null:await m(a.authorNodeId),rationale:a.rationale,created_at:a.createdAt}))}),c=new Set(e.revisions.flatMap(a=>a.authorNodeId===null?[]:[a.authorNodeId]));h.push({doc:o,owner:t,body:e.revisions[e.revisions.length-1].body,authors:c})}for(const{doc:e,owner:t,body:o,authors:c}of h){const a=[];for(const s of _(o)){const g=s.includes("/")?[s]:[`${t.type==="node"?t.id:t.name}/${s}`,s];for(const E of g){const l=await $(n,I,E);if("object"in l){a.push({written_as:s,dst:l.object});break}if("deleted"in l){a.push({written_as:s,dst:l.deleted});break}}}await R(n,e,a);for(const s of c)await M(n,await m(s),e),await T(n,await m(s),e,"write")}await n.prepareUncached("INSERT INTO canvas_meta (key, value) VALUES (?, ?)").run(v,JSON.stringify({id:P,report:r.report}))}d(z,"applyPlan");export{v as IMPORT_DONE_KEY,P as IMPORT_ID,z as applyPlan,Y as readImportRecord,q as storePlanBodies};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var R=Object.defineProperty;var p=(e,n)=>R(e,"name",{value:n,configurable:!0});import{existsSync as
|
|
2
|
-
LEFT JOIN identities i ON i.object = o.id WHERE o.type IN ('person','app','repo','profile')`).all();return
|
|
1
|
+
var R=Object.defineProperty;var p=(e,n)=>R(e,"name",{value:n,configurable:!0});import{existsSync as f}from"node:fs";import{join as l}from"node:path";import{openDb as m,withCanvasWrite as g}from"../../core/canvas/db.js";import{nodeAgentDir as C,nodeRecordDir as S,onDiskMigrationMarkerPath as k}from"../../core/canvas/paths.js";import{identifyCheckout as E}from"../../core/graph/repo-sync/identity.js";import{profileSpace as L,userRoot as j}from"../../core/layout.js";import{commitConvertedProjectCaps as I,listLegacyProjectCaps as P,listStoredProfiles as _}from"../../core/scoped-state/profiles.js";import{userStore as b}from"../../core/scoped-state/paths.js";import{userScopeRoot as y}from"../../core/scope.js";import{LEGACY_LOCAL_PERSON as O}from"../../core/canvas/legacy-person.js";import{applyPlan as x,readImportRecord as w,storePlanBodies as M}from"./apply.js";import{readLegacyCorpus as v,readMarkerIds as D}from"./legacy/discover.js";import{planImport as A}from"./plan.js";const F=p(()=>({stores:[],counts:{},rewrites:[],droppedFields:[],skipped:[],resolutions:[]}),"emptyReport");async function N(){const e=y(),n=_(e),t=P(e),c=b(e).path,s=n.map(({profileId:r,manifest:a})=>{const i=a.grantee??"app:terminal",d=new Map(t.filter(o=>o.profileId===r).map(o=>[o.path,o.memory]));return{id:r,name:a.name,app:i,memory:L(i,r).dir,projects:a.projects.map(o=>o.path),raw:{projects:a.projects.map(o=>d.has(o.path)?{path:o.path,memory:d.get(o.path)}:{path:o.path})},manifestPath:`${c}#${r}`}}),u=await(await m()).prepareUncached("SELECT node_id, cwd, grantee FROM node_rows ORDER BY created").all(),h=u.map(r=>{const a=r.grantee===O?"app:terminal":r.grantee,i=a?.startsWith("app:")?C(r.node_id,a):null,d=i!==null&&(f(l(i,"context"))||f(l(i,"reports")))?i:S(r.node_id);return{id:r.node_id,dir:d}});return{roots:{userMemory:l(j(),"memory"),profiles:s,nodeCwds:[...new Set(u.map(r=>r.cwd))],nodes:h},stored:n}}p(N,"legacyRoots");async function T(){const{roots:e,stored:n}=await N(),t=v(e),c=await(await m()).prepareUncached(`SELECT o.type, o.name AS handle, i.external_id AS externalId FROM objects o
|
|
2
|
+
LEFT JOIN identities i ON i.object = o.id WHERE o.type IN ('person','app','repo','profile')`).all();return A(t,{roots:e,markerIds:D(k()),repoIdentity:E,existingOwners:c,storedProfiles:n.map(s=>({id:s.profileId,deliveryLimits:{...s.manifest.delivery_limits??{}}}))})}p(T,"plan");async function Z(){return await w(await m())!==null}p(Z,"canvasImportDone");async function ee(e){const n=await w(await m());if(n!==null)return{report:e.dryRun?n:F(),blockers:[],dryRun:e.dryRun,pending:!1};const t=await T();if(e.dryRun||t.blockers.length>0)return{report:t.report,blockers:t.blockers,dryRun:e.dryRun,pending:!0};const c=M(t);return I(y(),t.profileLimits),await g(async s=>await x(s,t,c)),{report:t.report,blockers:[],dryRun:!1,pending:!1}}p(ee,"runCanvasImport");export{Z as canvasImportDone,ee as runCanvasImport};
|
package/dist/types.d.ts
CHANGED
|
@@ -553,6 +553,8 @@ export interface ProfileManifest {
|
|
|
553
553
|
delivery_limits?: DeliveryLimits;
|
|
554
554
|
/** App that owns this profile; older terminal profiles have no grantee. */
|
|
555
555
|
grantee?: string;
|
|
556
|
+
/** The environment the profile's nodes run in; absent means the install's machine environment. */
|
|
557
|
+
environment_id?: string;
|
|
556
558
|
created_at: string;
|
|
557
559
|
last_used_at: string | null;
|
|
558
560
|
}
|
|
@@ -23,7 +23,7 @@ Then join your laptop to the account:
|
|
|
23
23
|
|
|
24
24
|
Things to know before you join:
|
|
25
25
|
|
|
26
|
-
- **Joining rewrites your crtr home.**
|
|
26
|
+
- **Joining rewrites your crtr home.** The person this install minted becomes your Northlight user, the next time the daemon starts: your nodes, crons, usage and history in this home are then yours on the account. If you would rather not do that to your everyday home, join from a home you can throw away.
|
|
27
27
|
- **There is no unjoin.** The only undo is removing the machine on `/account/machines`.
|
|
28
28
|
- **Removing the machine stops it.** Once you choose **Remove** next to a machine, it gets no new tokens at once and the ones it holds expire within 5 minutes. After that its `cloud` commands answer `machine_revoked`: "This machine was removed from your Northlight account." It stays that way until you create a new join token and run `crtr sys join` again. Files on the computer stay on it.
|
|
29
29
|
- **Your hosted machine is unchanged.** `/account/machines` lists it as "Northlight (hosted)", the default. Your chat agent at `https://chat.northlight.ai` still runs `crtr cloud apps …` as before.
|
|
@@ -7,6 +7,14 @@ 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.539
|
|
11
|
+
|
|
12
|
+
A run started with no `kind` and no `model`, in a profile with no `default_kind` (every app's own default profile), now starts as the `general` kind on the `medium` rung. It used to start as `developer` on that kind's model (`openai/medium`). `profile.list` reports `general` as such a profile's `default_kind`. On an app-paid grant the run lands on the app's `medium` inference rung, where it used to land on `strong`. Pass `model` on `run.start` to keep another rung.
|
|
13
|
+
|
|
14
|
+
Profiles name the machine environment they run on. Additive: no request or response an existing app sends or reads changes.
|
|
15
|
+
|
|
16
|
+
- Profile reads (`GET /v1/profiles`, `GET /v1/profiles/:name`) carry an optional `environment_id`: the profile's pinned environment, or this machine's environment when none is pinned. A runtime that predates the field omits it.
|
|
17
|
+
|
|
10
18
|
## 0.3.534
|
|
11
19
|
|
|
12
20
|
A cron armed by a caller with no scope limit follows the grant, and a narrowed scope pauses it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.539",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
},
|
|
141
141
|
"license": "UNLICENSED",
|
|
142
142
|
"dependencies": {
|
|
143
|
-
"@north-light/identity": "0.3.
|
|
143
|
+
"@north-light/identity": "0.3.539",
|
|
144
144
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
145
145
|
"@earendil-works/pi-ai": "1.1.0",
|
|
146
146
|
"@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.539",
|
|
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.539",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"workspaces": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@milkdown/preset-gfm": "^7.22.3",
|
|
25
25
|
"@milkdown/prose": "^7.22.3",
|
|
26
26
|
"@milkdown/utils": "^7.22.3",
|
|
27
|
-
"@north-light/identity": "0.3.
|
|
27
|
+
"@north-light/identity": "0.3.539",
|
|
28
28
|
"cron-parser": "^5.6.0",
|
|
29
29
|
"esbuild": "^0.27.7",
|
|
30
30
|
"jose": "^6.2.12",
|
|
@@ -12455,17 +12455,17 @@
|
|
|
12455
12455
|
},
|
|
12456
12456
|
"packages/crouter-api": {
|
|
12457
12457
|
"name": "@north-light/api",
|
|
12458
|
-
"version": "0.3.
|
|
12458
|
+
"version": "0.3.539",
|
|
12459
12459
|
"license": "UNLICENSED"
|
|
12460
12460
|
},
|
|
12461
12461
|
"packages/crouter-env-docker": {
|
|
12462
12462
|
"name": "@north-light/env-docker",
|
|
12463
|
-
"version": "0.3.
|
|
12463
|
+
"version": "0.3.539",
|
|
12464
12464
|
"license": "UNLICENSED"
|
|
12465
12465
|
},
|
|
12466
12466
|
"packages/crouter-identity": {
|
|
12467
12467
|
"name": "@north-light/identity",
|
|
12468
|
-
"version": "0.3.
|
|
12468
|
+
"version": "0.3.539",
|
|
12469
12469
|
"license": "UNLICENSED",
|
|
12470
12470
|
"dependencies": {
|
|
12471
12471
|
"jose": "^6.2.1"
|
|
@@ -12473,20 +12473,20 @@
|
|
|
12473
12473
|
},
|
|
12474
12474
|
"packages/crouter-plugin": {
|
|
12475
12475
|
"name": "@north-light/plugin",
|
|
12476
|
-
"version": "0.3.
|
|
12476
|
+
"version": "0.3.539",
|
|
12477
12477
|
"license": "UNLICENSED",
|
|
12478
12478
|
"dependencies": {
|
|
12479
|
-
"@north-light/api": "0.3.
|
|
12480
|
-
"@north-light/identity": "0.3.
|
|
12479
|
+
"@north-light/api": "0.3.539",
|
|
12480
|
+
"@north-light/identity": "0.3.539"
|
|
12481
12481
|
}
|
|
12482
12482
|
},
|
|
12483
12483
|
"packages/crouter-sdk": {
|
|
12484
12484
|
"name": "@north-light/sdk",
|
|
12485
|
-
"version": "0.3.
|
|
12485
|
+
"version": "0.3.539",
|
|
12486
12486
|
"license": "UNLICENSED",
|
|
12487
12487
|
"dependencies": {
|
|
12488
|
-
"@north-light/api": "0.3.
|
|
12489
|
-
"@north-light/identity": "0.3.
|
|
12488
|
+
"@north-light/api": "0.3.539",
|
|
12489
|
+
"@north-light/identity": "0.3.539",
|
|
12490
12490
|
"jose": "^6.2.1"
|
|
12491
12491
|
},
|
|
12492
12492
|
"bin": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var i=Object.defineProperty;var a=(r,e)=>i(r,"name",{value:e,configurable:!0});import{withCanvasWrite as p}from"../core/canvas/db.js";import{rewriteLocalNodeIdentity as l}from"../core/canvas/canvas.js";import{rewriteLocalNodeTokenIdentity as m}from"../core/canvas/node-tokens.js";import{joinedOwner as w}from"../core/directory/join.js";import{LOCAL_TERMINAL_GRANT_ID as t}from"../core/grants/local.js";import{dropLocalGrantMirror as c}from"../core/grants/mirror.js";import{rewriteLocalRunEventGrantee as u}from"../core/runs/events.js";import{rewriteLocalIdempotencyGrantee as E}from"../core/runs/operations.js";const o="joined_owner_applied";async function d(){const r=w();return r===null?null:await p(async e=>{if((await e.prepare("SELECT value FROM canvas_meta WHERE key = ?").get(o))?.value===r)return null;const n=await l(e,r,t)+await m(e,r,t)+await c(e)+await u(e)+await E(e);return await e.prepare("INSERT INTO canvas_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(o,r),{owner:r,rows:n}})}a(d,"applyJoinedOwner");export{d as applyJoinedOwner};
|