@north-light/cli 0.3.548 → 0.3.550

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/custom-objects.d.ts +1 -1
  4. package/dist/api/dto/docs.d.ts +12 -0
  5. package/dist/api/dto/objects.d.ts +17 -1
  6. package/dist/api/dto/objects.js +1 -1
  7. package/dist/api/dto/permissions.d.ts +76 -0
  8. package/dist/api/dto/permissions.js +1 -0
  9. package/dist/api/index.d.ts +3 -2
  10. package/dist/api/index.js +1 -1
  11. package/dist/api/routes.d.ts +4 -0
  12. package/dist/api/routes.js +1 -1
  13. package/dist/builtin-memory/crouter-concepts/documents.md +1 -1
  14. package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +2 -2
  15. package/dist/builtin-memory/crouter-sdk/release-notes.md +11 -0
  16. package/dist/builtin-memory/internal/INDEX.md +2 -1
  17. package/dist/builtin-memory/internal/agent-shaping.md +1 -1
  18. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  19. package/dist/builtin-memory/northlight-apps/guides/files-and-documents.md +3 -3
  20. package/dist/builtin-memory/northlight-apps/guides/many-users.md +1 -1
  21. package/dist/builtin-memory/northlight-apps/guides/test-your-app.md +1 -1
  22. package/dist/clients/attach/viewer.js +9 -9
  23. package/dist/commands/canvas/list.js +2 -2
  24. package/dist/commands/canvas/share.d.ts +2 -0
  25. package/dist/commands/canvas/share.js +2 -0
  26. package/dist/commands/canvas.js +1 -1
  27. package/dist/commands/doc/edit.js +1 -1
  28. package/dist/commands/doc/shared.js +1 -1
  29. package/dist/commands/doc/write.js +1 -1
  30. package/dist/commands/sys/sync-skills.js +2 -2
  31. package/dist/core/canvas/canvas.js +20 -20
  32. package/dist/core/canvas/db.js +4 -4
  33. package/dist/core/canvas/export-sqlite.js +2 -2
  34. package/dist/core/canvas/import-sqlite.js +5 -5
  35. package/dist/core/canvas/outcome-actions.js +3 -3
  36. package/dist/core/canvas/person.js +11 -9
  37. package/dist/core/canvas/schema-pg.sql +49 -5
  38. package/dist/core/environments.js +1 -1
  39. package/dist/core/feed/feed.js +1 -1
  40. package/dist/core/grants/mirror.d.ts +9 -1
  41. package/dist/core/grants/mirror.js +2 -2
  42. package/dist/core/grants/sync.js +1 -1
  43. package/dist/core/graph/access.d.ts +8 -49
  44. package/dist/core/graph/access.js +4 -7
  45. package/dist/core/graph/custom.d.ts +5 -2
  46. package/dist/core/graph/custom.js +9 -9
  47. package/dist/core/graph/deletion.js +5 -4
  48. package/dist/core/graph/documents.d.ts +12 -3
  49. package/dist/core/graph/documents.js +13 -7
  50. package/dist/core/graph/edges.js +3 -3
  51. package/dist/core/graph/events.js +12 -8
  52. package/dist/core/graph/kinds/document.js +4 -4
  53. package/dist/core/graph/kinds/kind.js +1 -1
  54. package/dist/core/graph/kinds/node.js +5 -5
  55. package/dist/core/graph/names.d.ts +16 -1
  56. package/dist/core/graph/names.js +11 -3
  57. package/dist/core/graph/objects.d.ts +19 -2
  58. package/dist/core/graph/objects.js +16 -10
  59. package/dist/core/graph/package-docs.js +5 -5
  60. package/dist/core/graph/package-files.js +2 -2
  61. package/dist/core/graph/reader.js +3 -3
  62. package/dist/core/graph/repo-sync/exchange.js +2 -2
  63. package/dist/core/graph/rules.d.ts +7 -5
  64. package/dist/core/graph/rules.js +3 -3
  65. package/dist/core/graph/types.d.ts +12 -1
  66. package/dist/core/graph/types.js +1 -1
  67. package/dist/core/graph/watches.d.ts +1 -1
  68. package/dist/core/graph/watches.js +9 -8
  69. package/dist/core/peer/envelope.d.ts +1 -1
  70. package/dist/core/peer/envelope.js +1 -1
  71. package/dist/core/peer/stubs.d.ts +1 -1
  72. package/dist/core/peer/stubs.js +1 -1
  73. package/dist/core/permissions/boot.d.ts +17 -0
  74. package/dist/core/permissions/boot.js +18 -0
  75. package/dist/core/permissions/builtin.d.ts +8 -0
  76. package/dist/core/permissions/builtin.js +20 -0
  77. package/dist/core/permissions/ceiling.d.ts +57 -0
  78. package/dist/core/permissions/ceiling.js +22 -0
  79. package/dist/core/permissions/compile.d.ts +14 -0
  80. package/dist/core/permissions/compile.js +1 -0
  81. package/dist/core/permissions/errors.d.ts +7 -0
  82. package/dist/core/permissions/errors.js +1 -0
  83. package/dist/core/permissions/evaluator.d.ts +28 -0
  84. package/dist/core/permissions/evaluator.js +6 -0
  85. package/dist/core/permissions/facts.d.ts +56 -0
  86. package/dist/core/permissions/facts.js +2 -0
  87. package/dist/core/permissions/schema-parser.d.ts +29 -0
  88. package/dist/core/permissions/schema-parser.js +2 -0
  89. package/dist/core/permissions/schemas.d.ts +21 -0
  90. package/dist/core/permissions/schemas.js +2 -0
  91. package/dist/core/permissions/sql.d.ts +16 -0
  92. package/dist/core/permissions/sql.js +1 -0
  93. package/dist/core/runtime/broker/frame-client.d.ts +1 -1
  94. package/dist/core/runtime/broker/frame-client.js +1 -1
  95. package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
  96. package/dist/core/runtime/broker/frame-dispatch.js +1 -1
  97. package/dist/core/runtime/broker/frame-read.d.ts +4 -3
  98. package/dist/core/runtime/broker/frame-read.js +1 -1
  99. package/dist/core/runtime/broker-protocol.d.ts +1 -1
  100. package/dist/core/runtime/host.js +3 -3
  101. package/dist/core/runtime/placement.js +1 -1
  102. package/dist/core/runtime/roadmap.js +1 -1
  103. package/dist/core/substrate/delivery/corpus.d.ts +2 -2
  104. package/dist/core/substrate/delivery/corpus.js +3 -3
  105. package/dist/core/substrate/delivery/deliver.js +2 -2
  106. package/dist/core/substrate/delivery/render-event.js +6 -6
  107. package/dist/core/substrate/subject-fields.d.ts +2 -0
  108. package/dist/core/substrate/subject.js +1 -1
  109. package/dist/daemon/api/app-listener.js +12 -12
  110. package/dist/daemon/api/bridge.d.ts +1 -1
  111. package/dist/daemon/api/bridge.js +4 -4
  112. package/dist/daemon/api/handlers/attach.d.ts +4 -1
  113. package/dist/daemon/api/handlers/attach.js +1 -1
  114. package/dist/daemon/api/handlers/canvas.js +5 -5
  115. package/dist/daemon/api/handlers/check.d.ts +2 -0
  116. package/dist/daemon/api/handlers/check.js +1 -0
  117. package/dist/daemon/api/handlers/custom-objects.js +2 -2
  118. package/dist/daemon/api/handlers/docs.js +1 -1
  119. package/dist/daemon/api/handlers/files.js +1 -1
  120. package/dist/daemon/api/handlers/memory.js +7 -6
  121. package/dist/daemon/api/handlers/nodes.js +1 -1
  122. package/dist/daemon/api/handlers/objects.js +3 -3
  123. package/dist/daemon/api/handlers/relations.d.ts +2 -0
  124. package/dist/daemon/api/handlers/relations.js +1 -0
  125. package/dist/daemon/api/handlers/schemas.d.ts +12 -0
  126. package/dist/daemon/api/handlers/schemas.js +1 -0
  127. package/dist/daemon/api/handlers/shares.d.ts +2 -0
  128. package/dist/daemon/api/handlers/shares.js +1 -0
  129. package/dist/daemon/api/operations.js +1 -1
  130. package/dist/daemon/api/router.d.ts +10 -6
  131. package/dist/daemon/api/router.js +1 -1
  132. package/dist/daemon/api/server.js +1 -1
  133. package/dist/daemon/api/vendor-signin.js +21 -24
  134. package/dist/daemon/crtrd.js +12 -12
  135. package/dist/daemon/reconcilers/delivery-lane.js +2 -2
  136. package/dist/migrations/004-canvas-documents/fields.d.ts +1 -1
  137. package/dist/migrations/004-canvas-documents/fields.js +2 -2
  138. package/docs/apps/guides/files-and-documents.md +3 -3
  139. package/docs/apps/guides/many-users.md +1 -1
  140. package/docs/apps/guides/test-your-app.md +1 -1
  141. package/docs/cli/documents-and-preferences.md +1 -1
  142. package/docs/concepts/documents.md +1 -1
  143. package/docs/concepts/scopes-and-trust.md +2 -2
  144. package/docs/sdk/release-notes.md +11 -0
  145. package/package.json +2 -2
  146. package/packages/crouter-identity/dist/index.js +14 -1
  147. package/packages/crouter-identity/package.json +1 -1
  148. package/runtime.lock.json +13 -13
  149. package/dist/core/graph/spaces/app.d.ts +0 -3
  150. package/dist/core/graph/spaces/app.js +0 -1
  151. package/dist/core/graph/spaces/index.d.ts +0 -18
  152. package/dist/core/graph/spaces/index.js +0 -1
  153. package/dist/core/graph/spaces/own-memory.d.ts +0 -18
  154. package/dist/core/graph/spaces/own-memory.js +0 -1
  155. package/dist/core/graph/spaces/person.d.ts +0 -5
  156. package/dist/core/graph/spaces/person.js +0 -3
  157. package/dist/core/graph/spaces/repo.d.ts +0 -14
  158. package/dist/core/graph/spaces/repo.js +0 -1
  159. package/dist/core/runs/confinement.d.ts +0 -25
  160. package/dist/core/runs/confinement.js +0 -2
@@ -1,8 +1,5 @@
1
- var R=Object.defineProperty;var s=(n,t)=>R(n,"name",{value:t,configurable:!0});import{covers as S}from"@north-light/identity";import{objectPlace as O,runPlace as x,runsSeeEachOther as N}from"../runs/confinement.js";import{missingOwnMemoryScope as w}from"./spaces/own-memory.js";import{SPACES as g,identityOf as l}from"./spaces/index.js";import{insideRepo as L}from"./spaces/repo.js";async function C(n,t){return await n.prepare(`WITH RECURSIVE chain(id, depth) AS (
2
- SELECT owner, 1 FROM objects WHERE id = ? AND owner IS NOT NULL
1
+ var c=Object.defineProperty;var n=(e,t)=>c(e,"name",{value:t,configurable:!0});import{check as r,resolveReader as p}from"../permissions/evaluator.js";async function s(e,t){return await e.prepare(`WITH RECURSIVE chain(id, owner, depth) AS (
2
+ SELECT id, owner, 0 FROM objects WHERE id = ?
3
3
  UNION ALL
4
- SELECT o.owner, c.depth + 1 FROM objects o JOIN chain c ON o.id = c.id WHERE o.owner IS NOT NULL
5
- ) SELECT o.* FROM chain c CROSS JOIN objects o WHERE o.id = c.id ORDER BY c.depth`).all(t.id)}s(C,"ownerChain");async function P(n,t){return(await o(n,t)).space}s(P,"spaceOf");async function o(n,t){const a=await C(n,t),i=a.length?a[a.length-1]:t,e=g[i.type];if(!e)throw new Error(`${t.type} ${t.id} has no space: its owner chain ends at ${i.type} ${i.id}`);const p=a.length?[...a.slice(0,-1).reverse(),t]:[];return{space:i,path:p,impl:e}}s(o,"locate");async function y(n,t){return t.type!=="document"?!1:(await n.prepare("SELECT source_path FROM documents WHERE object = ?").get(t.id))?.source_path!=null}s(y,"isShipped");async function v(n,t){return(await n.prepare(`SELECT a.* FROM objects a WHERE a.type = 'app' AND a.deleted_at IS NULL AND EXISTS (
6
- SELECT 1 FROM objects o JOIN documents d ON d.object = o.id
7
- WHERE o.owner = a.id AND o.type = 'document' AND o.deleted_at IS NULL AND d.source_path IS NOT NULL)
8
- ORDER BY a.name = 'crtr', a.name`).all()).filter(i=>f(n,t,i))}s(v,"toolApps");function f(n,t,a){return a.type==="app"}s(f,"mayUseTools");const T=s(n=>n.class!=="app"||n.place!==void 0,"isRun");async function A(n,t,a){const i=`${(await l(n,a)).external_id.replace(/^app:/,"")}:`;return t.some(e=>e.startsWith(i)&&!e.slice(i.length).includes(":"))}s(A,"holdsProviderGroup");async function I(n,t,a){return t.class==="person"?!0:t.class==="object"?t.owner.id===a.id:t.class==="app"?t.app.id===a.id||a.type==="profile"&&a.owner===t.app.id:t.app.id===a.id||a.type==="profile"&&t.profile?.id===a.id||a.type==="repo"&&await L(n,t,a,"rw")}s(I,"mayActAs");async function _(n,t,a){if(t.class!=="node"&&!(t.class==="app"&&t.place!==void 0))return!0;const i=t.class==="app"?t.place:await x(t.node.id,n);if(i===null)return!0;const e=await O(a.id,n);return e===null?!i.isolated:N(i,e)}s(_,"customReachable");const h=s(n=>n.type.includes("."),"isCustom");async function J(n,t,a){return await E(n,await o(n,t),t,a)}s(J,"objectsTargets");async function E(n,{space:t,path:a},i,e){const p=[];if(t.type==="person")p.push("user");else if(t.type==="app"&&a.length>0){const c=a[0];if(c.type==="profile")a.length>1&&p.push(`profile:${(await l(n,c)).external_id}`);else if(c.type==="node"||h(c)){const r=(await l(n,t)).external_id;r!==e&&p.push(r)}}return i.type==="node"&&p.push("node"),p}s(E,"targetsOf");async function u(n,t,a,i,e){if(t.class==="person"||t.scopes===null)return!1;const p=t.scopes;if(p.length===0||a!=="link"&&i.space.type==="app"&&t.app.id===i.space.id)return!1;const c=(await l(n,t.app)).external_id;return(await E(n,i,e,c)).some(r=>{const m=`crtr:objects:${a}:${r}`;return p.some(d=>S(d,m))})}s(u,"holdsInLocated");async function M(n,t,a,i){return t.class==="person"||t.scopes===null?!1:await u(n,t,a,await o(n,i),i)}s(M,"holdsObjectsScope");async function B(n,t,a,i,e){const p=i.owner===null?null:await n.prepareUncached("SELECT * FROM objects WHERE id = ?").get(i.owner)??null;if(p===null)return!1;const c={class:"app",app:t,scopes:a??[]};return await I(n,c,p)?a===null||await M(n,c,"link",e):!1}s(B,"linkAuthorizes");async function Y(n,t,a){return(await U(n,a))(t)}s(Y,"canRead");async function U(n,t){let a=null,i=null;return async e=>{if(e.class==="person")return!0;if(a??=await o(n,t),a.space.type==="app"&&a.path.length>0){if(i??=await y(n,t),i&&f(n,e,a.space))return!0;if(h(t)){if(e.app.id===a.space.id&&!await _(n,e,t))return!1;if(e.app.id!==a.space.id&&e.scopes!==null&&f(n,e,a.space)&&(t.privacy==="public"||T(e)&&await A(n,e.scopes,a.space)))return!0}}return await u(n,e,"read",a,t)||e.class==="object"&&h(e.object)&&await u(n,e,"watch",a,t)?!0:await a.impl.canRead(n,e,a.space,a.path,t)}}s(U,"readCheck");async function z(n,t,a){if(await y(n,a))return!1;const i=await o(n,a);if(await u(n,t,"write",i,a))return!0;const{space:e,path:p,impl:c}=i;return await c.canChange(n,t,e,p,a)}s(z,"canChange");async function G(n,t,a,i){if(t.class==="person")return null;const{space:e,path:p}=await o(n,a);return e.type==="app"&&t.app.id===e.id?await w(n,t,e,p,a,i):null}s(G,"missingMemoryScope");async function V(n,t,a,i){if(t.class==="person")return null;const e=await o(n,a),{space:p}=e;return p.type!=="app"||t.app.id!==p.id?null:await w(n,t,p,e.path.length===0?[a]:[...e.path],null,i)}s(V,"missingMemoryScopeUnder");export{z as canChange,Y as canRead,_ as customReachable,M as holdsObjectsScope,A as holdsProviderGroup,y as isShipped,B as linkAuthorizes,I as mayActAs,f as mayUseTools,G as missingMemoryScope,V as missingMemoryScopeUnder,J as objectsTargets,C as ownerChain,U as readCheck,P as spaceOf,v as toolApps};
4
+ SELECT o.id, o.owner, c.depth + 1 FROM objects o JOIN chain c ON o.id = c.owner WHERE c.depth < 12
5
+ ) SELECT o.* FROM chain c JOIN objects o ON o.id = c.id ORDER BY c.depth DESC LIMIT 1`).get(t.id)??t}n(s,"spaceOf");async function d(e,t){return t.type!=="document"?!1:(await e.prepare("SELECT source_path FROM documents WHERE object = ?").get(t.id))?.source_path!=null}n(d,"isShipped");async function w(e,t,a){return await r(e,t,"read",a.id)}n(w,"canRead");async function f(e,t,a){return await d(e,a)||a.privacy!=="public"&&(await s(e,a)).type==="repo"&&a.owner!==null?!1:await r(e,t,"edit",a.id)}n(f,"canChange");async function h(e,t,a,i){if(t.class==="person")return!1;const o=await p(e,t);return a==="write"?await r(e,o,"edit",i.id):await r(e,o,"read",i.id,a==="read"?{}:{verb:a})}n(h,"holdsObjectsScope");export{f as canChange,w as canRead,h as holdsObjectsScope,d as isShipped,s as spaceOf};
@@ -1,8 +1,11 @@
1
1
  import type { Level, ObjectRow, Privacy, Reader } from './types.js';
2
- import { customReachable, mayActAs } from './access.js';
3
2
  import { onQueueSignal } from './receivers.js';
4
3
  import type { CanvasDb } from '../canvas/db.js';
5
- export { customReachable, mayActAs, onQueueSignal };
4
+ /** Whether this reader may register, publish or watch as an object owned by `owner`. */
5
+ export declare function mayActAs(db: CanvasDb, reader: Reader, owner: ObjectRow): Promise<boolean>;
6
+ /** Whether the reader reaches the custom object: it may read it. */
7
+ export declare function customReachable(db: CanvasDb, reader: Reader, object: ObjectRow): Promise<boolean>;
8
+ export { onQueueSignal };
6
9
  export type CustomReceiver = 'queue' | {
7
10
  url: string;
8
11
  } | {
@@ -1,12 +1,12 @@
1
- var $=Object.defineProperty;var l=(e,t)=>$(e,"name",{value:t,configurable:!0});import{createHash as x,randomBytes as J,timingSafeEqual as P}from"node:crypto";import{CrtrError as j,usage as f}from"../errors.js";import{ExitCode as S}from"../../types.js";import{createObject as q,liveDocumentNamed as F}from"./objects.js";import{invalidateCustomKind as A}from"./kinds/registry.js";import{customReachable as D,mayActAs as k}from"./access.js";import{publish as O,HOP_LIMIT as M,HopLimitError as B}from"./events.js";import{checkoutsForCwd as N}from"./repo-sync/repos.js";import{onQueueSignal as K,signalQueue as b}from"./receivers.js";import{receiverEndpoint as V,isPublicReceiverAddress as G}from"./receiver-url.js";import{isIP as Q}from"node:net";import{KIND as L,LOCAL as H,MAX_KIND_EVENTS as Y,RESERVED_KIND_ROOTS as z,eventCatalogReasons as X}from"../plugin-objects-manifest.js";const Z=["public","friends","private"];function h(e,t){throw new j(e,t,S.GENERAL)}l(h,"conflict");function R(e){return x("sha256").update(e).digest("hex")}l(R,"hash");function I(){return`crtr_pub_${J(32).toString("base64url")}`}l(I,"issueToken");function ee(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw f("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const i=V(e.url).hostname.replace(/^\[|\]$/g,"");if(Q(i)&&!G(i))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw f(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw f("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}l(ee,"receiverJson");async function Ee(e,t,i){const{kind:n,name:a,owner:s,description:o,events:u,receiver:d,rotateToken:w,privacy:p}=i;if(p!==void 0&&!Z.includes(p))throw f(`invalid privacy ${JSON.stringify(p)}`);if(await k(e,t,s)||h("forbidden",`cannot act as ${s.id}`),!L.test(n)||z.includes(n.split(".")[0]))throw f(`invalid or reserved kind ${n}`);if(!a.trim()||a!==a.trim()||a.includes("\0"))throw f(`invalid object name ${JSON.stringify(a)}`);const m=ee(d);if(d!=="queue"&&d!==null&&d!==void 0&&"argv"in d&&t.class!=="person"&&t.class!=="node"&&h("argv_forbidden","argv receiver requires a person or node principal"),u===void 0){const c=await e.prepare("SELECT source FROM object_kinds WHERE name = ?").get(n);if(c?.source!=="manifest"&&c?.source!=="peer")throw f("events is required unless the kind is declared by an installed plugin")}else if(Object.keys(u).length>Y||Object.entries(u).some(([c,_])=>!H.test(c)||!Number.isInteger(_?.level)||_.level<1||_.level>10||_.supersedes!==void 0&&typeof _.supersedes!="boolean"||_.ends_wait!==void 0&&typeof _.ends_wait!="boolean"))throw f("invalid custom event catalog");const r=await F(e,s.id,a);if(r&&!await D(e,t,r)&&h("forbidden",`cannot act as ${s.id}`),r&&t.class!=="person"&&t.class!=="node"){const c=(await e.prepare("SELECT receiver_json FROM custom_objects WHERE object = ?").get(r.id))?.receiver_json;c&&typeof JSON.parse(c)=="object"&&"argv"in JSON.parse(c)&&h("argv_forbidden","argv receiver requires a person or node principal")}r&&r.type!==n&&h("name_taken",`${s.name}/${a} is already held by ${r.id}`);const g=u===void 0?null:await te(e,{name:n,owner:s,description:o,events:u,read:null,source:"register"});if(!r){const c=await q(e,{type:n,kind:null,owner:s.id,privacy:p??"public",name:a,preview:""}),_=I();return await e.prepare(`INSERT INTO custom_objects (object, description, receiver_json, token_hash)
2
- VALUES (?, ?, ?, ?)`).run(c.id,o,m,R(_)),t.class==="node"&&await e.prepare("UPDATE custom_objects SET registered_by = ? WHERE object = ?").run(t.node.id,c.id),m!==null&&await e.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(c.id),await O(e,{event:"created",object:c,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{}},async C=>await N(e,C)),{object:c,token:_,created:!0,changed:[]}}const v=await e.prepare("SELECT description, receiver_json FROM custom_objects WHERE object = ?").get(r.id);v||h("name_taken",`${s.name}/${a} is not a custom object`);const E=[];if(v.description!==o&&E.push("description"),v.receiver_json!==m&&E.push("receiver"),g?.changed.includes("events")&&E.push("events"),g?.changed.includes("description")&&!E.includes("description")&&E.push("description"),p!==void 0&&r.privacy!==p&&E.push("privacy"),w&&E.push("token"),E.length===0)return{object:r,created:!1,changed:E};const y=w?I():void 0,T=E.includes("privacy")?{...r,privacy:p}:r;if(T!==r&&await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(p,r.id),await e.prepare("UPDATE custom_objects SET description = ?, receiver_json = ?, token_hash = COALESCE(?, token_hash) WHERE object = ?").run(o,m,y===void 0?null:R(y),r.id),await e.prepare("UPDATE objects SET reception = ? WHERE id = ?").run(m===null?0:2,r.id),v.receiver_json!==m){const c=Date.now();if(m===null){const _=await e.prepare(`UPDATE deliveries SET canceled_at = ?, updated_at = ?
3
- WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(c,c,r.id);await e.prepare("UPDATE custom_objects SET dropped = dropped + ? WHERE object = ?").run(_.changes,r.id)}else await e.prepare(`UPDATE deliveries SET next_attempt_at = ?, updated_at = ?
4
- WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(m==='"queue"'?null:c,c,r.id);b(r.id)}return w&&b(r.id),await O(e,{event:"configured",object:T,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{changed:E.join(",")}},async c=>await N(e,c)),{object:T,...y===void 0?{}:{token:y},created:!1,changed:E}}l(Ee,"registerCustomObject");async function te(e,t){const{name:i,owner:n,description:a,events:s,read:o,source:u}=t;if(!L.test(i))throw f(`invalid kind ${i}`);if(X(s,i).length>0)throw f("invalid custom event catalog");const d=await e.prepare("SELECT owner, description, events_json, read_json, source, declared FROM object_kinds WHERE name = ?").get(i),w=W(s),p=u==="manifest"&&o!==null?JSON.stringify({command:o.command}):null,m=new Date().toISOString();if(!d)return await e.prepare(`INSERT INTO object_kinds (name, owner, description, events_json, created_at, updated_at, read_json, source, declared)
5
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)`).run(i,n.id,a,w,m,m,p,u),A(e,i),{outcome:"inserted",changed:[]};const r=d.owner===n.id;if(u==="peer"){if(d.source!=="peer")return{outcome:"skipped",changed:[]}}else if(d.source!=="peer"&&(r||h("kind_owned",`${i} belongs to another owner`),u==="register"&&d.source==="manifest"))return{outcome:"skipped",changed:[]};const g=[];return W(JSON.parse(d.events_json))!==w&&g.push("events"),d.description!==a&&g.push("description"),(d.read_json??null)!==p&&g.push("read"),g.length===0&&r&&d.source===u&&d.declared===1?{outcome:"unchanged",changed:g}:(await e.prepare("UPDATE object_kinds SET owner = ?, description = ?, events_json = ?, read_json = ?, source = ?, declared = 1, updated_at = ? WHERE name = ?").run(n.id,a,w,p,u,m,i),A(e,i),{outcome:"updated",changed:g})}l(te,"upsertKind");async function _e(e,t,i,n){const a=await e.prepare("SELECT events_json FROM object_kinds WHERE name = ?").get(t.type);if(!a||t.deleted_at!==null)throw f(`${t.id} is not a live custom object`);const s=JSON.parse(a.events_json);if(!H.test(n.event)||!Object.hasOwn(s,n.event))throw new j("unknown_event",`${t.type} declares ${Object.keys(s).join(", ")}, not ${n.event}`,S.USAGE,{declared:Object.keys(s),event:n.event});if(typeof n.summary!="string")throw f("summary must be text");const o=n.state===void 0?null:JSON.stringify(n.state);if(n.state!==void 0&&o===void 0)throw f("state must be JSON-serializable");if(Buffer.byteLength(JSON.stringify({summary:n.summary,...n.state===void 0?{}:{state:n.state}}))>64*1024)throw new j("body_too_large","custom event summary and state exceed 64 KiB",S.USAGE);if(o!==null&&Buffer.byteLength(o)>64*1024)throw f("state exceeds 64 KiB");const u=n.cause??null,d=u===null?null:await e.prepare(`SELECT depth FROM messages WHERE message_id = ?
6
- UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(u,u);if(u!==null&&d===void 0)throw new j("unknown_cause",`no message ${u}`,S.USAGE);if(d&&d.depth>=M)throw new B(d.depth+1);const w=new Date().toISOString();return await e.prepare("UPDATE custom_objects SET summary = ?, state_json = ?, published_at = ? WHERE object = ?").run(n.summary,o,w,t.id),await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(n.summary.split(`
7
- `)[0],t.id),await O(e,{event:`${t.type}.${n.event}`,object:t,actor:i,attrs:{},messageBody:{summary:n.summary,...n.state===void 0?{}:{state:n.state}},...n.cause===void 0?{}:{cause:n.cause}},async p=>await N(e,p))}l(_e,"publishCustom");async function we(e,t,i){if(t.deleted_at!==null)return;await O(e,{event:"deleted",object:t,actor:i,attrs:{}},async a=>await N(e,a));const n=new Date().toISOString();await e.prepare("UPDATE objects SET deleted_at = ?, reception = 0 WHERE id = ?").run(n,t.id),await e.prepare("UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL").run(Date.now(),Date.now(),t.id),b(t.id)}l(we,"unregisterCustomObject");function W(e){return JSON.stringify(Object.fromEntries(Object.entries(e).sort(([t],[i])=>t.localeCompare(i)).map(([t,i])=>[t,{level:i.level,...i.supersedes===void 0?{}:{supersedes:i.supersedes},...i.ends_wait===void 0?{}:{ends_wait:i.ends_wait}}])))}l(W,"canonicalEvents");async function ge(e,t,i=0){return await U(e,t.id,i,"pending")}l(ge,"pendingFor");async function he(e,t){const i=await e.prepare("SELECT destination_id FROM deliveries WHERE delivery_id = ?").get(t);return i?(await U(e,i.destination_id,-1,"handed_off")).find(n=>n.delivery_id===t)??null:null}l(he,"pushLineFor");async function U(e,t,i,n){return(await e.prepare(`SELECT d.delivery_id, m.message_id, m.seq, m.event, m.level, m.object_id, m.actor_id, m.body_json, m.body_ref, m.depth, m.created_at,
1
+ var C=Object.defineProperty;var l=(e,t)=>C(e,"name",{value:t,configurable:!0});import{createHash as x,randomBytes as P,timingSafeEqual as $}from"node:crypto";import{CrtrError as j,usage as f}from"../errors.js";import{ExitCode as S}from"../../types.js";import{createObject as J,liveDocumentNamed as q}from"./objects.js";import{invalidateCustomKind as A}from"./kinds/registry.js";import{publish as O,HOP_LIMIT as F,HopLimitError as M}from"./events.js";import{checkoutsForCwd as N}from"./repo-sync/repos.js";import{onQueueSignal as B,signalQueue as b}from"./receivers.js";import{receiverEndpoint as K,isPublicReceiverAddress as V}from"./receiver-url.js";import{isIP as G}from"node:net";import{KIND as k,LOCAL as D,MAX_KIND_EVENTS as Q,RESERVED_KIND_ROOTS as Y,eventCatalogReasons as z}from"../plugin-objects-manifest.js";import{check as L}from"../permissions/evaluator.js";import{userOf as X}from"../permissions/evaluator.js";import{nodePlace as Z}from"../permissions/ceiling.js";import{PermissionError as ee}from"../permissions/errors.js";import{writeActsFor as te}from"../permissions/facts.js";async function ne(e,t,i){return t.class!=="person"&&t.app.id===i.id?!0:await L(e,t,"edit",i.id)}l(ne,"mayActAs");async function ie(e,t,i){return await L(e,t,"read",i.id)}l(ie,"customReachable");const se=["public","private"];function h(e,t){throw new j(e,t,S.GENERAL)}l(h,"conflict");function R(e){return x("sha256").update(e).digest("hex")}l(R,"hash");function H(){return`crtr_pub_${P(32).toString("base64url")}`}l(H,"issueToken");function ae(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw f("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const i=K(e.url).hostname.replace(/^\[|\]$/g,"");if(G(i)&&!V(i))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw f(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw f("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}l(ae,"receiverJson");async function re(e,t){return t.class==="node"?(await Z(e,t.node.id))?.isolated===!0:t.class==="app"||t.class==="object"?t.place?.isolated===!0:!1}l(re,"inIsolatedRun");async function Ne(e,t,i){const{kind:n,name:a,owner:s,description:o,events:u,receiver:c,rotateToken:w,privacy:p}=i;if(p!==void 0&&!se.includes(p))throw f(`invalid privacy ${JSON.stringify(p)}`);if(await ne(e,t,s)||h("forbidden",`cannot act as ${s.id}`),await re(e,t))throw new ee(403,"isolated_run","a custom object cannot be registered from an isolated run");if(!k.test(n)||Y.includes(n.split(".")[0]))throw f(`invalid or reserved kind ${n}`);if(!a.trim()||a!==a.trim()||a.includes("\0"))throw f(`invalid object name ${JSON.stringify(a)}`);const m=ae(c);if(c!=="queue"&&c!==null&&c!==void 0&&"argv"in c&&t.class!=="person"&&t.class!=="node"&&h("argv_forbidden","argv receiver requires a person or node principal"),u===void 0){const d=await e.prepare("SELECT source FROM object_kinds WHERE name = ?").get(n);if(d?.source!=="manifest"&&d?.source!=="peer")throw f("events is required unless the kind is declared by an installed plugin")}else if(Object.keys(u).length>Q||Object.entries(u).some(([d,_])=>!D.test(d)||!Number.isInteger(_?.level)||_.level<1||_.level>10||_.supersedes!==void 0&&typeof _.supersedes!="boolean"||_.ends_wait!==void 0&&typeof _.ends_wait!="boolean"))throw f("invalid custom event catalog");const r=await q(e,s.id,a);if(r&&!await ie(e,t,r)&&h("forbidden",`cannot act as ${s.id}`),r&&t.class!=="person"&&t.class!=="node"){const d=(await e.prepare("SELECT receiver_json FROM custom_objects WHERE object = ?").get(r.id))?.receiver_json;d&&typeof JSON.parse(d)=="object"&&"argv"in JSON.parse(d)&&h("argv_forbidden","argv receiver requires a person or node principal")}r&&r.type!==n&&h("name_taken",`${s.name}/${a} is already held by ${r.id}`);const g=u===void 0?null:await oe(e,{name:n,owner:s,description:o,events:u,read:null,source:"register"});if(!r){const d=await J(e,{type:n,kind:null,owner:s.id,privacy:p??"public",name:a,preview:""});s.type==="app"&&await te(e,d.id,await X(e,t));const _=H();return await e.prepare(`INSERT INTO custom_objects (object, description, receiver_json, token_hash)
2
+ VALUES (?, ?, ?, ?)`).run(d.id,o,m,R(_)),t.class==="node"&&await e.prepare("UPDATE custom_objects SET registered_by = ? WHERE object = ?").run(t.node.id,d.id),m!==null&&await e.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(d.id),await O(e,{event:"created",object:d,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{}},async U=>await N(e,U)),{object:d,token:_,created:!0,changed:[]}}const y=await e.prepare("SELECT description, receiver_json FROM custom_objects WHERE object = ?").get(r.id);y||h("name_taken",`${s.name}/${a} is not a custom object`);const E=[];if(y.description!==o&&E.push("description"),y.receiver_json!==m&&E.push("receiver"),g?.changed.includes("events")&&E.push("events"),g?.changed.includes("description")&&!E.includes("description")&&E.push("description"),p!==void 0&&r.privacy!==p&&E.push("privacy"),w&&E.push("token"),E.length===0)return{object:r,created:!1,changed:E};const v=w?H():void 0,T=E.includes("privacy")?{...r,privacy:p}:r;if(T!==r&&await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(p,r.id),await e.prepare("UPDATE custom_objects SET description = ?, receiver_json = ?, token_hash = COALESCE(?, token_hash) WHERE object = ?").run(o,m,v===void 0?null:R(v),r.id),await e.prepare("UPDATE objects SET reception = ? WHERE id = ?").run(m===null?0:2,r.id),y.receiver_json!==m){const d=Date.now();if(m===null){const _=await e.prepare(`UPDATE deliveries SET canceled_at = ?, updated_at = ?
3
+ WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(d,d,r.id);await e.prepare("UPDATE custom_objects SET dropped = dropped + ? WHERE object = ?").run(_.changes,r.id)}else await e.prepare(`UPDATE deliveries SET next_attempt_at = ?, updated_at = ?
4
+ WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(m==='"queue"'?null:d,d,r.id);b(r.id)}return w&&b(r.id),await O(e,{event:"configured",object:T,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{changed:E.join(",")}},async d=>await N(e,d)),{object:T,...v===void 0?{}:{token:v},created:!1,changed:E}}l(Ne,"registerCustomObject");async function oe(e,t){const{name:i,owner:n,description:a,events:s,read:o,source:u}=t;if(!k.test(i))throw f(`invalid kind ${i}`);if(z(s,i).length>0)throw f("invalid custom event catalog");const c=await e.prepare("SELECT owner, description, events_json, read_json, source, declared FROM object_kinds WHERE name = ?").get(i),w=I(s),p=u==="manifest"&&o!==null?JSON.stringify({command:o.command}):null,m=new Date().toISOString();if(!c)return await e.prepare(`INSERT INTO object_kinds (name, owner, description, events_json, created_at, updated_at, read_json, source, declared)
5
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)`).run(i,n.id,a,w,m,m,p,u),A(e,i),{outcome:"inserted",changed:[]};const r=c.owner===n.id;if(u==="peer"){if(c.source!=="peer")return{outcome:"skipped",changed:[]}}else if(c.source!=="peer"&&(r||h("kind_owned",`${i} belongs to another owner`),u==="register"&&c.source==="manifest"))return{outcome:"skipped",changed:[]};const g=[];return I(JSON.parse(c.events_json))!==w&&g.push("events"),c.description!==a&&g.push("description"),(c.read_json??null)!==p&&g.push("read"),g.length===0&&r&&c.source===u&&c.declared===1?{outcome:"unchanged",changed:g}:(await e.prepare("UPDATE object_kinds SET owner = ?, description = ?, events_json = ?, read_json = ?, source = ?, declared = 1, updated_at = ? WHERE name = ?").run(n.id,a,w,p,u,m,i),A(e,i),{outcome:"updated",changed:g})}l(oe,"upsertKind");async function Te(e,t,i,n){const a=await e.prepare("SELECT events_json FROM object_kinds WHERE name = ?").get(t.type);if(!a||t.deleted_at!==null)throw f(`${t.id} is not a live custom object`);const s=JSON.parse(a.events_json);if(!D.test(n.event)||!Object.hasOwn(s,n.event))throw new j("unknown_event",`${t.type} declares ${Object.keys(s).join(", ")}, not ${n.event}`,S.USAGE,{declared:Object.keys(s),event:n.event});if(typeof n.summary!="string")throw f("summary must be text");const o=n.state===void 0?null:JSON.stringify(n.state);if(n.state!==void 0&&o===void 0)throw f("state must be JSON-serializable");if(Buffer.byteLength(JSON.stringify({summary:n.summary,...n.state===void 0?{}:{state:n.state}}))>64*1024)throw new j("body_too_large","custom event summary and state exceed 64 KiB",S.USAGE);if(o!==null&&Buffer.byteLength(o)>64*1024)throw f("state exceeds 64 KiB");const u=n.cause??null,c=u===null?null:await e.prepare(`SELECT depth FROM messages WHERE message_id = ?
6
+ UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(u,u);if(u!==null&&c===void 0)throw new j("unknown_cause",`no message ${u}`,S.USAGE);if(c&&c.depth>=F)throw new M(c.depth+1);const w=new Date().toISOString();return await e.prepare("UPDATE custom_objects SET summary = ?, state_json = ?, published_at = ? WHERE object = ?").run(n.summary,o,w,t.id),await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(n.summary.split(`
7
+ `)[0],t.id),await O(e,{event:`${t.type}.${n.event}`,object:t,actor:i,attrs:{},messageBody:{summary:n.summary,...n.state===void 0?{}:{state:n.state}},...n.cause===void 0?{}:{cause:n.cause}},async p=>await N(e,p))}l(Te,"publishCustom");async function be(e,t,i){if(t.deleted_at!==null)return;await O(e,{event:"deleted",object:t,actor:i,attrs:{}},async a=>await N(e,a));const n=new Date().toISOString();await e.prepare("UPDATE objects SET deleted_at = ?, reception = 0 WHERE id = ?").run(n,t.id),await e.prepare("UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL").run(Date.now(),Date.now(),t.id),b(t.id)}l(be,"unregisterCustomObject");function I(e){return JSON.stringify(Object.fromEntries(Object.entries(e).sort(([t],[i])=>t.localeCompare(i)).map(([t,i])=>[t,{level:i.level,...i.supersedes===void 0?{}:{supersedes:i.supersedes},...i.ends_wait===void 0?{}:{ends_wait:i.ends_wait}}])))}l(I,"canonicalEvents");async function Re(e,t,i=0){return await W(e,t.id,i,"pending")}l(Re,"pendingFor");async function Ae(e,t){const i=await e.prepare("SELECT destination_id FROM deliveries WHERE delivery_id = ?").get(t);return i?(await W(e,i.destination_id,-1,"handed_off")).find(n=>n.delivery_id===t)??null:null}l(Ae,"pushLineFor");async function W(e,t,i,n){return(await e.prepare(`SELECT d.delivery_id, m.message_id, m.seq, m.event, m.level, m.object_id, m.actor_id, m.body_json, m.body_ref, m.depth, m.created_at,
8
8
  o.type, o.name, owner.id AS owner_id
9
9
  FROM deliveries d JOIN messages m ON m.message_id = d.message_id JOIN objects o ON o.id = m.object_id
10
10
  LEFT JOIN objects owner ON owner.id = o.owner
11
- WHERE d.destination_id = ? AND d.state = ? AND d.canceled_at IS NULL AND m.seq > ? ORDER BY m.seq`).all(t,n,i)).map(s=>{const o=s.body_json?JSON.parse(s.body_json):{};return{delivery_id:s.delivery_id,message_id:s.message_id,seq:s.seq,event:s.event,level:s.level,object:{id:s.object_id,type:s.type,name:s.name,owner:s.owner_id},actor:s.actor_id,attrs:o.push?.attrs??{},...o.summary===void 0?{}:{summary:o.summary},...o.state===void 0?{}:{state:o.state},...o.push?.pointer===void 0?{}:{pointer:o.push.pointer},depth:s.depth,created_at:s.created_at}})}l(U,"queueLines");async function ve(e,t,i){const n=Date.now(),a=[];let s=0;for(const o of i){const u=await e.prepare(`UPDATE deliveries SET state = 'acknowledged', acknowledged_at = ?, updated_at = ?
12
- WHERE delivery_id = ? AND destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(n,n,o,t.id);Number(u.changes)?s++:a.push(o)}return{acknowledged:s,...a.length?{unknown:a}:{}}}l(ve,"acknowledge");async function ye(e,t,i){const n=await e.prepare("SELECT token_hash FROM custom_objects WHERE object = ?").get(t.id),a=Buffer.from(n?.token_hash??"".padEnd(64,"0"),"hex"),s=Buffer.from(R(i),"hex");return i.startsWith("crtr_pub_")&&t.deleted_at===null&&a.length===s.length&&P(a,s)&&n?.token_hash!=null}l(ye,"verifyPublishToken");export{ve as acknowledge,D as customReachable,k as mayActAs,K as onQueueSignal,ge as pendingFor,_e as publishCustom,he as pushLineFor,Ee as registerCustomObject,we as unregisterCustomObject,te as upsertKind,ye as verifyPublishToken};
11
+ WHERE d.destination_id = ? AND d.state = ? AND d.canceled_at IS NULL AND m.seq > ? ORDER BY m.seq`).all(t,n,i)).map(s=>{const o=s.body_json?JSON.parse(s.body_json):{};return{delivery_id:s.delivery_id,message_id:s.message_id,seq:s.seq,event:s.event,level:s.level,object:{id:s.object_id,type:s.type,name:s.name,owner:s.owner_id},actor:s.actor_id,attrs:o.push?.attrs??{},...o.summary===void 0?{}:{summary:o.summary},...o.state===void 0?{}:{state:o.state},...o.push?.pointer===void 0?{}:{pointer:o.push.pointer},depth:s.depth,created_at:s.created_at}})}l(W,"queueLines");async function ke(e,t,i){const n=Date.now(),a=[];let s=0;for(const o of i){const u=await e.prepare(`UPDATE deliveries SET state = 'acknowledged', acknowledged_at = ?, updated_at = ?
12
+ WHERE delivery_id = ? AND destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(n,n,o,t.id);Number(u.changes)?s++:a.push(o)}return{acknowledged:s,...a.length?{unknown:a}:{}}}l(ke,"acknowledge");async function De(e,t,i){const n=await e.prepare("SELECT token_hash FROM custom_objects WHERE object = ?").get(t.id),a=Buffer.from(n?.token_hash??"".padEnd(64,"0"),"hex"),s=Buffer.from(R(i),"hex");return i.startsWith("crtr_pub_")&&t.deleted_at===null&&a.length===s.length&&$(a,s)&&n?.token_hash!=null}l(De,"verifyPublishToken");export{ke as acknowledge,ie as customReachable,ne as mayActAs,B as onQueueSignal,Re as pendingFor,Te as publishCustom,Ae as pushLineFor,Ne as registerCustomObject,be as unregisterCustomObject,oe as upsertKind,De as verifyPublishToken};
@@ -1,6 +1,7 @@
1
- var u=Object.defineProperty;var r=(e,t)=>u(e,"name",{value:t,configurable:!0});import{hasUnreconciledWorktree as f}from"../canvas/worktrees.js";import{deleteDocument as O}from"./documents.js";import{publish as d}from"./events.js";import{getById as s}from"./objects.js";import{rememberDeletedIsolated as R}from"../runs/confinement.js";import{withCanvasWrite as c}from"../canvas/db.js";async function j(e,t){if(t.type!=="node")throw new Error(`deleteNodeWithOwned: ${t.id} is a ${t.type}, not a node`);if(await f(t.id))throw new Error(`refusing to delete node ${t.id}: its managed worktree is not reconciled`);await c(async()=>await m(e,t),{savepoint:!0})}r(j,"deleteNodeWithOwned");async function I(e,t){const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
2
- WHERE i.scheme = 'profile' AND i.external_id = ? AND o.type = 'profile' AND o.deleted_at IS NULL`).get(t);return i?(await c(async()=>{await w(e,i),await e.prepare("UPDATE objects SET deleted_at = ?, name = ? WHERE id = ?").run(new Date().toISOString(),`${i.name}.deleted-${i.id}`,i.id),await d(e,{event:"deleted",object:await s(e,i.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(i.id,i.id)},{savepoint:!0}),!0):!1}r(I,"deleteProfileWithOwned");async function m(e,t){const i=await w(e,t);await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i,t.id),await d(e,{event:"deleted",object:await s(e,t.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(t.id,t.id),await R(t.id,e),await e.prepare("UPDATE nodes SET run_id = NULL WHERE run_id = ? AND node_id != ? AND isolated = 0").run(t.id,t.id),await e.prepare("DELETE FROM nodes WHERE node_id = ?").run(t.id)}r(m,"deleteInside");async function w(e,t){const i=await e.prepare(`WITH RECURSIVE sub(id) AS (
1
+ var u=Object.defineProperty;var n=(e,t)=>u(e,"name",{value:t,configurable:!0});import{hasUnreconciledWorktree as f}from"../canvas/worktrees.js";import{deleteDocument as R}from"./documents.js";import{publish as E}from"./events.js";import{getById as c}from"./objects.js";import{writeActsFor as N}from"../permissions/facts.js";import{rememberDeletedIsolated as O}from"../permissions/ceiling.js";import{withCanvasWrite as w}from"../canvas/db.js";async function A(e,t){if(t.type!=="node")throw new Error(`deleteNodeWithOwned: ${t.id} is a ${t.type}, not a node`);if(await f(t.id))throw new Error(`refusing to delete node ${t.id}: its managed worktree is not reconciled`);await w(async()=>await m(e,t),{savepoint:!0})}n(A,"deleteNodeWithOwned");async function H(e,t){const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
2
+ WHERE i.scheme = 'profile' AND i.external_id = ? AND o.type = 'profile' AND o.deleted_at IS NULL`).get(t);return i?(await w(async()=>{const o=await e.prepare(`UPDATE objects o SET owner = n.app FROM nodes n WHERE n.node_id = o.id AND o.owner = ? AND n.app IS NOT NULL
3
+ RETURNING o.id, n.sub`).all(i.id);for(const{id:s,sub:r}of o)r&&await N(e,s,r);await p(e,i),await e.prepare("UPDATE objects SET deleted_at = ?, name = ? WHERE id = ?").run(new Date().toISOString(),`${i.name}.deleted-${i.id}`,i.id),await E(e,{event:"deleted",object:await c(e,i.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(i.id,i.id)},{savepoint:!0}),!0):!1}n(H,"deleteProfileWithOwned");async function m(e,t){const i=await p(e,t);await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i,t.id),await E(e,{event:"deleted",object:await c(e,t.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(t.id,t.id),await O(t.id,e),await e.prepare("UPDATE nodes SET run_id = NULL WHERE run_id = ? AND node_id != ? AND isolated = 0").run(t.id,t.id),await e.prepare("DELETE FROM nodes WHERE node_id = ?").run(t.id)}n(m,"deleteInside");async function p(e,t){const i=await e.prepare(`WITH RECURSIVE sub(id) AS (
3
4
  SELECT id FROM objects WHERE owner = ?
4
5
  UNION SELECT o.id FROM objects o JOIN sub ON o.owner = sub.id)
5
- SELECT o.* FROM objects o JOIN sub ON sub.id = o.id WHERE o.deleted_at IS NULL`).all(t.id),o=new Set(i.map(a=>a.id)),l=e.prepare(`SELECT e.src FROM edges e JOIN objects s ON s.id = e.src
6
- WHERE e.type = 'links-to' AND e.dst = ? AND s.deleted_at IS NULL`);for(let a=!0;a;){a=!1;for(const n of i)n.type!=="document"||!o.has(n.id)||(await l.all(n.id)).some(({src:p})=>!o.has(p))&&(o.delete(n.id),a=!0)}const E=new Date().toISOString();for(const a of i)if(o.has(a.id)){if(a.type==="document"){await O(e,null,a);continue}await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(E,a.id),await d(e,{event:"deleted",object:await s(e,a.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(a.id,a.id)}return E}r(w,"deleteOwned");export{j as deleteNodeWithOwned,I as deleteProfileWithOwned};
6
+ SELECT o.* FROM objects o JOIN sub ON sub.id = o.id WHERE o.deleted_at IS NULL`).all(t.id),o=new Set(i.map(a=>a.id)),s=e.prepare(`SELECT e.src FROM edges e JOIN objects s ON s.id = e.src
7
+ WHERE e.type = 'links-to' AND e.dst = ? AND s.deleted_at IS NULL`);for(let a=!0;a;){a=!1;for(const d of i)d.type!=="document"||!o.has(d.id)||(await s.all(d.id)).some(({src:l})=>!o.has(l))&&(o.delete(d.id),a=!0)}const r=new Date().toISOString();for(const a of i)if(o.has(a.id)){if(a.type==="document"){await R(e,null,a);continue}await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(r,a.id),await E(e,{event:"deleted",object:await c(e,a.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(a.id,a.id)}return r}n(p,"deleteOwned");export{A as deleteNodeWithOwned,H as deleteProfileWithOwned};
@@ -17,6 +17,13 @@ export interface WriteSpec extends DocumentFields {
17
17
  kind: string;
18
18
  preview: string;
19
19
  privacy: Privacy;
20
+ /** The permission schema type the document is evaluated under (`firstlight/memory`); omitted = `crtr/object`. */
21
+ schemaType?: string;
22
+ /** Facts written on the new document in the same transaction (`{ relation: 'editor', subject: 'user:abc' }`); they need a `schemaType`. */
23
+ relations?: Array<{
24
+ relation: string;
25
+ subject: string;
26
+ }>;
20
27
  }
21
28
  export type DocumentChange = {
22
29
  body: string;
@@ -103,9 +110,11 @@ export declare function editDocument(db: CanvasDb, reader: Reader, doc: ObjectRo
103
110
  revision: number;
104
111
  };
105
112
  }>;
106
- /** Change record fields (preview, summary, privacy, rules, …) without a new revision. A null
107
- * reader is repo sync, which carries no reader and checks no access. */
108
- export declare function setDocumentRecord(db: CanvasDb, reader: Reader | null, doc: ObjectRow, fields: DocumentFields): Promise<ObjectRow>;
113
+ /** Change record fields (preview, summary, privacy, rules, …). A null reader is repo sync, which carries no reader, checks no access and
114
+ * records no history. Any other caller's change is recorded as a revision with the current body, so `history` attributes it (author,
115
+ * `acting_app`, `acting_for`) with the rationale. It descends from the highest head only, so a document that needs a merge still does. A caller
116
+ * that held the heads before keeps holding them; one that had not read the body does not gain a read base it never earned. */
117
+ export declare function setDocumentRecord(db: CanvasDb, reader: Reader | null, doc: ObjectRow, fields: DocumentFields, rationale?: string): Promise<ObjectRow>;
109
118
  /** Record that the reader holds the document's current heads: a `read` watch with that read
110
119
  * base, and a `read` push. Delivery's content load calls this without reading the body. */
111
120
  export declare function recordRead(db: CanvasDb, reader: Reader, doc: ObjectRow, reposFn?: ReposFn): Promise<void>;
@@ -1,10 +1,16 @@
1
- var oe=Object.defineProperty;var s=(t,n)=>oe(t,"name",{value:n,configurable:!0});import{randomUUID as se}from"node:crypto";import{CrtrError as v,notFound as N,usage as p}from"../errors.js";import{ExitCode as R}from"../../types.js";import{canChange as j,canRead as P,missingMemoryScope as ce,missingMemoryScopeUnder as ue,ownerChain as we}from"./access.js";import{bodyOf as pe,get as le,put as B}from"./bodies.js";import{dropUnresolved as me,linksOf as de,recordActedOn as g,setLinks as G,setUnresolved as fe,unresolvedLinksCouldName as he}from"./edges.js";import{publish as x}from"./events.js";import{resolveName as ye}from"./names.js";import{createObject as Ee,getById as m}from"./objects.js";import{liveLookups as _e,readerForNode as ve,readerForObject as Ne}from"./reader.js";import{replaceRules as K,setRulesOwner as Re}from"./rules.js";import{recordWatch as I}from"./watches.js";import{withCanvasWrite as h}from"../canvas/db.js";import{filterAsync as q,forEachAsync as ge,mapAsync as S}from"../async-array.js";function E(t){return t.class==="node"?t.node:t.class==="app"?t.app:t.class==="object"?t.object:t.person}s(E,"actorOf");function Se(t,n){return new v("conflict",t,R.GENERAL,n)}s(Se,"conflict");function y(t){if(t.type!=="document")throw p(`${t.type} ${t.id} is not a document`)}s(y,"assertDocument");async function Y(t,n){const e=await we(t,n);return(e.length?e[e.length-1]:n).type}s(Y,"spaceType");async function $(t,n,e){if(e!=="public"&&await Y(t,n)==="repo")throw p(`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($,"assertPublicInRepo");async function H(t,n,e,i){if(e.trim()===""||e!==e.trim())throw p(`invalid document name ${JSON.stringify(e)}`);const a=await t.prepare("SELECT id FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.id,e);if(a&&a.id!==i?.id)throw Se(`${n.name} already has a document named ${e}`,{name:e,holder:a.id})}s(H,"assertNameFree");async function U(t,n,e){if(!await P(t,n,e))throw N(`no document ${e.id}`)}s(U,"readable");function V(t){return new v("scope_missing",`missing scope ${t}`,R.GENERAL,{scope:t})}s(V,"scopeMissing");async function T(t,n,e){await U(t,n,e);const{source_path:i}=await O(t,e);if(i!==null)throw new v("forbidden",`${e.name} is a built-in document shipped by a package (served from ${i}); \`crtr doc\` cannot change it`,R.GENERAL,{id:e.id,next:"Edit it in the source repository of the package that ships it (for crtr's own documents, the crouter repo), not with `crtr doc`."});const a=await ce(t,n,e,"rw");if(a!==null)throw V(a);if(!await j(t,n,e))throw new v("forbidden",`you cannot change ${e.name}`,R.GENERAL,{id:e.id})}s(T,"changeable");async function X(t,n){const e=await t.prepare(`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent
2
- WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rev_id);return{rev_id:n.rev_id,seq:n.seq,number:n.number,body_hash:n.body_hash,author:n.author,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,parents:e.map(i=>i.rev_id)}}s(X,"toRevision");async function C(t,n){const e=await t.prepare(`SELECT * FROM revisions r WHERE r.document = ?
3
- AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await S(e,async i=>await X(t,i))}s(C,"heads");function Oe(t){return t.map(n=>n.rev_id).sort().join(",")}s(Oe,"headsKeyOf");async function O(t,n){const e=await t.prepare("SELECT * FROM documents WHERE object = ?").get(n.id);if(!e)throw N(`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(O,"documentRecord");async function A(t,n,e){const i=await t.prepare(`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at)
4
- VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,e.rev_id??se(),e.number,e.body_hash,e.author?.id??null,e.rationale,e.verbatim?1:0,e.created_at??new Date().toISOString()),a=t.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const r of e.parents)await a.run(i.rev_id,r);return i}s(A,"insertRevision");function De(t){const n=new Set;for(const e of t.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(e[1].trim());return[...n]}s(De,"linksIn");async function z(t,n,e){const i=[],a=[];for(const r of e){const o=await ye(t,n,r);"object"in o?i.push({written_as:r,dst:o.object}):"deleted"in o?i.push({written_as:r,dst:o.deleted}):"remote"in o||a.push(r)}return{links:i,unresolved:a}}s(z,"resolveLinks");async function Q(t,n,e,i){const{links:a,unresolved:r}=await z(t,n,De(i));return await ae(t,await G(t,e,a)),await fe(t,e,r),r}s(Q,"setRevisionLinks");async function W(t,n,e){const i=new Map;for(const{src:a,name:r}of await he(t,n.name,n.id))i.set(a,[...i.get(a)??[],r]);for(const[a,r]of i){const o=await m(t,a),c=o?.owner==null?null:await m(t,o.owner);if(!o||o.deleted_at||o.home_machine!==null||!c||c.deleted_at)continue;const u=c.type==="node"?await ve(t,c.id,e??(async()=>[]),_e(t),!0):await Ne(t,o,e),w=(await z(t,u,r)).links.filter(l=>l.dst.id===n.id);w.length!==0&&(await G(t,o,[...await de(t,o),...w]),await me(t,o,w.map(l=>l.written_as)))}}s(W,"linkNewlyNamed");async function ke(t,n,e){return n.type==="node"?n.id:await t.prepare(`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.id
1
+ var se=Object.defineProperty;var s=(e,n)=>se(e,"name",{value:n,configurable:!0});import{randomUUID as ce}from"node:crypto";import{CrtrError as N,notFound as R,usage as l}from"../errors.js";import{ExitCode as g}from"../../types.js";import{bodyOf as ue,get as we,put as B}from"./bodies.js";import{dropUnresolved as pe,linksOf as le,recordActedOn as O,setLinks as q,setUnresolved as de,unresolvedLinksCouldName as me}from"./edges.js";import{publish as k}from"./events.js";import{readableOrDeleted as fe,resolveName as ye}from"./names.js";import{createObject as he,getById as f}from"./objects.js";import{liveLookups as Ee,readerForNode as _e,readerForObject as ve}from"./reader.js";import{replaceRules as G,setRulesOwner as Ne}from"./rules.js";import{recordWatch as j}from"./watches.js";import{withCanvasWrite as y}from"../canvas/db.js";import{filterAsync as Re,forEachAsync as ge,mapAsync as x}from"../async-array.js";import{check as A,resolveReader as Oe,userOf as K}from"../permissions/evaluator.js";import{missingMemoryScope as Y}from"../permissions/ceiling.js";import{deleteFact as Se,parseSubject as Te,writeActsFor as De,writeFact as ke}from"../permissions/facts.js";import{PermissionError as je}from"../permissions/errors.js";function E(e){return e.class==="node"?e.node:e.class==="app"?e.app:e.class==="object"?e.object:e.person}s(E,"actorOf");async function C(e,n){const t=`user:${await K(e,n)}`;return n.class==="person"?{app:null,for:t}:{app:(await e.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(n.app.id))?.external_id??null,for:t}}s(C,"actingOf");function xe(e,n){return new N("conflict",e,g.GENERAL,n)}s(xe,"conflict");function h(e){if(e.type!=="document")throw l(`${e.type} ${e.id} is not a document`)}s(h,"assertDocument");async function V(e,n){return(await e.prepare(`WITH RECURSIVE chain(id, owner, type, depth) AS (
2
+ SELECT id, owner, type, 0 FROM objects WHERE id = ?
3
+ UNION ALL
4
+ SELECT o.id, o.owner, o.type, c.depth + 1 FROM objects o JOIN chain c ON o.id = c.owner WHERE c.depth < 12
5
+ ) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function W(e,n,t){if(t!=="public"&&await V(e,n)==="repo")throw l(`a repo owns only public documents; ${n.name} cannot own a ${t} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(W,"assertPublicInRepo");async function M(e,n,t,a){if(t.trim()===""||t!==t.trim())throw l(`invalid document name ${JSON.stringify(t)}`);const i=await e.prepare("SELECT id FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.id,t);if(i&&i.id!==a?.id)throw xe(`${n.name} already has a document named ${t}`,{name:t,holder:i.id})}s(M,"assertNameFree");async function U(e,n,t){if(!await A(e,n,"read",t.id))throw R(`no document ${t.id}`)}s(U,"readable");function X(e){return new N("scope_missing",`missing scope ${e}`,g.GENERAL,{scope:e})}s(X,"scopeMissing");async function L(e,n,t){await U(e,n,t);const{source_path:a}=await v(e,t);if(a!==null)throw new N("forbidden",`${t.name} is a built-in document shipped by a package (served from ${a}); \`crtr doc\` cannot change it`,g.GENERAL,{id:t.id,next:"Edit it in the source repository of the package that ships it (for crtr's own documents, the crouter repo), not with `crtr doc`."});const i=await Y(e,n,t.id,"rw");if(i!==null)throw X(i);if(!await A(e,n,"edit",t.id))throw new N("forbidden",`you cannot change ${t.name}`,g.GENERAL,{id:t.id})}s(L,"changeable");async function z(e,n){const t=await e.prepare(`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent
6
+ WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rev_id);return{rev_id:n.rev_id,seq:n.seq,number:n.number,body_hash:n.body_hash,author:n.author,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,acting_app:n.acting_app,acting_for:n.acting_for,parents:t.map(a=>a.rev_id)}}s(z,"toRevision");async function I(e,n){const t=await e.prepare(`SELECT * FROM revisions r WHERE r.document = ?
7
+ AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await x(t,async a=>await z(e,a))}s(I,"heads");function Ae(e){return e.map(n=>n.rev_id).sort().join(",")}s(Ae,"headsKeyOf");async function v(e,n){const t=await e.prepare("SELECT * FROM documents WHERE object = ?").get(n.id);if(!t)throw R(`no document record for ${n.id}`);return{summary:t.summary,heads_key:t.heads_key,revision:t.revision,why_it_exists:t.why_it_exists,unlisted:t.unlisted===1,lint_ignore:t.lint_ignore_json===null?null:JSON.parse(t.lint_ignore_json),extensions:t.extensions_json===null?null:JSON.parse(t.extensions_json),source_path:t.source_path}}s(v,"documentRecord");async function S(e,n,t){const a=await e.prepare(`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at, acting_app, acting_for)
8
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,t.rev_id??ce(),t.number,t.body_hash,t.author?.id??null,t.rationale,t.verbatim?1:0,t.created_at??new Date().toISOString(),t.acting?.app??null,t.acting?.for??null),i=e.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const r of t.parents)await i.run(a.rev_id,r);return a}s(S,"insertRevision");function Le(e){const n=new Set;for(const t of e.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(t[1].trim());return[...n]}s(Le,"linksIn");async function Q(e,n,t){const a=[],i=[];for(const r of t){const o=await ye(e,n,r);"object"in o?a.push({written_as:r,dst:o.object}):"deleted"in o?a.push({written_as:r,dst:o.deleted}):"remote"in o||i.push(r)}return{links:a,unresolved:i}}s(Q,"resolveLinks");async function Z(e,n,t,a){const{links:i,unresolved:r}=await Q(e,n,Le(a));return await ie(e,await q(e,t,i)),await de(e,t,r),r}s(Z,"setRevisionLinks");async function F(e,n,t){const a=new Map;for(const{src:i,name:r}of await me(e,n.name,n.id))a.set(i,[...a.get(i)??[],r]);for(const[i,r]of a){const o=await f(e,i),u=o?.owner==null?null:await f(e,o.owner);if(!o||o.deleted_at||o.home_machine!==null||!u||u.deleted_at)continue;const w=u.type==="node"?await _e(e,u.id,t??(async()=>[]),Ee(e),!0):await ve(e,o,t),c=(await Q(e,w,r)).links.filter(p=>p.dst.id===n.id);c.length!==0&&(await q(e,o,[...await le(e,o),...c]),await pe(e,o,c.map(p=>p.written_as)))}}s(F,"linkNewlyNamed");async function Ie(e,n,t){return n.type==="node"?n.id:await e.prepare(`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.id
5
9
  WHERE o.name = ? AND o.type <> ? AND o.type IN ('person','app','repo','profile')
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(ke,"ownerPrefix");async function Z(t,n,e){const i=await t.prepare(`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
7
- WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(i.length===0)return e;const a=new Map;for(const r of i){if(r.deleted_at||r.written_as===r.id)continue;const o=!r.written_as.includes("/");if(o&&r.name===r.written_as)continue;const c=r.owner===null?null:await m(t,r.owner),u=c?`${await ke(t,c,r.name)}/${r.name}`:r.name,w=o&&r.owner===n.owner?r.name:u;w!==r.written_as&&a.set(r.written_as,w)}return a.size===0?e:e.replace(/\[\[([^[\]\n]+?)\]\]/g,(r,o)=>{const c=a.get(o.trim());return c===void 0?r:`[[${c}]]`})}s(Z,"withCurrentNames");async function M(t,n,e){if(e.preview!==void 0){if(e.preview.trim()==="")throw p("a document needs a preview",{field:"preview"});await t.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(e.preview,n.id)}e.privacy!==void 0&&(await $(t,await m(t,n.owner),e.privacy),await t.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(e.privacy,n.id));const i=s(async(r,o)=>await t.prepareUncached(`UPDATE documents SET ${r} = ? WHERE object = ?`).run(o,n.id),"set");e.summary!==void 0&&await i("summary",e.summary),e.whyItExists!==void 0&&await i("why_it_exists",e.whyItExists),e.unlisted!==void 0&&await i("unlisted",e.unlisted?1:0),e.lintIgnore!==void 0&&await i("lint_ignore_json",e.lintIgnore===null?null:JSON.stringify(e.lintIgnore)),e.extensions!==void 0&&await i("extensions_json",e.extensions===null?null:JSON.stringify(e.extensions));const a=await m(t,n.id);return e.rules!==void 0&&await K(t,a,e.rules),a}s(M,"applyDocumentFields");async function b(t,n,e){if(n.owner.deleted_at)throw p(`${n.owner.name} is deleted`);if(n.preview.trim()==="")throw p("a document needs a preview",{field:"preview"});await $(t,n.owner,n.privacy),await H(t,n.owner,n.name);const i=await Ee(t,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.id,privacy:n.privacy,name:n.name,preview:n.preview});await t.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(i.id);const a=await M(t,i,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await W(t,a,e),a}s(b,"createDocument");async function F(t,n){const e=await C(t,n),i=Oe(e),a=Math.max(0,...e.map(r=>r.number));return await t.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(i,a,n.id),{heads_key:i,revision:a,headCount:e.length}}s(F,"setHeads");async function Ke(t,n,e,i,a){const r=B(i);return await h(async()=>{const o=E(n),c=await ue(t,n,e.owner,"rw");if(c!==null)throw V(c);const u=await b(t,e,a),w=await A(t,u,{number:1,body_hash:r,author:o,rationale:null,parents:[]}),{heads_key:l}=await F(t,u);if(!await j(t,n,u))throw N(`no owner ${e.owner.name} you can write to`);const d=await Q(t,n,u,i);return await g(t,o,u),await I(t,o,u,"write",{readHeadsKey:l}),await x(t,{event:"created",object:u,actor:o,attrs:{revision:1},pointer:w.rev_id},a),{object:u,revision:1,unresolved:d}},{savepoint:!0})}s(Ke,"writeDocument");async function qe(t,n,e){return(await t.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(E(n).id,e.id))?.read_heads_key??null}s(qe,"readBase");function xe(t,n){let e=t;for(const[i,{old:a,new:r}]of n.entries()){if(a==="")throw p(`block ${i+1}: a replacement needs non-empty old text`,{field:"replace"});const o=e.indexOf(a);if(o<0)throw p(`block ${i+1}: old text not found: ${JSON.stringify(a.slice(0,120))}`,{field:"replace",old:a});if(e.indexOf(a,o+1)>=0)throw p(`block ${i+1}: old text occurs more than once: ${JSON.stringify(a.slice(0,120))}`,{field:"replace",old:a});e=e.slice(0,o)+r+e.slice(o+a.length)}return e}s(xe,"applyReplacements");async function Ye(t,n,e,i,a,r,o={},c){if(y(e),await T(t,n,e),e.deleted_at)throw p(`${e.name} is deleted`);if(r===null)throw new v("read_first",`read ${e.name} before editing it`,R.GENERAL,{id:e.id});if(a.trim()==="")throw p("an edit needs a rationale",{field:"rationale"});const u=await O(t,e);if(u.heads_key!==r)return{stale:{heads_key:u.heads_key,revision:u.revision}};const w=await C(t,e);let l;if("body"in i)l=i.body;else{if(w.length!==1)throw p(`${e.name} has ${w.length} heads and needs a merge; pass the whole merged body`,{heads:w.length});l=xe(await Z(t,e,le(w[0].body_hash)),i.replace)}const d=B(l);try{return await h(async()=>{const f=E(n),D=1+Math.max(0,...w.map(_=>_.number)),J=await A(t,e,{number:D,body_hash:d,author:f,rationale:a,verbatim:i.verbatim===!0,parents:w.map(_=>_.rev_id)}),L=J.rev_id,ie=await t.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(L,D,e.id,r);if(Number(ie.changes)===0){const _=await O(t,e);throw new ee(_.heads_key,_.revision)}const k=await M(t,e,o),re=await Q(t,n,k,l);return await g(t,f,k),await I(t,f,k,"write",{readHeadsKey:L}),await x(t,{event:"edited",object:k,actor:f,pointer:J.rev_id,attrs:{revision:D,from_revision:u.revision,rationale:a}},c),{revision:D,heads_key:L,unresolved:re}},{savepoint:!0})}catch(f){if(f instanceof ee)return{stale:{heads_key:f.heads_key,revision:f.revision}};throw f}}s(Ye,"editDocument");class ee extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,e){super("stale"),this.heads_key=n,this.revision=e}}async function Ve(t,n,e,i){return y(e),n&&await T(t,n,e),await h(async()=>{const a=await M(t,e,i);return n&&await g(t,E(n),a),a},{savepoint:!0})}s(Ve,"setDocumentRecord");async function Te(t,n,e,i){const a=E(n),{heads_key:r}=await O(t,e);await I(t,a,e,"read",{readHeadsKey:r}),await x(t,{event:"read",object:e,actor:a,attrs:{}},i)}s(Te,"recordRead");async function Xe(t,n,e,i){return y(e),await U(t,n,e),await h(async()=>{const a=await O(t,e),r=await S(await C(t,e),async c=>({...c,body:await Z(t,e,pe(c.body_hash,a.source_path))})),o=(await q(await S(await t.prepare("SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ? ORDER BY written_as").all(e.id),async c=>({written_as:c.written_as??"",object:await m(t,c.dst)})),async c=>c.object&&await P(t,n,c.object))).map(c=>({...c,deleted:c.object.deleted_at!==null}));return e.deleted_at||await Te(t,n,e,i),{object:e,record:a,heads:r,needsMerge:r.length>1,links:o}},{savepoint:!0})}s(Xe,"readDocument");async function ze(t,n,e){y(e),await U(t,n,e);const i=await t.prepare("SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, seq DESC").all(e.id);return await S(i,async a=>await X(t,a))}s(ze,"history");async function Qe(t,n,e,i,a){if(y(e),e.deleted_at)throw p(`${e.name} is deleted`);return n&&await T(t,n,e),await h(async()=>{const r=i.owner??await m(t,e.owner),o=i.name??e.name;await H(t,r,o,e),await $(t,r,e.privacy),await t.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,r.id,e.id);const c=await m(t,e.id);if(r.id!==e.owner&&(await Re(t,c),n&&!await j(t,n,c)))throw N(`no owner ${r.name} you can move documents to`);return n&&await g(t,E(n),c),(o!==e.name||r.id!==e.owner)&&await W(t,c,a),c},{savepoint:!0})}s(Qe,"moveDocument");async function Ze(t,n,e,i,a){return y(e),e.deleted_at?e:(n&&await T(t,n,e),await h(async()=>{const r=await te(t,n,e,i,a);return await ae(t,await ne(t,e.id),i),r},{savepoint:!0}))}s(Ze,"deleteDocument");async function be(t,n,e,i){return y(n),n.deleted_at?await h(async()=>{await H(t,await m(t,n.owner),e,n),await t.prepare("UPDATE objects SET deleted_at = NULL, name = ? WHERE id = ?").run(e,n.id);const a=await m(t,n.id);return await W(t,a,i),a},{savepoint:!0}):n}s(be,"restoreDocument");async function te(t,n,e,i,a){await t.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(a??new Date().toISOString(),e.id);const r=await m(t,e.id),o=n?E(n):null;return o&&await g(t,o,r),await x(t,{event:"deleted",object:r,actor:o,attrs:{}},i),await t.prepare("DELETE FROM watches WHERE object = ?").run(e.id),await K(t,r,[]),r}s(te,"markDeleted");async function ne(t,n){return(await t.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n)).map(({dst:e})=>e)}s(ne,"linkTargets");async function ae(t,n,e){const i=t.prepare(`SELECT 1 FROM objects d JOIN objects w ON w.id = d.owner
10
+ AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,t)?`${n.type}:${n.name}`:n.name}s(Ie,"ownerPrefix");async function b(e,n,t){const a=await e.prepare(`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
11
+ WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(a.length===0)return t;const i=new Map;for(const r of a){if(r.deleted_at||r.written_as===r.id)continue;const o=!r.written_as.includes("/");if(o&&r.name===r.written_as)continue;const u=r.owner===null?null:await f(e,r.owner),w=u?`${await Ie(e,u,r.name)}/${r.name}`:r.name,c=o&&r.owner===n.owner?r.name:w;c!==r.written_as&&i.set(r.written_as,c)}return i.size===0?t:t.replace(/\[\[([^[\]\n]+?)\]\]/g,(r,o)=>{const u=i.get(o.trim());return u===void 0?r:`[[${u}]]`})}s(b,"withCurrentNames");async function J(e,n,t){if(t.preview!==void 0){if(t.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(t.preview,n.id)}t.privacy!==void 0&&(await W(e,await f(e,n.owner),t.privacy),await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(t.privacy,n.id));const a=s(async(r,o)=>await e.prepareUncached(`UPDATE documents SET ${r} = ? WHERE object = ?`).run(o,n.id),"set");t.summary!==void 0&&await a("summary",t.summary),t.whyItExists!==void 0&&await a("why_it_exists",t.whyItExists),t.unlisted!==void 0&&await a("unlisted",t.unlisted?1:0),t.lintIgnore!==void 0&&await a("lint_ignore_json",t.lintIgnore===null?null:JSON.stringify(t.lintIgnore)),t.extensions!==void 0&&await a("extensions_json",t.extensions===null?null:JSON.stringify(t.extensions));const i=await f(e,n.id);return t.rules!==void 0&&await G(e,i,t.rules),i}s(J,"applyDocumentFields");async function ee(e,n,t,a="crtr"){if(n.owner.deleted_at)throw l(`${n.owner.name} is deleted`);if(n.relations?.length&&n.schemaType===void 0)throw new je(400,"schema_type_required","relations need a schemaType: they are facts about a typed resource",{field:"relations"});if(n.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await W(e,n.owner,n.privacy),await M(e,n.owner,n.name);const i=await he(e,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.id,privacy:n.privacy,name:n.name,preview:n.preview,...n.schemaType!==void 0?{schema_type:n.schemaType}:{}});for(const{relation:o,subject:u}of n.relations??[])await ke(e,{resourceType:n.schemaType,resourceId:i.id,relation:o,subject:Te(u),writtenBy:a});await e.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(i.id);const r=await J(e,i,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await F(e,r,t),r}s(ee,"createDocument");async function $(e,n){const t=await I(e,n),a=Ae(t),i=Math.max(0,...t.map(r=>r.number));return await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(a,i,n.id),{heads_key:a,revision:i,headCount:t.length}}s($,"setHeads");async function nt(e,n,t,a,i){const r=B(a);return await y(async()=>{const o=E(n),u=await Y(e,n,t.owner.id,"rw",!0);if(u!==null)throw X(u);const w=await C(e,n),c=await ee(e,t,i,w.app??w.for);t.owner.type==="app"&&t.schemaType===void 0&&await De(e,c.id,await K(e,n));const p=await S(e,c,{number:1,body_hash:r,author:o,rationale:null,parents:[],acting:w});if(!await A(e,n,"edit",c.id))throw R(`no owner ${t.owner.name} you can write to`);const{heads_key:d}=await $(e,c),m=await Z(e,n,c,a);return await O(e,o,c),await j(e,o,c,"write",{readHeadsKey:d}),await k(e,{event:"created",object:c,actor:o,attrs:{revision:1},pointer:p.rev_id},i),{object:c,revision:1,unresolved:m}},{savepoint:!0})}s(nt,"writeDocument");async function $e(e,n,t){return(await e.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(E(n).id,t.id))?.read_heads_key??null}s($e,"readBase");function He(e,n){let t=e;for(const[a,{old:i,new:r}]of n.entries()){if(i==="")throw l(`block ${a+1}: a replacement needs non-empty old text`,{field:"replace"});const o=t.indexOf(i);if(o<0)throw l(`block ${a+1}: old text not found: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});if(t.indexOf(i,o+1)>=0)throw l(`block ${a+1}: old text occurs more than once: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});t=t.slice(0,o)+r+t.slice(o+i.length)}return t}s(He,"applyReplacements");async function at(e,n,t,a,i,r,o={},u){if(h(t),await L(e,n,t),t.deleted_at)throw l(`${t.name} is deleted`);if(r===null)throw new N("read_first",`read ${t.name} before editing it`,g.GENERAL,{id:t.id});if(i.trim()==="")throw l("an edit needs a rationale",{field:"rationale"});const w=await v(e,t);if(w.heads_key!==r)return{stale:{heads_key:w.heads_key,revision:w.revision}};const c=await I(e,t);let p;if("body"in a)p=a.body;else{if(c.length!==1)throw l(`${t.name} has ${c.length} heads and needs a merge; pass the whole merged body`,{heads:c.length});p=He(await b(e,t,we(c[0].body_hash)),a.replace)}const d=B(p);try{return await y(async()=>{const m=E(n),T=1+Math.max(0,...c.map(_=>_.number)),P=await S(e,t,{number:T,body_hash:d,author:m,rationale:i,verbatim:a.verbatim===!0,parents:c.map(_=>_.rev_id),acting:await C(e,n)}),H=P.rev_id,re=await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(H,T,t.id,r);if(Number(re.changes)===0){const _=await v(e,t);throw new te(_.heads_key,_.revision)}const D=await J(e,t,o),oe=await Z(e,n,D,p);return await O(e,m,D),await j(e,m,D,"write",{readHeadsKey:H}),await k(e,{event:"edited",object:D,actor:m,pointer:P.rev_id,attrs:{revision:T,from_revision:w.revision,rationale:i}},u),{revision:T,heads_key:H,unresolved:oe}},{savepoint:!0})}catch(m){if(m instanceof te)return{stale:{heads_key:m.heads_key,revision:m.revision}};throw m}}s(at,"editDocument");class te extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,t){super("stale"),this.heads_key=n,this.revision=t}}async function it(e,n,t,a,i){if(h(t),n&&(await L(e,n,t),i===void 0||i.trim()===""))throw l("an edit needs a rationale",{field:"rationale"});return await y(async()=>{const r=await J(e,t,a);if(n){const o=E(n),u=await v(e,t),w=await $e(e,n,t),c=await I(e,t),p=c[0];await S(e,t,{number:1+Math.max(...c.map(m=>m.number)),body_hash:p.body_hash,author:o,rationale:i,parents:[p.rev_id],acting:await C(e,n)});const{heads_key:d}=await $(e,t);await O(e,o,r),await j(e,o,t,"write",w===u.heads_key?{readHeadsKey:d}:{})}return r},{savepoint:!0})}s(it,"setDocumentRecord");async function Ce(e,n,t,a){const i=E(n),{heads_key:r}=await v(e,t);await j(e,i,t,"read",{readHeadsKey:r}),await k(e,{event:"read",object:t,actor:i,attrs:{}},a)}s(Ce,"recordRead");async function rt(e,n,t,a){return h(t),await U(e,n,t),await y(async()=>{const i=await v(e,t),r=await x(await I(e,t),async c=>({...c,body:await b(e,t,ue(c.body_hash,i.source_path))})),o=await fe(e,await Oe(e,n),"o"),w=(await e.prepareUncached(`SELECT e.written_as AS _written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
12
+ WHERE e.type = 'links-to' AND e.src = ? AND (${o.sql}) ORDER BY e.written_as`).all(t.id,...o.params)).map(({_written_as:c,...p})=>({written_as:c??"",object:p,deleted:p.deleted_at!==null}));return t.deleted_at||await Ce(e,n,t,a),{object:t,record:i,heads:r,needsMerge:r.length>1,links:w}},{savepoint:!0})}s(rt,"readDocument");async function ot(e,n,t){h(t),await U(e,n,t);const a=await e.prepare("SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, seq DESC").all(t.id);return await x(a,async i=>await z(e,i))}s(ot,"history");async function st(e,n,t,a,i){if(h(t),t.deleted_at)throw l(`${t.name} is deleted`);return n&&await L(e,n,t),await y(async()=>{const r=a.owner??await f(e,t.owner),o=a.name??t.name;await M(e,r,o,t),await W(e,r,t.privacy),await e.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,r.id,t.id);const u=await f(e,t.id);if(r.id!==t.owner&&(await Ne(e,u),n&&!await A(e,n,"edit",u.id)))throw R(`no owner ${r.name} you can move documents to`);return n&&await O(e,E(n),u),(o!==t.name||r.id!==t.owner)&&await F(e,u,i),u},{savepoint:!0})}s(st,"moveDocument");async function ct(e,n,t,a,i){return h(t),t.deleted_at?t:(n&&await L(e,n,t),await y(async()=>{const r=await ne(e,n,t,a,i);return await We(e,t),await ie(e,await ae(e,t.id),a),r},{savepoint:!0}))}s(ct,"deleteDocument");async function We(e,n){if(n.schema_type==null)return;const t=await e.prepare(`SELECT resource_type, resource_id, relation, subject_type, subject_id, subject_relation FROM relations
13
+ WHERE (resource_type = ? AND resource_id = ?) OR (subject_type = ? AND subject_id = ?)`).all(n.schema_type,n.id,n.schema_type,n.id);for(const a of t)await Se(e,{resourceType:a.resource_type,resourceId:a.resource_id,relation:a.relation,subject:{type:a.subject_type,id:a.subject_id,relation:a.subject_relation}})}s(We,"deleteFactsOf");async function ut(e,n,t,a){return h(n),n.deleted_at?await y(async()=>{await M(e,await f(e,n.owner),t,n),await e.prepare("UPDATE objects SET deleted_at = NULL, name = ? WHERE id = ?").run(t,n.id);const i=await f(e,n.id);return await F(e,i,a),i},{savepoint:!0}):n}s(ut,"restoreDocument");async function ne(e,n,t,a,i){await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i??new Date().toISOString(),t.id);const r=await f(e,t.id),o=n?E(n):null;return o&&await O(e,o,r),await k(e,{event:"deleted",object:r,actor:o,attrs:{}},a),await e.prepare("DELETE FROM watches WHERE object = ?").run(t.id),await G(e,r,[]),r}s(ne,"markDeleted");async function ae(e,n){return(await e.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n)).map(({dst:t})=>t)}s(ae,"linkTargets");async function ie(e,n,t){const a=e.prepare(`SELECT 1 FROM objects d JOIN objects w ON w.id = d.owner
8
14
  WHERE d.id = ? AND d.type = 'document' AND d.deleted_at IS NULL AND w.deleted_at IS NOT NULL
9
15
  AND NOT EXISTS (SELECT 1 FROM edges e JOIN objects s ON s.id = e.src
10
- WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),a=[...n];for(;a.length>0;){const r=a.pop();await i.get(r)!==void 0&&(await te(t,null,await m(t,r),e),a.push(...await ne(t,r)))}}s(ae,"collectUnlinked");async function et(t,n,e){if(n.revisions.length===0)throw p(`import of ${n.name}: no revisions`);return await h(async()=>{const i=await b(t,n,e);await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.revisions[0].created_at,i.id);let a=null;return await ge(n.revisions,async(r,o)=>{a=(await A(t,i,{...r,number:o+1,parents:a===null?[]:[a]})).rev_id}),await F(t,i),await m(t,i.id)},{savepoint:!0})}s(et,"importDocument");async function tt(t,n,e){if(y(n),n.privacy!=="public"&&await Y(t,n)==="repo")throw p(`a repo owns only public documents; refusing revisions of ${n.name}`);return await h(async()=>{const i=t.prepare("SELECT rev_id, number, document FROM revisions WHERE rev_id = ?");let a=await q(e,async c=>!await i.get(c.rev_id));for(;a.length>0;){const c=[];for(const u of a){const w=await S(u.parents,async d=>await i.get(d));if(w.some(d=>d===void 0)){c.push(u);continue}const l=w;if(l.some(d=>d.document!==n.id))throw p(`revision ${u.rev_id} names a parent of another document`);await A(t,n,{...u,author:null,number:1+Math.max(0,...l.map(d=>d.number)),parents:l.map(d=>d.rev_id)})}if(c.length===a.length)throw p(`revisions of ${n.name} name unknown parents: ${c.map(u=>u.rev_id).join(", ")}`);a=c}const{heads_key:r,headCount:o}=await F(t,n);return{heads_key:r,headCount:o}},{savepoint:!0})}s(tt,"insertImportedRevisions");async function nt(t,n){const e=await m(t,n);if(!e||e.type!=="document")throw N(`no document ${n}`);return e}s(nt,"documentById");export{E as actorOf,M as applyDocumentFields,xe as applyReplacements,ae as collectUnlinked,Ze as deleteDocument,nt as documentById,O as documentRecord,Ye as editDocument,C as heads,ze as history,et as importDocument,tt as insertImportedRevisions,De as linksIn,Qe as moveDocument,qe as readBase,Xe as readDocument,Te as recordRead,be as restoreDocument,Ve as setDocumentRecord,Q as setRevisionLinks,Z as withCurrentNames,Ke as writeDocument};
16
+ WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),i=[...n];for(;i.length>0;){const r=i.pop();await a.get(r)!==void 0&&(await ne(e,null,await f(e,r),t),i.push(...await ae(e,r)))}}s(ie,"collectUnlinked");async function wt(e,n,t){if(n.revisions.length===0)throw l(`import of ${n.name}: no revisions`);return await y(async()=>{const a=await ee(e,n,t);await e.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.revisions[0].created_at,a.id);let i=null;return await ge(n.revisions,async(r,o)=>{i=(await S(e,a,{...r,number:o+1,parents:i===null?[]:[i]})).rev_id}),await $(e,a),await f(e,a.id)},{savepoint:!0})}s(wt,"importDocument");async function pt(e,n,t){if(h(n),n.privacy!=="public"&&await V(e,n)==="repo")throw l(`a repo owns only public documents; refusing revisions of ${n.name}`);return await y(async()=>{const a=e.prepare("SELECT rev_id, number, document FROM revisions WHERE rev_id = ?");let i=await Re(t,async u=>!await a.get(u.rev_id));for(;i.length>0;){const u=[];for(const w of i){const c=await x(w.parents,async d=>await a.get(d));if(c.some(d=>d===void 0)){u.push(w);continue}const p=c;if(p.some(d=>d.document!==n.id))throw l(`revision ${w.rev_id} names a parent of another document`);await S(e,n,{...w,author:null,number:1+Math.max(0,...p.map(d=>d.number)),parents:p.map(d=>d.rev_id)})}if(u.length===i.length)throw l(`revisions of ${n.name} name unknown parents: ${u.map(w=>w.rev_id).join(", ")}`);i=u}const{heads_key:r,headCount:o}=await $(e,n);return{heads_key:r,headCount:o}},{savepoint:!0})}s(pt,"insertImportedRevisions");async function lt(e,n){const t=await f(e,n);if(!t||t.type!=="document")throw R(`no document ${n}`);return t}s(lt,"documentById");export{E as actorOf,J as applyDocumentFields,He as applyReplacements,ie as collectUnlinked,ct as deleteDocument,lt as documentById,v as documentRecord,at as editDocument,I as heads,ot as history,wt as importDocument,pt as insertImportedRevisions,Le as linksIn,st as moveDocument,$e as readBase,rt as readDocument,Ce as recordRead,ut as restoreDocument,it as setDocumentRecord,Z as setRevisionLinks,b as withCurrentNames,nt as writeDocument};
@@ -1,3 +1,3 @@
1
- var y=Object.defineProperty;var i=(t,n)=>y(t,"name",{value:n,configurable:!0});import{canRead as f}from"./access.js";import{getById as w}from"./objects.js";import{mapAsync as N}from"../async-array.js";async function _(t,n,e){await t.prepare(`INSERT INTO edges (type, src, dst, written_as, created_at) VALUES ('acted-on', ?, ?, NULL, ?)
2
- ON CONFLICT DO NOTHING`).run(n.id,e.id,new Date().toISOString())}i(_,"recordActedOn");async function k(t,n,e){const s=(await t.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n.id)).map(({dst:a})=>a);await t.prepare("DELETE FROM edges WHERE type = 'links-to' AND src = ?").run(n.id);const o=t.prepare(`INSERT INTO edges (type, src, dst, written_as, created_at) VALUES ('links-to', ?, ?, ?, ?)
3
- ON CONFLICT DO NOTHING`),d=new Date().toISOString();for(const a of e)await o.run(n.id,a.dst.id,a.written_as,d);const c=new Set(e.map(a=>a.dst.id));return s.filter(a=>!c.has(a))}i(k,"setLinks");async function D(t,n,e){await t.prepare("DELETE FROM unresolved_links WHERE src = ?").run(n.id);const s=t.prepare("INSERT INTO unresolved_links (src, name) VALUES (?, ?) ON CONFLICT DO NOTHING");for(const o of e)await s.run(n.id,o)}i(D,"setUnresolved");async function A(t,n,e){const s=t.prepare("DELETE FROM unresolved_links WHERE src = ? AND name = ?");for(const o of e)await s.run(n.id,o)}i(A,"dropUnresolved");async function I(t,n,e){return await t.prepare("SELECT src, name FROM unresolved_links WHERE name = ? OR name = ? OR right(name, length(CAST(? AS text)) + 1) = '/' || CAST(? AS text) ORDER BY src, name").all(n,e,n,n)}i(I,"unresolvedLinksCouldName");async function C(t,n){const e=await t.prepare("SELECT e.dst, e.written_as FROM edges e WHERE e.type = 'links-to' AND e.src = ? ORDER BY e.written_as").all(n.id);return await N(e,async s=>({written_as:s.written_as??"",dst:await w(t,s.dst)}))}i(C,"linksOf");async function g(t,n,e){return await R(t,n,e,{type:"links-to",direction:"in"})}i(g,"backlinks");async function R(t,n,e,s={}){if(!await f(t,n,e))return[];const o=s.direction??"both",d=s.type?[s.type]:["acted-on","links-to"],c=[];for(const r of d)o!=="in"&&c.push(...await t.prepare("SELECT * FROM edges WHERE type = ? AND src = ?").all(r,e.id)),o!=="out"&&c.push(...await t.prepare("SELECT * FROM edges WHERE type = ? AND dst = ?").all(r,e.id));const a=new Set,l=[];for(const r of c){const u=`${r.type}|${r.src}|${r.dst}`;if(a.has(u))continue;a.add(u);const p=r.src===e.id?e:await w(t,r.src),E=r.dst===e.id?e:await w(t,r.dst);if(!p||!E)continue;const O=p===e?E:p;O!==e&&!await f(t,n,O)||l.push({type:r.type,src:p,dst:E,written_as:r.written_as,created_at:r.created_at})}return l}i(R,"edgesOf");export{g as backlinks,A as dropUnresolved,R as edgesOf,C as linksOf,_ as recordActedOn,k as setLinks,D as setUnresolved,I as unresolvedLinksCouldName};
1
+ var f=Object.defineProperty;var i=(e,t)=>f(e,"name",{value:t,configurable:!0});import{check as y,permits as R,resolveReader as T}from"../permissions/evaluator.js";import{getById as E}from"./objects.js";import{mapAsync as S}from"../async-array.js";async function I(e,t,n){await e.prepare(`INSERT INTO edges (type, src, dst, written_as, created_at) VALUES ('acted-on', ?, ?, NULL, ?)
2
+ ON CONFLICT DO NOTHING`).run(t.id,n.id,new Date().toISOString())}i(I,"recordActedOn");async function _(e,t,n){const a=(await e.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(t.id)).map(({dst:r})=>r);await e.prepare("DELETE FROM edges WHERE type = 'links-to' AND src = ?").run(t.id);const o=e.prepare(`INSERT INTO edges (type, src, dst, written_as, created_at) VALUES ('links-to', ?, ?, ?, ?)
3
+ ON CONFLICT DO NOTHING`),c=new Date().toISOString();for(const r of n)await o.run(t.id,r.dst.id,r.written_as,c);const p=new Set(n.map(r=>r.dst.id));return a.filter(r=>!p.has(r))}i(_,"setLinks");async function x(e,t,n){await e.prepare("DELETE FROM unresolved_links WHERE src = ?").run(t.id);const a=e.prepare("INSERT INTO unresolved_links (src, name) VALUES (?, ?) ON CONFLICT DO NOTHING");for(const o of n)await a.run(t.id,o)}i(x,"setUnresolved");async function C(e,t,n){const a=e.prepare("DELETE FROM unresolved_links WHERE src = ? AND name = ?");for(const o of n)await a.run(t.id,o)}i(C,"dropUnresolved");async function g(e,t,n){return await e.prepare("SELECT src, name FROM unresolved_links WHERE name = ? OR name = ? OR right(name, length(CAST(? AS text)) + 1) = '/' || CAST(? AS text) ORDER BY src, name").all(t,n,t,t)}i(g,"unresolvedLinksCouldName");async function h(e,t){const n=await e.prepare("SELECT e.dst, e.written_as FROM edges e WHERE e.type = 'links-to' AND e.src = ? ORDER BY e.written_as").all(t.id);return await S(n,async a=>({written_as:a.written_as??"",dst:await E(e,a.dst)}))}i(h,"linksOf");async function F(e,t,n){return await m(e,t,n,{type:"links-to",direction:"in"})}i(F,"backlinks");async function m(e,t,n,a={}){const o=await T(e,t);if(!await y(e,o,"read",n.id))return[];const c=a.direction??"both",p=a.type?[a.type]:["acted-on","links-to"],r=await R(e,o,"read","x"),d=[];for(const s of p)c!=="in"&&d.push(...await e.prepareUncached(`SELECT e.* FROM edges e JOIN objects x ON x.id = e.dst WHERE e.type = ? AND e.src = ? AND ${r.sql}`).all(s,n.id,...r.params)),c!=="out"&&d.push(...await e.prepareUncached(`SELECT e.* FROM edges e JOIN objects x ON x.id = e.src WHERE e.type = ? AND e.dst = ? AND ${r.sql}`).all(s,n.id,...r.params));const l=new Set,w=[];for(const s of d){const O=`${s.type}|${s.src}|${s.dst}`;if(l.has(O))continue;l.add(O);const u=s.src===n.id?n:await E(e,s.src),N=s.dst===n.id?n:await E(e,s.dst);!u||!N||w.push({type:s.type,src:u,dst:N,written_as:s.written_as,created_at:s.created_at})}return w}i(m,"edgesOf");export{F as backlinks,C as dropUnresolved,m as edgesOf,h as linksOf,I as recordActedOn,_ as setLinks,x as setUnresolved,g as unresolvedLinksCouldName};
@@ -1,11 +1,15 @@
1
- var x=Object.defineProperty;var d=(n,e)=>x(n,"name",{value:e,configurable:!0});import{EventEmitter as H}from"node:events";const v=new H;v.setMaxListeners(0);async function pe(n,e){const o=d(i=>e(i),"listener");return v.on(n,o),()=>v.off(n,o)}d(pe,"subscribeDocumentEvents");import{evalCondition as $}from"../predicate.js";import{finalizeNodeMail as B,insertMessage as J}from"../feed/messages.js";import{linkAuthorizes as K,ownerChain as U,readCheck as G}from"./access.js";import{getById as q}from"./objects.js";import{appScopesNow as z,publishLookups as P,readerForObject as V}from"./reader.js";import{configFromJson as k}from"./watches.js";import{CrtrError as L}from"../errors.js";import{ExitCode as A}from"../../types.js";import{eventSpec as X,kindOf as C}from"./kinds/registry.js";import{machineNameOf as M}from"../peer/names.js";import{settleObjectWaits as Y}from"../canvas/waits.js";import{RECEIVERS as F}from"./receivers.js";import{DEFAULT_LISTEN as D}from"./types.js";import{afterCommit as Q}from"../canvas/db.js";function Z(n,e){if(n.level===void 0||n.level===e.level||e.levels?.includes(n.level))return n.level??e.level;const o=[e.level,...e.levels??[]].filter((i,t,r)=>r.indexOf(i)===t).sort((i,t)=>i-t);throw new L("invalid_level",`${n.object.type} ${n.event} publishes at level ${o.join(", ")}, not ${n.level}`,A.USAGE,{kind:n.object.type,event:n.event,level:n.level,allowed:o})}d(Z,"resolveLevel");function ee(n,e){const o=n.split("."),i=e.split(".");for(let t=0;t<o.length;t++){if(o[t]===">")return t===o.length-1&&i.length>t;if(t>=i.length||o[t]!=="*"&&o[t]!==i[t])return!1}return o.length===i.length}d(ee,"matchSubject");const W={"next-turn":1,"after-turn":2,steer:3,interrupt:4};function te(n,e){let o=null;for(const i of n){let t=null;for(const r of i)r.threshold<=e&&(!t||r.threshold>t.threshold)&&(t=r);t&&(!o||W[t.delivery]>W[o])&&(o=t.delivery)}return o}d(te,"resolveDelivery");async function ve(n,e,o){if(e.config)return e.config;const i=await n.prepare("SELECT listen_json FROM objects WHERE id = ?").get(o.id);return i?.listen_json?k(i.listen_json):D}d(ve,"listenConfigFor");function ne(n,e){return e.inboxKind??(n.level>=7?"urgent":"update")}d(ne,"inboxKind");async function oe(n,e){return e.type==="node"?e.id:await n.prepare(`SELECT 1 FROM objects WHERE type IN ('person','app','repo','profile')
2
- AND name = ? AND id != ? LIMIT 1`).get(e.name,e.id)?`${e.type}:${e.name}`:e.name}d(oe,"ownerHandleOf");async function ie(n,e,o){const i=e.object.owner===null?null:await n.prepare("SELECT * FROM objects WHERE id = ?").get(e.object.owner),t=i?.id??null,r=i?await oe(n,i):null;return{event:e.event,level:e.level,object:{id:e.object.id,type:e.object.type,kind:e.object.kind,owner:t,owner_handle:r,name:e.object.name,...e.object.home_machine?{machine:M(e.object.home_machine),machine_id:e.object.home_machine}:{}},actor:e.actor?.id??null,...e.actor?.home_machine?{actor_machine:M(e.actor.home_machine),actor_machine_id:e.actor.home_machine}:{},attrs:o,...e.pointer===void 0?{}:{pointer:e.pointer}}}d(ie,"pushFields");async function se(n,e,o,i){const t=await ie(n,e,i),r=e.messageBody!==void 0&&o.carriesBody===!0,f=r?typeof e.messageBody=="object"&&e.messageBody!==null&&!Array.isArray(e.messageBody)?{...e.messageBody,push:t}:{body:e.messageBody,push:t}:{push:t},c=r?e.messageBody:null,l=c?.bodyRef===void 0?null:c;return{kind:ne(e,o),label:l?.label??(typeof c?.label=="string"?c.label:e.object.type.includes(".")&&typeof e.messageBody?.summary=="string"?e.messageBody.summary.split(`
3
- `)[0]:`${e.event} ${e.object.type} ${e.object.name}`),...l?.bodyRef===void 0?{}:{bodyRef:l.bodyRef},...l?.disposition===void 0?{}:{disposition:l.disposition},senderNodeId:e.actor?.type==="node"?e.actor.id:null,senderName:e.actor===null||e.actor===void 0?null:e.actor.home_machine?`${e.actor.name} on ${M(e.actor.home_machine)}`:e.actor.name,bodyJson:f}}d(se,"messageInput");async function re(n,e,o,i,t){if(!o.supersedes)return[{attrs:e.attrs,destinations:[...i]}];const r=n.prepare(`SELECT d.delivery_id, d.message_id, d.state, m.body_json FROM deliveries d
1
+ var W=Object.defineProperty;var d=(t,e)=>W(t,"name",{value:e,configurable:!0});import{EventEmitter as H}from"node:events";const O=new H;O.setMaxListeners(0);async function de(t,e){const o=d(i=>e(i),"listener");return O.on(t,o),()=>O.off(t,o)}d(de,"subscribeDocumentEvents");import{evalCondition as x}from"../predicate.js";import{finalizeNodeMail as $,insertMessage as B}from"../feed/messages.js";import{check as S,resolveReader as J}from"../permissions/evaluator.js";import{getById as U}from"./objects.js";import{appScopesNow as K,publishLookups as G,readerForObject as q}from"./reader.js";import{configFromJson as M}from"./watches.js";import{CrtrError as L}from"../errors.js";import{ExitCode as v}from"../../types.js";import{eventSpec as V,kindOf as C}from"./kinds/registry.js";import{machineNameOf as T}from"../peer/names.js";import{settleObjectWaits as Y}from"../canvas/waits.js";import{RECEIVERS as F}from"./receivers.js";import{DEFAULT_LISTEN as k}from"./types.js";import{afterCommit as z}from"../canvas/db.js";function P(t,e){if(t.level===void 0||t.level===e.level||e.levels?.includes(t.level))return t.level??e.level;const o=[e.level,...e.levels??[]].filter((i,n,r)=>r.indexOf(i)===n).sort((i,n)=>i-n);throw new L("invalid_level",`${t.object.type} ${t.event} publishes at level ${o.join(", ")}, not ${t.level}`,v.USAGE,{kind:t.object.type,event:t.event,level:t.level,allowed:o})}d(P,"resolveLevel");function X(t,e){const o=t.split("."),i=e.split(".");for(let n=0;n<o.length;n++){if(o[n]===">")return n===o.length-1&&i.length>n;if(n>=i.length||o[n]!=="*"&&o[n]!==i[n])return!1}return o.length===i.length}d(X,"matchSubject");const D={"next-turn":1,"after-turn":2,steer:3,interrupt:4};function Q(t,e){let o=null;for(const i of t){let n=null;for(const r of i)r.threshold<=e&&(!n||r.threshold>n.threshold)&&(n=r);n&&(!o||D[n.delivery]>D[o])&&(o=n.delivery)}return o}d(Q,"resolveDelivery");async function Ne(t,e,o){if(e.config)return e.config;const i=await t.prepare("SELECT listen_json FROM objects WHERE id = ?").get(o.id);return i?.listen_json?M(i.listen_json):k}d(Ne,"listenConfigFor");function Z(t,e){return e.inboxKind??(t.level>=7?"urgent":"update")}d(Z,"inboxKind");async function ee(t,e){return e.type==="node"?e.id:await t.prepare(`SELECT 1 FROM objects WHERE type IN ('person','app','repo','profile')
2
+ AND name = ? AND id != ? LIMIT 1`).get(e.name,e.id)?`${e.type}:${e.name}`:e.name}d(ee,"ownerHandleOf");async function te(t,e,o){const i=e.object.owner===null?null:await t.prepare("SELECT * FROM objects WHERE id = ?").get(e.object.owner),n=i?.id??null,r=i?await ee(t,i):null,f=e.object.type==="node"?await t.prepare("SELECT i.external_id FROM nodes n JOIN identities i ON i.object = n.app AND i.scheme = 'app' WHERE n.node_id = ?").get(e.object.id):void 0;return{event:e.event,level:e.level,object:{id:e.object.id,type:e.object.type,kind:e.object.kind,owner:n,owner_handle:r,...f?{app:f.external_id}:{},name:e.object.name,...e.object.home_machine?{machine:T(e.object.home_machine),machine_id:e.object.home_machine}:{}},actor:e.actor?.id??null,...e.actor?.home_machine?{actor_machine:T(e.actor.home_machine),actor_machine_id:e.actor.home_machine}:{},attrs:o,...e.pointer===void 0?{}:{pointer:e.pointer}}}d(te,"pushFields");async function ne(t,e,o,i){const n=await te(t,e,i),r=e.messageBody!==void 0&&o.carriesBody===!0,f=r?typeof e.messageBody=="object"&&e.messageBody!==null&&!Array.isArray(e.messageBody)?{...e.messageBody,push:n}:{body:e.messageBody,push:n}:{push:n},c=r?e.messageBody:null,l=c?.bodyRef===void 0?null:c;return{kind:Z(e,o),label:l?.label??(typeof c?.label=="string"?c.label:e.object.type.includes(".")&&typeof e.messageBody?.summary=="string"?e.messageBody.summary.split(`
3
+ `)[0]:`${e.event} ${e.object.type} ${e.object.name}`),...l?.bodyRef===void 0?{}:{bodyRef:l.bodyRef},...l?.disposition===void 0?{}:{disposition:l.disposition},senderNodeId:e.actor?.type==="node"?e.actor.id:null,senderName:e.actor===null||e.actor===void 0?null:e.actor.home_machine?`${e.actor.name} on ${T(e.actor.home_machine)}`:e.actor.name,bodyJson:f}}d(ne,"messageInput");async function oe(t,e,o,i,n){if(!o.supersedes)return[{attrs:e.attrs,destinations:[...i]}];const r=t.prepare(`SELECT d.delivery_id, d.message_id, d.state, m.body_json FROM deliveries d
4
4
  JOIN messages m ON m.message_id = d.message_id
5
5
  WHERE d.destination_id = ? AND d.state IN ('pending', 'handed_off')
6
6
  AND d.canceled_at IS NULL AND m.event = ? AND m.object_id = ?
7
- ORDER BY m.seq`),f=n.prepare("DELETE FROM deliveries WHERE delivery_id = ?"),c=n.prepare("SELECT * FROM objects WHERE id = ?"),l=new Map,E=new Set;for(const m of i){const g=t(m),O=await c.get(g),I=O===void 0?void 0:await C(n,O.type),_=await r.all(g,e.event,e.object.id),j=new Set;for(const a of _)a.state==="handed_off"?await I?.supersedeClaimed?.(n,a.delivery_id)&&j.add(a.delivery_id):(await f.run(a.delivery_id),E.add(a.message_id));const h=_.filter(a=>a.state==="pending"||j.has(a.delivery_id)).at(-1),b=h?.message_id??null,s=l.get(b)??{prior:h?.body_json?JSON.parse(h.body_json).push?.attrs??null:null,destinations:[]};s.destinations.push(m),l.set(b,s)}const N=n.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const m of E)await N.run(m,m);return[...l.values()].map(m=>({attrs:o.merge?o.merge(m.prior,e):e.attrs,destinations:m.destinations}))}d(re,"supersede");async function ae(n,e,o,i,t){const r=await q(n,i);if(r===null||r.type!=="app")return!1;const f=await n.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(e.id)!==void 0,c=f?null:await z(n,r,t);return(f||c!==null)&&await K(n,r,c,e,o)}d(ae,"linkDelivers");const T=8;class ce extends L{static{d(this,"HopLimitError")}constructor(e){super("hop_limit",`event depth ${e} exceeds ${T}`,A.GENERAL,{depth:e,limit:T})}}async function Ie(n,e,o=async()=>[]){const i=await X(n,e.object.type,e.event),t={...e,level:Z(e,i)};t.object.type==="document"&&(t.event==="edited"||t.event==="deleted")&&Q(()=>v.emit(t.object.id,t.event));const r=e.cause??null,f=r===null?null:await n.prepare(`SELECT depth FROM messages WHERE message_id = ?
8
- UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(r,r);if(r!==null&&f===void 0)throw new L("unknown_cause",`no message ${r}`,A.USAGE,{cause:r});const c=f===null?0:f.depth+1;if(c>T)throw new ce(c);(i.endsWait===!0||t.event==="deleted")&&await Y(n,t.object.id);const l=await U(n,t.object),E=`w.watcher, o.id AS watcher_id, o.type AS watcher_type, o.reception, s.pattern, s.filter_json,
9
- COALESCE(s.config_json, o.listen_json) AS config_json, w.link_app`,N=await n.prepareUncached(i.durable?`SELECT ${E} FROM watches w JOIN watch_subscriptions s ON s.watch = w.watch_id
10
- JOIN objects o ON o.id = w.watcher WHERE w.object = ? AND w.watcher != ? AND o.reception >= 1`:`SELECT ${E} FROM objects o JOIN watches w ON w.watcher = o.id JOIN watch_subscriptions s ON s.watch = w.watch_id
11
- WHERE o.reception = 2 AND w.object = ? AND w.watcher != ?`).all(t.object.id,t.actor?.id??"");if(N.length===0)return{delivered:0,message_id:null,depth:c};const m={event:t.event,level:t.level,id:t.object.id,type:t.object.type,kind:t.object.kind,actor:t.actor?.id??null,...t.attrs},g=new Map;for(const s of N){if(t.onlyWatchers!==void 0&&!t.onlyWatchers.includes(s.watcher_id)||!ee(s.pattern,t.event)||s.filter_json!==null&&!$(JSON.parse(s.filter_json),m))continue;const a=g.get(s.watcher)??{id:s.watcher_id,type:s.watcher_type,reception:s.reception,configs:[],configured:!1,linkApp:s.link_app};a.configs.push(s.config_json?k(s.config_json):D),a.configured||=s.config_json!==null,g.set(s.watcher,a)}const O=(l.length?l[l.length-1]:t.object).type==="repo",I=d(async()=>[],"noRepos"),_=await G(n,t.object),j=P(n),h=[];for(const s of g.values()){const a=await n.prepare("SELECT * FROM objects WHERE id = ?").get(s.id),u=await C(n,a.type),R=u.receiverFor?await u.receiverFor(n,a):u.receiver;if(R===void 0)continue;const w=R==="peer"&&!s.configured?"next-turn":te(s.configs,t.level);if(w===null)continue;const y=await V(n,a,O?o:I,j);if(y.scopes===null&&(y.class==="node"||y.owner.type!=="person"))continue;const p=s.linkApp!==null&&!await _(y)&&await ae(n,t.object,a,s.linkApp,j);!p&&!await _(y)||h.push({id:s.id,reception:s.reception,delivery:w,receiver:R,...p?{linked:p}:{}})}if(h.length===0)return{delivered:0,message_id:null,depth:c};let b=null;for(const s of await re(n,t,i,h,a=>a.id)){const a=new Map;for(const p of s.destinations)a.set(p.receiver,[...a.get(p.receiver)??[],p]);const u=await se(n,t,i,s.attrs),R=[...a].map(([p,S])=>F[p].bodyOwner(S)).find(p=>p!==null)??null,w=B({...u,urgency:"normal"},R),y=await J(n,{operationId:w.operationId,createdAt:w.createdAt,senderNodeId:u.senderNodeId,senderName:u.senderName,kind:u.kind,label:u.label,bodyRef:w.bodyRef,bodyJson:w.bodyJson,disposition:u.disposition??null,event:{event:t.event,objectId:t.object.id,level:t.level,actorId:t.actor?.id??null},causeMessageId:r,depth:c});b??=y;for(const[p,S]of a)await F[p].enqueue(n,{messageId:y,event:t.event,level:t.level,senderNodeId:u.senderNodeId,elevatesTier:i.elevatesTier===!0,...t.idempotencyKey===void 0?{}:{idempotencyKey:t.idempotencyKey}},S)}return{delivered:h.length,message_id:b,depth:c}}d(Ie,"publish");export{T as HOP_LIMIT,ce as HopLimitError,ve as listenConfigFor,ee as matchSubject,Ie as publish,te as resolveDelivery,Z as resolveLevel,pe as subscribeDocumentEvents,re as supersede};
7
+ ORDER BY m.seq`),f=t.prepare("DELETE FROM deliveries WHERE delivery_id = ?"),c=t.prepare("SELECT * FROM objects WHERE id = ?"),l=new Map,_=new Set;for(const m of i){const b=n(m),R=await c.get(b),N=R===void 0?void 0:await C(t,R.type),y=await r.all(b,e.event,e.object.id),j=new Set;for(const u of y)u.state==="handed_off"?await N?.supersedeClaimed?.(t,u.delivery_id)&&j.add(u.delivery_id):(await f.run(u.delivery_id),_.add(u.message_id));const s=y.filter(u=>u.state==="pending"||j.has(u.delivery_id)).at(-1),a=s?.message_id??null,p=l.get(a)??{prior:s?.body_json?JSON.parse(s.body_json).push?.attrs??null:null,destinations:[]};p.destinations.push(m),l.set(a,p)}const I=t.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const m of _)await I.run(m,m);return[...l.values()].map(m=>({attrs:o.merge?o.merge(m.prior,e):e.attrs,destinations:m.destinations}))}d(oe,"supersede");async function ie(t,e,o,i,n){const r=await U(t,i);if(r===null||r.type!=="app")return!1;const f=await t.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(e.id)!==void 0,c=f?null:await K(t,r,n);if(!f&&c===null||e.owner===null)return!1;const l=await J(t,{class:"app",app:r,scopes:c??[]});return(e.owner===r.id||await S(t,l,"edit",e.owner))&&(c===null||await S(t,l,"read",o.id,{verb:"link"}))}d(ie,"linkDelivers");async function se(t,e){return((await t.prepare(`WITH RECURSIVE chain(id, depth) AS (
8
+ SELECT owner, 1 FROM objects WHERE id = ? AND owner IS NOT NULL
9
+ UNION ALL
10
+ SELECT o.owner, c.depth + 1 FROM objects o JOIN chain c ON o.id = c.id WHERE o.owner IS NOT NULL
11
+ ) SELECT o.type FROM chain c JOIN objects o ON o.id = c.id ORDER BY c.depth DESC LIMIT 1`).get(e.id))?.type??e.type)==="repo"}d(se,"inRepoSpace");const A=8;class re extends L{static{d(this,"HopLimitError")}constructor(e){super("hop_limit",`event depth ${e} exceeds ${A}`,v.GENERAL,{depth:e,limit:A})}}async function Oe(t,e,o=async()=>[]){const i=await V(t,e.object.type,e.event),n={...e,level:P(e,i)};n.object.type==="document"&&(n.event==="edited"||n.event==="deleted")&&z(()=>O.emit(n.object.id,n.event));const r=e.cause??null,f=r===null?null:await t.prepare(`SELECT depth FROM messages WHERE message_id = ?
12
+ UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(r,r);if(r!==null&&f===void 0)throw new L("unknown_cause",`no message ${r}`,v.USAGE,{cause:r});const c=f===null?0:f.depth+1;if(c>A)throw new re(c);(i.endsWait===!0||n.event==="deleted")&&await Y(t,n.object.id);const l=`w.watcher, o.id AS watcher_id, o.type AS watcher_type, o.reception, s.pattern, s.filter_json,
13
+ COALESCE(s.config_json, o.listen_json) AS config_json, w.link_app`,_=await t.prepareUncached(i.durable?`SELECT ${l} FROM watches w JOIN watch_subscriptions s ON s.watch = w.watch_id
14
+ JOIN objects o ON o.id = w.watcher WHERE w.object = ? AND w.watcher != ? AND o.reception >= 1`:`SELECT ${l} FROM objects o JOIN watches w ON w.watcher = o.id JOIN watch_subscriptions s ON s.watch = w.watch_id
15
+ WHERE o.reception = 2 AND w.object = ? AND w.watcher != ?`).all(n.object.id,n.actor?.id??"");if(_.length===0)return{delivered:0,message_id:null,depth:c};const I={event:n.event,level:n.level,id:n.object.id,type:n.object.type,kind:n.object.kind,actor:n.actor?.id??null,...n.attrs},m=new Map;for(const s of _){if(n.onlyWatchers!==void 0&&!n.onlyWatchers.includes(s.watcher_id)||!X(s.pattern,n.event)||s.filter_json!==null&&!x(JSON.parse(s.filter_json),I))continue;const a=m.get(s.watcher)??{id:s.watcher_id,type:s.watcher_type,reception:s.reception,configs:[],configured:!1,linkApp:s.link_app};a.configs.push(s.config_json?M(s.config_json):k),a.configured||=s.config_json!==null,m.set(s.watcher,a)}const b=await se(t,n.object),R=d(async()=>[],"noRepos"),N=G(t),y=[];for(const s of m.values()){const a=await t.prepare("SELECT * FROM objects WHERE id = ?").get(s.id),p=await C(t,a.type),u=p.receiverFor?await p.receiverFor(t,a):p.receiver;if(u===void 0)continue;const E=u==="peer"&&!s.configured?"next-turn":Q(s.configs,n.level);if(E===null)continue;const h=await q(t,a,b?o:R,N);if(h.scopes===null&&(h.class==="node"||h.owner.type!=="person"))continue;const w=await S(t,h,"read",n.object.id),g=s.linkApp!==null&&!w&&await ie(t,n.object,a,s.linkApp,N);!g&&!w||y.push({id:s.id,reception:s.reception,delivery:E,receiver:u,...g?{linked:g}:{}})}if(y.length===0)return{delivered:0,message_id:null,depth:c};let j=null;for(const s of await oe(t,n,i,y,a=>a.id)){const a=new Map;for(const w of s.destinations)a.set(w.receiver,[...a.get(w.receiver)??[],w]);const p=await ne(t,n,i,s.attrs),u=[...a].map(([w,g])=>F[w].bodyOwner(g)).find(w=>w!==null)??null,E=$({...p,urgency:"normal"},u),h=await B(t,{operationId:E.operationId,createdAt:E.createdAt,senderNodeId:p.senderNodeId,senderName:p.senderName,kind:p.kind,label:p.label,bodyRef:E.bodyRef,bodyJson:E.bodyJson,disposition:p.disposition??null,event:{event:n.event,objectId:n.object.id,level:n.level,actorId:n.actor?.id??null},causeMessageId:r,depth:c});j??=h;for(const[w,g]of a)await F[w].enqueue(t,{messageId:h,event:n.event,level:n.level,senderNodeId:p.senderNodeId,elevatesTier:i.elevatesTier===!0,...n.idempotencyKey===void 0?{}:{idempotencyKey:n.idempotencyKey}},g)}return{delivered:y.length,message_id:j,depth:c}}d(Oe,"publish");export{A as HOP_LIMIT,re as HopLimitError,Ne as listenConfigFor,X as matchSubject,Oe as publish,Q as resolveDelivery,P as resolveLevel,de as subscribeDocumentEvents,oe as supersede};
@@ -1,5 +1,5 @@
1
- var _=Object.defineProperty;var m=(e,t)=>_(e,"name",{value:t,configurable:!0});import{recordMemoryReadsIn as v}from"../../canvas/memory-reads.js";import{deliveryDocOf as x,nodeSubject as D,ownerHandle as N}from"../../substrate/delivery/corpus.js";import{recordContentDelivery as S}from"../../substrate/delivery/deliver.js";import{renderExplicitRead as $}from"../../substrate/delivery/render-event.js";import{canRead as R}from"../access.js";import{documentRecord as O,readDocument as j}from"../documents.js";import{loadLedger as k,saveLedger as A}from"../exposures.js";import{getById as E}from"../objects.js";import{rulesOf as h}from"../rules.js";import{objectDTO as d}from"./dto.js";import{withBase as I}from"./kind.js";import{attr as l,header as L,section as M}from"./render.js";import{filterAsync as T,mapAsync as p}from"../../async-array.js";async function b(e,t,n){const i=`${n.name}/`,s=await e.prepare(`SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND home_machine IS NULL AND owner IS NOT DISTINCT FROM ?
2
- AND substr(name, 1, ?) = ? ORDER BY name`).all(n.owner,i.length,i);return await p(await T(s,async a=>!a.name.slice(i.length).includes("/")&&await R(e,t,a)),async a=>await d(e,a))}m(b,"listingOf");async function B(e,t,n){const i=t.class==="node",s=i?await k(e,t.node.id):{node:"",preferenceSnapshot:null,objects:new Map,listed:new Map},a={db:e,reader:t,subject:i?await D(e,t):null,ledger:s,channel:"transcript"},o=await $(a,await x(e,t,n));return i&&(await S(e,t,o.delivered),await A(e,s)),{attachments:o.attachments,linked:o.linked.map(c=>c.object)}}m(B,"explicitReadAttachments");async function C(e,t,n){if(t.class!=="node"||n.kind!=="knowledge"&&n.kind!=="preference"||n.owner===null)return;const i=await E(e,n.owner);i!==null&&await v(e,t.node.id,[{name:n.name,scope:N(i),at:new Date().toISOString(),type:"read"}])}m(C,"recordExplicitMemoryRead");function W(e,t){const n=[];if(t.attachments&&n.push(t.attachments,""),n.push(L("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>
1
+ var v=Object.defineProperty;var m=(e,t)=>v(e,"name",{value:t,configurable:!0});import{recordMemoryReadsIn as _}from"../../canvas/memory-reads.js";import{deliveryDocOf as D,nodeSubject as N,ownerHandle as $}from"../../substrate/delivery/corpus.js";import{recordContentDelivery as x}from"../../substrate/delivery/deliver.js";import{renderExplicitRead as S}from"../../substrate/delivery/render-event.js";import{permits as R,resolveReader as A}from"../../permissions/evaluator.js";import{documentRecord as O,readDocument as j}from"../documents.js";import{loadLedger as k,saveLedger as E}from"../exposures.js";import{getById as I}from"../objects.js";import{rulesOf as p}from"../rules.js";import{objectDTO as d}from"./dto.js";import{withBase as L}from"./kind.js";import{attr as l,header as M,section as T}from"./render.js";import{mapAsync as h}from"../../async-array.js";async function b(e,t,n){const i=`${n.name}/`,s=await R(e,await A(e,t),"read","o"),o=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.type = 'document' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.owner IS NOT DISTINCT FROM ?
2
+ AND substr(o.name, 1, ?) = ? AND strpos(substr(o.name, ? + 1), '/') = 0 AND ${s.sql} ORDER BY o.name`).all(n.owner,i.length,i,i.length,...s.params);return await h(o,async a=>await d(e,a))}m(b,"listingOf");async function B(e,t,n){const i=t.class==="node",s=i?await k(e,t.node.id):{node:"",preferenceSnapshot:null,objects:new Map,listed:new Map},o={db:e,reader:t,subject:i?await N(e,t):null,ledger:s,channel:"transcript"},a=await S(o,await D(e,t,n));return i&&(await x(e,t,a.delivered),await E(e,s)),{attachments:a.attachments,linked:a.linked.map(u=>u.object)}}m(B,"explicitReadAttachments");async function C(e,t,n){if(t.class!=="node"||n.kind!=="knowledge"&&n.kind!=="preference"||n.owner===null)return;const i=await I(e,n.owner);i!==null&&await _(e,t.node.id,[{name:n.name,scope:$(i),at:new Date().toISOString(),type:"read"}])}m(C,"recordExplicitMemoryRead");function U(e,t){const n=[];if(t.attachments&&n.push(t.attachments,""),n.push(M("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((i,s)=>{n.push(`<head${l("rev",i.rev_id)}${l("number",i.number)}${l("author",i.author_id)}>`,t.bodies[s]??"","</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 i=t.listing.some(o=>o.owner?.id===e.owner?.id&&o.name.startsWith(`${e.name}/`)),s=t.listing.some(o=>o.owner?.id!==e.owner?.id||!o.name.startsWith(`${e.name}/`)),a=i&&s?`Names under ${e.name}/ and documents it links to`:i?`Names under ${e.name}/`:"Documents it links to";n.push("",...M(a,t.listing))}return n.join(`
5
- `)}m(W,"render");function F(e){const t=e.attrs.revision,n=e.attrs.from_revision??(typeof t=="number"?t-1:null),i=e.attrs.rationale;return{...e.attrs,from_revision:n,revision:t,authors:e.actor?[e.actor.id]:[],rationales:typeof i=="string"?[i]:[]}}m(F,"editAttrs");const H=m((e,t)=>{const n=F(t);if(e===null)return n;const i=e.authors??[];return{...n,from_revision:e.from_revision??null,authors:[...i,...n.authors.filter(s=>!i.includes(s))],rationales:[...e.rationales??[],...n.rationales]}},"mergeEdits"),ne={name:"document",addressable:"name",events:I({edited:{level:8,supersedes:!0,merge:H},deleted:{level:8}}),async listItem(e,t){return{summary:(await O(e,t)).summary??null,rules:(await h(e,t)).length}},async read(e,t,n,i){const s=await j(e,t,n,i.reposFn);await C(e,t,n);const a=s.record,o=await b(e,t,n),{attachments:c,linked:w}=await B(e,t,n),y=new Set(o.map(r=>r.id)),f=[...o,...await p(w.filter(r=>!y.has(r.id)),async r=>await d(e,r))],u={summary:a.summary??null,revision:a.revision,heads:s.heads.map(r=>({rev_id:r.rev_id,number:r.number,author_id:r.author,created_at:r.created_at})),bodies:s.heads.map(r=>r.body),needs_merge:s.needsMerge,...f.length>0?{listing:f}:{},...c===""?{}:{attachments:c}};return i.fields&&(u.fields={why_it_exists:a.why_it_exists??null,unlisted:a.unlisted,lint_ignore:a.lint_ignore??[],extensions:a.extensions??{},rules:(await h(e,n)).map(({matchFrontmatter:r,...g})=>({...g,...r===void 0?{}:{match_frontmatter:r}}))}),{data:u,text:W(await d(e,n),u)}}};export{ne as documentKind};
4
+ </fields>`),t.needs_merge?(t.heads.forEach((i,s)=>{n.push(`<head${l("rev",i.rev_id)}${l("number",i.number)}${l("author",i.author_id)}>`,t.bodies[s]??"","</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 i=t.listing.some(a=>a.owner?.id===e.owner?.id&&a.name.startsWith(`${e.name}/`)),s=t.listing.some(a=>a.owner?.id!==e.owner?.id||!a.name.startsWith(`${e.name}/`)),o=i&&s?`Names under ${e.name}/ and documents it links to`:i?`Names under ${e.name}/`:"Documents it links to";n.push("",...T(o,t.listing))}return n.join(`
5
+ `)}m(U,"render");function W(e){const t=e.attrs.revision,n=e.attrs.from_revision??(typeof t=="number"?t-1:null),i=e.attrs.rationale;return{...e.attrs,from_revision:n,revision:t,authors:e.actor?[e.actor.id]:[],rationales:typeof i=="string"?[i]:[]}}m(W,"editAttrs");const F=m((e,t)=>{const n=W(t);if(e===null)return n;const i=e.authors??[];return{...n,from_revision:e.from_revision??null,authors:[...i,...n.authors.filter(s=>!i.includes(s))],rationales:[...e.rationales??[],...n.rationales]}},"mergeEdits"),ne={name:"document",addressable:"name",events:L({edited:{level:8,supersedes:!0,merge:F},deleted:{level:8}}),async listItem(e,t){return{summary:(await O(e,t)).summary??null,rules:(await p(e,t)).length}},async read(e,t,n,i){const s=await j(e,t,n,i.reposFn);await C(e,t,n);const o=s.record,a=await b(e,t,n),{attachments:u,linked:w}=await B(e,t,n),y=new Set(a.map(r=>r.id)),f=[...a,...await h(w.filter(r=>!y.has(r.id)),async r=>await d(e,r))],c={summary:o.summary??null,revision:o.revision,heads:s.heads.map(r=>({rev_id:r.rev_id,number:r.number,author_id:r.author,created_at:r.created_at})),bodies:s.heads.map(r=>r.body),needs_merge:s.needsMerge,...f.length>0?{listing:f}:{},...u===""?{}:{attachments:u}};return i.fields&&(c.fields={why_it_exists:o.why_it_exists??null,unlisted:o.unlisted,lint_ignore:o.lint_ignore??[],extensions:o.extensions??{},rules:(await p(e,n)).map(({matchFrontmatter:r,...g})=>({...g,...r===void 0?{}:{match_frontmatter:r}}))}),{data:c,text:U(await d(e,n),c)}}};export{ne as documentKind};
@@ -1 +1 @@
1
- var r=Object.defineProperty;var l=(e,t)=>r(e,"name",{value:t,configurable:!0});const d={created:{level:4},read:{level:1},watched:{level:1},deleted:{level:5},configured:{level:1}};function n(e={}){return{...d,...e}}l(n,"withBase");export{d as BASE_EVENTS,n as withBase};
1
+ var r=Object.defineProperty;var l=(e,t)=>r(e,"name",{value:t,configurable:!0});const d={created:{level:4},read:{level:1},watched:{level:1},deleted:{level:5},configured:{level:1},"relation.written":{level:1},"relation.deleted":{level:1}};function o(e={}){return{...d,...e}}l(o,"withBase");export{d as BASE_EVENTS,o as withBase};
@@ -1,5 +1,5 @@
1
- var E=Object.defineProperty;var l=(t,r)=>E(t,"name",{value:r,configurable:!0});import{settleMatchingControllerWait as _}from"../../canvas/waits.js";import{consumeDeadlineOnWake as T,effectiveTier as b,listenDeliveryTier as R,newId as I}from"../../feed/messages.js";import{readGoal as N}from"../../runtime/kickoff.js";import{canRead as h}from"../access.js";import{objectDTO as f}from"./dto.js";import{withBase as S}from"./kind.js";import{recordLiveRead as D}from"./live-read.js";import{attr as L,header as O,section as m}from"./render.js";import{filterAsync as j,mapAsync as g}from"../../async-array.js";const p=[3,5,7];function A(t){return t?.split(`
2
- `).map(r=>r.trim()).find(r=>r!=="")??""}l(A,"firstLine");const K={bodyOwner:l(t=>t[0]?.id??null,"bodyOwner"),async enqueue(t,r,a){const d=Date.now(),s=r.senderNodeId,n=a.map(e=>e.id),c=`(${n.map(()=>"?").join(",")})`,u=s===null?new Set:new Set((await t.prepareUncached(`SELECT DISTINCT node_id FROM waits WHERE kind = 'controller' AND target_id = ? AND satisfied_at IS NULL AND node_id IN ${c}`).all(s,...n)).map(e=>e.node_id)),i=new Set((await t.prepareUncached(`SELECT DISTINCT anchor_node FROM crons WHERE cancel_on_wake = 1 AND anchor_node IN ${c}`).all(...n)).map(e=>e.anchor_node)),w=t.prepare(`INSERT INTO deliveries (delivery_id, message_id, destination_id, urgency, wake, state, created_at, updated_at, idempotency_key)
3
- VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?)`);for(const e of a){const o=R(e.delivery),v=e.reception===2?o.wake:0,y=r.elevatesTier?await b(e.id,o.urgency):o.urgency;await w.run(I(),r.messageId,e.id,y,v,d,d,r.idempotencyKey===void 0?null:`${r.idempotencyKey}:${e.id}`),u.has(e.id)&&await _(e.id,s,d,t),v===1&&y!=="deferred"&&i.has(e.id)&&await T(e.id)}}},q={name:"node",addressable:"id",receiver:"inbox",receiverFor:l(async(t,r)=>await t.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(r.id)===void 0?"inbox":"peer","receiverFor"),events:S({configured:{level:1,durable:!0},messaged:{level:2,durable:!0},sent:{level:1,durable:!0},"report.update":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"report.final":{level:7,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"final"},outcome:{level:1,durable:!0,supersedes:!0,carriesBody:!0},"report.result":{level:5,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"completed"},"notice.crash":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.deadline":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.close":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0}}),async read(t,r,a,d){const s=await t.prepare("SELECT status FROM nodes WHERE node_id = ?").get(a.id),n=l(async e=>await g(await j(e,async o=>await h(t,r,o)),async o=>await f(t,o)),"visible"),c=await t.prepare(`SELECT o.* FROM nodes n JOIN objects o ON o.id = n.node_id
4
- WHERE n.parent = ? AND o.deleted_at IS NULL ORDER BY o.seq`).all(a.id),u=await t.prepare("SELECT * FROM objects WHERE owner = ? AND deleted_at IS NULL ORDER BY seq").all(a.id);await D(t,r,a,d.reposFn);const i={status:s?.status??"deleted",mandate_first_line:A(await N(a.id)),children:await n(c),owned:await n(u.filter(e=>e.type!=="job")),jobs:await n(u.filter(e=>e.type==="job"))},w=[O("object",await f(t,a),L("status",i.status)),`Mandate: ${i.mandate_first_line}`,...m("Children",i.children),...m("Owns",i.owned),...m("Jobs",i.jobs)].join(`
5
- `);return{data:i,text:w}}};export{K as nodeInbox,q as nodeKind};
1
+ var y=Object.defineProperty;var c=(e,r)=>y(e,"name",{value:r,configurable:!0});import{settleMatchingControllerWait as _}from"../../canvas/waits.js";import{consumeDeadlineOnWake as T,effectiveTier as N,listenDeliveryTier as R,newId as b}from"../../feed/messages.js";import{readGoal as h}from"../../runtime/kickoff.js";import{permits as I,resolveReader as D}from"../../permissions/evaluator.js";import{objectDTO as f}from"./dto.js";import{withBase as S}from"./kind.js";import{recordLiveRead as L}from"./live-read.js";import{attr as O,header as j,section as v}from"./render.js";import{mapAsync as A}from"../../async-array.js";const w=[3,5,7];function g(e){return e?.split(`
2
+ `).map(r=>r.trim()).find(r=>r!=="")??""}c(g,"firstLine");const q={bodyOwner:c(e=>e[0]?.id??null,"bodyOwner"),async enqueue(e,r,a){const s=Date.now(),l=r.senderNodeId,n=a.map(t=>t.id),d=`(${n.map(()=>"?").join(",")})`,m=l===null?new Set:new Set((await e.prepareUncached(`SELECT DISTINCT node_id FROM waits WHERE kind = 'controller' AND target_id = ? AND satisfied_at IS NULL AND node_id IN ${d}`).all(l,...n)).map(t=>t.node_id)),u=new Set((await e.prepareUncached(`SELECT DISTINCT anchor_node FROM crons WHERE cancel_on_wake = 1 AND anchor_node IN ${d}`).all(...n)).map(t=>t.anchor_node)),o=e.prepare(`INSERT INTO deliveries (delivery_id, message_id, destination_id, urgency, wake, state, created_at, updated_at, idempotency_key)
3
+ VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?)`);for(const t of a){const i=R(t.delivery),p=t.reception===2?i.wake:0,E=r.elevatesTier?await N(t.id,i.urgency):i.urgency;await o.run(b(),r.messageId,t.id,E,p,s,s,r.idempotencyKey===void 0?null:`${r.idempotencyKey}:${t.id}`),m.has(t.id)&&await _(t.id,l,s,e),p===1&&E!=="deferred"&&u.has(t.id)&&await T(t.id)}}},K={name:"node",addressable:"id",receiver:"inbox",receiverFor:c(async(e,r)=>await e.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(r.id)===void 0?"inbox":"peer","receiverFor"),events:S({configured:{level:1,durable:!0},messaged:{level:2,durable:!0},sent:{level:1,durable:!0},"report.update":{level:5,levels:w,durable:!0,carriesBody:!0,elevatesTier:!0},"report.final":{level:7,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"final"},outcome:{level:1,durable:!0,supersedes:!0,carriesBody:!0},"report.result":{level:5,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"completed"},"notice.crash":{level:5,levels:w,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.deadline":{level:5,levels:w,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.close":{level:5,levels:w,durable:!0,carriesBody:!0,elevatesTier:!0}}),async read(e,r,a,s){const l=await e.prepare("SELECT status FROM nodes WHERE node_id = ?").get(a.id),n=await I(e,await D(e,r),"read","o"),d=c(async i=>await A(i,async p=>await f(e,p)),"visible"),m=await e.prepareUncached(`SELECT o.* FROM nodes n JOIN objects o ON o.id = n.node_id
4
+ WHERE n.parent = ? AND o.deleted_at IS NULL AND ${n.sql} ORDER BY o.seq`).all(a.id,...n.params),u=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.owner = ? AND o.deleted_at IS NULL AND ${n.sql} ORDER BY o.seq`).all(a.id,...n.params);await L(e,r,a,s.reposFn);const o={status:l?.status??"deleted",mandate_first_line:g(await h(a.id)),children:await d(m),owned:await d(u.filter(i=>i.type!=="job")),jobs:await d(u.filter(i=>i.type==="job"))},t=[j("object",await f(e,a),O("status",o.status)),`Mandate: ${o.mandate_first_line}`,...v("Children",o.children),...v("Owns",o.owned),...v("Jobs",o.jobs)].join(`
5
+ `);return{data:o,text:t}}};export{q as nodeInbox,K as nodeKind};
@@ -1,3 +1,5 @@
1
+ import { type EvalReader } from '../permissions/evaluator.js';
2
+ import { type Frag } from '../permissions/sql.js';
1
3
  import type { ObjectRow, Reader } from './types.js';
2
4
  import type { CanvasDb } from '../canvas/db.js';
3
5
  export type Resolution = {
@@ -15,6 +17,13 @@ export type Resolution = {
15
17
  * first, the profile's project repos, profile, app, you, then the apps whose tools the run may use (installed
16
18
  * plugins) with `crtr` last. */
17
19
  export declare function viewOf(db: CanvasDb, reader: Reader): Promise<ObjectRow[]>;
20
+ /** SQL true when the reader may read the candidate `alias`, live or deleted. A live object is `permits(read)`. A deleted object other than a node
21
+ * has lost its parent in the evaluator's `relation_source` (a reaped node keeps it, so its live documents stay readable), so a deleted one without a schema type is read through the facts on itself and on each owner up its
22
+ * chain (the authority it held before deletion), within the reader's ceiling. The "every app is visible" rule is not such a grant. A deleted
23
+ * schema-typed object has had its facts removed, so only the `permits` branch answers it: its own namespace's app. */
24
+ export declare function readableOrDeleted(db: CanvasDb, ev: EvalReader, alias: string): Promise<Frag>;
25
+ /** Whether the reader may read the object `id`, live or deleted. */
26
+ export declare function canReadOrDeleted(db: CanvasDb, ev: EvalReader, id: string): Promise<boolean>;
18
27
  /** The owner types that carry a handle; `<type>:<handle>/<name>` names one exactly. */
19
28
  export declare const HANDLE_TYPES: readonly ["person", "app", "repo", "profile"];
20
29
  /** A ref without its `[[ ]]` wrapper. */
@@ -32,8 +41,14 @@ export declare function ownersByHandle(db: CanvasDb, handle: string, type?: stri
32
41
  /** Precomputed for one reader across many resolutions (a corpus names every document). */
33
42
  export interface ResolveContext {
34
43
  view: ObjectRow[];
35
- readable: (o: ObjectRow) => Promise<boolean>;
44
+ ev: EvalReader;
36
45
  }
46
+ /** For each of `names`, the id of the live object `resolveName(name, context)` answers, or null when it answers none (a deleted, ambiguous or
47
+ * missing name, or a live object under the name's own split, which is named otherwise), computed for all of them in two queries. No entry
48
+ * when only `resolveName` itself can tell: a wrapped or old-report name, or a name that is an object id. A corpus resolves every document's
49
+ * name to choose its display name; one `resolveName` each walks the whole view, twice for a name it misses, with a permission-checked
50
+ * query per owner. */
51
+ export declare function viewResolutions(db: CanvasDb, context: ResolveContext, names: readonly string[]): Promise<Map<string, string | null>>;
37
52
  export declare function resolveName(db: CanvasDb, reader: Reader, raw: string, context?: ResolveContext): Promise<Resolution>;
38
53
  /** Every readable stub of another machine's object that `raw` (`<owner handle>/<name>`, `<type>:<owner handle>/<name>` or a bare name) names:
39
54
  * empty when none does, more than one when the name is ambiguous. `resolveName` never answers a stub, so a stub cannot shadow a local