@north-light/cli 0.3.496 → 0.3.498
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtin-memory/crouter-sdk/release-notes.md +2 -1
- package/dist/clients/attach/viewer.js +9 -9
- package/dist/commands/cron.js +5 -5
- package/dist/core/canvas/migrations.js +61 -55
- package/dist/core/graph/documents.js +5 -5
- package/dist/core/graph/kinds/document.js +4 -4
- package/dist/core/graph/names.js +3 -3
- package/dist/core/graph/objects.d.ts +2 -0
- package/dist/core/graph/objects.js +8 -8
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/graph/repo-sync/repos.js +3 -3
- package/dist/core/peer/stubs.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/docs.js +1 -1
- package/dist/daemon/api/reader.js +1 -1
- package/dist/daemon/cron/ended.d.ts +14 -0
- package/dist/daemon/cron/ended.js +2 -0
- package/dist/daemon/cron/passes.js +1 -1
- package/dist/launcher/limits/manager.js +1 -1
- package/dist/launcher/spec.d.ts +2 -2
- package/docs/sdk/release-notes.md +2 -1
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var ne=Object.defineProperty;var s=(t,n)=>ne(t,"name",{value:n,configurable:!0});import{prepared as c}from"../sqlite-statements.js";import{randomUUID as re}from"node:crypto";import{CrtrError as O,notFound as R,usage as m}from"../errors.js";import{ExitCode as D}from"../../types.js";import{canChange as k,canRead as J,missingMemoryScope as oe,missingMemoryScopeUnder as ie,ownerChain as se}from"./access.js";import{bodyOf as ae,get as ue,put as P}from"./bodies.js";import{recordActedOn as N,setLinks as ce}from"./edges.js";import{publish as x}from"./events.js";import{resolveName as le}from"./names.js";import{createObject as me,getById as we,getByRowid as d}from"./objects.js";import{replaceRules as B,setRulesOwner as pe}from"./rules.js";import{recordWatch as $}from"./watches.js";function y(t){return t.class==="node"?t.node:t.class==="app"?t.app:t.class==="object"?t.object:t.person}s(y,"actorOf");let de=0;function E(t,n){const e=`graph_doc_${++de}`;t.exec(`SAVEPOINT ${e}`);try{const r=n();return t.exec(`RELEASE ${e}`),r}catch(r){throw t.exec(`ROLLBACK TO ${e}`),t.exec(`RELEASE ${e}`),r}}s(E,"atomic");function fe(t,n){return new O("conflict",t,D.GENERAL,n)}s(fe,"conflict");function _(t){if(t.type!=="document")throw m(`${t.type} ${t.id} is not a document`)}s(_,"assertDocument");function K(t,n){const e=se(t,n);return(e.length?e[e.length-1]:n).type}s(K,"spaceType");function I(t,n,e){if(e!=="public"&&K(t,n)==="repo")throw m(`a repo owns only public documents; ${n.name} cannot own a ${e} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(I,"assertPublicInRepo");function H(t,n,e,r){if(e.trim()===""||e!==e.trim())throw m(`invalid document name ${JSON.stringify(e)}`);const o=c(t,"SELECT id FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL").get(n.rowid,e);if(o&&o.id!==r?.id)throw fe(`${n.name} already has a document named ${e}`,{name:e,holder:o.id})}s(H,"assertNameFree");function C(t,n,e){if(!J(t,n,e))throw R(`no document ${e.id}`)}s(C,"readable");function G(t){return new O("scope_missing",`missing scope ${t}`,D.GENERAL,{scope:t})}s(G,"scopeMissing");function T(t,n,e){C(t,n,e);const r=oe(t,n,e,"rw");if(r!==null)throw G(r);if(!k(t,n,e))throw new O("forbidden",`you cannot change ${e.name}`,D.GENERAL,{id:e.id})}s(T,"changeable");function V(t,n){const e=c(t,`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rowid = rp.parent
|
|
1
|
+
var ne=Object.defineProperty;var s=(t,n)=>ne(t,"name",{value:n,configurable:!0});import{prepared as c}from"../sqlite-statements.js";import{randomUUID as re}from"node:crypto";import{CrtrError as O,notFound as R,usage as m}from"../errors.js";import{ExitCode as D}from"../../types.js";import{canChange as k,canRead as J,missingMemoryScope as oe,missingMemoryScopeUnder as ie,ownerChain as se}from"./access.js";import{bodyOf as ae,get as ue,put as P}from"./bodies.js";import{recordActedOn as N,setLinks as ce}from"./edges.js";import{publish as x}from"./events.js";import{resolveName as le}from"./names.js";import{createObject as me,getById as we,getByRowid as d}from"./objects.js";import{replaceRules as B,setRulesOwner as pe}from"./rules.js";import{recordWatch as $}from"./watches.js";function y(t){return t.class==="node"?t.node:t.class==="app"?t.app:t.class==="object"?t.object:t.person}s(y,"actorOf");let de=0;function E(t,n){const e=`graph_doc_${++de}`;t.exec(`SAVEPOINT ${e}`);try{const r=n();return t.exec(`RELEASE ${e}`),r}catch(r){throw t.exec(`ROLLBACK TO ${e}`),t.exec(`RELEASE ${e}`),r}}s(E,"atomic");function fe(t,n){return new O("conflict",t,D.GENERAL,n)}s(fe,"conflict");function _(t){if(t.type!=="document")throw m(`${t.type} ${t.id} is not a document`)}s(_,"assertDocument");function K(t,n){const e=se(t,n);return(e.length?e[e.length-1]:n).type}s(K,"spaceType");function I(t,n,e){if(e!=="public"&&K(t,n)==="repo")throw m(`a repo owns only public documents; ${n.name} cannot own a ${e} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(I,"assertPublicInRepo");function H(t,n,e,r){if(e.trim()===""||e!==e.trim())throw m(`invalid document name ${JSON.stringify(e)}`);const o=c(t,"SELECT id FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.rowid,e);if(o&&o.id!==r?.id)throw fe(`${n.name} already has a document named ${e}`,{name:e,holder:o.id})}s(H,"assertNameFree");function C(t,n,e){if(!J(t,n,e))throw R(`no document ${e.id}`)}s(C,"readable");function G(t){return new O("scope_missing",`missing scope ${t}`,D.GENERAL,{scope:t})}s(G,"scopeMissing");function T(t,n,e){C(t,n,e);const r=oe(t,n,e,"rw");if(r!==null)throw G(r);if(!k(t,n,e))throw new O("forbidden",`you cannot change ${e.name}`,D.GENERAL,{id:e.id})}s(T,"changeable");function V(t,n){const e=c(t,`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rowid = rp.parent
|
|
2
2
|
WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rowid);return{rowid:n.rowid,rev_id:n.rev_id,number:n.number,body_hash:n.body_hash,author:n.author===null?null:d(t,n.author)?.id??null,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,parents:e.map(r=>r.rev_id)}}s(V,"toRevision");function W(t,n){return c(t,`SELECT * FROM revisions r WHERE r.document = ?
|
|
3
|
-
AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid) ORDER BY r.number DESC, r.rev_id`).all(n.rowid).map(r=>V(t,r))}s(W,"heads");function he(t){return t.map(n=>n.rev_id).sort().join(",")}s(he,"headsKeyOf");function A(t,n){const e=c(t,"SELECT * FROM documents WHERE object = ?").get(n.rowid);if(!e)throw R(`no document record for ${n.id}`);return{summary:e.summary,heads_key:e.heads_key,revision:e.revision,why_it_exists:e.why_it_exists,unlisted:e.unlisted===1,lint_ignore:e.lint_ignore_json===null?null:JSON.parse(e.lint_ignore_json),extensions:e.extensions_json===null?null:JSON.parse(e.extensions_json),source_path:e.source_path}}s(A,"documentRecord");function
|
|
4
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.rowid,e.rev_id??re(),e.number,e.body_hash,e.author?.rowid??null,e.rationale,e.verbatim?1:0,e.created_at??new Date().toISOString()),o=c(t,"INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const i of e.parents)o.run(r.rowid,i);return r}s(
|
|
3
|
+
AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid) ORDER BY r.number DESC, r.rev_id`).all(n.rowid).map(r=>V(t,r))}s(W,"heads");function he(t){return t.map(n=>n.rev_id).sort().join(",")}s(he,"headsKeyOf");function A(t,n){const e=c(t,"SELECT * FROM documents WHERE object = ?").get(n.rowid);if(!e)throw R(`no document record for ${n.id}`);return{summary:e.summary,heads_key:e.heads_key,revision:e.revision,why_it_exists:e.why_it_exists,unlisted:e.unlisted===1,lint_ignore:e.lint_ignore_json===null?null:JSON.parse(e.lint_ignore_json),extensions:e.extensions_json===null?null:JSON.parse(e.extensions_json),source_path:e.source_path}}s(A,"documentRecord");function L(t,n,e){const r=c(t,`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at)
|
|
4
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.rowid,e.rev_id??re(),e.number,e.body_hash,e.author?.rowid??null,e.rationale,e.verbatim?1:0,e.created_at??new Date().toISOString()),o=c(t,"INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const i of e.parents)o.run(r.rowid,i);return r}s(L,"insertRevision");function Ee(t){const n=new Set;for(const e of t.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(e[1].trim());return[...n]}s(Ee,"linksIn");function Y(t,n,e,r){const o=[],i=[];for(const a of Ee(r)){const u=le(t,n,a);"object"in u?o.push({written_as:a,dst:u.object}):"deleted"in u?o.push({written_as:a,dst:u.deleted}):"remote"in u||i.push(a)}return b(t,ce(t,e,o)),i}s(Y,"setRevisionLinks");function _e(t,n,e){return n.type==="node"?n.id:c(t,`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.rowid
|
|
5
5
|
WHERE o.name = ? AND o.type <> ? AND o.type IN ('person','app','repo','profile')
|
|
6
6
|
AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,e)?`${n.type}:${n.name}`:n.name}s(_e,"ownerPrefix");function X(t,n,e){const r=c(t,`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.rowid = e.dst
|
|
7
|
-
WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.rowid);if(r.length===0)return e;const o=new Map;for(const i of r){if(i.deleted_at||i.written_as===i.id)continue;const a=!i.written_as.includes("/");if(a&&i.name===i.written_as)continue;const u=i.owner===null?null:d(t,i.owner),l=u?`${_e(t,u,i.name)}/${i.name}`:i.name,w=a&&i.owner===n.owner?i.name:l;w!==i.written_as&&o.set(i.written_as,w)}return o.size===0?e:e.replace(/\[\[([^[\]\n]+?)\]\]/g,(i,a)=>{const u=o.get(a.trim());return u===void 0?i:`[[${u}]]`})}s(X,"withCurrentNames");function
|
|
7
|
+
WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.rowid);if(r.length===0)return e;const o=new Map;for(const i of r){if(i.deleted_at||i.written_as===i.id)continue;const a=!i.written_as.includes("/");if(a&&i.name===i.written_as)continue;const u=i.owner===null?null:d(t,i.owner),l=u?`${_e(t,u,i.name)}/${i.name}`:i.name,w=a&&i.owner===n.owner?i.name:l;w!==i.written_as&&o.set(i.written_as,w)}return o.size===0?e:e.replace(/\[\[([^[\]\n]+?)\]\]/g,(i,a)=>{const u=o.get(a.trim());return u===void 0?i:`[[${u}]]`})}s(X,"withCurrentNames");function U(t,n,e){if(e.preview!==void 0){if(e.preview.trim()==="")throw m("a document needs a preview",{field:"preview"});c(t,"UPDATE objects SET preview = ? WHERE rowid = ?").run(e.preview,n.rowid)}e.privacy!==void 0&&(I(t,d(t,n.owner),e.privacy),c(t,"UPDATE objects SET privacy = ? WHERE rowid = ?").run(e.privacy,n.rowid));const r=s((i,a)=>t.prepare(`UPDATE documents SET ${i} = ? WHERE object = ?`).run(a,n.rowid),"set");e.summary!==void 0&&r("summary",e.summary),e.whyItExists!==void 0&&r("why_it_exists",e.whyItExists),e.unlisted!==void 0&&r("unlisted",e.unlisted?1:0),e.lintIgnore!==void 0&&r("lint_ignore_json",e.lintIgnore===null?null:JSON.stringify(e.lintIgnore)),e.extensions!==void 0&&r("extensions_json",e.extensions===null?null:JSON.stringify(e.extensions));const o=d(t,n.rowid);return e.rules!==void 0&&B(t,o,e.rules),o}s(U,"applyDocumentFields");function q(t,n){if(n.owner.deleted_at)throw m(`${n.owner.name} is deleted`);if(n.preview.trim()==="")throw m("a document needs a preview",{field:"preview"});I(t,n.owner,n.privacy),H(t,n.owner,n.name);const e=me(t,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.rowid,privacy:n.privacy,name:n.name,preview:n.preview});return c(t,"INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(e.rowid),U(t,e,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]})}s(q,"createDocument");function M(t,n){const e=W(t,n),r=he(e),o=Math.max(0,...e.map(i=>i.number));return c(t,"UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(r,o,n.rowid),{heads_key:r,revision:o,headCount:e.length}}s(M,"setHeads");function Ie(t,n,e,r,o){const i=P(r);return E(t,()=>{const a=y(n),u=ie(t,n,e.owner,"rw");if(u!==null)throw G(u);const l=q(t,e),w=L(t,l,{number:1,body_hash:i,author:a,rationale:null,parents:[]}),{heads_key:f}=M(t,l);if(!k(t,n,l))throw R(`no owner ${e.owner.name} you can write to`);const p=Y(t,n,l,r);return N(t,a,l),$(t,a,l,"write",{readHeadsKey:f}),x(t,{event:"created",object:l,actor:a,attrs:{revision:1},pointer:w.rev_id},o),{object:l,revision:1,unresolved:p}})}s(Ie,"writeDocument");function He(t,n,e){return c(t,"SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(y(n).rowid,e.rowid)?.read_heads_key??null}s(He,"readBase");function ye(t,n){let e=t;for(const[r,{old:o,new:i}]of n.entries()){if(o==="")throw m(`block ${r+1}: a replacement needs non-empty old text`,{field:"replace"});const a=e.indexOf(o);if(a<0)throw m(`block ${r+1}: old text not found: ${JSON.stringify(o.slice(0,120))}`,{field:"replace",old:o});if(e.indexOf(o,a+1)>=0)throw m(`block ${r+1}: old text occurs more than once: ${JSON.stringify(o.slice(0,120))}`,{field:"replace",old:o});e=e.slice(0,a)+i+e.slice(a+o.length)}return e}s(ye,"applyReplacements");function Ce(t,n,e,r,o,i,a={},u){if(_(e),T(t,n,e),e.deleted_at)throw m(`${e.name} is deleted`);if(i===null)throw new O("read_first",`read ${e.name} before editing it`,D.GENERAL,{id:e.id});if(o.trim()==="")throw m("an edit needs a rationale",{field:"rationale"});const l=A(t,e);if(l.heads_key!==i)return{stale:{heads_key:l.heads_key,revision:l.revision}};const w=W(t,e);let f;if("body"in r)f=r.body;else{if(w.length!==1)throw m(`${e.name} has ${w.length} heads and needs a merge; pass the whole merged body`,{heads:w.length});f=ye(X(t,e,ue(w[0].body_hash)),r.replace)}const p=P(f);try{return E(t,()=>{const h=y(n),g=1+Math.max(0,...w.map(v=>v.number)),F=L(t,e,{number:g,body_hash:p,author:h,rationale:o,verbatim:r.verbatim===!0,parents:w.map(v=>v.rowid)}),j=F.rev_id,ee=c(t,"UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(j,g,e.rowid,i);if(Number(ee.changes)===0){const v=A(t,e);throw new z(v.heads_key,v.revision)}const S=U(t,e,a),te=Y(t,n,S,f);return N(t,h,S),$(t,h,S,"write",{readHeadsKey:j}),x(t,{event:"edited",object:S,actor:h,pointer:F.rev_id,attrs:{revision:g,from_revision:l.revision,rationale:o}},u),{revision:g,heads_key:j,unresolved:te}})}catch(h){if(h instanceof z)return{stale:{heads_key:h.heads_key,revision:h.revision}};throw h}}s(Ce,"editDocument");class z extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,e){super("stale"),this.heads_key=n,this.revision=e}}function We(t,n,e,r){return _(e),n&&T(t,n,e),E(t,()=>{const o=U(t,e,r);return n&&N(t,y(n),o),o})}s(We,"setDocumentRecord");function ve(t,n,e,r){const o=y(n),{heads_key:i}=A(t,e);$(t,o,e,"read",{readHeadsKey:i}),x(t,{event:"read",object:e,actor:o,attrs:{}},r)}s(ve,"recordRead");function Ue(t,n,e,r){return _(e),C(t,n,e),E(t,()=>{const o=A(t,e),i=W(t,e).map(u=>({...u,body:X(t,e,ae(u.body_hash,o.source_path))})),a=c(t,"SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ? ORDER BY written_as").all(e.rowid).map(u=>({written_as:u.written_as??"",object:d(t,u.dst)})).filter(u=>u.object&&J(t,n,u.object)).map(u=>({...u,deleted:u.object.deleted_at!==null}));return e.deleted_at||ve(t,n,e,r),{object:e,record:o,heads:i,needsMerge:i.length>1,links:a}})}s(Ue,"readDocument");function Me(t,n,e){return _(e),C(t,n,e),c(t,"SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, rowid DESC").all(e.rowid).map(o=>V(t,o))}s(Me,"history");function Fe(t,n,e,r){if(_(e),e.deleted_at)throw m(`${e.name} is deleted`);return n&&T(t,n,e),E(t,()=>{const o=r.owner??d(t,e.owner),i=r.name??e.name;H(t,o,i,e),I(t,o,e.privacy),c(t,"UPDATE objects SET name = ?, owner = ? WHERE rowid = ?").run(i,o.rowid,e.rowid);const a=d(t,e.rowid);if(o.rowid!==e.owner&&(pe(t,a),n&&!k(t,n,a)))throw R(`no owner ${o.name} you can move documents to`);return n&&N(t,y(n),a),a})}s(Fe,"moveDocument");function Je(t,n,e,r,o){return _(e),e.deleted_at?e:(n&&T(t,n,e),E(t,()=>{const i=Q(t,n,e,r,o);return b(t,Z(t,e.rowid),r),i}))}s(Je,"deleteDocument");function Pe(t,n,e){return _(n),n.deleted_at?E(t,()=>(H(t,d(t,n.owner),e,n),c(t,"UPDATE objects SET deleted_at = NULL, name = ? WHERE rowid = ?").run(e,n.rowid),d(t,n.rowid))):n}s(Pe,"restoreDocument");function Q(t,n,e,r,o){c(t,"UPDATE objects SET deleted_at = ? WHERE rowid = ?").run(o??new Date().toISOString(),e.rowid);const i=d(t,e.rowid),a=n?y(n):null;return a&&N(t,a,i),x(t,{event:"deleted",object:i,actor:a,attrs:{}},r),c(t,"DELETE FROM watches WHERE object = ?").run(e.rowid),B(t,i,[]),i}s(Q,"markDeleted");function Z(t,n){return c(t,"SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n).map(({dst:e})=>e)}s(Z,"linkTargets");function b(t,n,e){const r=c(t,`SELECT 1 FROM objects d JOIN objects w ON w.rowid = d.owner
|
|
8
8
|
WHERE d.rowid = ? AND d.type = 'document' AND d.deleted_at IS NULL AND w.deleted_at IS NOT NULL
|
|
9
9
|
AND NOT EXISTS (SELECT 1 FROM edges e JOIN objects s ON s.rowid = e.src
|
|
10
|
-
WHERE e.type = 'links-to' AND e.dst = d.rowid AND e.src != d.rowid AND s.deleted_at IS NULL)`),o=[...n];for(;o.length>0;){const i=o.pop();r.get(i)!==void 0&&(Q(t,null,d(t,i),e),o.push(...Z(t,i)))}}s(b,"collectUnlinked");function Be(t,n){if(n.revisions.length===0)throw m(`import of ${n.name}: no revisions`);return E(t,()=>{const e=q(t,n);c(t,"UPDATE objects SET created_at = ? WHERE rowid = ?").run(n.revisions[0].created_at,e.rowid);let r=null;return n.revisions.forEach((o,i)=>{r=
|
|
10
|
+
WHERE e.type = 'links-to' AND e.dst = d.rowid AND e.src != d.rowid AND s.deleted_at IS NULL)`),o=[...n];for(;o.length>0;){const i=o.pop();r.get(i)!==void 0&&(Q(t,null,d(t,i),e),o.push(...Z(t,i)))}}s(b,"collectUnlinked");function Be(t,n){if(n.revisions.length===0)throw m(`import of ${n.name}: no revisions`);return E(t,()=>{const e=q(t,n);c(t,"UPDATE objects SET created_at = ? WHERE rowid = ?").run(n.revisions[0].created_at,e.rowid);let r=null;return n.revisions.forEach((o,i)=>{r=L(t,e,{...o,number:i+1,parents:r===null?[]:[r]}).rowid}),M(t,e),d(t,e.rowid)})}s(Be,"importDocument");function Ke(t,n,e){if(_(n),n.privacy!=="public"&&K(t,n)==="repo")throw m(`a repo owns only public documents; refusing revisions of ${n.name}`);return E(t,()=>{const r=c(t,"SELECT rowid, number, document FROM revisions WHERE rev_id = ?");let o=e.filter(u=>!r.get(u.rev_id));for(;o.length>0;){const u=[];for(const l of o){const w=l.parents.map(p=>r.get(p));if(w.some(p=>p===void 0)){u.push(l);continue}const f=w;if(f.some(p=>p.document!==n.rowid))throw m(`revision ${l.rev_id} names a parent of another document`);L(t,n,{...l,author:null,number:1+Math.max(0,...f.map(p=>p.number)),parents:f.map(p=>p.rowid)})}if(u.length===o.length)throw m(`revisions of ${n.name} name unknown parents: ${u.map(l=>l.rev_id).join(", ")}`);o=u}const{heads_key:i,headCount:a}=M(t,n);return{heads_key:i,headCount:a}})}s(Ke,"insertImportedRevisions");function Ge(t,n){const e=we(t,n);if(!e||e.type!=="document")throw R(`no document ${n}`);return e}s(Ge,"documentById");export{y as actorOf,U as applyDocumentFields,ye as applyReplacements,b as collectUnlinked,Je as deleteDocument,Ge as documentById,A as documentRecord,Ce as editDocument,W as heads,Me as history,Be as importDocument,Ke as insertImportedRevisions,Ee as linksIn,Fe as moveDocument,He as readBase,Ue as readDocument,ve as recordRead,Pe as restoreDocument,We as setDocumentRecord,Y as setRevisionLinks,X as withCurrentNames,Ie as writeDocument};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var y=Object.defineProperty;var m=(e,t)=>y(e,"name",{value:t,configurable:!0});import{prepared as
|
|
2
|
-
AND substr(name, 1, ?) = ? ORDER BY name`).all(n.owner,r.length,r).filter(s=>!s.name.slice(r.length).includes("/")&&
|
|
1
|
+
var y=Object.defineProperty;var m=(e,t)=>y(e,"name",{value:t,configurable:!0});import{prepared as _}from"../../sqlite-statements.js";import{recordMemoryReadsIn as v}from"../../canvas/memory-reads.js";import{deliveryDocOf as x,nodeSubject as S,ownerHandle as $}from"../../substrate/delivery/corpus.js";import{recordContentDelivery as D}from"../../substrate/delivery/deliver.js";import{renderExplicitRead as R}from"../../substrate/delivery/render-event.js";import{canRead as N}from"../access.js";import{documentRecord as j,readDocument as k}from"../documents.js";import{loadLedger as E,saveLedger as O}from"../exposures.js";import{getByRowid as A}from"../objects.js";import{rulesOf as h}from"../rules.js";import{objectDTO as d}from"./dto.js";import{withBase as L}from"./kind.js";import{attr as l,header as I,section as M}from"./render.js";function b(e,t,n){const r=`${n.name}/`;return _(e,`SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND home_machine IS NULL AND owner IS ?
|
|
2
|
+
AND substr(name, 1, ?) = ? ORDER BY name`).all(n.owner,r.length,r).filter(s=>!s.name.slice(r.length).includes("/")&&N(e,t,s)).map(s=>d(e,s))}m(b,"listingOf");function B(e,t,n){const r=t.class==="node",o=r?E(e,t.node.rowid):{node:0,preferenceSnapshot:null,objects:new Map,listed:new Map},s={db:e,reader:t,subject:r?S(e,t):null,ledger:o,channel:"transcript"},a=R(s,x(e,t,n));return r&&(D(e,t,a.delivered),O(e,o)),{attachments:a.attachments,linked:a.linked.map(u=>u.object)}}m(B,"explicitReadAttachments");function T(e,t,n){if(t.class!=="node"||n.kind!=="knowledge"&&n.kind!=="preference"||n.owner===null)return;const r=A(e,n.owner);r!==null&&v(e,t.node.id,[{name:n.name,scope:$(r),at:new Date().toISOString(),type:"read"}])}m(T,"recordExplicitMemoryRead");function W(e,t){const n=[];if(t.attachments&&n.push(t.attachments,""),n.push(I("object",e,l("privacy",e.privacy)+l("revision",t.revision))),n.push(`Preview: ${e.preview}`),t.summary&&n.push(`Summary: ${t.summary}`),t.fields!==void 0&&n.push(`<fields>
|
|
3
3
|
${JSON.stringify(t.fields,null,2)}
|
|
4
|
-
</fields>`),t.needs_merge?(t.heads.forEach((r,o)=>{n.push(`<head${l("rev",r.rev_id)}${l("number",r.number)}${l("author",r.author_id)}>`,t.bodies[o]??"","</head>")}),n.push(`This document has ${t.heads.length} heads. Your next \`crtr doc edit\` writes the merged body and joins them into one revision.`)):n.push("",t.bodies[0]??""),t.listing&&t.listing.length>0){const r=t.listing.some(a=>a.owner?.id===e.owner?.id&&a.name.startsWith(`${e.name}/`)),o=t.listing.some(a=>a.owner?.id!==e.owner?.id||!a.name.startsWith(`${e.name}/`)),s=r&&o?`Names under ${e.name}/ and documents it links to`:r?`Names under ${e.name}/`:"Documents it links to";n.push("",...
|
|
5
|
-
`)}m(W,"render");function C(e){const t=e.attrs.revision,n=e.attrs.from_revision??(typeof t=="number"?t-1:null),r=e.attrs.rationale;return{...e.attrs,from_revision:n,revision:t,authors:e.actor?[e.actor.id]:[],rationales:typeof r=="string"?[r]:[]}}m(C,"editAttrs");const H=m((e,t)=>{const n=C(t);if(e===null)return n;const r=e.authors??[];return{...n,from_revision:e.from_revision??null,authors:[...r,...n.authors.filter(o=>!r.includes(o))],rationales:[...e.rationales??[],...n.rationales]}},"mergeEdits"),te={name:"document",addressable:"name",events:
|
|
4
|
+
</fields>`),t.needs_merge?(t.heads.forEach((r,o)=>{n.push(`<head${l("rev",r.rev_id)}${l("number",r.number)}${l("author",r.author_id)}>`,t.bodies[o]??"","</head>")}),n.push(`This document has ${t.heads.length} heads. Your next \`crtr doc edit\` writes the merged body and joins them into one revision.`)):n.push("",t.bodies[0]??""),t.listing&&t.listing.length>0){const r=t.listing.some(a=>a.owner?.id===e.owner?.id&&a.name.startsWith(`${e.name}/`)),o=t.listing.some(a=>a.owner?.id!==e.owner?.id||!a.name.startsWith(`${e.name}/`)),s=r&&o?`Names under ${e.name}/ and documents it links to`:r?`Names under ${e.name}/`:"Documents it links to";n.push("",...M(s,t.listing))}return n.join(`
|
|
5
|
+
`)}m(W,"render");function C(e){const t=e.attrs.revision,n=e.attrs.from_revision??(typeof t=="number"?t-1:null),r=e.attrs.rationale;return{...e.attrs,from_revision:n,revision:t,authors:e.actor?[e.actor.id]:[],rationales:typeof r=="string"?[r]:[]}}m(C,"editAttrs");const H=m((e,t)=>{const n=C(t);if(e===null)return n;const r=e.authors??[];return{...n,from_revision:e.from_revision??null,authors:[...r,...n.authors.filter(o=>!r.includes(o))],rationales:[...e.rationales??[],...n.rationales]}},"mergeEdits"),te={name:"document",addressable:"name",events:L({edited:{level:8,supersedes:!0,merge:H},deleted:{level:8}}),listItem(e,t){return{summary:j(e,t).summary??null,rules:h(e,t).length}},read(e,t,n,r){const o=k(e,t,n,r.reposFn);T(e,t,n);const s=o.record,a=b(e,t,n),{attachments:u,linked:p}=B(e,t,n),g=new Set(a.map(i=>i.id)),f=[...a,...p.filter(i=>!g.has(i.id)).map(i=>d(e,i))],c={summary:s.summary??null,revision:s.revision,heads:o.heads.map(i=>({rev_id:i.rev_id,number:i.number,author_id:i.author,created_at:i.created_at})),bodies:o.heads.map(i=>i.body),needs_merge:o.needsMerge,...f.length>0?{listing:f}:{},...u===""?{}:{attachments:u}};return r.fields&&(c.fields={why_it_exists:s.why_it_exists??null,unlisted:s.unlisted,lint_ignore:s.lint_ignore??[],extensions:s.extensions??{},rules:h(e,n).map(({matchFrontmatter:i,...w})=>({...w,...i===void 0?{}:{match_frontmatter:i}}))}),{data:c,text:W(d(e,n),c)}}};export{te as documentKind};
|
package/dist/core/graph/names.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
AND deleted_at IS ${l?"NOT NULL ORDER BY deleted_at DESC":"NULL"}`).all(e.rowid,s)}
|
|
3
|
-
OR (type IN ('person','app','repo','profile') AND (name = ? OR id = ?))`).all(e,e,e)}
|
|
1
|
+
var j=Object.defineProperty;var m=(t,e)=>j(t,"name",{value:e,configurable:!0});import{prepared as R}from"../sqlite-statements.js";import{isSafeNodeId as I}from"../canvas/paths.js";import{canRead as y,toolApps as g}from"./access.js";import{getById as T}from"./objects.js";function _(t,e){const s=R(t,"SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get(),l=g(t,e),o=e.class==="node"?[e.node,...e.repos,...e.projectRepos,e.profile,e.app]:e.class==="app"?[e.app]:e.class==="object"?[e.owner,...e.repos,e.app]:[],a=new Set;return[...o,s,...l].filter(r=>!!r&&!a.has(r.rowid)&&!!a.add(r.rowid))}m(_,"viewOf");function E(t,e,s,l){return t.prepare(`SELECT * FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ? AND home_machine IS NULL
|
|
2
|
+
AND deleted_at IS ${l?"NOT NULL ORDER BY deleted_at DESC":"NULL"}`).all(e.rowid,s)}m(E,"namedObjects");const F=["person","app","repo","profile"],h=/^(person|app|repo|profile|node):([^/]+)\/(.+)$/;function x(t,e,s){return s==="node"?R(t,"SELECT * FROM objects WHERE type = 'node' AND id = ?").all(e):s?R(t,"SELECT * FROM objects WHERE type = ? AND (name = ? OR id = ?) AND home_machine IS NULL").all(s,e,e):R(t,`SELECT * FROM objects WHERE (type = 'node' AND id = ?)
|
|
3
|
+
OR (type IN ('person','app','repo','profile') AND (name = ? OR id = ?) AND home_machine IS NULL)`).all(e,e,e)}m(x,"ownersByHandle");function W(t,e,s,l){let o=s.trim().replace(/^\[\[(.*)\]\]$/,"$1").trim();const a=/^([^:/]+):reports\/(.+?)(?:\.md)?$/.exec(o);a&&!h.test(o)&&(o=`${a[1]}/reports/${a[2]}`);const r=l?.readable??(c=>y(t,e,c)),u=T(t,o);if(u&&r(u))return u.deleted_at?{deleted:u}:{object:u};const L=l?.view??_(t,e);for(const c of L){const i=E(t,c,o,!1).find(r);if(i)return{object:i}}const p=h.exec(o),w=o.indexOf("/");if(p||w>0){const c=p?p[2]:o.slice(0,w),i=p?p[3]:o.slice(w+1),d=x(t,c,p?.[1]),D=m(n=>{const f=L.findIndex(O=>O.rowid===n.rowid);return f<0?1/0:f},"rank"),A=d.filter(n=>D(n)<1/0).sort((n,f)=>D(n)-D(f));for(const n of A){const f=E(t,n,i,!1).find(r);if(f)return{object:f}}const N=d.flatMap(n=>E(t,n,i,!1)).filter(r);if(N.length===1)return{object:N[0]};if(N.length>1)return{ambiguous:N};const S=d.flatMap(n=>E(t,n,i,!0)).find(r);return S?{deleted:S}:!p&&d.length===0&&I(c)?{remote:o}:{missing:!0}}for(const c of L){const i=E(t,c,o,!0).find(r);if(i)return{deleted:i}}return{missing:!0}}m(W,"resolveName");export{F as HANDLE_TYPES,x as ownersByHandle,W as resolveName,_ as viewOf};
|
|
@@ -11,6 +11,8 @@ export declare function createObject(db: DatabaseSync, spec: {
|
|
|
11
11
|
privacy: Privacy;
|
|
12
12
|
name: string;
|
|
13
13
|
preview: string;
|
|
14
|
+
/** Set for a stub, in the insert itself: the name indexes cover only rows without it. */
|
|
15
|
+
home_machine?: string;
|
|
14
16
|
}): ObjectRow;
|
|
15
17
|
/** The owner object for an identity, created on first sight. Idempotent on
|
|
16
18
|
* (scheme, externalId). Handles are unique per owner type: a handle already held
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
var
|
|
2
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(e.id??
|
|
3
|
-
WHERE i.scheme = ? AND i.external_id = ?`).get(o,i);if(
|
|
1
|
+
var _=Object.defineProperty;var s=(t,e)=>_(t,"name",{value:e,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{CrtrError as O,notFound as b}from"../errors.js";import{ExitCode as R}from"../../types.js";import{newNodeId as A}from"../runtime/nodes.js";import{kindOf as I}from"./kinds/registry.js";import{canRead as h}from"./access.js";import{bodyOf as T}from"./bodies.js";const y=new Set(["person","app","repo","profile"]);function N(t){return t||null}s(N,"rowFrom");function x(t,e){return N(d(t,"SELECT * FROM objects WHERE id = ?").get(e))}s(x,"getById");function P(t,e){return N(d(t,"SELECT * FROM objects WHERE rowid = ?").get(e))}s(P,"getByRowid");function D(t,e){return I(t,e.type),d(t,`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at, home_machine)
|
|
2
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(e.id??A(),e.type,e.kind,e.owner,e.privacy,e.name,e.preview,new Date().toISOString(),e.home_machine??null)}s(D,"createObject");function S(t,e,o,i,n,r){if(!y.has(e))throw new Error(`ensureOwner: ${e} is not an owner type`);const E=d(t,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
|
|
3
|
+
WHERE i.scheme = ? AND i.external_id = ?`).get(o,i);if(E)return E;const a=d(t,`SELECT o.id, o.type, i.scheme, i.external_id FROM objects o
|
|
4
4
|
LEFT JOIN identities i ON i.object = o.rowid
|
|
5
|
-
WHERE o.name = ? AND o.type =
|
|
5
|
+
WHERE o.name = ? AND o.type = ? AND o.home_machine IS NULL`).get(n,e);if(a)throw new O("conflict",`handle "${n}" is held by ${a.type} ${a.id} (${a.scheme}:${a.external_id}); cannot give it to ${e} ${o}:${i}`,R.GENERAL,{handle:n,holder:a.id});const u=D(t,{type:e,kind:null,owner:e==="profile"?r?.rowid??null:null,privacy:"public",name:n,preview:""});return d(t,"INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(u.rowid,o,i),u}s(S,"ensureOwner");function J(t,e,o){const i=d(t,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
|
|
6
6
|
WHERE i.scheme = 'profile' AND i.external_id = ? AND o.type = 'profile' AND o.deleted_at IS NULL`).get(e),n=d(t,`SELECT o.id FROM objects o
|
|
7
|
-
WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.rowid <> ?`).get(o,i?i.rowid:-1);if(n)throw new
|
|
7
|
+
WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.rowid <> ?`).get(o,i?i.rowid:-1);if(n)throw new O("conflict",`handle "${o}" is already held by profile object ${n.id}; cannot rename profile ${e} to it`,R.GENERAL,{handle:o,holder:n.id});if(!i)return!1;const r=i;return r.name===o?!1:(g(t,r,o),!0)}s(J,"renameProfileObject");function B(t,e){if(e==="user:local")return S(t,"person","local-user",e,"user");if(/^app:[a-z0-9][a-z0-9._-]*$/.test(e))return S(t,"app","app",e,e.slice(4));throw new Error(`node grantee ${JSON.stringify(e)} is neither app:<name> nor user:local`)}s(B,"ownerForGrantee");function G(t,e,o){return N(d(t,"SELECT * FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(e,o))}s(G,"liveDocumentNamed");function K(t,e){const o=x(t,e);if(!o||o.type!=="node")throw b(`no node object ${e}`);return o}s(K,"nodeObject");function g(t,e,o){d(t,"UPDATE objects SET name = ? WHERE rowid = ?").run(o,e.rowid)}s(g,"setObjectName");function Y(t,e,o){d(t,"UPDATE objects SET kind = ? WHERE rowid = ?").run(o,e.rowid)}s(Y,"setObjectKind");function z(t,e,o,i={}){const n=["rowid > ?"],r=[i.after_rowid??0];return o.type!==void 0&&(n.push("type = ?"),r.push(o.type)),o.kind!==void 0&&(n.push("kind = ?"),r.push(o.kind)),o.owner!==void 0&&(n.push("owner = ?"),r.push(o.owner.rowid)),o.privacy!==void 0&&(n.push("privacy = ?"),r.push(o.privacy)),o.name!==void 0&&(n.push("name = ?"),r.push(o.name)),n.push("home_machine IS NULL"),n.push(o.deleted?"deleted_at IS NOT NULL":"deleted_at IS NULL"),C(t,e,`SELECT * FROM objects WHERE ${n.join(" AND ")}`,r,Math.min(i.limit??100,100))}s(z,"listObjects");function V(t,e,o){const n=[],r=`%${o.text.replace(/[\\%_]/g,c=>`\\${c}`)}%`;if(!o.grep){const c=d(t,`SELECT o.*, d.summary AS _summary FROM objects o LEFT JOIN documents d ON d.object = o.rowid
|
|
8
8
|
WHERE o.deleted_at IS NULL AND o.home_machine IS NULL AND (o.name LIKE ?1 ESCAPE '\\' OR o.preview LIKE ?1 ESCAPE '\\' OR d.summary LIKE ?1 ESCAPE '\\')
|
|
9
|
-
ORDER BY o.rowid`).all(r),
|
|
10
|
-
AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid)`);for(const c of
|
|
11
|
-
`),w
|
|
9
|
+
ORDER BY o.rowid`).all(r),p=o.text.toLowerCase();for(const l of c){const{_summary:w,...m}=l,f=m;if(!h(t,e,f))continue;const L=f.name.toLowerCase().includes(p)?"name":f.preview.toLowerCase().includes(p)?"preview":"summary";if(n.push({object:f,field:L,...L==="summary"?{text:String(w)}:{}}),n.length>=100)return n}}if(!o.body&&!o.grep)return n;const E=new Set(n.map(c=>c.object.rowid)),a=o.grep?(c=>p=>c.test(p))(new RegExp(o.text)):c=>c.includes(o.text),u=d(t,"SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL ORDER BY rowid").all(),j=d(t,`SELECT r.body_hash, d.source_path FROM revisions r JOIN documents d ON d.object = r.document WHERE r.document = ?
|
|
10
|
+
AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid)`);for(const c of u)if(!(E.has(c.rowid)||!h(t,e,c)))for(const{body_hash:p,source_path:l}of j.all(c.rowid)){const w=T(p,l).split(`
|
|
11
|
+
`),m=w.findIndex(a);if(!(m<0)){if(n.push({object:c,field:"body",line:m+1,text:w[m]}),n.length>=100)return n;break}}return n}s(V,"searchObjects");function C(t,e,o,i,n){const r=[],E=t.prepare(`${o} ORDER BY rowid LIMIT ${n*4}`);for(;;){const a=E.all(...i);for(const u of a)if(h(t,e,u)&&r.push(u),r.length===n)return r;if(a.length<n*4)return r;i[0]=a[a.length-1].rowid}}s(C,"readablePages");export{D as createObject,S as ensureOwner,x as getById,P as getByRowid,z as listObjects,G as liveDocumentNamed,K as nodeObject,B as ownerForGrantee,J as renameProfileObject,V as searchObjects,Y as setObjectKind,g as setObjectName};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var C=Object.defineProperty;var p=(o,e)=>C(o,"name",{value:e,configurable:!0});import{prepared as f}from"../sqlite-statements.js";import{coveredByAny as m,isSandboxedNode as
|
|
2
|
-
sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(n?.owner??null,e);if(!n||!c)throw new Error(`no node ${e}`);const{grantee:l,sub:a,grant_id:u,scopes_json:i,cwd:d,profile_id:h}=c,_=n.owner===null?null:s.object(n.owner);if(!_)throw new Error(`node ${e} has no owner`);const R=i===null?null:JSON.parse(i);return{class:"node",node:n,app:_,scopes:H(l,a??"",u,R,s,r),launched:R,cwd:d,...x(d,h,t,s),profile:h===null?null:s.profile(h,_)}}p(
|
|
3
|
-
WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??S(o,"profile","profile",e,
|
|
1
|
+
var C=Object.defineProperty;var p=(o,e)=>C(o,"name",{value:e,configurable:!0});import{prepared as f}from"../sqlite-statements.js";import{coveredByAny as m,isSandboxedNode as L}from"../../shared/env.js";import{existsSync as A}from"node:fs";import{CrtrError as b}from"../errors.js";import{getStoredProfile as $}from"../scoped-state/profiles.js";import{userScopeRoot as M}from"../scope.js";import{parseScope as O}from"@north-light/identity";import{activeGrant as v,grantFor as N}from"../grants/mirror.js";import{prepared as F}from"../canvas/db.js";import{ApiError as T}from"../../api/errors.js";import{readConfig as W}from"../config.js";import{pluginAppToken as D}from"../secrets.js";import{SCOPE_VOCABULARY as E}from"../scopes.js";import{loadExactProfileManifest as B}from"../profiles/manifest.js";import{ensureOwner as S,getByRowid as P,nodeObject as I}from"./objects.js";function x(o,e,t,s){const r=e===null?[]:s.projectPaths(e),n=t(o),c=new Set(n.map(a=>a.repo.rowid)),l=[];for(const a of r){if(!A(a))continue;let u;try{u=t(a)}catch(i){if(i instanceof b&&i.code==="git-lookup-failed")continue;throw i}for(const i of u)c.has(i.repo.rowid)||(c.add(i.repo.rowid),l.push(i))}return{repos:n.map(a=>a.repo),checkouts:n,projectRepos:l.map(a=>a.repo),projectCheckouts:l}}p(x,"viewRepos");function ae(o,e,t){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:g(o)};if(e.class==="app")return{class:"app",app:j(o,e.app),scopes:e.scopes};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:g(o)};const l=N(e.actor.app,e.sub);if(!l)throw new T(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const a=e.actor.node_scopes;return{class:"app",app:j(o,e.actor.app),place:e.actor.place,launched:a,scopes:a===null?l.resolved:a.filter(u=>m(l.resolved,u))}}if(e.class==="cron"){const l=e.grant?v(e.grant)?.resolved??[]:E,a=e.scopes;return{class:"app",app:j(o,e.app),scopes:a===null?l:a.filter(u=>m(l,u)),launched:a,place:{run:e.origin_run??"",isolated:e.isolated}}}const s=w(o),r=I(o,e.node),n=r.owner===null?null:s.object(r.owner);if(!n)throw new Error(`node ${e.node} has no owner`);const c=f(o,"SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e.node);return{class:"node",node:r,app:n,scopes:H(e.app,e.sub,e.grant,e.scopes,s),launched:e.scopes,cwd:c.cwd,...x(c.cwd,c.profile_id,t,s),profile:c.profile_id===null?null:s.profile(c.profile_id,n)}}p(ae,"readerFor");function J(o,e,t,s=w(o),r=!1){const n=F(o,"SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),c=F(o,`SELECT (SELECT external_id FROM identities WHERE object = ?) AS grantee,
|
|
2
|
+
sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(n?.owner??null,e);if(!n||!c)throw new Error(`no node ${e}`);const{grantee:l,sub:a,grant_id:u,scopes_json:i,cwd:d,profile_id:h}=c,_=n.owner===null?null:s.object(n.owner);if(!_)throw new Error(`node ${e} has no owner`);const R=i===null?null:JSON.parse(i);return{class:"node",node:n,app:_,scopes:H(l,a??"",u,R,s,r),launched:R,cwd:d,...x(d,h,t,s),profile:h===null?null:s.profile(h,_)}}p(J,"readerForNode");function pe(o,e,t=()=>[],s=w(o)){const r=f(o,"SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.rowid);if(r!==void 0)return y(o,e,r.envelope_json===null?null:JSON.parse(r.envelope_json),s);if(e.type==="node")return J(o,e.id,t,s,!0);const n=e.owner===null?null:s.object(e.owner);if(!n)throw new Error(`watcher ${e.id} has no owner`);const l=(n.type==="app"?n:n.type==="profile"&&n.owner!==null?s.object(n.owner):null)??f(o,"SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??f(o,"SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!l)throw new Error("no terminal app or person object");const a=f(o,"SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(l.rowid)?.external_id??"app:terminal",u=f(o,"SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(a),i=n.type==="person"?null:u.length===0?a==="app:terminal"&&!s.terminalHasIssuer()?E:s.pluginScopes(a):u.length===1?JSON.parse(u[0].resolved_scopes_json).filter(d=>d.mode==="always").map(d=>O(d.scope)):null;return{class:"object",object:e,node:e,owner:n,app:l,scopes:i,repos:n.type==="repo"?[n]:[]}}p(pe,"readerForObject");function le(o,e,t){const s=f(o,"SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.rowid)?.external_id;if(s===void 0)return null;const r=f(o,"SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s);return r.length===0?s==="app:terminal"&&!t.terminalHasIssuer()?E:t.pluginScopes(s):r.length>1?null:JSON.parse(r[0].resolved_scopes_json).filter(n=>n.mode==="always").map(n=>O(n.scope))}p(le,"appScopesNow");function y(o,e,t,s){const r=g(o);if(t!==null&&t.app===null)return{class:"object",object:e,node:e,owner:r,app:r,scopes:null,repos:[]};const n=t?.app==null?null:j(o,t.app),c=t?.app==null?null:s.grantFor(t.app,t.sub),l=t===null||c===null?null:t.node_scopes===null?c.resolved:t.node_scopes.filter(u=>m(c.resolved,u)),a=n??(e.owner===null?r:s.object(e.owner)??r);return{class:"object",object:e,node:e,owner:a,app:a,scopes:l,launched:t?.node_scopes??null,repos:[],...t?.place==null?{}:{place:t.place}}}p(y,"readerForStub");function w(o){return{grantById:v,grantFor:N,terminalHasIssuer:p(()=>!!W("user").runtime?.issuer,"terminalHasIssuer"),pluginScopes:p(e=>D(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(e=>P(o,e),"object"),profile:p((e,t)=>U(o,e,t),"profile"),projectPaths:p(e=>L()?[]:$(M(),e)?.manifest.projects.map(t=>t.path)??[],"projectPaths")}}p(w,"liveLookups");function ce(o){const e=w(o),t=new Map,s=p((r,n)=>(t.has(r)||t.set(r,n()),t.get(r)),"memo");return{grantById:p(r=>s(`g\0${r}`,()=>e.grantById(r)),"grantById"),grantFor:p((r,n)=>s(`a\0${r}\0${n}`,()=>e.grantFor(r,n)),"grantFor"),terminalHasIssuer:p(()=>s("issuer",()=>e.terminalHasIssuer()),"terminalHasIssuer"),pluginScopes:p(r=>s(`t\0${r}`,()=>e.pluginScopes(r)),"pluginScopes"),object:p(r=>s(`o\0${r}`,()=>e.object(r)),"object"),profile:p((r,n)=>s(`p\0${r}\0${n.rowid}`,()=>e.profile(r,n)),"profile"),projectPaths:p(r=>s(`j\0${r}`,()=>e.projectPaths(r)),"projectPaths")}}p(ce,"publishLookups");function H(o,e,t,s,r,n=!1){const c=t?r.grantById(t):r.grantFor(o,e),l=c&&c.grantee===o?c.resolved:o==="app:terminal"&&!r.terminalHasIssuer()?E:null;if(l===null){if(n)return null;throw new Error(`the grant of ${o} is not active`)}return s===null?l:s.filter(a=>m(l,a))}p(H,"nodeScopes");function g(o){return S(o,"person","local-user","user:local","user")}p(g,"personObject");function j(o,e){return e==="user:local"?g(o):S(o,"app","app",e,e.replace(/^app:/,""))}p(j,"appObject");function U(o,e,t){return f(o,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
|
|
3
|
+
WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??S(o,"profile","profile",e,B(e).manifest.name,t)}p(U,"profileObject");export{le as appScopesNow,w as liveLookups,U as profileObject,ce as publishLookups,ae as readerFor,J as readerForNode,pe as readerForObject,x as viewRepos};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var h=Object.defineProperty;var s=(t,e)=>h(t,"name",{value:e,configurable:!0});import{prepared as a}from"../../sqlite-statements.js";import{resolve as d,sep as f}from"node:path";import{CrtrError as
|
|
2
|
-
WHERE o.type = 'repo' AND i.scheme = ? AND i.external_id = ?`).get(e,r)??null}s(
|
|
3
|
-
WHERE o.type = 'repo' AND i.scheme = 'local-path'`).all();for(const{path:o,...i}of p)(r===o||r.startsWith(o.endsWith(f)?o:o+f))&&n.push({root:o,repo:i});n.sort((o,i)=>i.root.length-o.root.length);const c=new Set;return n.filter(({repo:o})=>!c.has(o.rowid)&&(c.add(o.rowid),!0))}s(R,"checkoutsForCwd");export{R as checkoutsForCwd,O as ensureRepo,
|
|
1
|
+
var h=Object.defineProperty;var s=(t,e)=>h(t,"name",{value:e,configurable:!0});import{prepared as a}from"../../sqlite-statements.js";import{resolve as d,sep as f}from"node:path";import{CrtrError as m}from"../../errors.js";import{realpathOrSelf as l}from"../../fs-utils.js";import{ExitCode as u}from"../../../types.js";import{ensureOwner as E}from"../objects.js";import{checkoutChain as w,handleHint as N}from"./identity.js";function x(t,e,r){return a(t,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
|
|
2
|
+
WHERE o.type = 'repo' AND i.scheme = ? AND i.external_id = ?`).get(e,r)??null}s(x,"existingRepo");function O(t,e,r){const n=x(t,e.scheme,e.externalId);if(n)return n;const p=r??e.handleHint,c=a(t,"SELECT 1 FROM objects WHERE name = ? AND type = 'repo' AND home_machine IS NULL");let o=p;for(let i=2;c.get(o);i++)o=`${p}-${i}`;return E(t,"repo",e.scheme,e.externalId,o)}s(O,"ensureRepo");function F(t,e){return R(t,e).map(r=>r.repo)}s(F,"reposForCwd");function R(t,e){const r=l(d(e)),n=[];for(const o of w(r)){if(!o.ok)throw new m("git-lookup-failed",`cannot identify the repo at ${o.dir}: ${o.reason}`,u.GENERAL,{dir:o.dir});n.push({root:o.root,repo:O(t,o)})}const p=a(t,`SELECT o.*, i.external_id AS path FROM identities i JOIN objects o ON o.rowid = i.object
|
|
3
|
+
WHERE o.type = 'repo' AND i.scheme = 'local-path'`).all();for(const{path:o,...i}of p)(r===o||r.startsWith(o.endsWith(f)?o:o+f))&&n.push({root:o,repo:i});n.sort((o,i)=>i.root.length-o.root.length);const c=new Set;return n.filter(({repo:o})=>!c.has(o.rowid)&&(c.add(o.rowid),!0))}s(R,"checkoutsForCwd");export{R as checkoutsForCwd,O as ensureRepo,N as handleHint,F as reposForCwd};
|
package/dist/core/peer/stubs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var x=Object.defineProperty;var o=(n,e)=>x(n,"name",{value:e,configurable:!0});import{prepared as i}from"../sqlite-statements.js";import{createObject as A,ensureOwner as h,getById as y,getByRowid as p}from"../graph/objects.js";import{upsertKind as b}from"../graph/custom.js";import{CrtrError as f}from"../errors.js";import{ExitCode as m}from"../../types.js";import{normalizeMachineId as H}from"./names.js";const
|
|
1
|
+
var x=Object.defineProperty;var o=(n,e)=>x(n,"name",{value:e,configurable:!0});import{prepared as i}from"../sqlite-statements.js";import{createObject as A,ensureOwner as h,getById as y,getByRowid as p}from"../graph/objects.js";import{upsertKind as b}from"../graph/custom.js";import{CrtrError as f}from"../errors.js";import{ExitCode as m}from"../../types.js";import{normalizeMachineId as H}from"./names.js";const N=["public","friends","private"],W=o(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");function S(n,e){const r=typeof e=="string"?y(n,e):e;if(r===null)return null;const t=i(n,"SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(r.rowid);return t?{object:r,home_machine:t.home_machine,identity:t.envelope_json===null?null:JSON.parse(t.envelope_json)}:null}o(S,"stubOf");const I=o((n,e)=>i(n,"SELECT 1 FROM peer_stubs WHERE object = ?").get(e.rowid)!==void 0,"isStub");function d(n){return h(n,"person","local-user","user:local","user")}o(d,"localPerson");function w(n,e){return e==="user:local"?d(n):h(n,"app","app",e,e.replace(/^app:/,""))}o(w,"localApp");function F(n,e){return e.type==="app"?w(n,`app:${e.name}`):e.type==="person"?d(n):null}o(F,"localOwnerActor");function v(n,e,r){if(e!==null)return e.app===null?d(n):w(n,e.app);const t=r?.owner;return t?.type==="app"?w(n,t.external_id??`app:${t.handle}`):d(n)}o(v,"ownerFor");function g(n,e,r,t,_,u){const l=_===void 0?null:W(_),s=u?.privacy??"public";if(!N.includes(s))throw new f("invalid_event",`invalid privacy ${JSON.stringify(s)}`,m.GENERAL,{id:e});const c=y(n,e);if(c!==null){const a=S(n,c);if(a===null)throw new f("conflict",`${e} is a local ${c.type}, not a stub of ${t}`,m.GENERAL,{id:e});if(a.home_machine!==t)throw new f("conflict",`${e} is already a stub of ${a.home_machine}, not ${t}`,m.GENERAL,{id:e,home_machine:a.home_machine});return l!==null&&(i(n,"UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(l),c.rowid),i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(c.rowid)),u!==void 0&&(R(n,r,u),u.privacy!==void 0&&c.privacy!==s)?(i(n,"UPDATE objects SET privacy = ? WHERE rowid = ?").run(s,c.rowid),p(n,c.rowid)):c}const O=v(n,l,u);R(n,r,u);const E=A(n,{id:e,type:r,kind:u?.kind??null,owner:O.rowid,privacy:s,name:u?.name??e,preview:u?.preview??"",home_machine:t});return i(n,"INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(E.rowid,t,l===null?null:JSON.stringify(l),new Date().toISOString()),l!==null&&i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(E.rowid),p(n,E.rowid)}o(g,"ensureStub");function R(n,e,r){const t=r?.kind_decl;t===void 0||t.name!==e||b(n,{name:t.name,owner:v(n,null,{...r,owner:t.owner}),description:t.description,events:t.events,read:null,source:"peer"})}o(R,"holdKind");function J(n,e,r){const t=g(n,e,"node",H(r));return i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(t.rowid),p(n,t.rowid)}o(J,"ensureNodeStub");function M(n,e,r){const t=S(n,e);t===null||t.home_machine===r||(i(n,"UPDATE peer_stubs SET home_machine = ? WHERE object = ?").run(r,t.object.rowid),i(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(r,t.object.rowid))}o(M,"rehomeStub");function G(n,e){const r=j(n,e.owner===null?null:p(n,e.owner)),t=T(n,e.type);return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:r,privacy:e.privacy,...t===null?{}:{kind_decl:t}}}o(G,"describeObject");function j(n,e){if(e===null||e.type!=="person"&&e.type!=="app"&&e.type!=="repo"&&e.type!=="profile")return null;const r=i(n,"SELECT scheme, external_id FROM identities WHERE object = ?").get(e.rowid);return{id:e.id,type:e.type,scheme:r?.scheme??null,external_id:r?.external_id??null,handle:e.name}}o(j,"ownerRefOf");function T(n,e){const r=i(n,"SELECT owner, description, events_json FROM object_kinds WHERE name = ?").get(e),t=r===void 0?null:j(n,p(n,r.owner));return r===void 0||t===null?null:{name:e,description:r.description,events:JSON.parse(r.events_json),owner:t}}o(T,"kindDeclarationOf");function K(n,e,r){const t=T(n,e.type);return t===null?r:{...r,kind_decl:t}}o(K,"withKindDecl");export{G as describeObject,J as ensureNodeStub,g as ensureStub,W as identityOfEnvelope,I as isStub,T as kindDeclarationOf,F as localOwnerActor,M as rehomeStub,S as stubOf,K as withKindDecl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as z,sep as B}from"node:path";import{ApiError as a}from"../../../api/errors.js";import{armCron as rn,cancelCron as on,getCron as y,listCrons as T,listCronRuns as G,pokeHeldCrons as tn,setCronState as F}from"../../../core/canvas/crons.js";import{getNode as h}from"../../../core/canvas/canvas.js";import{appSpace as sn,cronFolder as an}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn,terminalGrant as fn}from"../../../core/grants/mirror.js";import{readConfig as _n}from"../../../core/config.js";import{assertScope as L}from"../../../core/scopes.js";import{InputError as i}from"../../../core/io.js";import{nextSlotAfter as hn}from"../../../core/wake.js";import{executeCron as mn,markSelfCanceledCronRun as gn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as Z}from"../map.js";import{appOf as H,assertCeiling as wn,canReadNode as bn}from"../router.js";import{checkCronBirth as vn,checkCronFire as yn}from"../../cron/authority.js";import{requireUsableProfile as kn}from"./profiles.js";function A(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new i({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(A,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new i({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");function Sn(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new i({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(h(n)===null)throw new i({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new i({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new i({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(Sn,"parseSinkSpec");const $n=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function M(e,n){if(!$n.test(e)||Number.isNaN(Date.parse(e)))throw new i({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(M,"requireUtcIso");function On(e){try{hn(e,new Date)}catch(n){throw new i({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(On,"requireRecur");function Cn(e){let n;try{n=JSON.parse(e)}catch{throw new i({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new i({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new i({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(Cn,"requireEnvJson");function E(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(E,"callerProfile");function Y(e,n){return n===null||e.scope==="global"||e.profile===n}t(Y,"inScope");function K(e){return e.principal.class==="person"?null:H(e.principal)}t(K,"callerApp");function U(e,n){return bn(e.principal,n.grantee,n.created_by??void 0)}t(U,"visibleTo");function In(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(In,"anchorNodeOf");function Nn(e,n,r,o,c,u){const l=typeof e.cwd=="string"&&e.cwd.trim()!==""?z(e.cwd):null;if(!cn()){if(l===null)throw new i({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return l}const p=o!==null&&h(o)!==null?o:void 0,d=t(N=>ln(n,N,p,c,void 0,u),"reaches");if(l!==null&&d(l))return l;const s=sn(n).files;if(l!==null&&e.cwd_is_default!==!0)throw new a(400,"invalid_request",`cwd ${l} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${s}`,{cwd:l,default:s},void 0,{param:"cwd"});const m=r===null?null:h(r)?.cwd??null;return m!==null&&d(m)?m:s}t(Nn,"resolveArmedCwd");function qn(e,n,r){const o=e.principal;if(o.class==="person"){const s=fn();return{sub:s?.sub??"user:local",grant_id:s?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const s=un(n);if(s===null)throw new a(401,"grant_revoked",`no active grant for ${n}`);const m=e.ceiling??o.scopes;return{sub:s.sub,grant_id:s.id,scopes:[...m??s.resolved],origin_run_id:null}}if(o.class==="cron"){const s=yn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!s.allowed)throw new a(s.reason==="scope_missing"?403:401,s.reason==="scope_missing"?"scope_missing":"grant_revoked",s.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?[...s.effectiveScopes]:[...o.scopes],origin_run_id:o.origin_run}}if(o.class!=="broker")throw new a(401,"unauthorized","this caller cannot arm a cron");const c=r??h(o.node),u=c?.grant_id??o.grant,l=c?.sub??o.sub,p=u!=null?dn(u):pn(n,l);if(p===null){if(n==="app:terminal"&&!_n("user").runtime?.issuer)return{sub:l,grant_id:u??null,scopes:c?.scopes??o.scopes,origin_run_id:c?.run_id??o.run};throw new a(401,"grant_revoked","the node grant is not active")}const d=c?.scopes??o.scopes;return{sub:p.sub,grant_id:p.id,scopes:d===null?[...p.resolved]:[...d],origin_run_id:c?.run_id??o.run}}t(qn,"armPrincipal");function jn(e){const n=e.body??{},r=A(n,"name"),o=A(n,"command"),c=M(A(n,"fire_at"),"fire_at"),u=typeof n.recur=="string"&&n.recur!==""?On(n.recur):null,l=typeof n.expires_at=="string"&&n.expires_at!==""?M(n.expires_at,"expires_at"):null,p=typeof n.env_json=="string"&&n.env_json!==""?Cn(n.env_json):null,d=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,s=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],d?.startsWith("node:")?"on-change":"on-failure"),m=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),N=O(n.tier,"tier",["normal","urgent","critical"],"normal"),R=s==="always"||s==="on-change";if(R&&d===null)throw new i({error:"sink_required",message:`on_output "${s}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!R&&d!==null)throw new i({error:"sink_unused",message:`on_output "${s}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=d!==null?Sn(d):"{}";if(d?.startsWith("spawn:")&&(wn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const _=vn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes});if(!_.allowed)throw new a(_.reason==="scope_missing"?403:401,_.reason==="scope_missing"?"scope_missing":"grant_revoked",_.message)}const b=H(e.principal)??"app:terminal",v=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==v)throw new a(403,"permission_error","a broker may only arm a cron as its own node");const g=v===null?null:h(v);if(g!==null){if(g.grantee!==b)throw new i({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});L(g,"crtr:schedule"),d?.startsWith("spawn:")&&L(g,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new a(400,"invalid_request","isolated must be a boolean");if(g?.isolated&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const D=g?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(D&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const q=D||n.isolated===!0,w=qn(e,b,g),j=nn(),S=q?an(b,j):null;if(S!==null&&n.cwd_is_default!==!0){const _=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?z(n.cwd):S);if(_===".."||_.startsWith(`..${B}`)||_.startsWith(B))throw new a(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??Nn(n,b,v,In(n),q,{sub:w.sub,grantId:w.grant_id,scopes:w.scopes}),f=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(f!==null&&h(f)!==null&&h(f).grantee!==b)throw new i({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(f!==null&&h(f)===null)throw new i({error:"anchor_not_found",message:`anchor node ${f} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const W=n.cancel_on_wake===!0;if(W&&f===null)throw new i({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new i({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const P=typeof n.profile=="string"&&n.profile!==""?kn(e,n.profile).profileId:null,x=O(n.scope,"scope",["profile","global"],P!==null?"profile":"global");if(x==="profile"&&P===null)throw new i({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return rn({cron_id:j,name:r,created_by:v,grantee:b,sub:w.sub,grant_id:w.grant_id,scopes:w.scopes,origin_run_id:w.origin_run_id,isolated:q,command:o,fire_at:c,recur:u,tz:typeof n.tz=="string"?n.tz:null,expires_at:l,anchor_node:f,cancel_on_wake:W,cwd:V,env_json:p,profile:P,scope:x,run_timeout_s:$,overlap:m,on_output:s,sink:Q,tier:N}),{status:201,body:k(y(j),null)}}t(jn,"handleArm");function Pn(e){const n=E(e);return{status:200,body:(n===null?T({all:!0}):[...T({profile:n}),...T({global:!0})].sort((o,c)=>o.fire_at<c.fire_at?-1:o.fire_at>c.fire_at?1:0)).filter(o=>U(e,o)).map(o=>k(o,J(o)))}}t(Pn,"handleList");function J(e){return G(e.cron_id)[0]??null}t(J,"lastRunOf");function C(e){const n=e.params.cronId,r=y(n);if(r===null||!U(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(!Y(r,E(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=K(e);if(r!==null&&n.grantee!==r)throw new a(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");function Tn(e){const n=C(e),r=G(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(Z)}}}t(Tn,"handleShow");function An(e){const n=I(e,C(e));F(n.cron_id,"paused");const r=y(n.cron_id);return{status:200,body:k(r,J(r))}}t(An,"handlePause");function En(e){const n=I(e,C(e));F(n.cron_id,"active");const r=y(n.cron_id);return{status:200,body:k(r,J(r))}}t(En,"handleResume");async function Un(e){const n=I(e,C(e));if(n.run_state==="running")throw new a(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await mn(n,{outOfBand:!0});if(r===null)throw new a(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:Z(r)}}t(Un,"handleRun");function Jn(e){const n=new Date().toISOString();return{status:200,body:{unparked:tn(n,K(e)),at:n}}}t(Jn,"handlePoke");function Rn(e){const n=e.params.cronId,r=y(n);if(r!==null&&!U(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!Y(r,E(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),c=e.principal.class==="cron"?o===e.principal.execution&&n===e.principal.cron:!0,u=o!==null&&c&&gn(n,o);return on(n,{preserveRunningProcess:u}),{status:204}}t(Rn,"handleCancel");const re=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:jn},{method:"GET",pattern:"/v1/crons",handler:Pn},{method:"GET",pattern:"/v1/crons/:cronId",handler:Tn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:An},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:En},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Un},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Rn}];export{re as cronRoutes};
|
|
1
|
+
var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as B,sep as G}from"node:path";import{ApiError as c}from"../../../api/errors.js";import{armCron as rn,parseCronSink as on,getCron as y,listCrons as T,listCronRuns as F,pokeHeldCrons as tn,setCronState as L}from"../../../core/canvas/crons.js";import{getNode as h}from"../../../core/canvas/canvas.js";import{appSpace as sn,cronFolder as an}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn,terminalGrant as fn}from"../../../core/grants/mirror.js";import{readConfig as _n}from"../../../core/config.js";import{assertScope as Z}from"../../../core/scopes.js";import{InputError as i}from"../../../core/io.js";import{nextSlotAfter as hn}from"../../../core/wake.js";import{executeCron as mn,markSelfCanceledCronRun as gn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as H}from"../map.js";import{appOf as A,assertCeiling as wn,canReadNode as bn}from"../router.js";import{checkCronBirth as vn,checkCronFire as yn}from"../../cron/authority.js";import{endCron as kn}from"../../cron/ended.js";import{requireUsableProfile as Sn}from"./profiles.js";function E(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new i({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(E,"requireString");function C(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new i({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(C,"requireEnum");function $n(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new i({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(h(n)===null)throw new i({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new i({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new i({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t($n,"parseSinkSpec");const Cn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function M(e,n){if(!Cn.test(e)||Number.isNaN(Date.parse(e)))throw new i({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(M,"requireUtcIso");function On(e){try{hn(e,new Date)}catch(n){throw new i({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(On,"requireRecur");function In(e){let n;try{n=JSON.parse(e)}catch{throw new i({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new i({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new i({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(In,"requireEnvJson");function U(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(U,"callerProfile");function Y(e,n){return n===null||e.scope==="global"||e.profile===n}t(Y,"inScope");function K(e){return e.principal.class==="person"?null:A(e.principal)}t(K,"callerApp");function J(e,n){return bn(e.principal,n.grantee,n.created_by??void 0)}t(J,"visibleTo");function Nn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(Nn,"anchorNodeOf");function qn(e,n,r,o,a,u){const l=typeof e.cwd=="string"&&e.cwd.trim()!==""?B(e.cwd):null;if(!cn()){if(l===null)throw new i({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return l}const p=o!==null&&h(o)!==null?o:void 0,d=t(N=>ln(n,N,p,a,void 0,u),"reaches");if(l!==null&&d(l))return l;const s=sn(n).files;if(l!==null&&e.cwd_is_default!==!0)throw new c(400,"invalid_request",`cwd ${l} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${s}`,{cwd:l,default:s},void 0,{param:"cwd"});const m=r===null?null:h(r)?.cwd??null;return m!==null&&d(m)?m:s}t(qn,"resolveArmedCwd");function jn(e,n,r){const o=e.principal;if(o.class==="person"){const s=fn();return{sub:s?.sub??"user:local",grant_id:s?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const s=un(n);if(s===null)throw new c(401,"grant_revoked",`no active grant for ${n}`);const m=e.ceiling??o.scopes;return{sub:s.sub,grant_id:s.id,scopes:[...m??s.resolved],origin_run_id:null}}if(o.class==="cron"){const s=yn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!s.allowed)throw new c(s.reason==="scope_missing"?403:401,s.reason==="scope_missing"?"scope_missing":"grant_revoked",s.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?[...s.effectiveScopes]:[...o.scopes],origin_run_id:o.origin_run}}if(o.class!=="broker")throw new c(401,"unauthorized","this caller cannot arm a cron");const a=r??h(o.node),u=a?.grant_id??o.grant,l=a?.sub??o.sub,p=u!=null?dn(u):pn(n,l);if(p===null){if(n==="app:terminal"&&!_n("user").runtime?.issuer)return{sub:l,grant_id:u??null,scopes:a?.scopes??o.scopes,origin_run_id:a?.run_id??o.run};throw new c(401,"grant_revoked","the node grant is not active")}const d=a?.scopes??o.scopes;return{sub:p.sub,grant_id:p.id,scopes:d===null?[...p.resolved]:[...d],origin_run_id:a?.run_id??o.run}}t(jn,"armPrincipal");function Pn(e){const n=e.body??{},r=E(n,"name"),o=E(n,"command"),a=M(E(n,"fire_at"),"fire_at"),u=typeof n.recur=="string"&&n.recur!==""?On(n.recur):null,l=typeof n.expires_at=="string"&&n.expires_at!==""?M(n.expires_at,"expires_at"):null,p=typeof n.env_json=="string"&&n.env_json!==""?In(n.env_json):null,d=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,s=C(n.on_output,"on_output",["silent","on-failure","always","on-change"],d?.startsWith("node:")?"on-change":"on-failure"),m=C(n.overlap,"overlap",["skip","queue","replace"],"skip"),N=C(n.tier,"tier",["normal","urgent","critical"],"normal"),D=s==="always"||s==="on-change";if(D&&d===null)throw new i({error:"sink_required",message:`on_output "${s}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!D&&d!==null)throw new i({error:"sink_unused",message:`on_output "${s}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=d!==null?$n(d):"{}";if(d?.startsWith("spawn:")&&(wn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const _=vn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes});if(!_.allowed)throw new c(_.reason==="scope_missing"?403:401,_.reason==="scope_missing"?"scope_missing":"grant_revoked",_.message)}const b=A(e.principal)??"app:terminal",v=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==v)throw new c(403,"permission_error","a broker may only arm a cron as its own node");const g=v===null?null:h(v);if(g!==null){if(g.grantee!==b)throw new i({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});Z(g,"crtr:schedule"),d?.startsWith("spawn:")&&Z(g,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new c(400,"invalid_request","isolated must be a boolean");if(g?.isolated&&n.isolated===!1)throw new c(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const W=g?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(W&&n.isolated===!1)throw new c(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const q=W||n.isolated===!0,w=jn(e,b,g),j=nn(),S=q?an(b,j):null;if(S!==null&&n.cwd_is_default!==!0){const _=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?B(n.cwd):S);if(_===".."||_.startsWith(`..${G}`)||_.startsWith(G))throw new c(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??qn(n,b,v,Nn(n),q,{sub:w.sub,grantId:w.grant_id,scopes:w.scopes}),f=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(f!==null&&h(f)!==null&&h(f).grantee!==b)throw new i({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(f!==null&&h(f)===null)throw new i({error:"anchor_not_found",message:`anchor node ${f} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const x=n.cancel_on_wake===!0;if(x&&f===null)throw new i({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new i({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const P=typeof n.profile=="string"&&n.profile!==""?Sn(e,n.profile).profileId:null,z=C(n.scope,"scope",["profile","global"],P!==null?"profile":"global");if(z==="profile"&&P===null)throw new i({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return rn({cron_id:j,name:r,created_by:v,grantee:b,sub:w.sub,grant_id:w.grant_id,scopes:w.scopes,origin_run_id:w.origin_run_id,isolated:q,command:o,fire_at:a,recur:u,tz:typeof n.tz=="string"?n.tz:null,expires_at:l,anchor_node:f,cancel_on_wake:x,cwd:V,env_json:p,profile:P,scope:z,run_timeout_s:$,overlap:m,on_output:s,sink:Q,tier:N}),{status:201,body:k(y(j),null)}}t(Pn,"handleArm");function Tn(e){const n=U(e);return{status:200,body:(n===null?T({all:!0}):[...T({profile:n}),...T({global:!0})].sort((o,a)=>o.fire_at<a.fire_at?-1:o.fire_at>a.fire_at?1:0)).filter(o=>J(e,o)).map(o=>k(o,R(o)))}}t(Tn,"handleList");function R(e){return F(e.cron_id)[0]??null}t(R,"lastRunOf");function O(e){const n=e.params.cronId,r=y(n);if(r===null||!J(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(!Y(r,U(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(O,"requireCron");function I(e,n){const r=K(e);if(r!==null&&n.grantee!==r)throw new c(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");function An(e){const n=O(e),r=F(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(H)}}}t(An,"handleShow");function En(e){const n=I(e,O(e));L(n.cron_id,"paused");const r=y(n.cron_id);return{status:200,body:k(r,R(r))}}t(En,"handlePause");function Un(e){const n=I(e,O(e));L(n.cron_id,"active");const r=y(n.cron_id);return{status:200,body:k(r,R(r))}}t(Un,"handleResume");async function Jn(e){const n=I(e,O(e));if(n.run_state==="running")throw new c(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await mn(n,{outOfBand:!0});if(r===null)throw new c(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:H(r)}}t(Jn,"handleRun");function Rn(e){const n=new Date().toISOString();return{status:200,body:{unparked:tn(n,K(e)),at:n}}}t(Rn,"handlePoke");function Dn(e,n){const r=e.principal;if(r.class==="cron")return r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const a=on(n.sink);return a?.kind==="node"&&a.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=A(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Dn,"cancelNotice");function Wn(e){const n=e.params.cronId,r=y(n);if(r!==null&&!J(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!Y(r,U(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),a=e.principal.class==="cron"?o===e.principal.execution&&n===e.principal.cron:!0,u=o!==null&&a&&gn(n,o);return r!==null&&kn(r,Dn(e,r),{preserveRunningProcess:u}),{status:204}}t(Wn,"handleCancel");const se=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:Pn},{method:"GET",pattern:"/v1/crons",handler:Tn},{method:"GET",pattern:"/v1/crons/:cronId",handler:An},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:En},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Un},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Rn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Jn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Wn}];export{se as cronRoutes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var re=Object.defineProperty;var u=(t,e)=>re(t,"name",{value:e,configurable:!0});import{prepared as P}from"../../../core/sqlite-statements.js";import{ApiError as _}from"../../../api/errors.js";import{DOC_KINDS as q,DOC_UNSET_FIELDS as H}from"../../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as V}from"../../../api/dto/objects.js";import{withCanvasWrite as k}from"../../../core/canvas/db.js";import{CrtrError as ie}from"../../../core/errors.js";import{emitEvent as se}from"../../../core/events/emit.js";import{canChange as K,spaceOf as ae}from"../../../core/graph/access.js";import{get as A}from"../../../core/graph/bodies.js";import{deleteDocument as ce,documentRecord as F,editDocument as de,heads as I,history as ue,linksIn as U,moveDocument as fe,readBase as le,setDocumentRecord as me,withCurrentNames as pe,writeDocument as he}from"../../../core/graph/documents.js";import{resolveName as X}from"../../../core/graph/names.js";import{getByRowid as M}from"../../../core/graph/objects.js";import{noteRepoDocumentsChanged as we}from"../../../core/graph/repo-sync/index.js";import{checkoutsForCwd as ye}from"../../../core/graph/repo-sync/repos.js";import{lintRules as ve,rulesOf as ge}from"../../../core/graph/rules.js";import{unifiedDiff as _e}from"../../../core/graph/diff.js";import{ownerFromFlag as W,readerFor as E,resolveRef as z}from"../reader.js";function v(t,e,n,o,s={}){return new _(t,e,n,{...s,error:e,message:n,next:o})}u(v,"refuse");const b=u((t,e)=>new _(400,"invalid_request",t,e),"invalid");function D(t){const e=t.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw b("the request body must be a JSON object");return e}u(D,"bodyOf");const L=u(t=>e=>ye(t,e),"reposFnFor");function C(t,e){if(t instanceof ie){const n=t.details;if(t.code==="read_first")throw v(409,"read_first",t.message,`Read it first with \`crtr canvas read ${e?.id??"<ref>"}\`, then rerun the edit.`,n??{});if(t.code==="conflict")throw new _(409,"name_taken",t.message,n);if(t.code==="forbidden")throw new _(403,"forbidden",t.message,n);if(t.code==="scope_missing")throw v(403,"scope_missing",t.message,`Launch the run with \`${String(n?.scope)}\` in its scopes list.`,n??{});if(n?.rule==="repo-public-only")throw v(422,"repo_documents_are_public",t.message,'Own it as `user` and add a rule with `if: {"repo": "<handle>"}` so it loads only in that repo.')}throw t}u(C,"mapGraphError");function x(t,e,n){const o=z(t,e,decodeURIComponent(n));if("remote"in o)throw v(404,"not_found",G(o.remote),"Check the name with `crtr canvas search`.");if("deleted"in o)throw new _(404,"not_found",`${o.deleted.name} is deleted`,{id:o.deleted.id,deleted:!0});if(o.type!=="document")throw b(`${n} is a ${o.type}, not a document`);return o}u(x,"refDocument");function G(t){return`no object named ${t} on this runtime; it may be on another runtime, or the name may be misspelled`}u(G,"remoteMessage");function be(t,e){const n=e.owner===null?null:M(t,e.owner);return n?n.type==="node"?n.id:n.name:""}u(be,"ownerHandle");function j(t,e){const n=be(t,e);return n?`${n}/${e.name}`:e.name}u(j,"canonicalName");function ke(t,e){const n=e.owner===null?null:M(t,e.owner),o=n?{id:n.id,type:n.type,handle:n.type==="node"?n.id:n.name}:null;return{id:e.id,type:e.type,kind:e.kind,owner:o,privacy:e.privacy,name:e.name,preview:e.preview??"",created_at:e.created_at,deleted_at:e.deleted_at}}u(ke,"objectDTO");function Ee(t,e){const n=e.owner===null?null:M(t,e.owner);return n?.type==="repo"?n:null}u(Ee,"repoOwner");function S(t,e,n,o){const s=[...new Map(n.flatMap(f=>{const i=Ee(t,f);return i?[[i.id,i]]:[]})).values()],a=e.class==="node"?e.cwd:o,r=e.class==="node"?[...e.checkouts,...e.projectCheckouts].map(f=>f.root):[];return{repos:s,dirs:[...new Set([...a!==void 0?[a]:[],...r])]}}u(S,"repoChange");function T(t){for(const e of t.repos)try{we(e,t.dirs)}catch(n){se({level:"warn",event:"repo_sync.failed",outcome:"failed",error:n,fields:{repo:e.name,step:"export",reason:n instanceof Error?n.message:String(n)}})}}u(T,"syncChanged");const Y=/^[A-Za-z0-9_-]+(\/[A-Za-z0-9_-]+)*$/;function $e(t,e){if(t===void 0)return;if(!Array.isArray(t)){e.push("`rules` must be a list");return}const n=t.map(s=>{if(typeof s!="object"||s===null||Array.isArray(s))return s;const{match_frontmatter:a,...r}=s;return"matchFrontmatter"in r?{...r,match_frontmatter:r.matchFrontmatter}:a===void 0?r:{...r,matchFrontmatter:a}}),o=ve(n);if(o){e.push(o.replaceAll("matchFrontmatter","match_frontmatter"));return}return n}u($e,"rulesFromDTO");function Z(t,e){const n={},o=u(i=>{const c=t[i];if(c!==void 0){if(typeof c!="string"){e.push(`\`${i}\` must be a string`);return}return c}},"str"),s=o("preview");s!==void 0&&(s.trim()===""||/[\r\n]/.test(s)?e.push("`preview` must be one non-empty line"):n.preview=s);const a=o("summary");a!==void 0&&(n.summary=a);const r=o("why_it_exists");if(r!==void 0&&(n.whyItExists=r),t.privacy!==void 0&&(V.includes(t.privacy)?n.privacy=t.privacy:e.push(`\`privacy\` must be one of ${V.join(", ")}`)),t.unlisted!==void 0&&(typeof t.unlisted!="boolean"?e.push("`unlisted` must be a boolean"):n.unlisted=t.unlisted),t.lint_ignore!==void 0){const i=t.lint_ignore;!Array.isArray(i)||i.some(c=>typeof c!="string")?e.push("`lint_ignore` must be a list of strings"):n.lintIgnore=i}if(t.extensions!==void 0){const i=t.extensions;typeof i!="object"||i===null||Array.isArray(i)||Object.values(i).some(c=>typeof c!="object"||c===null||Array.isArray(c))?e.push("`extensions` must map each plugin to an object of fields"):n.extensions=i}const f=$e(t.rules,e);return f!==void 0&&(n.rules=f),n}u(Z,"fieldsFrom");function J(t){if(t.length>0)throw b(t.join("; "),{errors:t})}u(J,"failOn");function Q(t,e,n,o){const s=U(n).filter(a=>"remote"in X(t,e,a));return{...o.length>0?{unresolved_links:o}:{},...s.length>0?{remote_links:s}:{}}}u(Q,"linkReport");function Oe(t){const e=D(t),n=[],o=e.name;(typeof o!="string"||!Y.test(o))&&n.push("`name` must be segments of letters, digits, `_` or `-` joined by `/`"),typeof e.body!="string"&&n.push("`body` must be a string"),typeof e.preview!="string"&&n.push("`preview` is required");const s=e.kind??"knowledge";q.includes(s)||n.push(s==="report"?"reports are written only by `crtr push`":`\`kind\` must be one of ${q.join(", ")}`);const a=Z(e,n);e.cwd!==void 0&&typeof e.cwd!="string"&&n.push("`cwd` must be a string"),J(n);const r=e,{dto:f,change:i}=k(c=>{const h=E(c,t),d=W(c,h,r.owner,r.cwd);if(!K(c,h,d))throw new _(404,"not_found",`no owner ${d.name} you can write to`);try{const{object:p,unresolved:l}=he(c,h,{...a,owner:d,name:r.name,kind:s,preview:a.preview,privacy:a.privacy??"public"},r.body,L(c));return{dto:{id:p.id,name:j(c,p),...Q(c,h,r.body,l)},change:S(c,h,[p],r.cwd)}}catch(p){C(p)}});return T(i),f}u(Oe,"write");function Ae(t,e){let n=t;e.forEach(({old:o,new:s},a)=>{const r=o===""?-1:n.indexOf(o);if(r<0)throw v(422,"replace_no_match",`replace[${a}]: old text not found in the revision you read`,"Copy the old text exactly from your last read and rerun.",{index:a});if(n.indexOf(o,r+1)>=0)throw v(422,"replace_ambiguous",`replace[${a}]: old text occurs more than once`,"Include more surrounding text so the old text matches once, and rerun.",{index:a});n=n.slice(0,r)+s+n.slice(r+o.length)})}u(Ae,"checkReplacements");function je(t){const e=D(t),n=[],o=e.body!==void 0,s=e.replace!==void 0;o&&s&&n.push("give `body` or `replace`, not both"),o&&typeof e.body!="string"&&n.push("`body` must be a string");const a=e.replace;s&&(!Array.isArray(a)||a.length===0||a.some(d=>typeof d!="object"||d===null||typeof d.old!="string"||typeof d.new!="string"))&&n.push("`replace` must be a non-empty list of {old, new} strings"),(typeof e.rationale!="string"||e.rationale.trim()==="")&&n.push("`rationale` is required"),e.verbatim!==void 0&&typeof e.verbatim!="boolean"&&n.push("`verbatim` must be a boolean"),e.base!==void 0&&(typeof e.base!="string"||e.base==="")&&n.push("`base` must be a non-empty heads key"),e.base!==void 0&&!o&&!s&&n.push("`base` applies only to a `body` or `replace` edit");const r=Z(e,n),f=e.unset??[];if(!Array.isArray(f)||f.some(d=>!H.includes(d)))n.push(`\`unset\` takes only ${H.join(", ")}`);else for(const d of f)d==="summary"&&(r.summary=null),d==="why_it_exists"&&(r.whyItExists=null),d==="unlisted"&&(r.unlisted=!1),d==="lint_ignore"&&(r.lintIgnore=null),d==="extensions"&&(r.extensions=null),d==="rules"&&(r.rules=[]);n.length===0&&!o&&!s&&Object.keys(r).length===0&&n.push("nothing to change: give `body`, `replace`, or a field"),J(n);const i=e,{dto:c,synced:h}=k(d=>{const p=E(d,t),l=x(d,p,t.params.ref),g=j(d,l),O=S(d,p,[l]);try{if(!o&&!s)return me(d,p,l,r),{dto:{id:l.id,name:g,revision:F(d,l).revision},synced:O};const w=i.base??le(d,p,l);if(s&&w!==null){const oe=F(d,l),B=I(d,l);oe.heads_key===w&&B.length===1&&Ae(pe(d,l,A(B[0].body_hash)),i.replace)}const R=i.verbatim===!0,m=o?{body:i.body,verbatim:R}:{replace:i.replace,verbatim:R},y=de(d,p,l,m,i.rationale,w,r,L(d));if("stale"in y)throw v(409,"stale_revision",`${g} changed after your read; it is now at revision ${y.stale.revision}`,`Read it again with \`crtr canvas read ${g}\`, then redo your change on the current text.`,{stale:y.stale});const N=A(I(d,l)[0].body_hash);return{dto:{id:l.id,name:g,revision:y.revision,...Q(d,p,N,y.unresolved)},synced:O}}catch(w){C(w,l)}});return T(h),c}u(je,"edit");function Re(t){const e=D(t);if(e.new_name===void 0&&e.owner===void 0)throw b("give a new name, an owner, or both");if(e.new_name!==void 0&&(typeof e.new_name!="string"||!Y.test(e.new_name)))throw b("`new_name` must be segments of letters, digits, `_` or `-` joined by `/`");const{dto:n,change:o}=k(s=>{const a=E(s,t),r=x(s,a,t.params.ref),f=e.owner===void 0?void 0:W(s,a,e.owner,e.cwd);try{const i=fe(s,a,r,{...e.new_name!==void 0?{name:e.new_name}:{},...f?{owner:f}:{}});return{dto:{id:i.id,name:j(s,i)},change:S(s,a,[r,i],e.cwd)}}catch(i){C(i,r)}});return T(o),n}u(Re,"move");function De(t){const{dto:e,change:n}=k(o=>{const s=E(o,t),a=x(o,s,t.params.ref),r=j(o,a),f=S(o,s,[a]);try{ce(o,s,a,L(o))}catch(i){C(i,a)}return{dto:{id:a.id,name:r},change:f}});return T(n),e}u(De,"remove");function Ce(t){const e=t.query.get("diff"),n=e===null?void 0:Number(e);if(n!==void 0&&!(Number.isInteger(n)&&n>0))throw b("`diff` must be a revision number");return k(o=>{const s=E(o,t),a=z(o,s,decodeURIComponent(t.params.ref));if("remote"in a)throw v(404,"not_found",G(a.remote),"Check the name with `crtr canvas search`.");const r="deleted"in a?a.deleted:a;if(r.type!=="document")throw b(`${r.name} is a ${r.type}, not a document`);const f=ue(o,s,r),i={object:ke(o,r),revisions:f.map(c=>({rev_id:c.rev_id,number:c.number,author_id:c.author,rationale:c.rationale,verbatim:c.verbatim,created_at:c.created_at,parents:c.parents}))};if(n!==void 0){const c=f.find(d=>d.number===n);if(!c)throw new _(404,"not_found",`${r.name} has no revision ${n}`);const h=c.parents[0]===void 0?void 0:f.find(d=>d.rev_id===c.parents[0]);i.diff=_e(h?A(h.body_hash):"",A(c.body_hash))}return i})}u(Ce,"revisionHistory");const ee=1e3,ne=1e3,te=3e3,Se=/^When\b.+\bbecause\b.+/,Te=u(t=>{const e=t.trim();return e===""?0:e.split(/\s+/).length},"words");function Ne(t,e){if(t.some(o=>o.on==="boot"&&o.deliver==="content")&&e>ee)return`body is ${e} words, over the ${ee}-word cap for a boot rule at content (the whole body enters every node's context). Keep the core here and move separate subjects into [[linked]] documents.`;const n=[...new Set(t.filter(o=>o.on!=="boot"&&o.deliver==="content").map(o=>o.on))];return n.length>0&&e>ne?`body is ${e} words, over the ${ne}-word cap for a content rule on ${n.join("/")}. Keep the core here and move separate subjects into [[linked]] documents.`:t.some(o=>o.on==="boot"&&o.deliver==="preview")&&e>te?`body is ${e} words, over the ${te}-word cap for a boot rule at preview. Keep the core here and move separate subjects into [[linked]] documents.`:null}u(Ne,"lengthFinding");function Fe(t){const e=D(t);return k(n=>{const o=E(n,t),s=e.owner===void 0?null:W(n,o,e.owner,e.cwd),a=P(n,"SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND kind != 'report' ORDER BY name").all(),r=[],f=new Map;for(const i of a){if(s&&i.owner!==s.rowid||!K(n,o,i))continue;const c=F(n,i),h=new Set(c.lint_ignore??[]),d=j(n,i),p=u((m,y,N)=>{h.has(m)||r.push({name:d,severity:y,rule:m,message:N})},"add"),l=ge(n,i),g=I(n,i)[0],O=g?A(g.body_hash):"",w=Ne(l,Te(O));w&&p("length","error",w),l.some(m=>m.deliver==="preview")&&!Se.test(i.preview??"")&&p("preview-shape","error",`a rule delivers this document at preview, so the preview must read "When <situation in the reader's work>, read this because <what the reader gains>"`);const R=new Set(P(n,"SELECT written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.rowid).map(m=>m.written_as));for(const m of U(O))R.has(m)||"remote"in X(n,o,m)||p("links","warning",`[[${m}]] names no object in your view`);if(l.some(m=>m.on==="workspace-open"&&m.deliver==="content")){const m=ae(n,i);m.type==="repo"&&!h.has("workspace-open")&&f.set(m.name,[...f.get(m.name)??[],d])}}for(const[i,c]of f)if(!(c.length<2))for(const h of c)r.push({name:h,severity:"warning",rule:"workspace-open",message:`repo ${i} has ${c.length} documents delivered at content on workspace-open (${c.join(", ")}); keep one and link the rest from it`});return{findings:r}})}u(Fe,"lint");const $=u((t,e=200)=>n=>({status:e,body:t(n)}),"ok"),Qe=[{method:"POST",pattern:"/v1/docs",handler:$(Oe,201)},{method:"PATCH",pattern:"/v1/docs/:ref",handler:$(je)},{method:"POST",pattern:"/v1/docs/:ref/move",handler:$(Re)},{method:"DELETE",pattern:"/v1/docs/:ref",handler:$(De)},{method:"GET",pattern:"/v1/docs/:ref/history",handler:$(Ce)},{method:"POST",pattern:"/v1/docs/lint",handler:$(Fe)}];export{Qe as docRoutes};
|
|
1
|
+
var re=Object.defineProperty;var u=(t,e)=>re(t,"name",{value:e,configurable:!0});import{prepared as P}from"../../../core/sqlite-statements.js";import{ApiError as _}from"../../../api/errors.js";import{DOC_KINDS as q,DOC_UNSET_FIELDS as H}from"../../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as U}from"../../../api/dto/objects.js";import{withCanvasWrite as k}from"../../../core/canvas/db.js";import{CrtrError as ie}from"../../../core/errors.js";import{emitEvent as se}from"../../../core/events/emit.js";import{canChange as V,spaceOf as ae}from"../../../core/graph/access.js";import{get as A}from"../../../core/graph/bodies.js";import{deleteDocument as ce,documentRecord as F,editDocument as de,heads as I,history as ue,linksIn as K,moveDocument as fe,readBase as me,setDocumentRecord as le,withCurrentNames as pe,writeDocument as he}from"../../../core/graph/documents.js";import{resolveName as X}from"../../../core/graph/names.js";import{getByRowid as L}from"../../../core/graph/objects.js";import{noteRepoDocumentsChanged as we}from"../../../core/graph/repo-sync/index.js";import{checkoutsForCwd as ye}from"../../../core/graph/repo-sync/repos.js";import{lintRules as ve,rulesOf as ge}from"../../../core/graph/rules.js";import{unifiedDiff as _e}from"../../../core/graph/diff.js";import{ownerFromFlag as M,readerFor as E,resolveRef as z}from"../reader.js";function v(t,e,n,o,s={}){return new _(t,e,n,{...s,error:e,message:n,next:o})}u(v,"refuse");const b=u((t,e)=>new _(400,"invalid_request",t,e),"invalid");function R(t){const e=t.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw b("the request body must be a JSON object");return e}u(R,"bodyOf");const W=u(t=>e=>ye(t,e),"reposFnFor");function S(t,e){if(t instanceof ie){const n=t.details;if(t.code==="read_first")throw v(409,"read_first",t.message,`Read it first with \`crtr canvas read ${e?.id??"<ref>"}\`, then rerun the edit.`,n??{});if(t.code==="conflict")throw new _(409,"name_taken",t.message,n);if(t.code==="forbidden")throw new _(403,"forbidden",t.message,n);if(t.code==="scope_missing")throw v(403,"scope_missing",t.message,`Launch the run with \`${String(n?.scope)}\` in its scopes list.`,n??{});if(n?.rule==="repo-public-only")throw v(422,"repo_documents_are_public",t.message,'Own it as `user` and add a rule with `if: {"repo": "<handle>"}` so it loads only in that repo.')}throw t}u(S,"mapGraphError");function x(t,e,n){const o=z(t,e,decodeURIComponent(n));if("remote"in o)throw v(404,"not_found",G(o.remote),"Check the name with `crtr canvas search`.");if("deleted"in o)throw new _(404,"not_found",`${o.deleted.name} is deleted`,{id:o.deleted.id,deleted:!0});if(o.type!=="document")throw b(`${n} is a ${o.type}, not a document`);return o}u(x,"refDocument");function G(t){return`no object named ${t} on this runtime; it may be on another runtime, or the name may be misspelled`}u(G,"remoteMessage");function be(t,e){const n=e.owner===null?null:L(t,e.owner);return n?n.type==="node"?n.id:n.name:""}u(be,"ownerHandle");function j(t,e){const n=be(t,e);return n?`${n}/${e.name}`:e.name}u(j,"canonicalName");function ke(t,e){const n=e.owner===null?null:L(t,e.owner),o=n?{id:n.id,type:n.type,handle:n.type==="node"?n.id:n.name}:null;return{id:e.id,type:e.type,kind:e.kind,owner:o,privacy:e.privacy,name:e.name,preview:e.preview??"",created_at:e.created_at,deleted_at:e.deleted_at}}u(ke,"objectDTO");function Ee(t,e){const n=e.owner===null?null:L(t,e.owner);return n?.type==="repo"?n:null}u(Ee,"repoOwner");function C(t,e,n,o){const s=[...new Map(n.flatMap(f=>{const i=Ee(t,f);return i?[[i.id,i]]:[]})).values()],a=e.class==="node"?e.cwd:o,r=e.class==="node"?[...e.checkouts,...e.projectCheckouts].map(f=>f.root):[];return{repos:s,dirs:[...new Set([...a!==void 0?[a]:[],...r])]}}u(C,"repoChange");function N(t){for(const e of t.repos)try{we(e,t.dirs)}catch(n){se({level:"warn",event:"repo_sync.failed",outcome:"failed",error:n,fields:{repo:e.name,step:"export",reason:n instanceof Error?n.message:String(n)}})}}u(N,"syncChanged");const Y=/^[A-Za-z0-9_-]+(\/[A-Za-z0-9_-]+)*$/;function $e(t,e){if(t===void 0)return;if(!Array.isArray(t)){e.push("`rules` must be a list");return}const n=t.map(s=>{if(typeof s!="object"||s===null||Array.isArray(s))return s;const{match_frontmatter:a,...r}=s;return"matchFrontmatter"in r?{...r,match_frontmatter:r.matchFrontmatter}:a===void 0?r:{...r,matchFrontmatter:a}}),o=ve(n);if(o){e.push(o.replaceAll("matchFrontmatter","match_frontmatter"));return}return n}u($e,"rulesFromDTO");function Z(t,e){const n={},o=u(i=>{const c=t[i];if(c!==void 0){if(typeof c!="string"){e.push(`\`${i}\` must be a string`);return}return c}},"str"),s=o("preview");s!==void 0&&(s.trim()===""||/[\r\n]/.test(s)?e.push("`preview` must be one non-empty line"):n.preview=s);const a=o("summary");a!==void 0&&(n.summary=a);const r=o("why_it_exists");if(r!==void 0&&(n.whyItExists=r),t.privacy!==void 0&&(U.includes(t.privacy)?n.privacy=t.privacy:e.push(`\`privacy\` must be one of ${U.join(", ")}`)),t.unlisted!==void 0&&(typeof t.unlisted!="boolean"?e.push("`unlisted` must be a boolean"):n.unlisted=t.unlisted),t.lint_ignore!==void 0){const i=t.lint_ignore;!Array.isArray(i)||i.some(c=>typeof c!="string")?e.push("`lint_ignore` must be a list of strings"):n.lintIgnore=i}if(t.extensions!==void 0){const i=t.extensions;typeof i!="object"||i===null||Array.isArray(i)||Object.values(i).some(c=>typeof c!="object"||c===null||Array.isArray(c))?e.push("`extensions` must map each plugin to an object of fields"):n.extensions=i}const f=$e(t.rules,e);return f!==void 0&&(n.rules=f),n}u(Z,"fieldsFrom");function J(t){if(t.length>0)throw b(t.join("; "),{errors:t})}u(J,"failOn");function Q(t,e,n,o){const s=K(n).filter(a=>"remote"in X(t,e,a));return{...o.length>0?{unresolved_links:o}:{},...s.length>0?{remote_links:s}:{}}}u(Q,"linkReport");function Oe(t){const e=R(t),n=[],o=e.name;(typeof o!="string"||!Y.test(o))&&n.push("`name` must be segments of letters, digits, `_` or `-` joined by `/`"),typeof e.body!="string"&&n.push("`body` must be a string"),typeof e.preview!="string"&&n.push("`preview` is required");const s=e.kind??"knowledge";q.includes(s)||n.push(s==="report"?"reports are written only by `crtr push`":`\`kind\` must be one of ${q.join(", ")}`);const a=Z(e,n);e.cwd!==void 0&&typeof e.cwd!="string"&&n.push("`cwd` must be a string"),J(n);const r=e,{dto:f,change:i}=k(c=>{const h=E(c,t),d=M(c,h,r.owner,r.cwd);if(!V(c,h,d))throw new _(404,"not_found",`no owner ${d.name} you can write to`);try{const{object:p,unresolved:m}=he(c,h,{...a,owner:d,name:r.name,kind:s,preview:a.preview,privacy:a.privacy??"public"},r.body,W(c));return{dto:{id:p.id,name:j(c,p),...Q(c,h,r.body,m)},change:C(c,h,[p],r.cwd)}}catch(p){S(p)}});return N(i),f}u(Oe,"write");function Ae(t,e){let n=t;e.forEach(({old:o,new:s},a)=>{const r=o===""?-1:n.indexOf(o);if(r<0)throw v(422,"replace_no_match",`replace[${a}]: old text not found in the revision you read`,"Copy the old text exactly from your last read and rerun.",{index:a});if(n.indexOf(o,r+1)>=0)throw v(422,"replace_ambiguous",`replace[${a}]: old text occurs more than once`,"Include more surrounding text so the old text matches once, and rerun.",{index:a});n=n.slice(0,r)+s+n.slice(r+o.length)})}u(Ae,"checkReplacements");function je(t){const e=R(t),n=[],o=e.body!==void 0,s=e.replace!==void 0;o&&s&&n.push("give `body` or `replace`, not both"),o&&typeof e.body!="string"&&n.push("`body` must be a string");const a=e.replace;s&&(!Array.isArray(a)||a.length===0||a.some(d=>typeof d!="object"||d===null||typeof d.old!="string"||typeof d.new!="string"))&&n.push("`replace` must be a non-empty list of {old, new} strings"),(typeof e.rationale!="string"||e.rationale.trim()==="")&&n.push("`rationale` is required"),e.verbatim!==void 0&&typeof e.verbatim!="boolean"&&n.push("`verbatim` must be a boolean"),e.base!==void 0&&(typeof e.base!="string"||e.base==="")&&n.push("`base` must be a non-empty heads key"),e.base!==void 0&&!o&&!s&&n.push("`base` applies only to a `body` or `replace` edit");const r=Z(e,n),f=e.unset??[];if(!Array.isArray(f)||f.some(d=>!H.includes(d)))n.push(`\`unset\` takes only ${H.join(", ")}`);else for(const d of f)d==="summary"&&(r.summary=null),d==="why_it_exists"&&(r.whyItExists=null),d==="unlisted"&&(r.unlisted=!1),d==="lint_ignore"&&(r.lintIgnore=null),d==="extensions"&&(r.extensions=null),d==="rules"&&(r.rules=[]);n.length===0&&!o&&!s&&Object.keys(r).length===0&&n.push("nothing to change: give `body`, `replace`, or a field"),J(n);const i=e,{dto:c,synced:h}=k(d=>{const p=E(d,t),m=x(d,p,t.params.ref),g=j(d,m),O=C(d,p,[m]);try{if(!o&&!s)return le(d,p,m,r),{dto:{id:m.id,name:g,revision:F(d,m).revision},synced:O};const w=i.base??me(d,p,m);if(s&&w!==null){const oe=F(d,m),B=I(d,m);oe.heads_key===w&&B.length===1&&Ae(pe(d,m,A(B[0].body_hash)),i.replace)}const D=i.verbatim===!0,l=o?{body:i.body,verbatim:D}:{replace:i.replace,verbatim:D},y=de(d,p,m,l,i.rationale,w,r,W(d));if("stale"in y)throw v(409,"stale_revision",`${g} changed after your read; it is now at revision ${y.stale.revision}`,`Read it again with \`crtr canvas read ${g}\`, then redo your change on the current text.`,{stale:y.stale});const T=A(I(d,m)[0].body_hash);return{dto:{id:m.id,name:g,revision:y.revision,...Q(d,p,T,y.unresolved)},synced:O}}catch(w){S(w,m)}});return N(h),c}u(je,"edit");function De(t){const e=R(t);if(e.new_name===void 0&&e.owner===void 0)throw b("give a new name, an owner, or both");if(e.new_name!==void 0&&(typeof e.new_name!="string"||!Y.test(e.new_name)))throw b("`new_name` must be segments of letters, digits, `_` or `-` joined by `/`");const{dto:n,change:o}=k(s=>{const a=E(s,t),r=x(s,a,t.params.ref),f=e.owner===void 0?void 0:M(s,a,e.owner,e.cwd);try{const i=fe(s,a,r,{...e.new_name!==void 0?{name:e.new_name}:{},...f?{owner:f}:{}});return{dto:{id:i.id,name:j(s,i)},change:C(s,a,[r,i],e.cwd)}}catch(i){S(i,r)}});return N(o),n}u(De,"move");function Re(t){const{dto:e,change:n}=k(o=>{const s=E(o,t),a=x(o,s,t.params.ref),r=j(o,a),f=C(o,s,[a]);try{ce(o,s,a,W(o))}catch(i){S(i,a)}return{dto:{id:a.id,name:r},change:f}});return N(n),e}u(Re,"remove");function Se(t){const e=t.query.get("diff"),n=e===null?void 0:Number(e);if(n!==void 0&&!(Number.isInteger(n)&&n>0))throw b("`diff` must be a revision number");return k(o=>{const s=E(o,t),a=z(o,s,decodeURIComponent(t.params.ref));if("remote"in a)throw v(404,"not_found",G(a.remote),"Check the name with `crtr canvas search`.");const r="deleted"in a?a.deleted:a;if(r.type!=="document")throw b(`${r.name} is a ${r.type}, not a document`);const f=ue(o,s,r),i={object:ke(o,r),revisions:f.map(c=>({rev_id:c.rev_id,number:c.number,author_id:c.author,rationale:c.rationale,verbatim:c.verbatim,created_at:c.created_at,parents:c.parents}))};if(n!==void 0){const c=f.find(d=>d.number===n);if(!c)throw new _(404,"not_found",`${r.name} has no revision ${n}`);const h=c.parents[0]===void 0?void 0:f.find(d=>d.rev_id===c.parents[0]);i.diff=_e(h?A(h.body_hash):"",A(c.body_hash))}return i})}u(Se,"revisionHistory");const ee=1e3,ne=1e3,te=3e3,Ce=/^When\b.+\bbecause\b.+/,Ne=u(t=>{const e=t.trim();return e===""?0:e.split(/\s+/).length},"words");function Te(t,e){if(t.some(o=>o.on==="boot"&&o.deliver==="content")&&e>ee)return`body is ${e} words, over the ${ee}-word cap for a boot rule at content (the whole body enters every node's context). Keep the core here and move separate subjects into [[linked]] documents.`;const n=[...new Set(t.filter(o=>o.on!=="boot"&&o.deliver==="content").map(o=>o.on))];return n.length>0&&e>ne?`body is ${e} words, over the ${ne}-word cap for a content rule on ${n.join("/")}. Keep the core here and move separate subjects into [[linked]] documents.`:t.some(o=>o.on==="boot"&&o.deliver==="preview")&&e>te?`body is ${e} words, over the ${te}-word cap for a boot rule at preview. Keep the core here and move separate subjects into [[linked]] documents.`:null}u(Te,"lengthFinding");function Fe(t){const e=R(t);return k(n=>{const o=E(n,t),s=e.owner===void 0?null:M(n,o,e.owner,e.cwd),a=P(n,"SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND home_machine IS NULL AND kind != 'report' ORDER BY name").all(),r=[],f=new Map;for(const i of a){if(s&&i.owner!==s.rowid||!V(n,o,i))continue;const c=F(n,i),h=new Set(c.lint_ignore??[]),d=j(n,i),p=u((l,y,T)=>{h.has(l)||r.push({name:d,severity:y,rule:l,message:T})},"add"),m=ge(n,i),g=I(n,i)[0],O=g?A(g.body_hash):"",w=Te(m,Ne(O));w&&p("length","error",w),m.some(l=>l.deliver==="preview")&&!Ce.test(i.preview??"")&&p("preview-shape","error",`a rule delivers this document at preview, so the preview must read "When <situation in the reader's work>, read this because <what the reader gains>"`);const D=new Set(P(n,"SELECT written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.rowid).map(l=>l.written_as));for(const l of K(O))D.has(l)||"remote"in X(n,o,l)||p("links","warning",`[[${l}]] names no object in your view`);if(m.some(l=>l.on==="workspace-open"&&l.deliver==="content")){const l=ae(n,i);l.type==="repo"&&!h.has("workspace-open")&&f.set(l.name,[...f.get(l.name)??[],d])}}for(const[i,c]of f)if(!(c.length<2))for(const h of c)r.push({name:h,severity:"warning",rule:"workspace-open",message:`repo ${i} has ${c.length} documents delivered at content on workspace-open (${c.join(", ")}); keep one and link the rest from it`});return{findings:r}})}u(Fe,"lint");const $=u((t,e=200)=>n=>({status:e,body:t(n)}),"ok"),Qe=[{method:"POST",pattern:"/v1/docs",handler:$(Oe,201)},{method:"PATCH",pattern:"/v1/docs/:ref",handler:$(je)},{method:"POST",pattern:"/v1/docs/:ref/move",handler:$(De)},{method:"DELETE",pattern:"/v1/docs/:ref",handler:$(Re)},{method:"GET",pattern:"/v1/docs/:ref/history",handler:$(Se)},{method:"POST",pattern:"/v1/docs/lint",handler:$(Fe)}];export{Qe as docRoutes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var E=Object.defineProperty;var
|
|
1
|
+
var E=Object.defineProperty;var c=(n,e)=>E(n,"name",{value:e,configurable:!0});import{prepared as h}from"../../core/sqlite-statements.js";import{ApiError as d}from"../../api/index.js";import{readerFor as R}from"../../core/graph/reader.js";import{canRead as v}from"../../core/graph/access.js";import{ownersByHandle as y,resolveName as x,viewOf as F}from"../../core/graph/names.js";import{ensureOwner as $}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";function M(n,e){return R(n,e.principal,o=>N(n,o))}c(M,"readerFor");function T(n,e,o){const t=x(n,e,o);if("object"in t)return t.object;if("deleted"in t||"remote"in t)return t;if("ambiguous"in t){const p=t.ambiguous.map(m=>`${_(n,m)}/${m.name}`);throw new d(409,"ambiguous_name",`'${o}' names more than one object: ${p.join(", ")}`,{candidates:p,next:"Read one of them by its full `<type>:<owner>/<name>`."})}const r=o.replace(/^\s*\[\[(.*)\]\]\s*$/,"$1").trim(),i=/^(person|app|repo|profile):([^/]+)$/.exec(r),s=(i?y(n,i[2],i[1]):y(n,r).filter(p=>p.type!=="node")).filter(p=>v(n,e,p));if(s.length>0){const p=F(n,e),m=c(u=>{const f=p.findIndex(g=>g.rowid===u.rowid);return f<0?1/0:f},"rank"),w=[...s].sort((u,f)=>m(u)-m(f))[0];if(m(w)===1/0&&s.length>1){const u=s.map(f=>`${f.type}:${f.name}`);throw new d(409,"ambiguous_name",`'${o}' names more than one owner: ${u.join(", ")}`,{candidates:u,next:"Name one as `<type>:<handle>`."})}return w.deleted_at?{deleted:w}:w}const a=`'${o}' not found: no object by that name in your view`,l=`Search for it with \`crtr canvas search ${r}\`.`;throw new d(404,"not_found",a,{error:"not_found",message:a,next:l})}c(T,"resolveRef");function _(n,e){if(e.owner===null)return"";const o=h(n,"SELECT type, id, name FROM objects WHERE rowid = ?").get(e.owner);return o.type==="node"?o.id:`${o.type}:${o.name}`}c(_,"typedOwnerHandle");function U(n,e,o,t){const r=o??(e.class==="node"?"node":e.class==="person"?"user":"app"),i=c(s=>new d(400,"invalid_owner",s,{next:"Run `crtr doc write --owner -h`."}),"refuse");if(r==="node"){if(e.class!=="node")throw i("`--owner node` needs a node; you are not running as one");return e.node}if(r==="user")return $(n,"person","local-user","user:local","user");if(r==="app"){if(e.class==="person")throw i("`--owner app` needs an app; a person has none \u2014 use `--owner user`");return e.app}if(r==="profile"){if(e.class!=="node"||!e.profile)throw i("`--owner profile` needs a profile; you have none");return e.profile}if(r==="repo"){const s=t?j(n,t):[],a=s[0]?s:e.class==="node"?e.repos:[];if(!a[0])throw i(`\`--owner repo\` needs a repo; ${t??(e.class==="node"?e.cwd:"your directory")} is not inside one`);return a[0]}if(r.startsWith("repo:"))return S(n,"repo",r.slice(5),i);if(r.startsWith("profile:")){const s=r.slice(8),a=h(n,"SELECT * FROM objects WHERE type = 'profile' AND name = ? AND home_machine IS NULL").get(s);if(a)return a;const l=L(s);if(l.kind!=="resolved")throw i(`no profile named '${s}'`);const p=e.class==="person"?e.person:e.app;return $(n,"profile","profile",l.entry.profileId,l.entry.manifest.name,p)}throw i(`unknown --owner value '${r}'`)}c(U,"ownerFromFlag");function S(n,e,o,t){const r=h(n,"SELECT * FROM objects WHERE type = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(e,o);if(!r)throw t(`no ${e} named '${o}'`);return r}c(S,"namedOwner");export{U as ownerFromFlag,M as readerFor,T as resolveRef};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Cron } from '../../core/canvas/crons.js';
|
|
2
|
+
export type CronEnd = {
|
|
3
|
+
kind: 'expired';
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'canceled';
|
|
6
|
+
by: string;
|
|
7
|
+
};
|
|
8
|
+
/** Delete `c` and, in the same write, mail its node sink the reason. Returns
|
|
9
|
+
* whether this call deleted the row. `end: null` deletes silently (the sink
|
|
10
|
+
* node canceled it itself). The notice is skipped when the sink is not a node
|
|
11
|
+
* that can still take mail: gone, deleting, or finalized with no wake ahead. */
|
|
12
|
+
export declare function endCron(c: Cron, end: CronEnd | null, options?: {
|
|
13
|
+
preserveRunningProcess?: boolean;
|
|
14
|
+
}): boolean;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var p=Object.defineProperty;var r=(e,n)=>p(e,"name",{value:n,configurable:!0});import{getNode as c}from"../../core/canvas/canvas.js";import{withCanvasWrite as f}from"../../core/canvas/db.js";import{CRON_RUN_RING as h,cancelCron as m,listCronRuns as y,parseCronSink as g}from"../../core/canvas/crons.js";import{hasNoNaturalCycle as $}from"../../core/runtime/revive-all.js";import{isParked as _}from"../../core/runtime/lifecycle.js";import{enqueueNodeMail as x}from"../../core/feed/messages.js";import{cadenceDisplay as v}from"../../core/wake.js";const k=/^delivered: /,b=r(e=>e.exit_code===0&&(e.stdout_head??"")==="","EMPTY_OK");function w(e){return e.filter(n=>n.exit_code!==null||!(n.delivered??"").startsWith("skipped"))}r(w,"realRuns");function N(e,n,s){const t=w(s),i=s.length>=h,d=t.find(u=>k.test(u.delivered??"")),a=n.kind==="expired"?`expired at ${e.expires_at} and was deleted${e.held?" while held by exit 75; the pending occurrence never fired":""}`:`was canceled by ${n.by} and deleted`,l=[`Cron "${e.name}" (${e.cron_id}, ${e.recur!=null?v(e.recur):"one-shot"}) ${a}. It was delivering to you, so nothing will wake you from it any more.`,"",`- runs made: ${i?`at least ${t.length} (the run log keeps the last ${h} entries)`:t.length}`,d!==void 0?`- last delivery to you: ${d.finished}`:`- last delivery to you: ${i?`none in its last ${t.length} runs`:"never delivered"}`],o=t[0];if(o!==void 0){const u=o.exit_code===null?"no exit observed":o.exit_code===-1?"timed out":`exit ${o.exit_code}`;l.push(`- last run: ${o.finished} \u2014 ${u} \u2014 ${o.delivered??""}`)}return t.length>0&&t.every(b)&&l.push("","Every recorded run exited 0 with empty stdout, so the check may have been failing without saying so. A pipeline exits with its last stage, so `probe 2>&1 | grep \u2026 | jq \u2026` hides a failed probe unless the command starts with `set -o pipefail`."),{label:`\u23F0 cron ${e.name} ${n.kind==="expired"?"expired":"canceled"}`,body:l.join(`
|
|
2
|
+
`)}}r(N,"endedMessage");function C(e){return e.deleting_at!=null||e.run_id!=null&&c(e.run_id)?.deleting_at!=null}r(C,"runIsDeleting");function K(e,n,s){const t=g(e.sink),i=t?.kind==="node"?c(t.node):null,d=n!==null&&i!==null&&!C(i)&&(!$(i)||_(i));return f(()=>{const a=d?y(e.cron_id):[];if(!m(e.cron_id,s))return!1;if(d){const{label:l,body:o}=N(e,n,a);x(i.node_id,{kind:"message",label:l,senderNodeId:null,bodyJson:{body:o},urgency:"normal",wake:1,idempotencyKey:`cron-ended:${e.cron_id}`})}return!0})}r(K,"endCron");export{K as endCron};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var o=(n,e)=>c(n,"name",{value:e,configurable:!0});import{randomUUID as l}from"node:crypto";import{cronDaemonInstanceId as s,liveRunFor as
|
|
1
|
+
var c=Object.defineProperty;var o=(n,e)=>c(n,"name",{value:e,configurable:!0});import{randomUUID as l}from"node:crypto";import{cronDaemonInstanceId as s,liveRunFor as u}from"./live-runs.js";import{advanceCron as a,dueOverlapCrons as p,expiredIdleCrons as f,listCronRuns as m,recordCronRun as _}from"../../core/canvas/crons.js";import{emitEvent as v}from"../../core/events/emit.js";import{nextSlotAfter as x}from"../../core/wake.js";import{endCron as h}from"./ended.js";const d="skipped (overlap=skip)";function A(n){for(const e of f(n))h(e,{kind:"expired"})&&v({level:"info",event:"cron.expired",fields:{cron_id:e.cron_id,name:e.name,expires_at:e.expires_at}})}o(A,"expireDueCrons");function k(n){return m(n)[0]?.delivered?.startsWith(d)===!0}o(k,"latestRunIsSkip");function E(n,e){for(const r of p(e)){if(r.overlap==="queue")continue;if(r.overlap==="skip"){let t;try{t=x(r.recur,new Date(n))}catch{continue}a(r.cron_id,t),k(r.cron_id)||_({run_id:l(),cron_id:r.cron_id,started:e,finished:e,duration_ms:0,exit_code:null,stdout_head:null,stderr_head:null,delivered:`${d}: fire landed while the previous run was in flight (consecutive skips are not re-recorded until a run settles)`});continue}const i=u(r.cron_id);r.run_lease_owner===s&&i!==void 0&&!i.replaced&&(i.replaced=!0,i.kill())}}o(E,"applyOverlapPolicies");export{E as applyOverlapPolicies,A as expireDueCrons};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var D=Object.defineProperty;var d=(c,t)=>D(c,"name",{value:t,configurable:!0});import{spawn as w}from"node:child_process";import{randomBytes as A}from"node:crypto";import{existsSync as S,statfsSync as _}from"node:fs";import{dirname as B,join as b}from"node:path";import{fileURLToPath as N}from"node:url";import{Cgroups as O,setOomScoreAdj as P}from"./cgroup.js";import{ADMISSION_TICKET_MS as q,APP_QUOTA_CLEANUP_FRACTION as U,BOOT_RESERVATION_BYTES as M,BOOT_WINDOW_MS as T,CONVERSATIONS_DISK_BYTES as I,appCgroupPath as C,appProjectId as v,bareApp as g,bootPendingBytes as E,budgetFor as F,conversationsProjectId as x,memoryAdmission as L,passesFraction as K,runGroupName as j,selectCachePurges as Q,tmpfsSizes as G}from"./policy.js";import{ProjectQuota as $}from"./quota.js";import{readSettings as z}from"./settings.js";function J(c,t,s,i,e){const r=b(B(N(import.meta.url)),"purge-worker.js");return new Promise((o,n)=>{const a=w("/bin/setpriv",["--reuid",String(s),"--regid",String(i),"--groups",String(i),"--no-new-privs","--",process.execPath,r,c,t,String(e)],{stdio:["ignore","pipe","pipe"]});let h="",l="";a.stdout.on("data",u=>{h+=String(u)}),a.stderr.on("data",u=>{l+=String(u)}),a.once("error",n),a.once("close",u=>{if(u!==0)return n(new Error(`purge worker exited ${u}: ${l.trim().slice(0,200)}`));try{o(JSON.parse(h))}catch(y){n(y)}})})}d(J,"spawnPurge");function V(c){return new Promise(t=>{if(!S(c))return t(null);const s=w("du",["-x","-B1","-s",c],{stdio:["ignore","pipe","ignore"],timeout:6e4});let i="";s.stdout.on("data",e=>{i+=String(e)}),s.once("error",()=>t(null)),s.once("close",(e,r)=>{const o=Number(i.split(/\s/)[0]);t(r===null&&(e===0||e===1)&&i.length>0&&Number.isFinite(o)?o:null)})})}d(V,"walkBytes");const W=6e4;function Y(c){const t=_(c),s=Number(t.blocks);return s>0?1-Number(t.bavail)/s:0}d(Y,"appsDiskUsedFraction");class at{static{d(this,"LimitsManager")}options;cgroups;quota;events=[];appliedMemory=new Map;appliedDisk=new Map;purging=new Set;purge;diskUsedFraction;setOom;now;walk;diskUsage=null;diskUsageRun=null;tickets=new Map;brokerRuns=new Map;constructor(t){this.options=t,this.cgroups=t.cgroups??new O,this.quota=t.quota??new $(t.appsRoot,t.stateDir),this.purge=t.purge??J,this.diskUsedFraction=t.diskUsedFraction??(()=>Y(t.appsRoot)),this.setOom=t.setOom??P,this.now=t.now??Date.now,this.walk=t.walk??V}note(t){this.events.push(t),this.events.length>200&&this.events.splice(0,this.events.length-200)}takeEvents(){return this.events.splice(0,this.events.length)}init(){this.cgroups.init()?this.cgroups.sweep():this.note({level:"warn",event:"limits.memory.unavailable",fields:{reason:this.cgroups.unavailable}}),this.quota.init()||this.note({level:"warn",event:"limits.disk.unavailable",fields:{reason:this.quota.unavailable}})}budget(t){return F(t,z(this.options.runtimeRoot))}applyApp(t,s,i=!1){const e=this.budget(t),r=g(t);if(this.cgroups.unavailable===null){const o=JSON.stringify([e.memoryBytes,e.pids]);if(i||this.appliedMemory.get(r)!==o)try{this.cgroups.applyApp(r,e),this.appliedMemory.set(r,o),this.note({level:"info",event:"limits.memory.applied",fields:{app:r,memory_bytes:e.memoryBytes,pids:e.pids}})}catch(n){this.note({level:"warn",event:"limits.memory.failed",fields:{app:r,error:String(n)}})}}if(this.quota.unavailable===null)try{if(this.quota.tag(r,s,{uid:0,gid:0})){const o=String(e.diskBytes);(i||this.appliedDisk.get(s)!==o)&&(this.quota.setLimit(v(s),e.diskBytes),this.quota.setLimit(x(s),I),this.appliedDisk.set(s,o),this.note({level:"info",event:"limits.disk.applied",fields:{app:r,disk_bytes:e.diskBytes}}))}}catch(o){this.note({level:"warn",event:"limits.disk.failed",fields:{app:r,error:String(o)}})}return e}admitBroker(t,s){const i={verdict:"admitted"};if(this.cgroups.unavailable!==null)return i;const e=this.now();for(const[m,f]of this.tickets)e-f.issuedAt>=q&&this.tickets.delete(m);if(this.tickets.has(s))return i;const r=g(t),o=this.cgroups.root,n=`${o}/apps`,a=C(o,r),h=[...this.tickets.values()],l=[...this.brokerRuns].map(([m,f])=>({dir:m,...f})),u=d((m,f,k)=>({max:f,current:this.cgroups.memoryCurrent(m)??0,pending:h.filter(p=>k(p.app)).length*M+l.filter(p=>k(p.app)&&e-p.startedAt<T).reduce((p,R)=>p+E(this.cgroups.memoryCurrent(R.dir)??0),0),hasBroker:h.some(p=>k(p.app))||l.some(p=>k(p.app))}),"level"),y=L({app:u(a,this.cgroups.exists(a)?this.cgroups.memoryLimit(a):this.budget(r).memoryBytes,m=>m===r),apps:u(n,this.cgroups.memoryLimit(n),()=>!0)});return y.verdict==="admitted"&&this.tickets.set(s,{app:r,issuedAt:e}),y}prepareRun(t,s,i=!1){const e=this.options.users.appFor(t)??String(t),r=this.applyApp(e,t);let o=null;if(this.cgroups.unavailable===null)try{o=this.cgroups.createRun(e,j(s,A(4).toString("hex")))}catch(n){this.note({level:"warn",event:"limits.memory.failed",nodeId:s,fields:{app:e,error:String(n)}})}return i&&(this.tickets.delete(s),o!==null&&this.brokerRuns.set(o,{app:g(e),nodeId:s,startedAt:this.now()})),this.purgeIfNearQuota(e,t,r),{cgroupDir:o,app:g(e),nodeId:s,budget:r,tmpfs:G(r)}}purgeIfNearQuota(t,s,i){if(this.quota.unavailable!==null||i.diskBytes===null)return;const e=this.quota.usage().get(v(s))?.usedBytes??0;K(e,i.diskBytes,U)&&this.purgeApp(t,s,"all").catch(()=>{})}confirmStarted(t,s){t.cgroupDir!==null&&!this.cgroups.contains(t.cgroupDir,s)&&this.note({level:"warn",event:"limits.memory.not_joined",nodeId:t.nodeId,fields:{app:t.app,pid:s}})}finishRun(t){if(t.cgroupDir===null)return{oomKilled:!1};const s=t.cgroupDir;this.brokerRuns.delete(s);const i=this.cgroups.oomKilled(s);this.cgroups.kill(s),i&&this.note({level:"warn",event:"run.oom_killed",nodeId:t.nodeId,fields:{app:t.app,memory_bytes:t.budget.memoryBytes}});let e=0;const r=d(()=>{this.cgroups.remove(s)||++e>=20||setTimeout(r,100).unref()},"retry");return r(),{oomKilled:i}}killRun(t){t?.cgroupDir&&this.cgroups.kill(t.cgroupDir)}protectDaemon(t,s){try{this.setOom(t,s)}catch(i){this.note({level:"warn",event:"limits.oom_score.failed",fields:{pid:t,error:String(i)}})}}show(){const t=this.quota.unavailable===null?this.quota.usage():new Map;return{memory:{available:this.cgroups.unavailable===null,reason:this.cgroups.unavailable},disk:{available:this.quota.unavailable===null,reason:this.quota.unavailable},apps:this.options.users.entries().map(({app:s,uid:i})=>{const e=this.budget(s);return{app:s,uid:i,memory_bytes:e.memoryBytes,disk_bytes:e.diskBytes,memory_source:e.memorySource,disk_source:e.diskSource,disk_used_bytes:t.get(v(i))?.usedBytes??null}})}}async diskUsageReport(){const t=this.diskUsage;return t&&this.now()-t.at<W?t.report:(this.diskUsageRun??=this.measureDisk().finally(()=>{this.diskUsageRun=null}),this.diskUsageRun)}async measureDisk(){const t=_(this.options.appsRoot),s=Number(t.bsize),i=Number(t.blocks)*s,e=i-Number(t.bfree)*s,r=[...new Set(this.options.users.entries().map(l=>l.app))],[o,n,a]=await Promise.all([Promise.all(r.map(async l=>({app:l,bytes:await this.walk(b(this.options.appsRoot,g(l)))
|
|
1
|
+
var D=Object.defineProperty;var d=(c,t)=>D(c,"name",{value:t,configurable:!0});import{spawn as w}from"node:child_process";import{randomBytes as A}from"node:crypto";import{existsSync as S,statfsSync as _}from"node:fs";import{dirname as B,join as b}from"node:path";import{fileURLToPath as N}from"node:url";import{Cgroups as O,setOomScoreAdj as P}from"./cgroup.js";import{ADMISSION_TICKET_MS as q,APP_QUOTA_CLEANUP_FRACTION as U,BOOT_RESERVATION_BYTES as M,BOOT_WINDOW_MS as T,CONVERSATIONS_DISK_BYTES as I,appCgroupPath as C,appProjectId as v,bareApp as g,bootPendingBytes as E,budgetFor as F,conversationsProjectId as x,memoryAdmission as L,passesFraction as K,runGroupName as j,selectCachePurges as Q,tmpfsSizes as G}from"./policy.js";import{ProjectQuota as $}from"./quota.js";import{readSettings as z}from"./settings.js";function J(c,t,s,i,e){const r=b(B(N(import.meta.url)),"purge-worker.js");return new Promise((o,n)=>{const a=w("/bin/setpriv",["--reuid",String(s),"--regid",String(i),"--groups",String(i),"--no-new-privs","--",process.execPath,r,c,t,String(e)],{stdio:["ignore","pipe","pipe"]});let h="",l="";a.stdout.on("data",u=>{h+=String(u)}),a.stderr.on("data",u=>{l+=String(u)}),a.once("error",n),a.once("close",u=>{if(u!==0)return n(new Error(`purge worker exited ${u}: ${l.trim().slice(0,200)}`));try{o(JSON.parse(h))}catch(y){n(y)}})})}d(J,"spawnPurge");function V(c){return new Promise(t=>{if(!S(c))return t(null);const s=w("du",["-x","-B1","-s",c],{stdio:["ignore","pipe","ignore"],timeout:6e4});let i="";s.stdout.on("data",e=>{i+=String(e)}),s.once("error",()=>t(null)),s.once("close",(e,r)=>{const o=Number(i.split(/\s/)[0]);t(r===null&&(e===0||e===1)&&i.length>0&&Number.isFinite(o)?o:null)})})}d(V,"walkBytes");const W=6e4;function Y(c){const t=_(c),s=Number(t.blocks);return s>0?1-Number(t.bavail)/s:0}d(Y,"appsDiskUsedFraction");class at{static{d(this,"LimitsManager")}options;cgroups;quota;events=[];appliedMemory=new Map;appliedDisk=new Map;purging=new Set;purge;diskUsedFraction;setOom;now;walk;diskUsage=null;diskUsageRun=null;tickets=new Map;brokerRuns=new Map;constructor(t){this.options=t,this.cgroups=t.cgroups??new O,this.quota=t.quota??new $(t.appsRoot,t.stateDir),this.purge=t.purge??J,this.diskUsedFraction=t.diskUsedFraction??(()=>Y(t.appsRoot)),this.setOom=t.setOom??P,this.now=t.now??Date.now,this.walk=t.walk??V}note(t){this.events.push(t),this.events.length>200&&this.events.splice(0,this.events.length-200)}takeEvents(){return this.events.splice(0,this.events.length)}init(){this.cgroups.init()?this.cgroups.sweep():this.note({level:"warn",event:"limits.memory.unavailable",fields:{reason:this.cgroups.unavailable}}),this.quota.init()||this.note({level:"warn",event:"limits.disk.unavailable",fields:{reason:this.quota.unavailable}})}budget(t){return F(t,z(this.options.runtimeRoot))}applyApp(t,s,i=!1){const e=this.budget(t),r=g(t);if(this.cgroups.unavailable===null){const o=JSON.stringify([e.memoryBytes,e.pids]);if(i||this.appliedMemory.get(r)!==o)try{this.cgroups.applyApp(r,e),this.appliedMemory.set(r,o),this.note({level:"info",event:"limits.memory.applied",fields:{app:r,memory_bytes:e.memoryBytes,pids:e.pids}})}catch(n){this.note({level:"warn",event:"limits.memory.failed",fields:{app:r,error:String(n)}})}}if(this.quota.unavailable===null)try{if(this.quota.tag(r,s,{uid:0,gid:0})){const o=String(e.diskBytes);(i||this.appliedDisk.get(s)!==o)&&(this.quota.setLimit(v(s),e.diskBytes),this.quota.setLimit(x(s),I),this.appliedDisk.set(s,o),this.note({level:"info",event:"limits.disk.applied",fields:{app:r,disk_bytes:e.diskBytes}}))}}catch(o){this.note({level:"warn",event:"limits.disk.failed",fields:{app:r,error:String(o)}})}return e}admitBroker(t,s){const i={verdict:"admitted"};if(this.cgroups.unavailable!==null)return i;const e=this.now();for(const[m,f]of this.tickets)e-f.issuedAt>=q&&this.tickets.delete(m);if(this.tickets.has(s))return i;const r=g(t),o=this.cgroups.root,n=`${o}/apps`,a=C(o,r),h=[...this.tickets.values()],l=[...this.brokerRuns].map(([m,f])=>({dir:m,...f})),u=d((m,f,k)=>({max:f,current:this.cgroups.memoryCurrent(m)??0,pending:h.filter(p=>k(p.app)).length*M+l.filter(p=>k(p.app)&&e-p.startedAt<T).reduce((p,R)=>p+E(this.cgroups.memoryCurrent(R.dir)??0),0),hasBroker:h.some(p=>k(p.app))||l.some(p=>k(p.app))}),"level"),y=L({app:u(a,this.cgroups.exists(a)?this.cgroups.memoryLimit(a):this.budget(r).memoryBytes,m=>m===r),apps:u(n,this.cgroups.memoryLimit(n),()=>!0)});return y.verdict==="admitted"&&this.tickets.set(s,{app:r,issuedAt:e}),y}prepareRun(t,s,i=!1){const e=this.options.users.appFor(t)??String(t),r=this.applyApp(e,t);let o=null;if(this.cgroups.unavailable===null)try{o=this.cgroups.createRun(e,j(s,A(4).toString("hex")))}catch(n){this.note({level:"warn",event:"limits.memory.failed",nodeId:s,fields:{app:e,error:String(n)}})}return i&&(this.tickets.delete(s),o!==null&&this.brokerRuns.set(o,{app:g(e),nodeId:s,startedAt:this.now()})),this.purgeIfNearQuota(e,t,r),{cgroupDir:o,app:g(e),nodeId:s,budget:r,tmpfs:G(r)}}purgeIfNearQuota(t,s,i){if(this.quota.unavailable!==null||i.diskBytes===null)return;const e=this.quota.usage().get(v(s))?.usedBytes??0;K(e,i.diskBytes,U)&&this.purgeApp(t,s,"all").catch(()=>{})}confirmStarted(t,s){t.cgroupDir!==null&&!this.cgroups.contains(t.cgroupDir,s)&&this.note({level:"warn",event:"limits.memory.not_joined",nodeId:t.nodeId,fields:{app:t.app,pid:s}})}finishRun(t){if(t.cgroupDir===null)return{oomKilled:!1};const s=t.cgroupDir;this.brokerRuns.delete(s);const i=this.cgroups.oomKilled(s);this.cgroups.kill(s),i&&this.note({level:"warn",event:"run.oom_killed",nodeId:t.nodeId,fields:{app:t.app,memory_bytes:t.budget.memoryBytes}});let e=0;const r=d(()=>{this.cgroups.remove(s)||++e>=20||setTimeout(r,100).unref()},"retry");return r(),{oomKilled:i}}killRun(t){t?.cgroupDir&&this.cgroups.kill(t.cgroupDir)}protectDaemon(t,s){try{this.setOom(t,s)}catch(i){this.note({level:"warn",event:"limits.oom_score.failed",fields:{pid:t,error:String(i)}})}}show(){const t=this.quota.unavailable===null?this.quota.usage():new Map;return{memory:{available:this.cgroups.unavailable===null,reason:this.cgroups.unavailable},disk:{available:this.quota.unavailable===null,reason:this.quota.unavailable},apps:this.options.users.entries().map(({app:s,uid:i})=>{const e=this.budget(s);return{app:s,uid:i,memory_bytes:e.memoryBytes,disk_bytes:e.diskBytes,memory_source:e.memorySource,disk_source:e.diskSource,disk_used_bytes:t.get(v(i))?.usedBytes??null}})}}async diskUsageReport(){const t=this.diskUsage;return t&&this.now()-t.at<W?t.report:(this.diskUsageRun??=this.measureDisk().finally(()=>{this.diskUsageRun=null}),this.diskUsageRun)}async measureDisk(){const t=_(this.options.appsRoot),s=Number(t.bsize),i=Number(t.blocks)*s,e=i-Number(t.bfree)*s,r=[...new Set(this.options.users.entries().map(l=>l.app))],[o,n,a]=await Promise.all([Promise.all(r.map(async l=>({app:l,bytes:await this.walk(b(this.options.appsRoot,g(l)))}))),this.walk(b(B(this.options.appsRoot),"user")),this.walk(this.options.runtimeRoot)]),h={total_bytes:i,used_bytes:e,apps:o.sort((l,u)=>(u.bytes??-1)-(l.bytes??-1)||l.app.localeCompare(u.app)),user_bytes:n,runtime_bytes:a,measured_at:this.now()};return this.diskUsage={at:this.now(),report:h},h}async purgeApp(t,s,i){const e=g(t),r=b(this.options.appsRoot,e,"cache");if(this.purging.has(e)||!S(r))return null;this.purging.add(e);try{const o=await this.purge(r,i,s,this.options.dataGid,this.now());return this.note({level:"info",event:"app.cache.purged",fields:{app:e,mode:i,files:o.files,bytes:o.bytes,dirs:o.dirs}}),{app:e,mode:i,files:o.files,bytes:o.bytes}}catch(o){return this.note({level:"warn",event:"app.cache.purge_failed",fields:{app:e,mode:i,error:String(o)}}),null}finally{this.purging.delete(e)}}async cachePass(){const t=this.quota.unavailable===null?this.quota.usage():new Map;let s=0;try{s=this.diskUsedFraction()}catch{}const i=this.options.users.entries(),e=Q(i.map(({app:n,uid:a})=>({app:n,usedBytes:t.get(v(a))?.usedBytes??0,limitBytes:this.budget(n).diskBytes})),s),r=new Map(i.map(n=>[n.app,n.uid]));return(await Promise.all(e.map(({app:n,mode:a})=>this.purgeApp(n,r.get(n),a)))).filter(n=>n!==null)}}export{at as LimitsManager,J as spawnPurge,V as walkBytes};
|
package/dist/launcher/spec.d.ts
CHANGED
|
@@ -116,10 +116,10 @@ export interface DiskUsageReport {
|
|
|
116
116
|
/** The data volume the apps live on, from statfs. */
|
|
117
117
|
total_bytes: number;
|
|
118
118
|
used_bytes: number;
|
|
119
|
-
/** Bytes under each registered app's folder, largest first. */
|
|
119
|
+
/** Bytes under each registered app's folder, largest first; null where the walk failed. */
|
|
120
120
|
apps: Array<{
|
|
121
121
|
app: string;
|
|
122
|
-
bytes: number;
|
|
122
|
+
bytes: number | null;
|
|
123
123
|
}>;
|
|
124
124
|
user_bytes: number | null;
|
|
125
125
|
runtime_bytes: number | null;
|