@north-light/cli 0.3.579 → 0.3.581

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 (228) hide show
  1. package/dist/api/client.d.ts +6 -1
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/execution-start.d.ts +14 -0
  6. package/dist/api/dto/health.d.ts +20 -0
  7. package/dist/api/dto/imports.d.ts +142 -0
  8. package/dist/api/dto/imports.js +0 -0
  9. package/dist/api/index.d.ts +1 -0
  10. package/dist/api/index.js +1 -1
  11. package/dist/api/routes.d.ts +2 -0
  12. package/dist/api/routes.js +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  14. package/dist/clients/attach/session/connection.d.ts +3 -1
  15. package/dist/clients/attach/session/connection.js +1 -1
  16. package/dist/clients/attach/viewer.js +510 -510
  17. package/dist/commands/api-client.d.ts +7 -4
  18. package/dist/commands/api-client.js +3 -3
  19. package/dist/commands/sys/daemon.js +1 -1
  20. package/dist/commands/sys/import/resume.d.ts +1 -0
  21. package/dist/commands/sys/import/resume.js +1 -0
  22. package/dist/commands/sys/import/start.d.ts +5 -0
  23. package/dist/commands/sys/import/start.js +4 -0
  24. package/dist/commands/sys/join.d.ts +2 -0
  25. package/dist/commands/sys/join.js +1 -1
  26. package/dist/commands/sys/machine/rotate.d.ts +1 -0
  27. package/dist/commands/sys/machine/rotate.js +1 -0
  28. package/dist/commands/sys/signin.d.ts +7 -0
  29. package/dist/commands/sys/signin.js +2 -0
  30. package/dist/commands/sys.js +1 -1
  31. package/dist/core/blobs/r2.d.ts +4 -0
  32. package/dist/core/blobs/r2.js +7 -4
  33. package/dist/core/canvas/daemon-owner.d.ts +1 -1
  34. package/dist/core/canvas/executions.d.ts +1 -1
  35. package/dist/core/canvas/export-sqlite.js +1 -1
  36. package/dist/core/canvas/node-tokens.d.ts +2 -0
  37. package/dist/core/canvas/node-tokens.js +3 -3
  38. package/dist/core/canvas/schema-pg/009-bodies.sql +10 -0
  39. package/dist/core/canvas/schema-version.d.ts +1 -1
  40. package/dist/core/canvas/schema-version.js +1 -1
  41. package/dist/core/directory/person-credential.d.ts +36 -0
  42. package/dist/core/directory/person-credential.js +1 -0
  43. package/dist/core/feed/feed.js +3 -3
  44. package/dist/core/feed/inbox.js +1 -1
  45. package/dist/core/feed/reports.js +2 -2
  46. package/dist/core/graph/access.d.ts +1 -1
  47. package/dist/core/graph/bodies-boot-copy.d.ts +14 -0
  48. package/dist/core/graph/bodies-boot-copy.js +1 -0
  49. package/dist/core/graph/bodies.d.ts +13 -6
  50. package/dist/core/graph/bodies.js +1 -1
  51. package/dist/core/graph/deletion.d.ts +4 -1
  52. package/dist/core/graph/deletion.js +3 -3
  53. package/dist/core/graph/documents.d.ts +4 -0
  54. package/dist/core/graph/documents.js +10 -10
  55. package/dist/core/graph/objects.js +11 -11
  56. package/dist/core/graph/package-docs.js +5 -5
  57. package/dist/core/graph/reader.js +2 -2
  58. package/dist/core/graph/repo-sync/exchange.js +2 -2
  59. package/dist/core/graph/repo-sync/ref-format.d.ts +0 -1
  60. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  61. package/dist/core/import/bundle/blob-keys.d.ts +10 -0
  62. package/dist/core/import/bundle/blob-keys.js +1 -0
  63. package/dist/core/import/bundle/layout.d.ts +89 -0
  64. package/dist/core/import/bundle/layout.js +5 -0
  65. package/dist/core/import/export/archive.d.ts +11 -0
  66. package/dist/core/import/export/archive.js +1 -0
  67. package/dist/core/import/export/cloud.d.ts +21 -0
  68. package/dist/core/import/export/cloud.js +1 -0
  69. package/dist/core/import/export/dump.d.ts +34 -0
  70. package/dist/core/import/export/dump.js +3 -0
  71. package/dist/core/import/export/files.d.ts +22 -0
  72. package/dist/core/import/export/files.js +1 -0
  73. package/dist/core/import/export/inline.d.ts +3 -0
  74. package/dist/core/import/export/inline.js +1 -0
  75. package/dist/core/import/export/source.d.ts +2 -0
  76. package/dist/core/import/export/source.js +1 -0
  77. package/dist/core/import/export/stores.d.ts +9 -0
  78. package/dist/core/import/export/stores.js +1 -0
  79. package/dist/core/import/load/bind.d.ts +19 -0
  80. package/dist/core/import/load/bind.js +15 -0
  81. package/dist/core/import/load/blobs.d.ts +10 -0
  82. package/dist/core/import/load/blobs.js +1 -0
  83. package/dist/core/import/load/catalog.d.ts +32 -0
  84. package/dist/core/import/load/catalog.js +9 -0
  85. package/dist/core/import/load/copy.d.ts +26 -0
  86. package/dist/core/import/load/copy.js +6 -0
  87. package/dist/core/import/load/path-map.d.ts +7 -0
  88. package/dist/core/import/load/path-map.js +1 -0
  89. package/dist/core/import/load/prepare.d.ts +37 -0
  90. package/dist/core/import/load/prepare.js +10 -0
  91. package/dist/core/import/load/rewrite.d.ts +39 -0
  92. package/dist/core/import/load/rewrite.js +9 -0
  93. package/dist/core/import/load/rollback.d.ts +3 -0
  94. package/dist/core/import/load/rollback.js +6 -0
  95. package/dist/core/import/load/run.d.ts +22 -0
  96. package/dist/core/import/load/run.js +3 -0
  97. package/dist/core/import/load/stage.d.ts +15 -0
  98. package/dist/core/import/load/stage.js +1 -0
  99. package/dist/core/import/load/stores.d.ts +24 -0
  100. package/dist/core/import/load/stores.js +2 -0
  101. package/dist/core/import/load/verify.d.ts +4 -0
  102. package/dist/core/import/load/verify.js +11 -0
  103. package/dist/core/peer/receiver.js +1 -1
  104. package/dist/core/runtime/broker.js +1 -1
  105. package/dist/core/runtime/revive.d.ts +5 -2
  106. package/dist/core/runtime/revive.js +2 -2
  107. package/dist/core/runtime/roadmap.js +3 -3
  108. package/dist/core/runtime/runner/blaxel/runner.js +3 -3
  109. package/dist/core/runtime/runner/index.d.ts +1 -0
  110. package/dist/core/runtime/runner/index.js +1 -1
  111. package/dist/core/runtime/runner/machine/call.d.ts +8 -0
  112. package/dist/core/runtime/runner/machine/call.js +3 -0
  113. package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
  114. package/dist/core/runtime/runner/machine/environment.js +1 -0
  115. package/dist/core/runtime/runner/machine/executor.d.ts +23 -0
  116. package/dist/core/runtime/runner/machine/executor.js +1 -0
  117. package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
  118. package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
  119. package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
  120. package/dist/core/runtime/runner/machine/ops.js +1 -0
  121. package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
  122. package/dist/core/runtime/runner/machine/runner.js +1 -0
  123. package/dist/core/runtime/runner/types.d.ts +7 -1
  124. package/dist/core/runtime/warm-pool.d.ts +2 -0
  125. package/dist/core/runtime/warm-pool.js +1 -1
  126. package/dist/core/secrets/env-rows.d.ts +1 -0
  127. package/dist/core/secrets/env-rows.js +1 -1
  128. package/dist/core/storage/purge.d.ts +6 -0
  129. package/dist/core/storage/purge.js +4 -0
  130. package/dist/core/subscription-state.d.ts +1 -0
  131. package/dist/core/subscription-state.js +1 -1
  132. package/dist/core/substrate/delivery/corpus.d.ts +2 -0
  133. package/dist/core/substrate/delivery/corpus.js +3 -3
  134. package/dist/core/substrate/delivery/deliver.js +3 -3
  135. package/dist/core/substrate/delivery/render-boot.js +25 -25
  136. package/dist/core/substrate/delivery/render-event.d.ts +1 -1
  137. package/dist/core/substrate/delivery/render-event.js +4 -4
  138. package/dist/core/substrate/delivery/types.d.ts +2 -2
  139. package/dist/daemon/api/bridge.js +3 -3
  140. package/dist/daemon/api/document-listener.js +4 -4
  141. package/dist/daemon/api/handlers/attach.js +1 -1
  142. package/dist/daemon/api/handlers/crons.js +1 -1
  143. package/dist/daemon/api/handlers/daemon.js +1 -1
  144. package/dist/daemon/api/handlers/docs.js +1 -1
  145. package/dist/daemon/api/handlers/execution-start.js +2 -2
  146. package/dist/daemon/api/handlers/files.js +1 -1
  147. package/dist/daemon/api/handlers/imports.d.ts +2 -0
  148. package/dist/daemon/api/handlers/imports.js +1 -0
  149. package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
  150. package/dist/daemon/api/handlers/machine-credential.js +1 -0
  151. package/dist/daemon/api/handlers/memory.js +4 -4
  152. package/dist/daemon/api/handlers/profiles.js +1 -1
  153. package/dist/daemon/api/handlers/providers.js +1 -1
  154. package/dist/daemon/api/handlers/reports.js +4 -4
  155. package/dist/daemon/api/handlers/runs.js +1 -1
  156. package/dist/daemon/api/import-auth.d.ts +6 -0
  157. package/dist/daemon/api/import-auth.js +1 -0
  158. package/dist/daemon/api/operations.d.ts +2 -2
  159. package/dist/daemon/api/operations.js +1 -1
  160. package/dist/daemon/api/router.d.ts +7 -1
  161. package/dist/daemon/api/router.js +1 -1
  162. package/dist/daemon/api/server.js +1 -1
  163. package/dist/daemon/boot-migrations.js +1 -1
  164. package/dist/daemon/broker-signals/bus.d.ts +3 -2
  165. package/dist/daemon/broker-signals/bus.js +1 -1
  166. package/dist/daemon/cron/armed-context.d.ts +1 -0
  167. package/dist/daemon/cron/armed-context.js +1 -1
  168. package/dist/daemon/cron/closed.d.ts +31 -0
  169. package/dist/daemon/cron/closed.js +1 -0
  170. package/dist/daemon/cron/ended.js +2 -2
  171. package/dist/daemon/cron/lease-recovery.js +1 -1
  172. package/dist/daemon/cron/machine-exec.d.ts +5 -0
  173. package/dist/daemon/cron/machine-exec.js +1 -0
  174. package/dist/daemon/cron/machine-fire.d.ts +26 -0
  175. package/dist/daemon/cron/machine-fire.js +1 -0
  176. package/dist/daemon/cron/recurrence.d.ts +14 -0
  177. package/dist/daemon/cron/recurrence.js +1 -0
  178. package/dist/daemon/cron/run-token.d.ts +7 -0
  179. package/dist/daemon/cron/run-token.js +1 -0
  180. package/dist/daemon/cron-run.js +2 -2
  181. package/dist/daemon/crtrd-cli.js +1 -1
  182. package/dist/daemon/crtrd.d.ts +2 -2
  183. package/dist/daemon/crtrd.js +10 -10
  184. package/dist/daemon/drain.d.ts +25 -0
  185. package/dist/daemon/drain.js +9 -0
  186. package/dist/daemon/due-sources.d.ts +1 -1
  187. package/dist/daemon/due-sources.js +1 -1
  188. package/dist/daemon/due.js +8 -8
  189. package/dist/daemon/fleet.js +5 -5
  190. package/dist/daemon/import-dump-run.d.ts +1 -0
  191. package/dist/daemon/import-dump-run.js +5 -0
  192. package/dist/daemon/machine-daemon.d.ts +7 -0
  193. package/dist/daemon/machine-daemon.js +1 -0
  194. package/dist/daemon/machine-link/client.d.ts +29 -0
  195. package/dist/daemon/machine-link/client.js +1 -0
  196. package/dist/daemon/machine-link/config.d.ts +14 -0
  197. package/dist/daemon/machine-link/config.js +3 -0
  198. package/dist/daemon/machine-link/credential.d.ts +7 -0
  199. package/dist/daemon/machine-link/credential.js +2 -0
  200. package/dist/daemon/machine-link/forward.d.ts +16 -0
  201. package/dist/daemon/machine-link/forward.js +1 -0
  202. package/dist/daemon/machine-link/frames.d.ts +49 -0
  203. package/dist/daemon/machine-link/frames.js +1 -0
  204. package/dist/daemon/machine-link/holder.d.ts +73 -0
  205. package/dist/daemon/machine-link/holder.js +9 -0
  206. package/dist/daemon/manage.d.ts +19 -3
  207. package/dist/daemon/manage.js +3 -3
  208. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  209. package/dist/daemon/reconcilers/delivery-lane.js +2 -2
  210. package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
  211. package/dist/daemon/reconcilers/import-lane.js +1 -0
  212. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  213. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
  214. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
  215. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  216. package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
  217. package/dist/daemon/reconcilers/purge-due-work.js +1 -0
  218. package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
  219. package/dist/daemon/reconcilers/row-claim.js +2 -2
  220. package/dist/daemon/review/finish.js +1 -1
  221. package/dist/daemon/runs/observer.js +3 -3
  222. package/dist/migrations/004-canvas-documents/apply.d.ts +2 -4
  223. package/dist/migrations/004-canvas-documents/apply.js +1 -1
  224. package/dist/migrations/004-canvas-documents/index.js +2 -2
  225. package/package.json +2 -2
  226. package/packages/crouter-identity/package.json +1 -1
  227. package/runtime.lock.json +13 -13
  228. package/scripts/sandbox-start.mjs +185 -2
@@ -1,54 +1,54 @@
1
- var Y=Object.defineProperty;var l=(e,s)=>Y(e,"name",{value:s,configurable:!0});import{heldAtOrAbove as $,hold as _,freezePreferences as q}from"../../graph/exposures.js";import{interpolateNodePaths as F}from"../../canvas/paths.js";import{buildSubPersonaMenu as z}from"./sub-persona-menu.js";import{rungAtLeast as k}from"../schema.js";import{loadCorpus as A}from"./corpus.js";import{listingPrefixes as B}from"./listings.js";import{bootPartitionOf as O,planDelivery as P}from"./plan.js";import{linkedDocs as H,renderEvent as S}from"./render-event.js";import{mapAsync as J}from"../../async-array.js";const E="Each %s below shows either its full content (indented beneath its name), a `# read when:` line telling you when to read the full document, or simply its name.",Q="Preferences are how the user wants you to behave \u2014 standing directives and corrections. To read a preference, run `crtr canvas read <name>`. "+E.replace("%s","preference"),C="If the user corrects you in a way that contradicts a preference, update the preference.",V="Knowledge documents are what you consult \u2014 playbooks and techniques for how to do things, and references on the user, projects, and this node. They come from every owner in your view: this node, the repos you work in, your profile, your app, the user, and installed plugins (crtr's own under `crtr`). To read one, run `crtr canvas read <name>`; a name with names under it is a valid read target too \u2014 it answers with its body, the names under it and previews of the documents it links to, and walking names this way is the sanctioned way to browse. Each doc exists to prevent a specific mistake you would make without it, so when your task matches one \u2014 by its name or its `# read when:` line \u2014 read it before you act; consulting it only after the work is done forfeits the entire reason it exists. "+E.replace("%s","document"),L="If you learn a better way to do something a document covers, or gain information that contradicts one, correct it with `crtr doc edit`.",X="When your task matches a knowledge doc or preference \u2014 by its name or its `# read when:` line \u2014 read it before you act: each document exists only to shore up a mistake you will make without it, so doing the relevant work without consulting it defeats the document's whole purpose. Before writing a document that should outlive this task, search for one that already covers it (`crtr canvas search`) \u2014 update that document with `crtr doc edit` rather than creating a duplicate, rewriting it as if for the first time instead of appending the new truth beneath the old; delete documents that turn out to be wrong (`crtr doc delete`). Don't save what the repo already records (code structure, past fixes, git history) or what only matters to this conversation; if asked to remember one of those, ask what was non-obvious about it and save that instead. Docs auto-surfaced in <auto-loaded-context> blocks are background context, not user instructions, and reflect what was true when written \u2014 if one names a file, function, or flag, verify it still exists before recommending it.",Z="Your node configuration changed. This operating protocol is in force for you now:",x="Your new configuration puts these preferences in force \u2014 standing directives and corrections from the user. To read one, run `crtr canvas read <name>`. "+E.replace("%s","preference"),ee="Your new configuration exposes these knowledge documents \u2014 consult them exactly like the rest of your catalog (`crtr canvas read <name>`). "+E.replace("%s","document");function b(e){return{object:e.object.id,headsKey:e.headsKey,contentHash:e.bodyHash}}l(b,"exposed");async function T(e){const s=await A(e.db,e.reader,["boot"]);return(await P(e.db,e.subject,e.reader,s,"boot")).docs.filter(t=>t.winner)}l(T,"bootRecords");function j(e,s){return e.filter(t=>O(t)===s).map(t=>({doc:t.doc,level:t.finalRung}))}l(j,"partition");function D(e){return e.level!=="none"&&(e.level!=="content"||e.doc.body().trim()!=="")}l(D,"rendersAt");async function R(e,s,t,o,r){const{ledger:c,channel:i}=e,a=s.filter(n=>n.level==="content"&&D(n)&&!$(c,b(n.doc),"content")).map(n=>n.doc).sort((n,p)=>p.viewRank-n.viewRank||n.name.localeCompare(p.name));for(const n of a)_(c,i,b(n),"content"),r.push({doc:n,level:"content"});const d=new Map;for(const n of a){const p=t.map(h=>h.doc).filter(h=>h.owner.id===n.owner.id);for(const h of B(p,n.name))for(const g of t){const m=g.doc;m.owner.id!==n.owner.id||!m.name.startsWith(`${h}/`)||m.name.slice(h.length+1).includes("/")||g.level==="content"||k(g.level,"preview")||m.unlisted||!m.ownNode&&!k(m.limit,"preview")||$(c,b(m),"preview")||d.set(m.object.id,{doc:m,level:"preview",cause:"directory listing attached to content delivery"})}}const u=new Map(t.map(n=>[n.doc.object.id,n])),v=new Set(a.map(n=>n.object.id));for(const n of a)for(const p of await H(e.db,e.reader,n)){const h=p.object.id,g=u.get(h);d.has(h)||v.has(h)||g!==void 0&&k(g.level,"preview")||O({doc:p})===O({doc:n})&&(!p.ownNode&&!k(p.limit,"preview")||$(c,b(p),"preview")||d.set(h,{doc:g?.doc??p,level:"preview",cause:"link preview attached to content delivery"}))}const y=(await J(a,async n=>await S(e,"document-read",{doc:n}))).flatMap(({content:n,delivered:p})=>(r.push(...p),n===""?[]:[n])),w=s.filter(n=>n.level!=="content").map(n=>d.get(n.doc.object.id)??n),f=new Set(w.map(n=>n.doc.object.id));w.push(...[...d.values()].filter(n=>!f.has(n.doc.object.id)));for(const n of w)D(n)&&(_(c,i,b(n.doc),n.level),r.push({doc:n.doc,level:n.level,...n.cause===void 0?{}:{cause:n.cause}}));return{content:a,attachments:y,tree:te(w,o)}}l(R,"renderGrouped");function I(e){return{segment:e,own:null,dirs:new Map,leaves:new Map,hidden:0,renders:!1,count:0}}l(I,"newDir");const ne=l(e=>k(e,"name"),"visible");function M(e,s){let t=e.hidden;for(const o of e.dirs.values())t+=M(o,e.segment==="");return e.renders=e.own!==null||e.leaves.size>0||[...e.dirs.values()].some(o=>o.renders)||s||e.segment==="",e.count=e.renders?t:0,e.renders?0:t}l(M,"resolveDir");function G(e,s,t){const o=e.level==="preview"?e.doc.preview.trim():"";return o===""?`${t}${s}`:`${t}${s} # read when: ${o}`}l(G,"entryLine");function K(e,s,t){const o=[...[...e.dirs.values()].filter(r=>r.renders).map(r=>({name:r.segment,dir:r})),...[...e.leaves].map(([r,c])=>({name:r,leaf:c}))].sort((r,c)=>r.name.localeCompare(c.name));e.count>0&&o.push({name:"",more:e.count}),o.forEach((r,c)=>{const i=c===o.length-1,a=s+(i?"\u2514\u2500 ":"\u251C\u2500 ");if("more"in r)t.push(`${a}[+${r.more} more]`);else if(r.dir!==void 0){const d=`${r.dir.segment}/`;t.push(r.dir.own===null?`${a}${d}`:G(r.dir.own,d,a)),K(r.dir,s+(i?" ":"\u2502 "),t)}else t.push(G(r.leaf,r.name,a))})}l(K,"renderChildren");function te(e,s){if(e.length===0)return"";const t=I(""),o=l(i=>{let a=t;for(const d of i){let u=a.dirs.get(d);u===void 0&&a.dirs.set(d,u=I(d)),a=u}return a},"dirAt"),r=new Set(e.map(i=>i.doc.displayName));for(const i of e){const a=i.doc.displayName.split("/"),d=[...r].some(u=>u.startsWith(`${i.doc.displayName}/`));ne(i.level)?d?o(a).own=i:o(a.slice(0,-1)).leaves.set(a[a.length-1],i):o(d?a:a.slice(0,-1)).hidden+=1}M(t,!1);const c=[s];return K(t,"",c),c.join(`
2
- `)}l(te,"buildTree");function N(e,s){const{reader:t}=e;return t.class==="node"?F(s,t.node.id,t.app.name):s}l(N,"interpolate");function W(e){return e.shipped}l(W,"isShipped");function U(e){const s=e.content.filter(W).map(o=>o.body().trim()).filter(o=>o!==""),t=e.content.filter(o=>!W(o)).map(o=>o.body().trim()===""?"":`<preference name="${o.displayName}">
3
- ${o.body().trim()}
4
- </preference>`).filter(o=>o!=="");return{shipped:s,editable:t}}l(U,"preferenceParts");async function pe(e){if(e.ledger.preferenceSnapshot!==null)return{content:e.ledger.preferenceSnapshot,delivered:[]};const s={...e,channel:"system"},t=[];let o=[],r=X;if(e.subject!==null){const i=j(await T(s),"preference"),a=await R(s,i,i,"preferences",t),{shipped:d,editable:u}=U(a),v=z(e.subject.kind);o=[...d,...a.attachments,...v===""?[]:[v]],(u.length>0||a.tree!=="")&&(r+=`
1
+ var B=Object.defineProperty;var l=(e,s)=>B(e,"name",{value:s,configurable:!0});import{heldAtOrAbove as E,hold as _,freezePreferences as F}from"../../graph/exposures.js";import{interpolateNodePaths as z}from"../../canvas/paths.js";import{buildSubPersonaMenu as H}from"./sub-persona-menu.js";import{rungAtLeast as k}from"../schema.js";import{loadCorpus as P,primeBodies as J}from"./corpus.js";import{listingPrefixes as Q}from"./listings.js";import{bootPartitionOf as T,planDelivery as S}from"./plan.js";import{linkedDocs as V,renderEvent as C}from"./render-event.js";import{filterAsync as L,mapAsync as $}from"../../async-array.js";const j="Each %s below shows either its full content (indented beneath its name), a `# read when:` line telling you when to read the full document, or simply its name.",X="Preferences are how the user wants you to behave \u2014 standing directives and corrections. To read a preference, run `crtr canvas read <name>`. "+j.replace("%s","preference"),I="If the user corrects you in a way that contradicts a preference, update the preference.",Z="Knowledge documents are what you consult \u2014 playbooks and techniques for how to do things, and references on the user, projects, and this node. They come from every owner in your view: this node, the repos you work in, your profile, your app, the user, and installed plugins (crtr's own under `crtr`). To read one, run `crtr canvas read <name>`; a name with names under it is a valid read target too \u2014 it answers with its body, the names under it and previews of the documents it links to, and walking names this way is the sanctioned way to browse. Each doc exists to prevent a specific mistake you would make without it, so when your task matches one \u2014 by its name or its `# read when:` line \u2014 read it before you act; consulting it only after the work is done forfeits the entire reason it exists. "+j.replace("%s","document"),M="If you learn a better way to do something a document covers, or gain information that contradicts one, correct it with `crtr doc edit`.",x="When your task matches a knowledge doc or preference \u2014 by its name or its `# read when:` line \u2014 read it before you act: each document exists only to shore up a mistake you will make without it, so doing the relevant work without consulting it defeats the document's whole purpose. Before writing a document that should outlive this task, search for one that already covers it (`crtr canvas search`) \u2014 update that document with `crtr doc edit` rather than creating a duplicate, rewriting it as if for the first time instead of appending the new truth beneath the old; delete documents that turn out to be wrong (`crtr doc delete`). Don't save what the repo already records (code structure, past fixes, git history) or what only matters to this conversation; if asked to remember one of those, ask what was non-obvious about it and save that instead. Docs auto-surfaced in <auto-loaded-context> blocks are background context, not user instructions, and reflect what was true when written \u2014 if one names a file, function, or flag, verify it still exists before recommending it.",ee="Your node configuration changed. This operating protocol is in force for you now:",ne="Your new configuration puts these preferences in force \u2014 standing directives and corrections from the user. To read one, run `crtr canvas read <name>`. "+j.replace("%s","preference"),te="Your new configuration exposes these knowledge documents \u2014 consult them exactly like the rest of your catalog (`crtr canvas read <name>`). "+j.replace("%s","document");function b(e){return{object:e.object.id,headsKey:e.headsKey,contentHash:e.bodyHash}}l(b,"exposed");async function D(e){const s=await P(e.db,e.reader,["boot"]),o=(await S(e.db,e.subject,e.reader,s,"boot")).docs.filter(t=>t.winner);return await J(e.db,o.filter(t=>t.finalRung==="content").map(t=>t.doc)),o}l(D,"bootRecords");function R(e,s){return e.filter(o=>T(o)===s).map(o=>({doc:o.doc,level:o.finalRung}))}l(R,"partition");async function A(e){return e.level!=="none"&&(e.level!=="content"||(await e.doc.body()).trim()!=="")}l(A,"rendersAt");async function N(e,s,o,t,r){const{ledger:c,channel:a}=e,i=(await L(s,async n=>n.level==="content"&&await A(n)&&!E(c,b(n.doc),"content"))).map(n=>n.doc).sort((n,p)=>p.viewRank-n.viewRank||n.name.localeCompare(p.name));for(const n of i)_(c,a,b(n),"content"),r.push({doc:n,level:"content"});const d=new Map;for(const n of i){const p=o.map(h=>h.doc).filter(h=>h.owner.id===n.owner.id);for(const h of Q(p,n.name))for(const g of o){const w=g.doc;w.owner.id!==n.owner.id||!w.name.startsWith(`${h}/`)||w.name.slice(h.length+1).includes("/")||g.level==="content"||k(g.level,"preview")||w.unlisted||!w.ownNode&&!k(w.limit,"preview")||E(c,b(w),"preview")||d.set(w.object.id,{doc:w,level:"preview",cause:"directory listing attached to content delivery"})}}const u=new Map(o.map(n=>[n.doc.object.id,n])),y=new Set(i.map(n=>n.object.id));for(const n of i)for(const p of await V(e.db,e.reader,n)){const h=p.object.id,g=u.get(h);d.has(h)||y.has(h)||g!==void 0&&k(g.level,"preview")||T({doc:p})===T({doc:n})&&(!p.ownNode&&!k(p.limit,"preview")||E(c,b(p),"preview")||d.set(h,{doc:g?.doc??p,level:"preview",cause:"link preview attached to content delivery"}))}const v=(await $(i,async n=>await C(e,"document-read",{doc:n}))).flatMap(({content:n,delivered:p})=>(r.push(...p),n===""?[]:[n])),m=s.filter(n=>n.level!=="content").map(n=>d.get(n.doc.object.id)??n),f=new Set(m.map(n=>n.doc.object.id));m.push(...[...d.values()].filter(n=>!f.has(n.doc.object.id)));for(const n of m)n.level==="none"||!await A(n)||(_(c,a,b(n.doc),n.level),r.push({doc:n.doc,level:n.level,...n.cause===void 0?{}:{cause:n.cause}}));return{content:i,attachments:v,tree:re(m,t)}}l(N,"renderGrouped");function G(e){return{segment:e,own:null,dirs:new Map,leaves:new Map,hidden:0,renders:!1,count:0}}l(G,"newDir");const oe=l(e=>k(e,"name"),"visible");function K(e,s){let o=e.hidden;for(const t of e.dirs.values())o+=K(t,e.segment==="");return e.renders=e.own!==null||e.leaves.size>0||[...e.dirs.values()].some(t=>t.renders)||s||e.segment==="",e.count=e.renders?o:0,e.renders?0:o}l(K,"resolveDir");function W(e,s,o){const t=e.level==="preview"?e.doc.preview.trim():"";return t===""?`${o}${s}`:`${o}${s} # read when: ${t}`}l(W,"entryLine");function U(e,s,o){const t=[...[...e.dirs.values()].filter(r=>r.renders).map(r=>({name:r.segment,dir:r})),...[...e.leaves].map(([r,c])=>({name:r,leaf:c}))].sort((r,c)=>r.name.localeCompare(c.name));e.count>0&&t.push({name:"",more:e.count}),t.forEach((r,c)=>{const a=c===t.length-1,i=s+(a?"\u2514\u2500 ":"\u251C\u2500 ");if("more"in r)o.push(`${i}[+${r.more} more]`);else if(r.dir!==void 0){const d=`${r.dir.segment}/`;o.push(r.dir.own===null?`${i}${d}`:W(r.dir.own,d,i)),U(r.dir,s+(a?" ":"\u2502 "),o)}else o.push(W(r.leaf,r.name,i))})}l(U,"renderChildren");function re(e,s){if(e.length===0)return"";const o=G(""),t=l(a=>{let i=o;for(const d of a){let u=i.dirs.get(d);u===void 0&&i.dirs.set(d,u=G(d)),i=u}return i},"dirAt"),r=new Set(e.map(a=>a.doc.displayName));for(const a of e){const i=a.doc.displayName.split("/"),d=[...r].some(u=>u.startsWith(`${a.doc.displayName}/`));oe(a.level)?d?t(i).own=a:t(i.slice(0,-1)).leaves.set(i[i.length-1],a):t(d?i:i.slice(0,-1)).hidden+=1}K(o,!1);const c=[s];return U(o,"",c),c.join(`
2
+ `)}l(re,"buildTree");function O(e,s){const{reader:o}=e;return o.class==="node"?z(s,o.node.id,o.app.name):s}l(O,"interpolate");function Y(e){return e.shipped}l(Y,"isShipped");async function q(e){const s=(await $(e.content.filter(Y),async t=>(await t.body()).trim())).filter(t=>t!==""),o=(await $(e.content.filter(t=>!Y(t)),async t=>{const r=(await t.body()).trim();return r===""?"":`<preference name="${t.displayName}">
3
+ ${r}
4
+ </preference>`})).filter(t=>t!=="");return{shipped:s,editable:o}}l(q,"preferenceParts");async function we(e){if(e.ledger.preferenceSnapshot!==null)return{content:e.ledger.preferenceSnapshot,delivered:[]};const s={...e,channel:"system"},o=[];let t=[],r=x;if(e.subject!==null){const a=R(await D(s),"preference"),i=await N(s,a,a,"preferences",o),{shipped:d,editable:u}=await q(i),y=H(e.subject.kind);t=[...d,...i.attachments,...y===""?[]:[y]],(u.length>0||i.tree!=="")&&(r+=`
5
5
 
6
- ${Q}`,u.length>0&&(r+=`
6
+ ${X}`,u.length>0&&(r+=`
7
7
 
8
8
  ${u.join(`
9
9
 
10
- `)}`),a.tree!==""&&(r+=`
10
+ `)}`),i.tree!==""&&(r+=`
11
11
 
12
- ${a.tree}`),r+=`
12
+ ${i.tree}`),r+=`
13
13
 
14
- ${C}`)}const c=[...o,`<memory kind="preference">
14
+ ${I}`)}const c=[...t,`<memory kind="preference">
15
15
  ${r}
16
16
  </memory>`].join(`
17
17
 
18
- `);return{content:q(e.ledger,N(e,c)),delivered:t}}l(pe,"renderPreferences");async function he(e){const s=[],t=j(await T(e),"knowledge"),o=await R(e,t,t,"knowledge",s),r=[...o.content.map(i=>i.body().trim()),...o.attachments].filter(i=>i!=="");if(r.length===0&&o.tree==="")return{content:"",delivered:s};let c=V;return r.length>0&&(c+=`
18
+ `);return{content:F(e.ledger,O(e,c)),delivered:o}}l(we,"renderPreferences");async function me(e){const s=[],o=R(await D(e),"knowledge"),t=await N(e,o,o,"knowledge",s),r=[...await $(t.content,async a=>(await a.body()).trim()),...t.attachments].filter(a=>a!=="");if(r.length===0&&t.tree==="")return{content:"",delivered:s};let c=Z;return r.length>0&&(c+=`
19
19
 
20
20
  ${r.join(`
21
21
 
22
- `)}`),o.tree!==""&&(c+=`
22
+ `)}`),t.tree!==""&&(c+=`
23
23
 
24
- ${o.tree}`),c+=`
24
+ ${t.tree}`),c+=`
25
25
 
26
- ${L}`,{content:N(e,`<memory kind="knowledge">
26
+ ${M}`,{content:O(e,`<memory kind="knowledge">
27
27
  ${c}
28
- </memory>`),delivered:s}}l(he,"renderKnowledge");async function me(e){const s=await A(e.db,e.reader,["workspace-open"]),t=(await P(e.db,e.subject,e.reader,s,"workspace-open")).docs.filter(c=>c.winner).map(c=>c.doc).sort((c,i)=>i.viewRank-c.viewRank||c.object.seq-i.object.seq),{content:o,delivered:r}=await S(e,"workspace-open",{},t);return{content:N(e,o),delivered:r}}l(me,"renderWorkspaceOpen");async function we(e){const{ledger:s}=e,t=[],o=await T(e),r=l(f=>f.filter(n=>D(n)&&!$(s,b(n.doc),n.level)),"fresh"),c=j(o,"preference"),i=await R(e,r(c),c,"preferences",t),{shipped:a,editable:d}=U(i),u=[];if((a.length>0||i.attachments.length>0)&&u.push(`${Z}
28
+ </memory>`),delivered:s}}l(me,"renderKnowledge");async function ye(e){const s=await P(e.db,e.reader,["workspace-open"]),o=(await S(e.db,e.subject,e.reader,s,"workspace-open")).docs.filter(c=>c.winner).map(c=>c.doc).sort((c,a)=>a.viewRank-c.viewRank||c.object.seq-a.object.seq),{content:t,delivered:r}=await C(e,"workspace-open",{},o);return{content:O(e,t),delivered:r}}l(ye,"renderWorkspaceOpen");async function ve(e){const{ledger:s}=e,o=[],t=await D(e),r=l(f=>L(f,async n=>await A(n)&&!E(s,b(n.doc),n.level)),"fresh"),c=R(t,"preference"),a=await N(e,await r(c),c,"preferences",o),{shipped:i,editable:d}=await q(a),u=[];if((i.length>0||a.attachments.length>0)&&u.push(`${ee}
29
29
 
30
- ${[...a,...i.attachments].join(`
30
+ ${[...i,...a.attachments].join(`
31
31
 
32
- `)}`),d.length>0||i.tree!==""){let f=x;d.length>0&&(f+=`
32
+ `)}`),d.length>0||a.tree!==""){let f=ne;d.length>0&&(f+=`
33
33
 
34
34
  ${d.join(`
35
35
 
36
- `)}`),i.tree!==""&&(f+=`
36
+ `)}`),a.tree!==""&&(f+=`
37
37
 
38
- ${i.tree}`),f+=`
38
+ ${a.tree}`),f+=`
39
39
 
40
- ${C}`,u.push(`<memory kind="preference">
40
+ ${I}`,u.push(`<memory kind="preference">
41
41
  ${f}
42
- </memory>`)}const v=j(o,"knowledge"),y=await R(e,r(v),v,"knowledge",t),w=[...y.content.map(f=>f.body().trim()),...y.attachments].filter(f=>f!=="");if(w.length>0||y.tree!==""){let f=ee;w.length>0&&(f+=`
42
+ </memory>`)}const y=R(t,"knowledge"),v=await N(e,await r(y),y,"knowledge",o),m=[...await $(v.content,async f=>(await f.body()).trim()),...v.attachments].filter(f=>f!=="");if(m.length>0||v.tree!==""){let f=te;m.length>0&&(f+=`
43
43
 
44
- ${w.join(`
44
+ ${m.join(`
45
45
 
46
- `)}`),y.tree!==""&&(f+=`
46
+ `)}`),v.tree!==""&&(f+=`
47
47
 
48
- ${y.tree}`),f+=`
48
+ ${v.tree}`),f+=`
49
49
 
50
- ${L}`,u.push(`<memory kind="knowledge">
50
+ ${M}`,u.push(`<memory kind="knowledge">
51
51
  ${f}
52
- </memory>`)}return{content:u.length===0?"":N(e,u.join(`
52
+ </memory>`)}return{content:u.length===0?"":O(e,u.join(`
53
53
 
54
- `)),delivered:t}}l(we,"renderConfigDelta");export{we as renderConfigDelta,he as renderKnowledge,pe as renderPreferences,me as renderWorkspaceOpen};
54
+ `)),delivered:o}}l(ve,"renderConfigDelta");export{ve as renderConfigDelta,me as renderKnowledge,we as renderPreferences,ye as renderWorkspaceOpen};
@@ -21,7 +21,7 @@ export interface RenderContext {
21
21
  }
22
22
  /** One document at one level: body inside the element for content, self-closing
23
23
  * otherwise (a preview carries its line as `readWhen`). Empty-bodied content renders self-closing. */
24
- export declare function renderEnvelope(doc: DeliveryDoc, level: Exclude<Rung, 'none'>): string;
24
+ export declare function renderEnvelope(doc: DeliveryDoc, level: Exclude<Rung, 'none'>): Promise<string>;
25
25
  export declare function wrapAutoLoaded(blocks: readonly string[]): string;
26
26
  /** The live documents `doc`'s current revision links to that the reader may
27
27
  * read, in link order, without `doc` itself. Remote and unresolved links have
@@ -1,8 +1,8 @@
1
- var N=Object.defineProperty;var a=(e,n)=>N(e,"name",{value:n,configurable:!0});import{permits as D,resolveReader as E}from"../../permissions/evaluator.js";import{heldAtOrAbove as g,hold as v,holdListing as O,listedAtOrAbove as H}from"../../graph/exposures.js";import{deliveryDocOf as K,listingCorpus as L,loadCorpus as w}from"./corpus.js";import{listingBlock as R,ownSetBlock as C,listingKey as P,listingLabel as x,listingPrefixes as k,renderListing as B}from"./listings.js";import{planDelivery as M}from"./plan.js";import{mapAsync as S}from"../../async-array.js";function b(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}a(b,"attr");function W(e,n){let t=`kind="${b(e.kind)}" name="${b(e.displayName)}"`;if(n==="content"){const i=e.body().trim();if(i!=="")return`<memory ${t}>
1
+ var N=Object.defineProperty;var c=(e,n)=>N(e,"name",{value:n,configurable:!0});import{permits as D,resolveReader as H}from"../../permissions/evaluator.js";import{heldAtOrAbove as v,hold as b,holdListing as K,listedAtOrAbove as E}from"../../graph/exposures.js";import{deliveryDocOf as O,listingCorpus as R,loadCorpus as g,primeBodies as L}from"./corpus.js";import{listingBlock as C,ownSetBlock as B,listingKey as P,listingLabel as x,listingPrefixes as k,renderListing as M}from"./listings.js";import{planDelivery as S}from"./plan.js";import{mapAsync as W}from"../../async-array.js";function w(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}c(w,"attr");async function q(e,n){let t=`kind="${w(e.kind)}" name="${w(e.displayName)}"`;if(n==="content"){const i=(await e.body()).trim();if(i!=="")return`<memory ${t}>
2
2
  ${i}
3
- </memory>`}else n==="preview"&&e.preview.trim()!==""&&(t+=` readWhen="${b(e.preview.trim())}"`);return`<memory ${t} />`}a(W,"renderEnvelope");function $(e){return e.length===0?"":`<auto-loaded-context>
3
+ </memory>`}else n==="preview"&&e.preview.trim()!==""&&(t+=` readWhen="${w(e.preview.trim())}"`);return`<memory ${t} />`}c(q,"renderEnvelope");function j(e){return e.length===0?"":`<auto-loaded-context>
4
4
  ${e.join(`
5
5
  `)}
6
- </auto-loaded-context>`}a($,"wrapAutoLoaded");async function q(e,n){let t=e.members.get(n.owner.id);return t===void 0&&(t=await L(e.ctx.db,e.ctx.reader,n.owner),e.members.set(n.owner.id,t)),t}a(q,"membersOf");async function I(e,n,t){const i=await D(e,await E(e,n),"read","o"),o=await e.prepareUncached(`SELECT o.* FROM edges e JOIN objects o ON o.id = e.dst
6
+ </auto-loaded-context>`}c(j,"wrapAutoLoaded");async function I(e,n){let t=e.members.get(n.owner.id);return t===void 0&&(t=await R(e.ctx.db,e.ctx.reader,n.owner),e.members.set(n.owner.id,t)),t}c(I,"membersOf");async function U(e,n,t){const i=await D(e,await H(e,n),"read","o"),o=await e.prepareUncached(`SELECT o.* FROM edges e JOIN objects o ON o.id = e.dst
7
7
  WHERE e.type = 'links-to' AND e.src = ? AND o.type = 'document' AND o.deleted_at IS NULL AND o.id <> ? AND ${i.sql}
8
- ORDER BY e.written_as`).all(t.object.id,t.object.id,...i.params);return await S(o,async s=>await K(e,n,s))}a(I,"linkedDocs");function U(e,n){return n.owner.id===e.owner.id&&n.name.startsWith(`${e.name}/`)&&!n.name.slice(e.name.length+1).includes("/")}a(U,"underOwnPrefix");async function _(e,n){const{ledger:t,channel:i}=e.ctx,o=[],s=[];for(const r of await I(e.ctx.db,e.ctx.reader,n)){if(U(n,r)&&!r.unlisted)continue;const l={object:r.object.id,headsKey:r.headsKey,contentHash:r.bodyHash};if(g(t,l,"preview"))continue;v(t,i,l,"preview"),e.delivered.push({doc:r,level:"preview",cause:"link preview attached to content delivery"}),s.push(r);const d=r.preview.trim();o.push(d===""?`[[${r.displayName}]]`:`[[${r.displayName}]]: ${d}`)}return{lines:o,linked:s}}a(_,"linkPreviews");async function j(e,n,t){const{ledger:i,channel:o}=e.ctx,s=await q(e,n),r=[];let l,d=[];for(const m of k(s,n.name)){const u=m===n.name,f=P(n,m);if(!(u&&t)&&H(i,f,"preview"))continue;const{lines:c,previewed:p}=B(s,m,i,o,!(u&&t));for(const y of p)e.delivered.push({doc:y,level:"preview",cause:"directory listing attached to content delivery"});c.length!==0&&(O(i,o,f,"preview"),u?d=c:r.push(R(x(n,m),c)))}const h=await _(e,n);return t?d.length>0&&(l=d):d.length+h.lines.length>0&&r.unshift(C(x(n,n.name),d,h.lines)),e.chainCorpus??=await w(e.ctx.db,e.ctx.reader,["document-read"]),r.push(...await A(e,e.chainCorpus,"document-read",{doc:n},"memory-read companion of content delivery")),{blocks:r,...l===void 0?{}:{listing:l},linked:h.linked}}a(j,"attachments");async function A(e,n,t,i,o){const{db:s,reader:r,subject:l,ledger:d,channel:h}=e.ctx,m=await M(s,l,r,n,t,i),u=[];for(const f of m.docs){const c=f.finalRung;if(!f.winner||c==="none")continue;const{doc:p}=f,y={object:p.object.id,headsKey:p.headsKey,contentHash:p.bodyHash};g(d,y,c)||(v(d,h,y,c),e.delivered.push({doc:p,level:c,...o===void 0?{}:{cause:o}}),c==="content"&&u.push(...(await j(e,p,!1)).blocks),u.push(W(p,c)))}return u}a(A,"renderPlan");async function V(e,n,t,i){i??=await w(e.db,e.reader,[n]);const o={ctx:e,delivered:[],members:new Map};return{content:$(await A(o,i,n,t)),delivered:o.delivered}}a(V,"renderEvent");async function X(e,n,t=!1){const i={ctx:e,delivered:[],members:new Map};v(e.ledger,e.channel,{object:n.object.id,headsKey:n.headsKey,contentHash:n.bodyHash},"content");const{blocks:o,listing:s,linked:r}=await j(i,n,!t);return{attachments:$(o),...s===void 0?{}:{listing:s},linked:r,delivered:i.delivered}}a(X,"renderExplicitRead");export{I as linkedDocs,W as renderEnvelope,V as renderEvent,X as renderExplicitRead,$ as wrapAutoLoaded};
8
+ ORDER BY e.written_as`).all(t.object.id,t.object.id,...i.params);return await W(o,async a=>await O(e,n,a))}c(U,"linkedDocs");function _(e,n){return n.owner.id===e.owner.id&&n.name.startsWith(`${e.name}/`)&&!n.name.slice(e.name.length+1).includes("/")}c(_,"underOwnPrefix");async function F(e,n){const{ledger:t,channel:i}=e.ctx,o=[],a=[];for(const r of await U(e.ctx.db,e.ctx.reader,n)){if(_(n,r)&&!r.unlisted)continue;const p={object:r.object.id,headsKey:r.headsKey,contentHash:r.bodyHash};if(v(t,p,"preview"))continue;b(t,i,p,"preview"),e.delivered.push({doc:r,level:"preview",cause:"link preview attached to content delivery"}),a.push(r);const d=r.preview.trim();o.push(d===""?`[[${r.displayName}]]`:`[[${r.displayName}]]: ${d}`)}return{lines:o,linked:a}}c(F,"linkPreviews");async function $(e,n,t){const{ledger:i,channel:o}=e.ctx,a=await I(e,n),r=[];let p,d=[];for(const u of k(a,n.name)){const f=u===n.name,s=P(n,u);if(!(f&&t)&&E(i,s,"preview"))continue;const{lines:l,previewed:m}=M(a,u,i,o,!(f&&t));for(const y of m)e.delivered.push({doc:y,level:"preview",cause:"directory listing attached to content delivery"});l.length!==0&&(K(i,o,s,"preview"),f?d=l:r.push(C(x(n,u),l)))}const h=await F(e,n);return t?d.length>0&&(p=d):d.length+h.lines.length>0&&r.unshift(B(x(n,n.name),d,h.lines)),e.chainCorpus??=await g(e.ctx.db,e.ctx.reader,["document-read"]),r.push(...await A(e,e.chainCorpus,"document-read",{doc:n},"memory-read companion of content delivery")),{blocks:r,...p===void 0?{}:{listing:p},linked:h.linked}}c($,"attachments");async function A(e,n,t,i,o){const{db:a,reader:r,subject:p,ledger:d,channel:h}=e.ctx,u=await S(a,p,r,n,t,i),f=[];await L(a,u.docs.filter(s=>s.winner&&s.finalRung==="content"&&!v(d,{object:s.doc.object.id,headsKey:s.doc.headsKey,contentHash:s.doc.bodyHash},"content")).map(s=>s.doc));for(const s of u.docs){const l=s.finalRung;if(!s.winner||l==="none")continue;const{doc:m}=s,y={object:m.object.id,headsKey:m.headsKey,contentHash:m.bodyHash};v(d,y,l)||(b(d,h,y,l),e.delivered.push({doc:m,level:l,...o===void 0?{}:{cause:o}}),l==="content"&&f.push(...(await $(e,m,!1)).blocks),f.push(await q(m,l)))}return f}c(A,"renderPlan");async function X(e,n,t,i){i??=await g(e.db,e.reader,[n]);const o={ctx:e,delivered:[],members:new Map};return{content:j(await A(o,i,n,t)),delivered:o.delivered}}c(X,"renderEvent");async function Z(e,n,t=!1){const i={ctx:e,delivered:[],members:new Map};b(e.ledger,e.channel,{object:n.object.id,headsKey:n.headsKey,contentHash:n.bodyHash},"content");const{blocks:o,listing:a,linked:r}=await $(i,n,!t);return{attachments:j(o),...a===void 0?{}:{listing:a},linked:r,delivered:i.delivered}}c(Z,"renderExplicitRead");export{U as linkedDocs,q as renderEnvelope,X as renderEvent,Z as renderExplicitRead,j as wrapAutoLoaded};
@@ -22,8 +22,8 @@ export interface DeliveryDoc {
22
22
  headsKey: string;
23
23
  /** Body hash of the highest head revision. */
24
24
  bodyHash: string;
25
- /** Lazy: preview and name deliveries never read the body. */
26
- body(): string;
25
+ /** Lazy: preview and name deliveries never read the body. One query for the first call; `primeBodies` fetches many documents' bodies in one. */
26
+ body(): Promise<string>;
27
27
  rules: RuleRow[];
28
28
  /** The profile's cap for this owner on `boot` / `workspace-open`. */
29
29
  limit: Rung;
@@ -1,8 +1,8 @@
1
- var j=Object.defineProperty;var a=(r,e)=>j(r,"name",{value:e,configurable:!0});import{WebSocketServer as K}from"ws";import{isSafeNodeId as P,viewSocketPath as Q}from"../../core/canvas/paths.js";import{getNode as T}from"../../core/canvas/index.js";import{requestPersonId as z,runAsPerson as G}from"../../core/canvas/person-state.js";import{BROKER_READ_CAPS as X,CLIENT_READ_CAPS as Y,FrameDecoder as Z,FrameOverflowError as I}from"../../core/runtime/broker-protocol.js";import{emitEvent as E}from"../../core/events/emit.js";import{classify as $}from"../../core/fault-classifier.js";import{clearFault as g,markNodeRead as ee,recordFault as x}from"../../core/canvas/recovery.js";import{decideAttachRevive as re}from"../../core/runtime/revive.js";import{connectViewSocket as te,connectWssView as ne,probeViewSocket as ae,waitForBrokerViewSocket as oe,waitForWssView as O}from"../../core/runtime/view-socket.js";import{liveExecutionForNode as ie}from"../../core/canvas/executions.js";import{runnerForExecution as ce}from"../../core/runtime/runner/index.js";import{isBeneathRefusal as se}from"../../core/spaces/open-beneath.js";import{attachViewer as V,detachViewer as le,holdViewerForRedial as fe,presentViewer as ue}from"./viewer-presence.js";const me=new K({noServer:!0,maxPayload:X.maxLineBytes}),de=25e3,he=6e4;function ye(r,e=de,s=he){let n=!0,h=!1,u=Date.now();const l=a(()=>{n=!0},"acknowledge"),i=a(()=>{h||(h=!0,clearInterval(o),r.off("pong",l))},"stop");r.on("pong",l);const o=setInterval(()=>{if(Date.now()-u>s){n=!0;return}if(!n){i(),r.terminate();return}n=!1;try{r.ping()}catch{i(),r.terminate()}},e);return o.unref(),{stop:i,noteActivity:a(()=>{u=Date.now()},"noteActivity")}}a(ye,"startRemoteAttachHeartbeat");function C(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}a(C,"clampReason");function w(r,e,s,n,h){const u=JSON.stringify({error:{code:n,message:h,origin:"runtime"}}),l=`HTTP/1.1 ${e} ${s}\r
1
+ var j=Object.defineProperty;var i=(r,e)=>j(r,"name",{value:e,configurable:!0});import{WebSocketServer as K}from"ws";import{isSafeNodeId as N,viewSocketPath as Q}from"../../core/canvas/paths.js";import{getNode as T}from"../../core/canvas/index.js";import{requestPersonId as z,runAsPerson as G}from"../../core/canvas/person-state.js";import{BROKER_READ_CAPS as X,CLIENT_READ_CAPS as Y,FrameDecoder as Z,FrameOverflowError as I}from"../../core/runtime/broker-protocol.js";import{emitEvent as E}from"../../core/events/emit.js";import{classify as $}from"../../core/fault-classifier.js";import{clearFault as g,markNodeRead as ee,recordFault as x}from"../../core/canvas/recovery.js";import{decideAttachRevive as re}from"../../core/runtime/revive.js";import{connectViewSocket as te,connectWssView as ne,probeViewSocket as ie,waitForBrokerViewSocket as ae,waitForWssView as P}from"../../core/runtime/view-socket.js";import{liveExecutionForNode as oe}from"../../core/canvas/executions.js";import{runnerForExecution as ce}from"../../core/runtime/runner/index.js";import{isBeneathRefusal as se}from"../../core/spaces/open-beneath.js";import{openMachineView as le,probeMachineView as fe,waitForMachineView as ue}from"../machine-link/forward.js";import{attachViewer as F,detachViewer as me,holdViewerForRedial as de,presentViewer as he}from"./viewer-presence.js";const pe=3e4,we=new K({noServer:!0,maxPayload:X.maxLineBytes}),ye=25e3,ve=6e4;function ke(r,e=ye,s=ve){let n=!0,h=!1,u=Date.now();const l=i(()=>{n=!0},"acknowledge"),a=i(()=>{h||(h=!0,clearInterval(o),r.off("pong",l))},"stop");r.on("pong",l);const o=setInterval(()=>{if(Date.now()-u>s){n=!0;return}if(!n){a(),r.terminate();return}n=!1;try{r.ping()}catch{a(),r.terminate()}},e);return o.unref(),{stop:a,noteActivity:i(()=>{u=Date.now()},"noteActivity")}}i(ke,"startRemoteAttachHeartbeat");function C(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}i(C,"clampReason");function y(r,e,s,n,h){const u=JSON.stringify({error:{code:n,message:h,origin:"runtime"}}),l=`HTTP/1.1 ${e} ${s}\r
2
2
  content-type: application/json; charset=utf-8\r
3
3
  content-length: ${Buffer.byteLength(u)}\r
4
4
  connection: close\r
5
5
  \r
6
- `+u;try{r.end(l)}catch{try{r.destroy()}catch{}}}a(w,"refuseUpgrade");function pe(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const s=r.headers["x-crtr-attach-revive"];return(Array.isArray(s)?s[0]:s)==="0"}a(pe,"reviveOptedOut");function we(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}a(we,"wakeParkedRequested");function F(r){return r===1e3||r===1001||r===1005}a(F,"isCleanViewerClose");function y(r,e,s){s.catch(n=>E({level:"error",event:e,node_id:r,error:n}))}a(y,"detach");async function D(r){const e=await ie(r);return e===null||e.runner==="process"?{kind:"unix",path:Q(r)}:ce(e.runner).viewerAddress(e)}a(D,"brokerViewAddress");function ve(r,e){return e.kind==="wss"?ne(e.url,e.headers):te(r)}a(ve,"connectBrokerView");function ke(r,e,s){return e.kind==="wss"?O(e.url,e.headers,void 0,s):ae(r,s)}a(ke,"probeBrokerView");async function ge(r,e,s,n){const h=[];let u=a(t=>{h.push(t)},"relayMessage");if(r.on("message",t=>u(t)),!P(e)){try{r.close(1008,C(`invalid node id: ${e}`))}catch{}return}if(await T(e)===null){try{r.close(1011,C(`no node ${e}`))}catch{}return}await g(e,{link:"viewer\u2194crtrd"});const l=ve(e,s),i=new Z(Y);let o=!1,v=!1,d,b=!1,B=!1,A="controller",N=Promise.resolve(),_=!1;const S=[];let R=!1;l.on("connect",()=>{b=!0,!o&&_&&y(e,"daemon.bridge.presence_failed",V(e,r)),y(e,"daemon.bridge.connect_failed",g(e,{link:"relay\u2194broker"}))});const p=a((t,f,c=!1)=>{if(!o){if(o=!0,y(e,"daemon.bridge.presence_failed",c?fe(e,r):le(e,r)),d?.stop(),!l.destroyed)try{l.destroy()}catch{}try{r.close(t,C(f))}catch{}}},"teardown");d=ye(r);const U=a(async t=>{d?.noteActivity();let f;try{f=i.push(t)}catch(c){const m=c instanceof I?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;p(1009,m);return}S.push(...f),R||await L()},"onData"),L=a(async()=>{R=!0;try{for(;S.length>0&&!o;){const t=S.splice(0);let f;try{f=await n(A)}catch(m){E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:m}),p(1011,"revoked");return}if(o)return;if(!f){p(1008,"revoked");return}let c=!1;for(const m of t)r.readyState===r.OPEN&&(r.send(JSON.stringify(m)),m?.type==="welcome"&&(c=!0));if(_&&c)try{await ue(e,r)&&await ee(e)}catch(m){E({level:"warn",event:"daemon.bridge.present_failed",node_id:e,error:m})}}}finally{R=!1}},"drainOutbound");l.on("data",t=>y(e,"daemon.bridge.data_failed",U(t)));const W=a(async t=>{const f=$("relay\u2194broker",t);await x(e,{link:"relay\u2194broker",op:"bridge socket",kind:f.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;p(1011,c,!0)},"onSocketError");l.on("error",t=>y(e,"daemon.bridge.socket_error_failed",W(t)));const H=a(async t=>{o||t||(await g(e,{link:"relay\u2194broker"}),p(1e3,"broker socket closed",!0))},"onSocketClose");l.on("close",t=>y(e,"daemon.bridge.socket_close_failed",H(t)));const M=a(async(t,f)=>{const c=f.toString("utf8");if(o){F(t)&&await g(e,{link:"viewer\u2194crtrd"});return}if(F(t))await g(e,{link:"viewer\u2194crtrd"});else if(!v){v=!0;const m=$("viewer\u2194crtrd",{code:t,reason:c});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:m.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}p(t,c||`remote attach closed (${t})`)},"onWsClose");r.on("close",(t,f)=>y(e,"daemon.bridge.ws_close_failed",M(t,f)));const J=a(async()=>{if(!v){v=!0;const t=$("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}p(1011,"remote attach websocket error")},"onWsError");r.on("error",()=>y(e,"daemon.bridge.ws_error_failed",J())),u=a(t=>{if(o||l.destroyed)return;d?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!B)try{const k=JSON.parse(c);k?.type==="hello"&&(B=!0,A=k.role==="observer"?"observer":"controller",_=k.attends!==!1,_&&b&&y(e,"daemon.bridge.presence_failed",V(e,r)))}catch{}const m=c.endsWith(`
6
+ `+u;try{r.end(l)}catch{try{r.destroy()}catch{}}}i(y,"refuseUpgrade");function ge(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const s=r.headers["x-crtr-attach-revive"];return(Array.isArray(s)?s[0]:s)==="0"}i(ge,"reviveOptedOut");function be(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}i(be,"wakeParkedRequested");function O(r){return r===1e3||r===1001||r===1005}i(O,"isCleanViewerClose");function p(r,e,s){s.catch(n=>E({level:"error",event:e,node_id:r,error:n}))}i(p,"detach");async function D(r){const e=await oe(r);return e===null||e.runner==="process"?{kind:"unix",path:Q(r)}:ce(e.runner).viewerAddress(e)}i(D,"brokerViewAddress");function _e(r,e){return e.kind==="machine"?le(e.environmentId,r):e.kind==="wss"?ne(e.url,e.headers):te(r)}i(_e,"connectBrokerView");function Ee(r,e,s){return e.kind==="machine"?fe(e.environmentId,r,s):e.kind==="wss"?P(e.url,e.headers,void 0,s):ie(r,s)}i(Ee,"probeBrokerView");async function Ae(r,e,s,n){const h=[];let u=i(t=>{h.push(t)},"relayMessage");if(r.on("message",t=>u(t)),!N(e)){try{r.close(1008,C(`invalid node id: ${e}`))}catch{}return}if(await T(e)===null){try{r.close(1011,C(`no node ${e}`))}catch{}return}await g(e,{link:"viewer\u2194crtrd"});const l=_e(e,s),a=new Z(Y);let o=!1,v=!1,d,b=!1,V=!1,A="controller",B=Promise.resolve(),_=!1;const S=[];let R=!1;l.on("connect",()=>{b=!0,!o&&_&&p(e,"daemon.bridge.presence_failed",F(e,r)),p(e,"daemon.bridge.connect_failed",g(e,{link:"relay\u2194broker"}))});const w=i((t,f,c=!1)=>{if(!o){if(o=!0,p(e,"daemon.bridge.presence_failed",c?de(e,r):me(e,r)),d?.stop(),!l.destroyed)try{l.destroy()}catch{}try{r.close(t,C(f))}catch{}}},"teardown");d=ke(r);const M=i(async t=>{d?.noteActivity();let f;try{f=a.push(t)}catch(c){const m=c instanceof I?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;w(1009,m);return}S.push(...f),R||await U()},"onData"),U=i(async()=>{R=!0;try{for(;S.length>0&&!o;){const t=S.splice(0);let f;try{f=await n(A)}catch(m){E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:m}),w(1011,"revoked");return}if(o)return;if(!f){w(1008,"revoked");return}let c=!1;for(const m of t)r.readyState===r.OPEN&&(r.send(JSON.stringify(m)),m?.type==="welcome"&&(c=!0));if(_&&c)try{await he(e,r)&&await ee(e)}catch(m){E({level:"warn",event:"daemon.bridge.present_failed",node_id:e,error:m})}}}finally{R=!1}},"drainOutbound");l.on("data",t=>p(e,"daemon.bridge.data_failed",M(t)));const W=i(async t=>{const f=$("relay\u2194broker",t);await x(e,{link:"relay\u2194broker",op:"bridge socket",kind:f.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;w(1011,c,!0)},"onSocketError");l.on("error",t=>p(e,"daemon.bridge.socket_error_failed",W(t)));const H=i(async t=>{o||t||(await g(e,{link:"relay\u2194broker"}),w(1e3,"broker socket closed",!0))},"onSocketClose");l.on("close",t=>p(e,"daemon.bridge.socket_close_failed",H(t)));const L=i(async(t,f)=>{const c=f.toString("utf8");if(o){O(t)&&await g(e,{link:"viewer\u2194crtrd"});return}if(O(t))await g(e,{link:"viewer\u2194crtrd"});else if(!v){v=!0;const m=$("viewer\u2194crtrd",{code:t,reason:c});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:m.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}w(t,c||`remote attach closed (${t})`)},"onWsClose");r.on("close",(t,f)=>p(e,"daemon.bridge.ws_close_failed",L(t,f)));const J=i(async()=>{if(!v){v=!0;const t=$("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}w(1011,"remote attach websocket error")},"onWsError");r.on("error",()=>p(e,"daemon.bridge.ws_error_failed",J())),u=i(t=>{if(o||l.destroyed)return;d?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!V)try{const k=JSON.parse(c);k?.type==="hello"&&(V=!0,A=k.role==="observer"?"observer":"controller",_=k.attends!==!1,_&&b&&p(e,"daemon.bridge.presence_failed",F(e,r)))}catch{}const m=c.endsWith(`
7
7
  `)?c:c+`
8
- `,q=a(()=>{try{l.write(m)}catch{}},"write");N=N.then(async()=>{o||(await n(A)?q():p(1008,"revoked"))}).catch(k=>{E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:k}),p(1011,"revoked")})},"relayMessage");for(const t of h.splice(0))u(t)}a(ge,"bridgeConnection");async function Fe(r,e,s,n,h){const u=z(),l=u===void 0?h:i=>G(u,()=>h(i));e.on("error",()=>{});try{if(!P(n)){w(e,400,"Bad Request","invalid_node_id",`invalid node id: ${n}`);return}if(await T(n)===null){w(e,404,"Not Found","node_not_found",`no node ${n}`);return}let i;if(pe(r)){if(i=await D(n),!await ke(n,i,250)){w(e,409,"Conflict","node_dormant",`node ${n} is dormant and revive was opted out (?revive=0)`);return}}else{let o;try{const d=await re(n,{resume:!0,capacity:"refuse"},we(r));if(d.parked){w(e,409,"Conflict","node_dormant",`node ${n} is parked; attach with ?wakeParked=1 to wake it`);return}o=d.result.launch?.exited}catch(d){const b=d instanceof Error?d.message:String(d);w(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${b}`);return}if(i=await D(n),!(i.kind==="wss"?await O(i.url,i.headers,o):await oe(n,o))){w(e,409,"Conflict","broker_not_ready",`broker view socket for ${n} did not become listenable after revive`);return}}if(e.destroyed)return;me.handleUpgrade(r,e,s,o=>y(n,"daemon.bridge.connection_failed",ge(o,n,i,l)))}catch(i){const o=i instanceof Error?i.message:String(i);if(se(i)){w(e,400,"Bad Request","invalid_request",o);return}w(e,500,"Internal Server Error","internal",`attach upgrade failed: ${o}`)}}a(Fe,"handleAttachUpgrade");export{de as REMOTE_ATTACH_HEARTBEAT_MS,he as REMOTE_ATTACH_QUIET_MS,Fe as handleAttachUpgrade,F as isCleanViewerClose,w as refuseUpgrade,ye as startRemoteAttachHeartbeat};
8
+ `,q=i(()=>{try{l.write(m)}catch{}},"write");B=B.then(async()=>{o||(await n(A)?q():w(1008,"revoked"))}).catch(k=>{E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:k}),w(1011,"revoked")})},"relayMessage");for(const t of h.splice(0))u(t)}i(Ae,"bridgeConnection");async function He(r,e,s,n,h){const u=z(),l=u===void 0?h:a=>G(u,()=>h(a));e.on("error",()=>{});try{if(!N(n)){y(e,400,"Bad Request","invalid_node_id",`invalid node id: ${n}`);return}if(await T(n)===null){y(e,404,"Not Found","node_not_found",`no node ${n}`);return}let a;if(ge(r)){if(a=await D(n),!await Ee(n,a,250)){y(e,409,"Conflict","node_dormant",`node ${n} is dormant and revive was opted out (?revive=0)`);return}}else{let o;try{const d=await re(n,{resume:!0,capacity:"refuse"},be(r));if(d.parked){y(e,409,"Conflict","node_dormant",`node ${n} is parked; attach with ?wakeParked=1 to wake it`);return}o=d.result.launch?.exited}catch(d){const b=d instanceof Error?d.message:String(d);y(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${b}`);return}if(a=await D(n),!(a.kind==="machine"?await ue(a.environmentId,n,o,pe):a.kind==="wss"?await P(a.url,a.headers,o):await ae(n,o))){y(e,409,"Conflict","broker_not_ready",`broker view socket for ${n} did not become listenable after revive`);return}}if(e.destroyed)return;we.handleUpgrade(r,e,s,o=>p(n,"daemon.bridge.connection_failed",Ae(o,n,a,l)))}catch(a){const o=a instanceof Error?a.message:String(a);if(se(a)){y(e,400,"Bad Request","invalid_request",o);return}y(e,500,"Internal Server Error","internal",`attach upgrade failed: ${o}`)}}i(He,"handleAttachUpgrade");export{ye as REMOTE_ATTACH_HEARTBEAT_MS,ve as REMOTE_ATTACH_QUIET_MS,He as handleAttachUpgrade,O as isCleanViewerClose,y as refuseUpgrade,ke as startRemoteAttachHeartbeat};
@@ -1,11 +1,11 @@
1
- var L=Object.defineProperty;var h=(n,a)=>L(n,"name",{value:a,configurable:!0});import{createHash as W,timingSafeEqual as A}from"node:crypto";import{createServer as U}from"node:http";import{readFileSync as z}from"node:fs";import{join as E,extname as B}from"node:path";import{fileURLToPath as G}from"node:url";import{withCanvasWrite as b}from"../../core/canvas/db.js";import{ApiError as I}from"../../api/errors.js";import{bodyOf as C}from"../../core/graph/bodies.js";import{canChange as H,isShipped as F}from"../../core/graph/access.js";import{documentRecord as x,editDocument as J,heads as O,withCurrentNames as k}from"../../core/graph/documents.js";import{getById as N}from"../../core/graph/objects.js";import{subscribeDocumentEvents as M}from"../../core/graph/events.js";import{emitEvent as V}from"../../core/events/emit.js";import{checkoutsForCwd as X}from"../../core/graph/repo-sync/repos.js";import{canonicalName as Z,repoChange as K,syncChanged as Q}from"./handlers/docs.js";import{escapeHtml as S}from"./vendor-signin.js";import{readerFor as $,resolveRef as Y,warmDocumentWatchers as q}from"./reader.js";import{mergeDocumentLines as ee}from"./document-merge.js";import{crtrVersion as te}from"../../shared/crtr-version.js";const ne="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",T={class:"person"},y=encodeURIComponent(te()),j=G(new URL("../../../dist/",import.meta.url));function m(n,a,t,i){n.writeHead(a,{"Content-Type":"application/json; charset=utf-8"}),n.end(JSON.stringify({error:{code:t,message:i}}))}h(m,"error");function P(n,a){const t=h(i=>W("sha256").update(i).digest(),"digest");return A(t(n),t(a))}h(P,"sameSecret");function oe(n,a){for(const t of(n??"").split(";")){const i=t.indexOf("=");if(i!==-1&&t.slice(0,i).trim()===a)return t.slice(i+1).trim()}}h(oe,"cookieValue");function R(n,a,t){n.writeHead(a,{"Content-Type":"application/json; charset=utf-8"}),n.end(JSON.stringify(t))}h(R,"json");async function D(n){return await b(async a=>{const t=await N(a,n.id);if(!t||t.deleted_at||t.type!=="document")return null;const i=await $(a,{principal:T}),l=await x(a,t),o=await O(a,t),e=await Z(a,t),u=o.length===1&&await H(a,i,t);return{id:t.id,name:e,revision:l.revision,heads_key:l.heads_key,body:o.length===1?await k(a,t,C(o[0].body_hash,l.source_path)):"",editable:u,...u?{}:{readonly_reason:o.length!==1?`has ${o.length} heads and needs a merge`:await F(a,t)?"shipped by a plugin":"you cannot change this document"}}})}h(D,"snapshot");async function p(n){return await b(async a=>{const t=await $(a,{principal:T});try{const i=await Y(a,t,n);return"deleted"in i||"remote"in i||i.type!=="document"?null:i}catch(i){if(i instanceof I&&i.code==="not_found")return null;throw i}})}h(p,"resolveDocument");async function ae(n){const a=[];let t=0;for await(const i of n){if(t+=i.length,t>2e6)throw new Error("request body exceeds 2 MB");a.push(i)}return JSON.parse(Buffer.concat(a).toString("utf8"))}h(ae,"requestBody");async function re(n){await q(n.id);const a=await b(async t=>{const i=await $(t,{principal:T}),l=await N(t,n.id);if(!l||l.type!=="document"||l.deleted_at)return{status:404,value:{error:{code:"not_found",message:"document not found"}}};const o=await x(t,l),e=await O(t,l);if(e.length!==1||!await H(t,i,l))return{status:403,value:{error:{code:"forbidden",message:"document is read-only"}}};const u=await k(t,l,C(e[0].body_hash,o.source_path)),c=await t.prepareUncached("SELECT body_hash FROM revisions WHERE document = ? AND rev_id = ?").get(l.id,n.base_heads_key);if(!c||await k(t,l,C(c.body_hash,o.source_path))!==n.base_body)return{status:409,value:{saved:!1,conflict:{revision:o.revision,heads_key:o.heads_key,current_body:u,your_body:n.body}}};const r=n.base_heads_key!==o.heads_key,s=r?ee(n.base_body,n.body,u):n.body;if(s===null)return{status:409,value:{saved:!1,conflict:{revision:o.revision,heads_key:o.heads_key,current_body:u,your_body:n.body}}};const d=await J(t,i,l,{body:s},n.rationale?.trim()||"edited by the person in the browser",o.heads_key,{},async w=>await X(t,w));return"stale"in d?{status:409,value:{saved:!1,conflict:{revision:d.stale.revision,heads_key:d.stale.heads_key,current_body:u,your_body:n.body}}}:{status:200,value:{saved:!0,revision:d.revision,heads_key:d.heads_key,body:s,merged:r},change:await K(t,i,[l])}});return a.change&&await Q(a.change),a}h(re,"save");let v="the document listener has not started";function Te(){return v}h(Te,"documentListenerUnavailable");function Ee(n,a){const t=`crtr_doc_${n}`,i=U((o,e)=>{(async()=>{e.setHeader("Content-Security-Policy",ne),e.setHeader("X-Content-Type-Options","nosniff");const u=[`127.0.0.1:${n}`,`localhost:${n}`];if(typeof o.headers.host!="string"||!u.includes(o.headers.host))return m(e,403,"forbidden","invalid Host");if(o.method==="POST"){if(!u.some(r=>o.headers.origin===`http://${r}`))return m(e,403,"forbidden","invalid Origin");if(o.headers["content-type"]!=="application/json")return m(e,415,"unsupported_media_type","Content-Type must be application/json")}const c=new URL(o.url??"/",`http://127.0.0.1:${n}`);if(!c.pathname.startsWith("/assets/")){const r=o.method==="GET"&&c.pathname.startsWith("/r/")?c.searchParams.get("t"):null;if(r!==null&&P(r,a))return c.searchParams.delete("t"),e.writeHead(303,{Location:`${c.pathname}${c.search}`,"Set-Cookie":`${t}=${a}; HttpOnly; SameSite=Strict; Path=/`,"Cache-Control":"no-store"}),e.end();const s=oe(o.headers.cookie,t);if(r!==null||s===void 0||!P(s,a))return c.pathname.startsWith("/r/")?(e.writeHead(401,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),e.end('<!doctype html><html><head><meta charset="utf-8"><title>Not authorized</title><link rel="icon" href="data:,"></head><body style="font-family:system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem"><h1>Not authorized</h1><p>Open this document from its link in the attach viewer.</p></body></html>')):m(e,401,"unauthorized","open the document link from the attach viewer")}if(o.method==="GET"&&c.pathname.startsWith("/r/")){let r;try{r=decodeURIComponent(c.pathname.slice(3))}catch{return m(e,400,"invalid_request","ref is not valid percent-encoding")}const s=await p(r);return s?(e.writeHead(200,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),e.end(`<!doctype html><html><head><meta charset="utf-8"><title>${S(s.name)}</title><link rel="icon" href="data:,"><link rel="stylesheet" href="/assets/${y}/editor.css"></head><body><div id="app" data-ref="${S(r)}"></div><script type="module" src="/assets/${y}/editor.js"><\/script></body></html>`)):(e.writeHead(404,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),e.end(`<!doctype html><html><head><meta charset="utf-8"><title>Document not found</title><link rel="icon" href="data:,"></head><body style="font-family:system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem"><h1>Document not found</h1><p>No document you can read is named <code>${S(r)}</code>.</p></body></html>`))}if(o.method==="GET"&&c.pathname==="/api/doc"){const r=c.searchParams.get("ref"),s=r?await p(r):null,d=s&&await D(s);return d?R(e,200,{...d,ref:r}):m(e,404,"not_found","document not found")}if(o.method==="POST"&&c.pathname==="/api/save"){const r=await ae(o);if(!r||typeof r!="object"||Array.isArray(r))return m(e,400,"invalid_request","invalid save body");const s=r;if(typeof s.id!="string"||typeof s.base_heads_key!="string"||typeof s.base_body!="string"||typeof s.body!="string"||s.rationale!==void 0&&typeof s.rationale!="string")return m(e,400,"invalid_request","invalid save body");const d=await re(s);return R(e,d.status,d.value)}if(o.method==="GET"&&c.pathname==="/api/events"){const r=c.searchParams.get("id");if(!r||!await p(r))return m(e,404,"not_found","document not found");e.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-store",Connection:"keep-alive"}),e.write(`: connected
1
+ var L=Object.defineProperty;var h=(n,a)=>L(n,"name",{value:a,configurable:!0});import{createHash as W,timingSafeEqual as A}from"node:crypto";import{createServer as U}from"node:http";import{readFileSync as B}from"node:fs";import{join as E,extname as z}from"node:path";import{fileURLToPath as G}from"node:url";import{withCanvasWrite as b}from"../../core/canvas/db.js";import{ApiError as I}from"../../api/errors.js";import{get as C}from"../../core/graph/bodies.js";import{canChange as H,isShipped as F}from"../../core/graph/access.js";import{documentRecord as x,editDocument as J,heads as N,withCurrentNames as k}from"../../core/graph/documents.js";import{getById as O}from"../../core/graph/objects.js";import{subscribeDocumentEvents as M}from"../../core/graph/events.js";import{emitEvent as V}from"../../core/events/emit.js";import{checkoutsForCwd as X}from"../../core/graph/repo-sync/repos.js";import{canonicalName as Z,repoChange as K,syncChanged as Q}from"./handlers/docs.js";import{escapeHtml as S}from"./vendor-signin.js";import{readerFor as $,resolveRef as Y,warmDocumentWatchers as q}from"./reader.js";import{mergeDocumentLines as ee}from"./document-merge.js";import{crtrVersion as te}from"../../shared/crtr-version.js";const ne="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",T={class:"person"},y=encodeURIComponent(te()),j=G(new URL("../../../dist/",import.meta.url));function m(n,a,t,r){n.writeHead(a,{"Content-Type":"application/json; charset=utf-8"}),n.end(JSON.stringify({error:{code:t,message:r}}))}h(m,"error");function P(n,a){const t=h(r=>W("sha256").update(r).digest(),"digest");return A(t(n),t(a))}h(P,"sameSecret");function ae(n,a){for(const t of(n??"").split(";")){const r=t.indexOf("=");if(r!==-1&&t.slice(0,r).trim()===a)return t.slice(r+1).trim()}}h(ae,"cookieValue");function R(n,a,t){n.writeHead(a,{"Content-Type":"application/json; charset=utf-8"}),n.end(JSON.stringify(t))}h(R,"json");async function D(n){return await b(async a=>{const t=await O(a,n.id);if(!t||t.deleted_at||t.type!=="document")return null;const r=await $(a,{principal:T}),l=await x(a,t),i=await N(a,t),e=await Z(a,t),u=i.length===1&&await H(a,r,t);return{id:t.id,name:e,revision:l.revision,heads_key:l.heads_key,body:i.length===1?await k(a,t,await C(a,i[0].body_hash)):"",editable:u,...u?{}:{readonly_reason:i.length!==1?`has ${i.length} heads and needs a merge`:await F(a,t)?"shipped by a plugin":"you cannot change this document"}}})}h(D,"snapshot");async function p(n){return await b(async a=>{const t=await $(a,{principal:T});try{const r=await Y(a,t,n);return"deleted"in r||"remote"in r||r.type!=="document"?null:r}catch(r){if(r instanceof I&&r.code==="not_found")return null;throw r}})}h(p,"resolveDocument");async function oe(n){const a=[];let t=0;for await(const r of n){if(t+=r.length,t>2e6)throw new Error("request body exceeds 2 MB");a.push(r)}return JSON.parse(Buffer.concat(a).toString("utf8"))}h(oe,"requestBody");async function re(n){await q(n.id);const a=await b(async t=>{const r=await $(t,{principal:T}),l=await O(t,n.id);if(!l||l.type!=="document"||l.deleted_at)return{status:404,value:{error:{code:"not_found",message:"document not found"}}};const i=await x(t,l),e=await N(t,l);if(e.length!==1||!await H(t,r,l))return{status:403,value:{error:{code:"forbidden",message:"document is read-only"}}};const u=await k(t,l,await C(t,e[0].body_hash)),c=await t.prepareUncached("SELECT body_hash FROM revisions WHERE document = ? AND rev_id = ?").get(l.id,n.base_heads_key);if(!c||await k(t,l,await C(t,c.body_hash))!==n.base_body)return{status:409,value:{saved:!1,conflict:{revision:i.revision,heads_key:i.heads_key,current_body:u,your_body:n.body}}};const o=n.base_heads_key!==i.heads_key,s=o?ee(n.base_body,n.body,u):n.body;if(s===null)return{status:409,value:{saved:!1,conflict:{revision:i.revision,heads_key:i.heads_key,current_body:u,your_body:n.body}}};const d=await J(t,r,l,{body:s},n.rationale?.trim()||"edited by the person in the browser",i.heads_key,{},async w=>await X(t,w));return"stale"in d?{status:409,value:{saved:!1,conflict:{revision:d.stale.revision,heads_key:d.stale.heads_key,current_body:u,your_body:n.body}}}:{status:200,value:{saved:!0,revision:d.revision,heads_key:d.heads_key,body:s,merged:o},change:await K(t,r,[l])}});return a.change&&await Q(a.change),a}h(re,"save");let v="the document listener has not started";function Te(){return v}h(Te,"documentListenerUnavailable");function Ee(n,a){const t=`crtr_doc_${n}`,r=U((i,e)=>{(async()=>{e.setHeader("Content-Security-Policy",ne),e.setHeader("X-Content-Type-Options","nosniff");const u=[`127.0.0.1:${n}`,`localhost:${n}`];if(typeof i.headers.host!="string"||!u.includes(i.headers.host))return m(e,403,"forbidden","invalid Host");if(i.method==="POST"){if(!u.some(o=>i.headers.origin===`http://${o}`))return m(e,403,"forbidden","invalid Origin");if(i.headers["content-type"]!=="application/json")return m(e,415,"unsupported_media_type","Content-Type must be application/json")}const c=new URL(i.url??"/",`http://127.0.0.1:${n}`);if(!c.pathname.startsWith("/assets/")){const o=i.method==="GET"&&c.pathname.startsWith("/r/")?c.searchParams.get("t"):null;if(o!==null&&P(o,a))return c.searchParams.delete("t"),e.writeHead(303,{Location:`${c.pathname}${c.search}`,"Set-Cookie":`${t}=${a}; HttpOnly; SameSite=Strict; Path=/`,"Cache-Control":"no-store"}),e.end();const s=ae(i.headers.cookie,t);if(o!==null||s===void 0||!P(s,a))return c.pathname.startsWith("/r/")?(e.writeHead(401,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),e.end('<!doctype html><html><head><meta charset="utf-8"><title>Not authorized</title><link rel="icon" href="data:,"></head><body style="font-family:system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem"><h1>Not authorized</h1><p>Open this document from its link in the attach viewer.</p></body></html>')):m(e,401,"unauthorized","open the document link from the attach viewer")}if(i.method==="GET"&&c.pathname.startsWith("/r/")){let o;try{o=decodeURIComponent(c.pathname.slice(3))}catch{return m(e,400,"invalid_request","ref is not valid percent-encoding")}const s=await p(o);return s?(e.writeHead(200,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),e.end(`<!doctype html><html><head><meta charset="utf-8"><title>${S(s.name)}</title><link rel="icon" href="data:,"><link rel="stylesheet" href="/assets/${y}/editor.css"></head><body><div id="app" data-ref="${S(o)}"></div><script type="module" src="/assets/${y}/editor.js"><\/script></body></html>`)):(e.writeHead(404,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),e.end(`<!doctype html><html><head><meta charset="utf-8"><title>Document not found</title><link rel="icon" href="data:,"></head><body style="font-family:system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem"><h1>Document not found</h1><p>No document you can read is named <code>${S(o)}</code>.</p></body></html>`))}if(i.method==="GET"&&c.pathname==="/api/doc"){const o=c.searchParams.get("ref"),s=o?await p(o):null,d=s&&await D(s);return d?R(e,200,{...d,ref:o}):m(e,404,"not_found","document not found")}if(i.method==="POST"&&c.pathname==="/api/save"){const o=await oe(i);if(!o||typeof o!="object"||Array.isArray(o))return m(e,400,"invalid_request","invalid save body");const s=o;if(typeof s.id!="string"||typeof s.base_heads_key!="string"||typeof s.base_body!="string"||typeof s.body!="string"||s.rationale!==void 0&&typeof s.rationale!="string")return m(e,400,"invalid_request","invalid save body");const d=await re(s);return R(e,d.status,d.value)}if(i.method==="GET"&&c.pathname==="/api/events"){const o=c.searchParams.get("id");if(!o||!await p(o))return m(e,404,"not_found","document not found");e.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-store",Connection:"keep-alive"}),e.write(`: connected
2
2
 
3
3
  `);const s=h(async _=>{if(_==="deleted"){e.write(`event: deleted
4
4
  data: {}
5
5
 
6
- `),e.end();return}const f=await p(r),g=f&&await D(f);g&&e.write(`event: edited
6
+ `),e.end();return}const f=await p(o),g=f&&await D(f);g&&e.write(`event: edited
7
7
  data: ${JSON.stringify({revision:g.revision,heads_key:g.heads_key})}
8
8
 
9
- `)},"notify"),d=await M(r,_=>{s(_).catch(f=>{V({level:"error",event:"daemon.document_events.notify_failed",error:f}),e.end()})}),w=setInterval(()=>e.write(`: keepalive
9
+ `)},"notify"),d=await M(o,_=>{s(_).catch(f=>{V({level:"error",event:"daemon.document_events.notify_failed",error:f}),e.end()})}),w=setInterval(()=>e.write(`: keepalive
10
10
 
11
- `),3e4);e.on("close",()=>{clearInterval(w),d()});return}if(o.method==="GET"&&c.pathname.startsWith(`/assets/${y}/`)){const r=c.pathname.slice(`/assets/${y}/`.length);if(!/^[a-zA-Z0-9_.-]+$/.test(r))return m(e,404,"not_found","asset not found");const s=r==="mermaid.js"?E(j,"clients/attach/assets/mermaid.min.js"):E(j,"doc-editor",r);try{const d=z(s);return e.writeHead(200,{"Content-Type":B(r)===".css"?"text/css; charset=utf-8":"text/javascript; charset=utf-8","Cache-Control":"public, max-age=31536000, immutable"}),e.end(d)}catch(d){if(d.code==="ENOENT")return m(e,404,"not_found","asset not found");throw d}}return m(e,404,"not_found","route not found")})().catch(u=>{e.headersSent?e.destroy():m(e,500,"server_failure",u instanceof Error?u.message:String(u))})});return{ready:new Promise((o,e)=>{i.once("error",u=>{v=`the document listener could not bind 127.0.0.1:${n}: ${u.message}`,e(u)}),i.listen(n,"127.0.0.1",()=>{i.removeAllListeners("error"),v=void 0,o()})}),close:h(()=>new Promise(o=>{v="the document listener is closed",i.closeAllConnections(),i.close(()=>o())}),"close")}}h(Ee,"createDocumentListener");export{Ee as createDocumentListener,Te as documentListenerUnavailable};
11
+ `),3e4);e.on("close",()=>{clearInterval(w),d()});return}if(i.method==="GET"&&c.pathname.startsWith(`/assets/${y}/`)){const o=c.pathname.slice(`/assets/${y}/`.length);if(!/^[a-zA-Z0-9_.-]+$/.test(o))return m(e,404,"not_found","asset not found");const s=o==="mermaid.js"?E(j,"clients/attach/assets/mermaid.min.js"):E(j,"doc-editor",o);try{const d=B(s);return e.writeHead(200,{"Content-Type":z(o)===".css"?"text/css; charset=utf-8":"text/javascript; charset=utf-8","Cache-Control":"public, max-age=31536000, immutable"}),e.end(d)}catch(d){if(d.code==="ENOENT")return m(e,404,"not_found","asset not found");throw d}}return m(e,404,"not_found","route not found")})().catch(u=>{e.headersSent?e.destroy():m(e,500,"server_failure",u instanceof Error?u.message:String(u))})});return{ready:new Promise((i,e)=>{r.once("error",u=>{v=`the document listener could not bind 127.0.0.1:${n}: ${u.message}`,e(u)}),r.listen(n,"127.0.0.1",()=>{r.removeAllListeners("error"),v=void 0,i()})}),close:h(()=>new Promise(i=>{v="the document listener is closed",r.closeAllConnections(),r.close(()=>i())}),"close")}}h(Ee,"createDocumentListener");export{Ee as createDocumentListener,Te as documentListenerUnavailable};
@@ -1 +1 @@
1
- var w=Object.defineProperty;var n=(t,e)=>w(t,"name",{value:e,configurable:!0});import{getNode as k}from"../../../core/canvas/canvas.js";import{decideAttachRevive as l}from"../../../core/runtime/revive.js";import{viewSocketPath as v}from"../../../core/canvas/paths.js";import{waitForBrokerViewSocket as h}from"../../../core/runtime/placement-tmux.js";import{notFound as m,usage as i}from"../../../core/errors.js";import{mayOnNode as u,ownerOnly as p}from"../router.js";function b(t){if(t==null)return{resume:!0,revive:!0,wakeParked:!0};if(typeof t!="object")throw i("attach body must be a JSON object { resume?, revive?, wakeParked? }");const e=t.resume,r=t.revive,a=t.wakeParked;if(e!==void 0&&typeof e!="boolean")throw i("resume must be a boolean");if(r!==void 0&&typeof r!="boolean")throw i("revive must be a boolean");if(a!==void 0&&typeof a!="boolean")throw i("wakeParked must be a boolean");return{resume:e??!0,revive:r??!0,wakeParked:a??!0}}n(b,"parseAttachBody");async function y(t,e,r){return e==="controller"?await u(t,"message",r):await u(t,"read",r)?!0:e==="either"&&await u(t,"message",r)}n(y,"mayAttachNode");async function P(t){const e=t.params.id;if(await k(e)===null)throw m(`unknown node: ${e}`,{received:e});if(!await y(t.principal,"either",e))throw m(`unknown node: ${e}`,{received:e});const{resume:r,revive:a,wakeParked:c}=b(t.body);let s=!1,d=!1,f=!1;if(a){const o=await l(e,{resume:r,capacity:"refuse"},c);f=o.parked,o.parked||(s=o.result.launch!==void 0,d=o.result.resumed,await h(e))}return{status:200,body:{node_id:e,socket_path:v(e),revived:s,resumed:d,parked:f}}}n(P,"handleAttach");const R=p([{method:"POST",pattern:"/v1/nodes/:id/attach",handler:P}]);export{R as attachRoutes,y as mayAttachNode};
1
+ var l=Object.defineProperty;var i=(r,e)=>l(r,"name",{value:e,configurable:!0});import{getNode as v}from"../../../core/canvas/canvas.js";import{decideAttachRevive as k}from"../../../core/runtime/revive.js";import{viewSocketPath as p}from"../../../core/canvas/paths.js";import{waitForBrokerViewSocket as b}from"../../../core/runtime/placement-tmux.js";import{liveExecutionForNode as y}from"../../../core/canvas/executions.js";import{runnerForExecution as P}from"../../../core/runtime/runner/index.js";import{waitForMachineView as A}from"../../machine-link/forward.js";import{general as N,notFound as w,usage as u}from"../../../core/errors.js";import{mayOnNode as d,ownerOnly as _}from"../router.js";const g=3e4;function x(r){if(r==null)return{resume:!0,revive:!0,wakeParked:!0};if(typeof r!="object")throw u("attach body must be a JSON object { resume?, revive?, wakeParked? }");const e=r.resume,t=r.revive,o=r.wakeParked;if(e!==void 0&&typeof e!="boolean")throw u("resume must be a boolean");if(t!==void 0&&typeof t!="boolean")throw u("revive must be a boolean");if(o!==void 0&&typeof o!="boolean")throw u("wakeParked must be a boolean");return{resume:e??!0,revive:t??!0,wakeParked:o??!0}}i(x,"parseAttachBody");async function E(r,e,t){return e==="controller"?await d(r,"message",t):await d(r,"read",t)?!0:e==="either"&&await d(r,"message",t)}i(E,"mayAttachNode");async function F(r){const e=r.params.id;if(await v(e)===null)throw w(`unknown node: ${e}`,{received:e});if(!await E(r.principal,"either",e))throw w(`unknown node: ${e}`,{received:e});const{resume:t,revive:o,wakeParked:h}=x(r.body);let c=!1,f=!1,m=!1;if(o){const a=await k(e,{resume:t,capacity:"refuse"},h);if(m=a.parked,!a.parked){c=a.result.launch!==void 0,f=a.result.resumed;const n=await y(e);if(n!==null&&n.runner==="machine"){const s=await P(n.runner).viewerAddress(n);if(s.kind!=="machine")throw new Error(`the machine execution for ${e} resolved a ${s.kind} viewer address`);if(!await A(s.environmentId,e,a.result.launch?.exited,g))throw N(`the broker for ${e} on its machine did not accept a viewer after revive`)}else await b(e)}}return{status:200,body:{node_id:e,socket_path:p(e),revived:c,resumed:f,parked:m}}}i(F,"handleAttach");const C=_([{method:"POST",pattern:"/v1/nodes/:id/attach",handler:F}]);export{C as attachRoutes,E as mayAttachNode};
@@ -1 +1 @@
1
- var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as G,sep as F}from"node:path";import{ApiError as s}from"../../../api/errors.js";import{armCron as rn,parseCronSink as on,getCron as k,listCrons as A,listCronRuns as L,pokeHeldCrons as tn,setCronState as Z}from"../../../core/canvas/crons.js";import{getNode as p}from"../../../core/canvas/canvas.js";import{appSpace as an,cronFolder as sn}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn}from"../../../core/grants/mirror.js";import{callerTerminalGrant as fn}from"../../../core/grants/terminal.js";import{assertScope as H}from"../../../core/scopes.js";import{InputError as c}from"../../../core/io.js";import{nextSlotAfter as wn}from"../../../core/wake.js";import{executeCron as _n,markSelfCanceledCronRun as hn}from"../../cron-run.js";import{toCronDTO as v,toCronRunDTO as M}from"../map.js";import{appOf as P,assertCeiling as mn,canReadNode as gn}from"../router.js";import{checkCronBirth as yn,checkCronFire as bn,cronRefusalError as kn}from"../../cron/authority.js";import{endCron as vn}from"../../cron/ended.js";import{requireUsableProfile as Sn}from"./profiles.js";import{filterAsync as $n,mapAsync as On}from"../../../core/async-array.js";import{personId as Cn}from"../../../core/canvas/person.js";function E(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new c({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(E,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new c({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");async function In(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new c({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await p(n)===null)throw new c({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new c({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new c({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(In,"parseSinkSpec");const qn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function Y(e,n){if(!qn.test(e)||Number.isNaN(Date.parse(e)))throw new c({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(Y,"requireUtcIso");function Nn(e){try{wn(e,new Date)}catch(n){throw new c({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(Nn,"requireRecur");function jn(e){let n;try{n=JSON.parse(e)}catch{throw new c({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new c({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new c({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(jn,"requireEnvJson");function U(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(U,"callerProfile");function K(e,n){return n===null||e.scope==="global"||e.profile===n}t(K,"inScope");function J(e){if(e.principal.class==="person"&&e.principal.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");return e.principal.class==="person"?null:P(e.principal)}t(J,"callerApp");async function R(e,n){if(!await gn(e.principal,n.grantee))return!1;const r=e.principal,o=r.class==="broker"?await p(r.node):r.class==="cron"?{run_id:r.origin_run,isolated:r.isolated}:r.class==="forwarded"&&r.actor.kind==="app"?{run_id:r.actor.place.run,isolated:r.actor.place.isolated}:null;return!(o!==null&&(o.isolated||n.isolated)&&o.run_id!==n.origin_run_id)}t(R,"visibleTo");function Tn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(Tn,"anchorNodeOf");async function An(e,n,r,o,i,d){const u=typeof e.cwd=="string"&&e.cwd.trim()!==""?G(e.cwd):null;if(!cn()){if(u===null)throw new c({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return u}const h=o!==null&&await p(o)!==null?o:void 0,l=t(async q=>await ln(n,q,h,i,void 0,d),"reaches");if(u!==null&&await l(u))return u;const a=an(n).files;if(u!==null&&e.cwd_is_default!==!0)throw new s(400,"invalid_request",`cwd ${u} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${a}`,{cwd:u,default:a},void 0,{param:"cwd"});const f=r===null?null:(await p(r))?.cwd??null;return f!==null&&await l(f)?f:a}t(An,"resolveArmedCwd");async function Pn(e,n,r){const o=e.principal;if(o.class==="person"){if(o.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");const a=await fn();return{sub:a?.sub??Cn(),grant_id:a?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const a=await un(n);if(a===null)throw new s(401,"grant_revoked",`no active grant for ${n}`);const f=e.ceiling??o.scopes;return{sub:a.sub,grant_id:a.id,scopes:f===null?null:[...f],origin_run_id:null}}if(o.class==="cron"){const a=await bn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!a.allowed)throw new s(a.reason==="scope_missing"?403:401,a.reason==="scope_missing"?"scope_missing":"grant_revoked",a.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run,armed_from_exec:o.from_exec}}if(o.class!=="broker")throw new s(401,"unauthorized","this caller cannot arm a cron");const i=r??await p(o.node),d=i?.grant_id??o.grant,u=i?.sub??o.sub,h=d!=null?await dn(d):await pn(n,u);if(h===null)throw new s(401,"grant_revoked","the node grant is not active");const l=i?.scopes??o.scopes;return{sub:h.sub,grant_id:h.id,scopes:l===null?null:[...l],origin_run_id:i?.run_id??o.run}}t(Pn,"armPrincipal");async function En(e){const n=e.body??{},r=E(n,"name"),o=E(n,"command"),i=Y(E(n,"fire_at"),"fire_at"),d=typeof n.recur=="string"&&n.recur!==""?Nn(n.recur):null,u=typeof n.expires_at=="string"&&n.expires_at!==""?Y(n.expires_at,"expires_at"):null,h=typeof n.env_json=="string"&&n.env_json!==""?jn(n.env_json):null,l=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,a=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],l?.startsWith("node:")?"on-change":"on-failure"),f=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),q=O(n.tier,"tier",["normal","urgent","critical"],"normal"),W=a==="always"||a==="on-change";if(W&&l===null)throw new c({error:"sink_required",message:`on_output "${a}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!W&&l!==null)throw new c({error:"sink_unused",message:`on_output "${a}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=l!==null?await In(l):"{}";if(l?.startsWith("spawn:")&&(mn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const y=await yn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,from_exec:e.principal.from_exec},{kind:l.slice(6)});if(!y.allowed)throw kn(y)}const g=P(e.principal)??"app:terminal",b=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==b)throw new s(403,"permission_error","a broker may only arm a cron as its own node");const m=b===null?null:await p(b);if(m!==null){if(m.grantee!==g)throw new c({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});H(m,"crtr:schedule"),l?.startsWith("spawn:")&&H(m,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new s(400,"invalid_request","isolated must be a boolean");if(m?.isolated&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const x=m?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(x&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const N=x||n.isolated===!0,w=await Pn(e,g,m),j=nn(),S=N?sn(g,j):null;if(S!==null&&n.cwd_is_default!==!0){const y=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?G(n.cwd):S);if(y===".."||y.startsWith(`..${F}`)||y.startsWith(F))throw new s(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??await An(n,g,b,Tn(n),N,{sub:w.sub,grantId:w.grant_id,scopes:w.scopes}),_=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(_!==null&&await p(_)!==null&&(await p(_)).grantee!==g)throw new c({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(_!==null&&await p(_)===null)throw new c({error:"anchor_not_found",message:`anchor node ${_} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const B=n.cancel_on_wake===!0;if(B&&_===null)throw new c({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new c({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const T=typeof n.profile=="string"&&n.profile!==""?(await Sn(e,n.profile)).profileId:null,z=O(n.scope,"scope",["profile","global"],T!==null?"profile":"global");if(z==="profile"&&T===null)throw new c({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await rn({cron_id:j,name:r,created_by:b,grantee:g,sub:w.sub,grant_id:w.grant_id,scopes:w.scopes,origin_run_id:w.origin_run_id,armed_from_exec:w.armed_from_exec===!0,isolated:N,command:o,fire_at:i,recur:d,tz:typeof n.tz=="string"?n.tz:null,expires_at:u,anchor_node:_,cancel_on_wake:B,cwd:V,env_json:h,profile:T,scope:z,run_timeout_s:$,overlap:f,on_output:a,sink:Q,tier:q}),{status:201,body:v(await k(j),null)}}t(En,"handleArm");async function Un(e){J(e);const n=U(e),r=e.query.get("created_by");if(r==="")throw new s(400,"invalid_request","created_by must be nonempty when present");const o=await $n((n===null?await A({all:!0}):[...await A({profile:n}),...await A({global:!0})].sort((i,d)=>i.fire_at<d.fire_at?-1:i.fire_at>d.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await R(e,i));return{status:200,body:await On(o,async i=>v(i,await D(i)))}}t(Un,"handleList");async function D(e){return(await L(e.cron_id))[0]??null}t(D,"lastRunOf");async function C(e){const n=e.params.cronId,r=await k(n);if(r===null||!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=J(e);if(r!==null&&n.grantee!==r)throw new s(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");async function Jn(e){const n=await C(e),r=await L(n.cron_id);return{status:200,body:{cron:v(n,r[0]??null),runs:r.map(M)}}}t(Jn,"handleShow");async function Rn(e){const n=I(e,await C(e));await Z(n.cron_id,"paused");const r=await k(n.cron_id);return{status:200,body:v(r,await D(r))}}t(Rn,"handlePause");async function Dn(e){const n=I(e,await C(e));await Z(n.cron_id,"active");const r=await k(n.cron_id);return{status:200,body:v(r,await D(r))}}t(Dn,"handleResume");async function Wn(e){const n=I(e,await C(e));if(n.run_state==="running")throw new s(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await _n(n,{outOfBand:!0});if(r===null)throw new s(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:M(r)}}t(Wn,"handleRun");async function xn(e){const n=new Date().toISOString();return{status:200,body:{unparked:await tn(n,J(e)),at:n}}}t(xn,"handlePoke");function Bn(e,n){const r=e.principal;if(r.class==="cron")return r.kind==="exec"?{kind:"canceled",by:r.app}:r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=on(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=P(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Bn,"cancelNotice");async function zn(e){const n=e.params.cronId,r=await k(n);if(r!==null&&!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?e.principal.kind==="cron"&&o===e.principal.execution&&n===e.principal.cron:!0,d=o!==null&&i&&hn(n,o);return r!==null&&await vn(r,d?null:Bn(e,r),{preserveRunningProcess:d}),{status:204}}t(zn,"handleCancel");const de=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:En},{method:"GET",pattern:"/v1/crons",handler:Un},{method:"GET",pattern:"/v1/crons/:cronId",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:Rn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Dn},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Wn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:zn}];export{de as cronRoutes};
1
+ var tn=Object.defineProperty;var t=(e,n)=>tn(e,"name",{value:n,configurable:!0});import{randomUUID as an}from"node:crypto";import{relative as sn,resolve as H,sep as M}from"node:path";import{ApiError as a}from"../../../api/errors.js";import{armCron as ln,parseCronSink as cn,getCron as v,listCrons as A,listCronRuns as Z,pokeHeldCrons as un,setCronState as Y}from"../../../core/canvas/crons.js";import{getNode as f}from"../../../core/canvas/canvas.js";import{appSpace as dn,cronFolder as pn}from"../../../core/layout.js";import{appProcessesSandboxed as fn,cwdIsMounted as wn}from"../../../core/spaces/process.js";import{activeGrant as _n,activeGrantForApp as hn,grantFor as mn}from"../../../core/grants/mirror.js";import{callerTerminalGrant as gn}from"../../../core/grants/terminal.js";import{assertScope as K}from"../../../core/scopes.js";import{InputError as l}from"../../../core/io.js";import{nextSlotAfter as yn}from"../../../core/wake.js";import{executeCron as bn,markSelfCanceledCronRun as kn}from"../../cron-run.js";import{toCronDTO as S,toCronRunDTO as Q}from"../map.js";import{appOf as R,assertCeiling as vn,canReadNode as Sn}from"../router.js";import{checkCronBirth as $n,checkCronFire as On,cronRefusalError as Cn}from"../../cron/authority.js";import{endCron as In}from"../../cron/ended.js";import{requireUsableProfile as qn}from"./profiles.js";import{lookupProfile as Nn}from"../../../core/profiles/manifest.js";import{filterAsync as Tn,mapAsync as jn}from"../../../core/async-array.js";import{personId as Pn}from"../../../core/canvas/person.js";import{openDb as En}from"../../../core/canvas/db.js";function U(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new l({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(U,"requireString");function C(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new l({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(C,"requireEnum");async function An(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new l({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await f(n)===null)throw new l({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new l({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new l({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(An,"parseSinkSpec");const Rn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function V(e,n){if(!Rn.test(e)||Number.isNaN(Date.parse(e)))throw new l({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(V,"requireUtcIso");function Un(e){try{yn(e,new Date)}catch(n){throw new l({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(Un,"requireRecur");function Jn(e){let n;try{n=JSON.parse(e)}catch{throw new l({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new l({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new l({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(Jn,"requireEnvJson");function J(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(J,"callerProfile");function X(e,n){return n===null||e.scope==="global"||e.profile===n}t(X,"inScope");function nn(e){return e.principal.class==="person"?null:R(e.principal)}t(nn,"callerApp");function I(e){return e.principal.class==="person"?e.principal.sub??null:null}t(I,"bearerPerson");async function en(e,n){return(await(await En()).prepare("SELECT person FROM profiles WHERE id = ?").get(e))?.person===n}t(en,"profileBelongsTo");async function W(e,n){const r=I(e);if(r!==null)return n.profile!==null&&await en(n.profile,r);if(!await Sn(e.principal,n.grantee))return!1;const o=e.principal,i=o.class==="broker"?await f(o.node):o.class==="cron"?{run_id:o.origin_run,isolated:o.isolated}:o.class==="forwarded"&&o.actor.kind==="app"?{run_id:o.actor.place.run,isolated:o.actor.place.isolated}:null;return!(i!==null&&(i.isolated||n.isolated)&&i.run_id!==n.origin_run_id)}t(W,"visibleTo");function Wn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(Wn,"anchorNodeOf");async function Dn(e,n,r,o,i,u){const p=typeof e.cwd=="string"&&e.cwd.trim()!==""?H(e.cwd):null;if(!fn()){if(p===null)throw new l({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return p}const g=o!==null&&await f(o)!==null?o:void 0,c=t(async T=>await wn(n,T,g,i,void 0,u),"reaches");if(p!==null&&await c(p))return p;const s=dn(n).files;if(p!==null&&e.cwd_is_default!==!0)throw new a(400,"invalid_request",`cwd ${p} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${s}`,{cwd:p,default:s},void 0,{param:"cwd"});const w=r===null?null:(await f(r))?.cwd??null;return w!==null&&await c(w)?w:s}t(Dn,"resolveArmedCwd");async function Bn(e,n,r){const o=e.principal;if(o.class==="person"){const s=await gn();return{sub:s?.sub??o.sub??Pn(),grant_id:s?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const s=await hn(n);if(s===null)throw new a(401,"grant_revoked",`no active grant for ${n}`);const w=e.ceiling??o.scopes;return{sub:s.sub,grant_id:s.id,scopes:w===null?null:[...w],origin_run_id:null}}if(o.class==="cron"){const s=await On({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!s.allowed)throw new a(s.reason==="scope_missing"?403:401,s.reason==="scope_missing"?"scope_missing":"grant_revoked",s.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run,armed_from_exec:o.from_exec}}if(o.class!=="broker")throw new a(401,"unauthorized","this caller cannot arm a cron");const i=r??await f(o.node),u=i?.grant_id??o.grant,p=i?.sub??o.sub,g=u!=null?await _n(u):await mn(n,p);if(g===null)throw new a(401,"grant_revoked","the node grant is not active");const c=i?.scopes??o.scopes;return{sub:g.sub,grant_id:g.id,scopes:c===null?null:[...c],origin_run_id:i?.run_id??o.run}}t(Bn,"armPrincipal");async function zn(e){const n=e.body??{},r=U(n,"name"),o=U(n,"command"),i=V(U(n,"fire_at"),"fire_at"),u=typeof n.recur=="string"&&n.recur!==""?Un(n.recur):null,p=typeof n.expires_at=="string"&&n.expires_at!==""?V(n.expires_at,"expires_at"):null,g=typeof n.env_json=="string"&&n.env_json!==""?Jn(n.env_json):null,c=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,s=C(n.on_output,"on_output",["silent","on-failure","always","on-change"],c?.startsWith("node:")?"on-change":"on-failure"),w=C(n.overlap,"overlap",["skip","queue","replace"],"skip"),T=C(n.tier,"tier",["normal","urgent","critical"],"normal"),B=s==="always"||s==="on-change";if(B&&c===null)throw new l({error:"sink_required",message:`on_output "${s}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!B&&c!==null)throw new l({error:"sink_unused",message:`on_output "${s}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const rn=c!==null?await An(c):"{}",z=I(e);if(z!==null)for(const d of[c?.startsWith("node:")?c.slice(5).trim():null,typeof n.created_by=="string"?n.created_by:null,typeof n.anchor_node=="string"?n.anchor_node:null]){const L=d===null||d===""?null:await f(d);if(L!==null&&L.sub!==z)throw new a(404,"not_found","node not found")}if(c?.startsWith("spawn:")&&(vn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const d=await $n({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,from_exec:e.principal.from_exec},{kind:c.slice(6)});if(!d.allowed)throw Cn(d)}const b=R(e.principal)??"app:terminal",k=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==k)throw new a(403,"permission_error","a broker may only arm a cron as its own node");const y=k===null?null:await f(k);if(y!==null){if(y.grantee!==b)throw new l({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});K(y,"crtr:schedule"),c?.startsWith("spawn:")&&K(y,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new a(400,"invalid_request","isolated must be a boolean");if(y?.isolated&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const F=y?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(F&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const j=F||n.isolated===!0,_=await Bn(e,b,y),P=an(),$=j?pn(b,P):null;if($!==null&&n.cwd_is_default!==!0){const d=sn($,typeof n.cwd=="string"&&n.cwd.trim()!==""?H(n.cwd):$);if(d===".."||d.startsWith(`..${M}`)||d.startsWith(M))throw new a(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const on=$??await Dn(n,b,k,Wn(n),j,{sub:_.sub,grantId:_.grant_id,scopes:_.scopes}),h=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(h!==null&&await f(h)!==null&&(await f(h)).grantee!==b)throw new l({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(h!==null&&await f(h)===null)throw new l({error:"anchor_not_found",message:`anchor node ${h} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const G=n.cancel_on_wake===!0;if(G&&h===null)throw new l({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const O=n.run_timeout_s??300;if(typeof O!="number"||!Number.isInteger(O)||O<=0)throw new l({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const E=I(e),m=typeof n.profile!="string"||n.profile===""?null:E===null?(await qn(e,n.profile)).profileId:(()=>{const d=Nn(n.profile);if(d.kind!=="resolved")throw new a(404,"not_found","profile not found");return d.entry.profileId})();if(E!==null&&(m===null||!await en(m,E)))throw new a(m===null?400:404,m===null?"invalid_request":"not_found",m===null?"a person's cron names the profile it belongs to":"profile not found",void 0,void 0,{param:"profile"});const x=C(n.scope,"scope",["profile","global"],m!==null?"profile":"global");if(x==="profile"&&m===null)throw new l({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await ln({cron_id:P,name:r,created_by:k,grantee:b,sub:_.sub,grant_id:_.grant_id,scopes:_.scopes,origin_run_id:_.origin_run_id,armed_from_exec:_.armed_from_exec===!0,isolated:j,command:o,fire_at:i,recur:u,tz:typeof n.tz=="string"?n.tz:null,expires_at:p,anchor_node:h,cancel_on_wake:G,cwd:on,env_json:g,profile:m,scope:x,run_timeout_s:O,overlap:w,on_output:s,sink:rn,tier:T}),{status:201,body:S(await v(P),null)}}t(zn,"handleArm");async function Fn(e){const n=J(e),r=e.query.get("created_by");if(r==="")throw new a(400,"invalid_request","created_by must be nonempty when present");const o=await Tn((n===null?await A({all:!0}):[...await A({profile:n}),...await A({global:!0})].sort((i,u)=>i.fire_at<u.fire_at?-1:i.fire_at>u.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await W(e,i));return{status:200,body:await jn(o,async i=>S(i,await D(i)))}}t(Fn,"handleList");async function D(e){return(await Z(e.cron_id))[0]??null}t(D,"lastRunOf");async function q(e){const n=e.params.cronId,r=await v(n);if(r===null||!await W(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(!X(r,J(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(q,"requireCron");function N(e,n){const r=nn(e);if(r!==null&&n.grantee!==r)throw new a(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(N,"requireOwnCron");async function Gn(e){const n=await q(e),r=await Z(n.cron_id);return{status:200,body:{cron:S(n,r[0]??null),runs:r.map(Q)}}}t(Gn,"handleShow");async function xn(e){const n=N(e,await q(e));await Y(n.cron_id,"paused");const r=await v(n.cron_id);return{status:200,body:S(r,await D(r))}}t(xn,"handlePause");async function Ln(e){const n=N(e,await q(e));await Y(n.cron_id,"active");const r=await v(n.cron_id);return{status:200,body:S(r,await D(r))}}t(Ln,"handleResume");async function Hn(e){const n=N(e,await q(e));if(n.run_state==="running")throw new a(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await bn(n,{outOfBand:!0});if(r===null)throw new a(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:Q(r)}}t(Hn,"handleRun");async function Mn(e){if(I(e)!==null)throw new a(403,"permission_error","a person's sign-in does not poke the whole control plane's crons");const n=new Date().toISOString();return{status:200,body:{unparked:await un(n,nn(e)),at:n}}}t(Mn,"handlePoke");function Zn(e,n){const r=e.principal;if(r.class==="cron")return r.kind==="exec"?{kind:"canceled",by:r.app}:r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=cn(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=R(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Zn,"cancelNotice");async function Yn(e){const n=e.params.cronId,r=await v(n);if(r!==null&&!await W(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!X(r,J(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&N(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?e.principal.kind==="cron"&&o===e.principal.execution&&n===e.principal.cron:!0,u=o!==null&&i&&kn(n,o);return r!==null&&await In(r,u?null:Zn(e,r),{preserveRunningProcess:u}),{status:204}}t(Yn,"handleCancel");const ye=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:zn},{method:"GET",pattern:"/v1/crons",handler:Fn},{method:"GET",pattern:"/v1/crons/:cronId",handler:Gn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Ln},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Mn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Hn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Yn}];export{ye as cronRoutes};
@@ -1 +1 @@
1
- var i=Object.defineProperty;var r=(e,t)=>i(e,"name",{value:t,configurable:!0});import{toMigrateStateDTO as u}from"../map.js";import{migrateLooseKeys as m}from"../../../core/layout-migrate/loose-keys.js";import{boundDaemonControl as s}from"../../control.js";import{ownerOnly as c}from"../router.js";function l(){const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot hand over"};const{graceMs:t}=e.requestHandover();return{status:202,body:{accepted:!0,predecessor_pid:process.pid,predecessor_epoch:e.epoch,grace_ms:t}}}r(l,"handleRestart");async function y(e){if(e.body!==void 0)throw{status:400,code:"invalid_request",message:"admission request must not include a body"};const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot admit normal work"};return{status:200,body:await t.admitStartup()}}r(y,"handleAdmit");async function h(e){const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot run the canvas import"};const o=e.body;if(o!==void 0&&(typeof o!="object"||o===null||Array.isArray(o)))throw{status:400,code:"invalid_request",message:"migration request body must be an object"};const n=o??{};if(n.dry_run!==void 0&&typeof n.dry_run!="boolean")throw{status:400,code:"invalid_request",message:"dry_run must be a boolean"};const d=await m(n.dry_run===!0),a=await t.repairMigrations({dryRun:n.dry_run===!0});return{status:200,body:{...u(a.result,{resumed:a.resumed,resumeError:a.resumeError}),loose_keys:d}}}r(h,"handleMigrate");const g=c([{method:"POST",pattern:"/v1/daemon/restart",handler:r(e=>l(),"handler")},{method:"POST",pattern:"/v1/daemon/migrate",handler:h},{method:"POST",pattern:"/v1/daemon/admit",handler:y}]);export{g as daemonRoutes};
1
+ var i=Object.defineProperty;var a=(t,e)=>i(t,"name",{value:e,configurable:!0});import{drainState as u,endDrain as m,isDrained as c,settleDrain as l,startDrain as y}from"../../drain.js";import{toMigrateStateDTO as b}from"../map.js";import{migrateLooseKeys as h}from"../../../core/layout-migrate/loose-keys.js";import{boundDaemonControl as s}from"../../control.js";import{ownerOnly as f}from"../router.js";function p(){const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot hand over"};const{graceMs:e}=t.requestHandover();return{status:202,body:{accepted:!0,predecessor_pid:process.pid,predecessor_epoch:t.epoch,grace_ms:e}}}a(p,"handleRestart");async function _(t){if(t.body!==void 0)throw{status:400,code:"invalid_request",message:"admission request must not include a body"};const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot admit normal work"};return{status:200,body:await e.admitStartup()}}a(_,"handleAdmit");async function v(t){const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot run the canvas import"};const n=t.body;if(n!==void 0&&(typeof n!="object"||n===null||Array.isArray(n)))throw{status:400,code:"invalid_request",message:"migration request body must be an object"};const o=n??{};if(o.dry_run!==void 0&&typeof o.dry_run!="boolean")throw{status:400,code:"invalid_request",message:"dry_run must be a boolean"};const d=await h(o.dry_run===!0),r=await e.repairMigrations({dryRun:o.dry_run===!0});return{status:200,body:{...b(r.result,{resumed:r.resumed,resumeError:r.resumeError}),loose_keys:d}}}a(v,"handleMigrate");async function w(t){const e=t.body??{};if(typeof e!="object"||e===null||Array.isArray(e))throw{status:400,code:"invalid_request",message:"drain request body must be an object"};const n=e.settle_limit_ms;if(n!==void 0&&(typeof n!="number"||!Number.isFinite(n)||n<0))throw{status:400,code:"invalid_request",message:"settle_limit_ms must be a non-negative number"};return await y(),{status:200,body:n===void 0?await u():await l({limitMs:n})}}a(w,"handleDrain");async function g(){const t=await c();return{status:200,body:{...await m(),was_drained:t}}}a(g,"handleEndDrain");const O=f([{method:"POST",pattern:"/v1/daemon/restart",handler:a(t=>p(),"handler")},{method:"POST",pattern:"/v1/daemon/migrate",handler:v},{method:"POST",pattern:"/v1/daemon/admit",handler:_},{method:"POST",pattern:"/v1/daemon/drain",handler:w},{method:"DELETE",pattern:"/v1/daemon/drain",handler:g}]);export{O as daemonRoutes};
@@ -1 +1 @@
1
- var re=Object.defineProperty;var u=(n,e)=>re(n,"name",{value:e,configurable:!0});import{ApiError as k}from"../../../api/errors.js";import{DOC_KINDS as U,DOC_UNSET_FIELDS as V}from"../../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as K}from"../../../api/dto/objects.js";import{openDb as ae,withCanvasWrite as A}from"../../../core/canvas/db.js";import{CrtrError as se}from"../../../core/errors.js";import{emitEvent as ce}from"../../../core/events/emit.js";import{canChange as de,spaceOf as ue}from"../../../core/graph/access.js";import{get as j}from"../../../core/graph/bodies.js";import{deleteDocument as fe,documentRecord as L,editDocument as me,heads as W,history as pe,linksIn as G,moveDocument as le,readBase as we,setDocumentRecord as he,withCurrentNames as ye,writeDocument as ge}from"../../../core/graph/documents.js";import{resolveName as X}from"../../../core/graph/names.js";import{getById as M}from"../../../core/graph/objects.js";import{noteRepoDocumentsChanged as ve}from"../../../core/graph/repo-sync/index.js";import{checkoutsForCwd as _e}from"../../../core/graph/repo-sync/repos.js";import{lintRules as be,rulesOf as ke}from"../../../core/graph/rules.js";import{unifiedDiff as Ae}from"../../../core/graph/diff.js";import{ownerForGrantee as Ee}from"../../../core/graph/objects.js";import{stubOf as $e}from"../../../core/peer/stubs.js";import{forwarding as Oe}from"../../peer/forward.js";import{resolvedLocally as je}from"./objects.js";import{ownerFromFlag as q,readerFor as E,resolveRef as x,warmReaderRepos as $}from"../reader.js";import{appCaller as z,asApiError as Re,requireOwnNamespace as De}from"./schemas.js";import{filterAsync as Ce,flatMapAsync as Ne}from"../../../core/async-array.js";function g(n,e,t,o,a={}){return new k(n,e,t,{...a,error:e,message:t,next:o})}u(g,"refuse");const b=u((n,e)=>new k(400,"invalid_request",n,e),"invalid");function C(n){const e=n.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw b("the request body must be a JSON object");return e}u(C,"bodyOf");const N=u(async n=>async e=>await _e(n,e),"reposFnFor");function S(n,e){if(n instanceof se){const t=n.details;if(n.code==="read_first")throw g(409,"read_first",n.message,`Read it first with \`crtr canvas read ${e?.id??"<ref>"}\`, then rerun the edit.`,t??{});if(n.code==="conflict")throw new k(409,"name_taken",n.message,t);if(n.code==="forbidden")throw typeof t?.next=="string"?g(403,"forbidden",n.message,t.next,t):new k(403,"forbidden",n.message,t);if(n.code==="scope_missing")throw g(403,"scope_missing",n.message,`Launch the run with \`${String(t?.scope)}\` in its scopes list.`,t??{});if(t?.rule==="repo-public-only")throw g(422,"repo_documents_are_public",n.message,'Own it as `user` and add a rule with `if: {"repo": "<handle>"}` so it loads only in that repo.')}throw Re(n)}u(S,"mapGraphError");async function B(n,e,t){const o=await x(n,e,decodeURIComponent(t));if("remote"in o)throw g(404,"not_found",Z(o.remote),"Check the name with `crtr canvas search`.");if("deleted"in o)throw new k(404,"not_found",`${o.deleted.name} is deleted`,{id:o.deleted.id,deleted:!0});if(o.type!=="document")throw b(`${t} is a ${o.type}, not a document`);return o}u(B,"refDocument");async function Y(n){const{stub:e}=await je(decodeURIComponent(n.params.ref),n);if(e===null)return;const t=await $e(await ae(),e);if(t.object.type!=="document")return;const o=await Oe.current.label(t.home_machine);throw g(404,"not_here",`this document lives on runtime ${o.name}; edit it (or read its history) from a node there`,`Run the command from a node on ${o.name}.`,{id:t.object.id,home_machine:o.id})}u(Y,"refuseRemoteDocument");function Z(n){return`no object named ${n} on this runtime; it may be on another runtime, or the name may be misspelled`}u(Z,"remoteMessage");async function Se(n,e){const t=e.owner===null?null:await M(n,e.owner);return t?t.type==="node"?t.id:t.name:""}u(Se,"ownerHandle");async function R(n,e){const t=await Se(n,e);return t?`${t}/${e.name}`:e.name}u(R,"canonicalName");async function Te(n,e){const t=e.owner===null?null:await M(n,e.owner),o=t?{id:t.id,type:t.type,handle:t.type==="node"?t.id:t.name}:null;return{id:e.id,type:e.type,kind:e.kind,owner:o,privacy:e.privacy,name:e.name,preview:e.preview??"",created_at:e.created_at,deleted_at:e.deleted_at,...e.type==="document"?{schema_type:e.schema_type??null}:{}}}u(Te,"objectDTO");async function Ie(n,e){const t=e.owner===null?null:await M(n,e.owner);return t?.type==="repo"?t:null}u(Ie,"repoOwner");async function T(n,e,t,o){const a=[...new Map(await Ne(t,async f=>{const i=await Ie(n,f);return i?[[i.id,i]]:[]})).values()],c=e.class==="node"?e.cwd:o,s=e.class==="node"?[...e.checkouts,...e.projectCheckouts].map(f=>f.root):[];return{repos:a,dirs:[...new Set([...c!==void 0?[c]:[],...s])]}}u(T,"repoChange");async function I(n){for(const e of n.repos)try{await ve(e,n.dirs)}catch(t){ce({level:"warn",event:"repo_sync.failed",outcome:"failed",error:t,fields:{repo:e.name,step:"export",reason:t instanceof Error?t.message:String(t)}})}}u(I,"syncChanged");const J=/^[A-Za-z0-9_-]+(\/[A-Za-z0-9_-]+)*$/;function Fe(n,e){if(n===void 0)return;if(!Array.isArray(n)){e.push("`rules` must be a list");return}const t=n.map(a=>{if(typeof a!="object"||a===null||Array.isArray(a))return a;const{match_frontmatter:c,...s}=a;return"matchFrontmatter"in s?{...s,match_frontmatter:s.matchFrontmatter}:c===void 0?s:{...s,matchFrontmatter:c}}),o=be(t);if(o){e.push(o.replaceAll("matchFrontmatter","match_frontmatter"));return}return t}u(Fe,"rulesFromDTO");function Q(n,e){const t={},o=u(i=>{const d=n[i];if(d!==void 0){if(typeof d!="string"){e.push(`\`${i}\` must be a string`);return}return d}},"str"),a=o("preview");a!==void 0&&(a.trim()===""||/[\r\n]/.test(a)?e.push("`preview` must be one non-empty line"):t.preview=a);const c=o("summary");c!==void 0&&(t.summary=c);const s=o("why_it_exists");if(s!==void 0&&(t.whyItExists=s),n.privacy!==void 0&&(K.includes(n.privacy)?t.privacy=n.privacy:e.push(`\`privacy\` must be one of ${K.join(", ")}`)),n.unlisted!==void 0&&(typeof n.unlisted!="boolean"?e.push("`unlisted` must be a boolean"):t.unlisted=n.unlisted),n.lint_ignore!==void 0){const i=n.lint_ignore;!Array.isArray(i)||i.some(d=>typeof d!="string")?e.push("`lint_ignore` must be a list of strings"):t.lintIgnore=i}if(n.extensions!==void 0){const i=n.extensions;typeof i!="object"||i===null||Array.isArray(i)||Object.values(i).some(d=>typeof d!="object"||d===null||Array.isArray(d))?e.push("`extensions` must map each plugin to an object of fields"):t.extensions=i}const f=Fe(n.rules,e);return f!==void 0&&(t.rules=f),t}u(Q,"fieldsFrom");function ee(n){if(n.length>0)throw b(n.join("; "),{errors:n})}u(ee,"failOn");async function te(n,e,t,o){const a=await Ce(G(t),async c=>"remote"in await X(n,e,c));return{...o.length>0?{unresolved_links:o}:{},...a.length>0?{remote_links:a}:{}}}u(te,"linkReport");async function Le(n){const e=C(n),t=[],o=e.name;(typeof o!="string"||!J.test(o))&&t.push("`name` must be segments of letters, digits, `_` or `-` joined by `/`"),typeof e.body!="string"&&t.push("`body` must be a string"),typeof e.preview!="string"&&t.push("`preview` is required");const a=e.kind??"knowledge";U.includes(a)||t.push(a==="report"?"reports are written only by `crtr push`":`\`kind\` must be one of ${U.join(", ")}`);const c=Q(e,t);e.cwd!==void 0&&typeof e.cwd!="string"&&t.push("`cwd` must be a string");const s=e.schema_type;s!==void 0&&typeof s!="string"&&t.push("`schema_type` must be a string");const f=e.relations;f!==void 0&&(!Array.isArray(f)||f.some(r=>typeof r!="object"||r===null||typeof r.relation!="string"||typeof r.subject!="string"))&&t.push("`relations` must be a list of {relation, subject} strings"),ee(t);const i=e;await $(n.principal,i.cwd);const{dto:d,change:h}=await A(async r=>{const m=await E(r,n);i.schema_type!==void 0?await De(r,z(n).app,i.schema_type):i.relations!==void 0&&z(n);const p=i.owner==="user"&&m.class==="app"&&m.sub!==void 0?await Ee(r,m.sub.startsWith("user:")?m.sub:`user:${m.sub}`):await q(r,m,i.owner,i.cwd);try{const{object:w,unresolved:_}=await ge(r,m,{...c,owner:p,name:i.name,kind:a,preview:c.preview,privacy:c.privacy??"public",...i.schema_type!==void 0?{schemaType:i.schema_type}:{},...i.relations!==void 0?{relations:i.relations}:{}},i.body,await N(r));return{dto:{id:w.id,name:await R(r,w),...await te(r,m,i.body,_)},change:await T(r,m,[w],i.cwd)}}catch(w){S(w)}});return await I(h),d}u(Le,"write");function We(n,e){let t=n;e.forEach(({old:o,new:a},c)=>{const s=o===""?-1:t.indexOf(o);if(s<0)throw g(422,"replace_no_match",`replace[${c}]: old text not found in the revision you read`,"Copy the old text exactly from your last read and rerun.",{index:c});if(t.indexOf(o,s+1)>=0)throw g(422,"replace_ambiguous",`replace[${c}]: old text occurs more than once`,"Include more surrounding text so the old text matches once, and rerun.",{index:c});t=t.slice(0,s)+a+t.slice(s+o.length)})}u(We,"checkReplacements");async function Me(n){const e=C(n),t=[],o=e.body!==void 0,a=e.replace!==void 0;o&&a&&t.push("give `body` or `replace`, not both"),o&&typeof e.body!="string"&&t.push("`body` must be a string");const c=e.replace;a&&(!Array.isArray(c)||c.length===0||c.some(r=>typeof r!="object"||r===null||typeof r.old!="string"||typeof r.new!="string"))&&t.push("`replace` must be a non-empty list of {old, new} strings"),(typeof e.rationale!="string"||e.rationale.trim()==="")&&t.push("`rationale` is required"),e.verbatim!==void 0&&typeof e.verbatim!="boolean"&&t.push("`verbatim` must be a boolean"),e.base!==void 0&&(typeof e.base!="string"||e.base==="")&&t.push("`base` must be a non-empty heads key"),e.base!==void 0&&!o&&!a&&t.push("`base` applies only to a `body` or `replace` edit");const s=Q(e,t),f=e.unset??[];if(!Array.isArray(f)||f.some(r=>!V.includes(r)))t.push(`\`unset\` takes only ${V.join(", ")}`);else for(const r of f)r==="summary"&&(s.summary=null),r==="why_it_exists"&&(s.whyItExists=null),r==="unlisted"&&(s.unlisted=!1),r==="lint_ignore"&&(s.lintIgnore=null),r==="extensions"&&(s.extensions=null),r==="rules"&&(s.rules=[]);t.length===0&&!o&&!a&&Object.keys(s).length===0&&t.push("nothing to change: give `body`, `replace`, or a field"),ee(t);const i=e;await Y(n),await $(n.principal);const{dto:d,synced:h}=await A(async r=>{const m=await E(r,n),p=await B(r,m,n.params.ref),w=await R(r,p),_=await T(r,m,[p]);try{if(!o&&!a)return await he(r,m,p,s,i.rationale),{dto:{id:p.id,name:w,revision:(await L(r,p)).revision},synced:_};const v=i.base??await we(r,m,p);if(a&&v!==null){const P=await L(r,p),H=await W(r,p);P.source_path===null&&P.heads_key===v&&H.length===1&&We(await ye(r,p,j(H[0].body_hash)),i.replace)}const D=i.verbatim===!0,l=o?{body:i.body,verbatim:D}:{replace:i.replace,verbatim:D},y=await me(r,m,p,l,i.rationale,v,s,await N(r));if("stale"in y)throw g(409,"stale_revision",`${w} changed after your read; it is now at revision ${y.stale.revision}`,`Read it again with \`crtr canvas read ${w}\`, then redo your change on the current text.`,{stale:y.stale});const F=j((await W(r,p))[0].body_hash);return{dto:{id:p.id,name:w,revision:y.revision,...await te(r,m,F,y.unresolved)},synced:_}}catch(v){S(v,p)}});return await I(h),d}u(Me,"edit");async function qe(n){const e=C(n);if(e.new_name===void 0&&e.owner===void 0)throw b("give a new name, an owner, or both");if(e.new_name!==void 0&&(typeof e.new_name!="string"||!J.test(e.new_name)))throw b("`new_name` must be segments of letters, digits, `_` or `-` joined by `/`");await $(n.principal,e.cwd);const{dto:t,change:o}=await A(async a=>{const c=await E(a,n),s=await B(a,c,n.params.ref),f=e.owner===void 0?void 0:await q(a,c,e.owner,e.cwd);try{const i=await le(a,c,s,{...e.new_name!==void 0?{name:e.new_name}:{},...f?{owner:f}:{}},await N(a));return{dto:{id:i.id,name:await R(a,i)},change:await T(a,c,[s,i],e.cwd)}}catch(i){S(i,s)}});return await I(o),t}u(qe,"move");async function Be(n){await $(n.principal);const{dto:e,change:t}=await A(async o=>{const a=await E(o,n),c=await B(o,a,n.params.ref),s=await R(o,c),f=await T(o,a,[c]);try{await fe(o,a,c,await N(o))}catch(i){S(i,c)}return{dto:{id:c.id,name:s},change:f}});return await I(t),e}u(Be,"remove");async function Pe(n){const e=n.query.get("diff"),t=e===null?void 0:Number(e);if(t!==void 0&&!(Number.isInteger(t)&&t>0))throw b("`diff` must be a revision number");return await Y(n),await $(n.principal),await A(async o=>{const a=await E(o,n),c=await x(o,a,decodeURIComponent(n.params.ref));if("remote"in c)throw g(404,"not_found",Z(c.remote),"Check the name with `crtr canvas search`.");const s="deleted"in c?c.deleted:c;if(s.type!=="document")throw b(`${s.name} is a ${s.type}, not a document`);const f=await pe(o,a,s),i={object:await Te(o,s),revisions:f.map(d=>({rev_id:d.rev_id,number:d.number,author_id:d.author,rationale:d.rationale,verbatim:d.verbatim,created_at:d.created_at,parents:d.parents,acting_app:d.acting_app,acting_for:d.acting_for}))};if(t!==void 0){const d=f.find(r=>r.number===t);if(!d)throw new k(404,"not_found",`${s.name} has no revision ${t}`);const h=d.parents[0]===void 0?void 0:f.find(r=>r.rev_id===d.parents[0]);i.diff=Ae(h?j(h.body_hash):"",j(d.body_hash))}return i})}u(Pe,"revisionHistory");const ne=1e3,oe=1e3,ie=3e3,He=/^When\b.+\bbecause\b.+/,Ue=u(n=>{const e=n.trim();return e===""?0:e.split(/\s+/).length},"words");function Ve(n,e){if(n.some(o=>o.on==="boot"&&o.deliver==="content")&&e>ne)return`body is ${e} words, over the ${ne}-word cap for a boot rule at content (the whole body enters every node's context). Keep the core here and move separate subjects into [[linked]] documents.`;const t=[...new Set(n.filter(o=>o.on!=="boot"&&o.deliver==="content").map(o=>o.on))];return t.length>0&&e>oe?`body is ${e} words, over the ${oe}-word cap for a content rule on ${t.join("/")}. Keep the core here and move separate subjects into [[linked]] documents.`:n.some(o=>o.on==="boot"&&o.deliver==="preview")&&e>ie?`body is ${e} words, over the ${ie}-word cap for a boot rule at preview. Keep the core here and move separate subjects into [[linked]] documents.`:null}u(Ve,"lengthFinding");async function Ke(n){const e=C(n);return await $(n.principal,e.cwd),await A(async t=>{const o=await E(t,n),a=e.owner===void 0?null:await q(t,o,e.owner,e.cwd),c=await t.prepare("SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND home_machine IS NULL AND kind != 'report' ORDER BY name").all(),s=[],f=new Map;for(const i of c){if(a&&i.owner!==a.id||!await de(t,o,i))continue;const d=await L(t,i),h=new Set(d.lint_ignore??[]),r=await R(t,i),m=u((l,y,F)=>{h.has(l)||s.push({name:r,severity:y,rule:l,message:F})},"add"),p=await ke(t,i),w=(await W(t,i))[0],_=w?j(w.body_hash):"",v=Ve(p,Ue(_));v&&m("length","error",v),p.some(l=>l.deliver==="preview")&&!He.test(i.preview??"")&&m("preview-shape","error",`a rule delivers this document at preview, so the preview must read "When <situation in the reader's work>, read this because <what the reader gains>"`);const D=new Set((await t.prepare("SELECT written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.id)).map(l=>l.written_as));for(const l of G(_)){if(D.has(l))continue;const y=await X(t,o,l);"remote"in y||"object"in y||"deleted"in y||m("links","warning",`[[${l}]] names no object in your view`)}if(p.some(l=>l.on==="workspace-open"&&l.deliver==="content")){const l=await ue(t,i);l.type==="repo"&&!h.has("workspace-open")&&f.set(l.name,[...f.get(l.name)??[],r])}}for(const[i,d]of f)if(!(d.length<2))for(const h of d)s.push({name:h,severity:"warning",rule:"workspace-open",message:`repo ${i} has ${d.length} documents delivered at content on workspace-open (${d.join(", ")}); keep one and link the rest from it`});return{findings:s}})}u(Ke,"lint");const O=u((n,e=200)=>async t=>({status:e,body:await n(t)}),"ok"),wt=[{method:"POST",pattern:"/v1/docs",handler:O(Le,201)},{method:"PATCH",pattern:"/v1/docs/:ref",handler:O(Me)},{method:"POST",pattern:"/v1/docs/:ref/move",handler:O(qe)},{method:"DELETE",pattern:"/v1/docs/:ref",handler:O(Be)},{method:"GET",pattern:"/v1/docs/:ref/history",handler:O(Pe)},{method:"POST",pattern:"/v1/docs/lint",handler:O(Ke)}];export{R as canonicalName,wt as docRoutes,T as repoChange,I as syncChanged};
1
+ var re=Object.defineProperty;var u=(n,e)=>re(n,"name",{value:e,configurable:!0});import{ApiError as A}from"../../../api/errors.js";import{DOC_KINDS as H,DOC_UNSET_FIELDS as U}from"../../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as V}from"../../../api/dto/objects.js";import{openDb as se,withCanvasWrite as E}from"../../../core/canvas/db.js";import{CrtrError as ce}from"../../../core/errors.js";import{emitEvent as de}from"../../../core/events/emit.js";import{canChange as ue,spaceOf as fe}from"../../../core/graph/access.js";import{get as x,getMany as K}from"../../../core/graph/bodies.js";import{deleteDocument as me,documentRecord as M,editDocument as pe,heads as W,history as le,linksIn as G,moveDocument as he,readBase as we,setDocumentRecord as ye,withCurrentNames as ge,writeDocument as ve}from"../../../core/graph/documents.js";import{resolveName as X}from"../../../core/graph/names.js";import{getById as B}from"../../../core/graph/objects.js";import{noteRepoDocumentsChanged as _e}from"../../../core/graph/repo-sync/index.js";import{checkoutsForCwd as be}from"../../../core/graph/repo-sync/repos.js";import{lintRules as ke,rulesOf as Ae}from"../../../core/graph/rules.js";import{unifiedDiff as Ee}from"../../../core/graph/diff.js";import{ownerForGrantee as $e}from"../../../core/graph/objects.js";import{stubOf as Oe}from"../../../core/peer/stubs.js";import{forwarding as je}from"../../peer/forward.js";import{resolvedLocally as Re}from"./objects.js";import{ownerFromFlag as q,readerFor as $,resolveRef as z,warmReaderRepos as O}from"../reader.js";import{appCaller as Y,asApiError as De,requireOwnNamespace as Ce}from"./schemas.js";import{filterAsync as Ne,flatMapAsync as Se}from"../../../core/async-array.js";function y(n,e,t,o,r={}){return new A(n,e,t,{...r,error:e,message:t,next:o})}u(y,"refuse");const k=u((n,e)=>new A(400,"invalid_request",n,e),"invalid");function S(n){const e=n.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw k("the request body must be a JSON object");return e}u(S,"bodyOf");const T=u(async n=>async e=>await be(n,e),"reposFnFor");function I(n,e){if(n instanceof ce){const t=n.details;if(n.code==="read_first")throw y(409,"read_first",n.message,`Read it first with \`crtr canvas read ${e?.id??"<ref>"}\`, then rerun the edit.`,t??{});if(n.code==="conflict")throw new A(409,"name_taken",n.message,t);if(n.code==="forbidden")throw typeof t?.next=="string"?y(403,"forbidden",n.message,t.next,t):new A(403,"forbidden",n.message,t);if(n.code==="scope_missing")throw y(403,"scope_missing",n.message,`Launch the run with \`${String(t?.scope)}\` in its scopes list.`,t??{});if(t?.rule==="repo-public-only")throw y(422,"repo_documents_are_public",n.message,'Own it as `user` and add a rule with `if: {"repo": "<handle>"}` so it loads only in that repo.')}throw De(n)}u(I,"mapGraphError");async function P(n,e,t){const o=await z(n,e,decodeURIComponent(t));if("remote"in o)throw y(404,"not_found",J(o.remote),"Check the name with `crtr canvas search`.");if("deleted"in o)throw new A(404,"not_found",`${o.deleted.name} is deleted`,{id:o.deleted.id,deleted:!0});if(o.type!=="document")throw k(`${t} is a ${o.type}, not a document`);return o}u(P,"refDocument");async function Z(n){const{stub:e}=await Re(decodeURIComponent(n.params.ref),n);if(e===null)return;const t=await Oe(await se(),e);if(t.object.type!=="document")return;const o=await je.current.label(t.home_machine);throw y(404,"not_here",`this document lives on runtime ${o.name}; edit it (or read its history) from a node there`,`Run the command from a node on ${o.name}.`,{id:t.object.id,home_machine:o.id})}u(Z,"refuseRemoteDocument");function J(n){return`no object named ${n} on this runtime; it may be on another runtime, or the name may be misspelled`}u(J,"remoteMessage");async function Te(n,e){const t=e.owner===null?null:await B(n,e.owner);return t?t.type==="node"?t.id:t.name:""}u(Te,"ownerHandle");async function D(n,e){const t=await Te(n,e);return t?`${t}/${e.name}`:e.name}u(D,"canonicalName");async function Ie(n,e){const t=e.owner===null?null:await B(n,e.owner),o=t?{id:t.id,type:t.type,handle:t.type==="node"?t.id:t.name}:null;return{id:e.id,type:e.type,kind:e.kind,owner:o,privacy:e.privacy,name:e.name,preview:e.preview??"",created_at:e.created_at,deleted_at:e.deleted_at,...e.type==="document"?{schema_type:e.schema_type??null}:{}}}u(Ie,"objectDTO");async function Fe(n,e){const t=e.owner===null?null:await B(n,e.owner);return t?.type==="repo"?t:null}u(Fe,"repoOwner");async function F(n,e,t,o){const r=[...new Map(await Se(t,async f=>{const a=await Fe(n,f);return a?[[a.id,a]]:[]})).values()],c=e.class==="node"?e.cwd:o,s=e.class==="node"?[...e.checkouts,...e.projectCheckouts].map(f=>f.root):[];return{repos:r,dirs:[...new Set([...c!==void 0?[c]:[],...s])]}}u(F,"repoChange");async function L(n){for(const e of n.repos)try{await _e(e,n.dirs)}catch(t){de({level:"warn",event:"repo_sync.failed",outcome:"failed",error:t,fields:{repo:e.name,step:"export",reason:t instanceof Error?t.message:String(t)}})}}u(L,"syncChanged");const Q=/^[A-Za-z0-9_-]+(\/[A-Za-z0-9_-]+)*$/;function Le(n,e){if(n===void 0)return;if(!Array.isArray(n)){e.push("`rules` must be a list");return}const t=n.map(r=>{if(typeof r!="object"||r===null||Array.isArray(r))return r;const{match_frontmatter:c,...s}=r;return"matchFrontmatter"in s?{...s,match_frontmatter:s.matchFrontmatter}:c===void 0?s:{...s,matchFrontmatter:c}}),o=ke(t);if(o){e.push(o.replaceAll("matchFrontmatter","match_frontmatter"));return}return t}u(Le,"rulesFromDTO");function ee(n,e){const t={},o=u(a=>{const d=n[a];if(d!==void 0){if(typeof d!="string"){e.push(`\`${a}\` must be a string`);return}return d}},"str"),r=o("preview");r!==void 0&&(r.trim()===""||/[\r\n]/.test(r)?e.push("`preview` must be one non-empty line"):t.preview=r);const c=o("summary");c!==void 0&&(t.summary=c);const s=o("why_it_exists");if(s!==void 0&&(t.whyItExists=s),n.privacy!==void 0&&(V.includes(n.privacy)?t.privacy=n.privacy:e.push(`\`privacy\` must be one of ${V.join(", ")}`)),n.unlisted!==void 0&&(typeof n.unlisted!="boolean"?e.push("`unlisted` must be a boolean"):t.unlisted=n.unlisted),n.lint_ignore!==void 0){const a=n.lint_ignore;!Array.isArray(a)||a.some(d=>typeof d!="string")?e.push("`lint_ignore` must be a list of strings"):t.lintIgnore=a}if(n.extensions!==void 0){const a=n.extensions;typeof a!="object"||a===null||Array.isArray(a)||Object.values(a).some(d=>typeof d!="object"||d===null||Array.isArray(d))?e.push("`extensions` must map each plugin to an object of fields"):t.extensions=a}const f=Le(n.rules,e);return f!==void 0&&(t.rules=f),t}u(ee,"fieldsFrom");function te(n){if(n.length>0)throw k(n.join("; "),{errors:n})}u(te,"failOn");async function ne(n,e,t,o){const r=await Ne(G(t),async c=>"remote"in await X(n,e,c));return{...o.length>0?{unresolved_links:o}:{},...r.length>0?{remote_links:r}:{}}}u(ne,"linkReport");async function Me(n){const e=S(n),t=[],o=e.name;(typeof o!="string"||!Q.test(o))&&t.push("`name` must be segments of letters, digits, `_` or `-` joined by `/`"),typeof e.body!="string"&&t.push("`body` must be a string"),typeof e.preview!="string"&&t.push("`preview` is required");const r=e.kind??"knowledge";H.includes(r)||t.push(r==="report"?"reports are written only by `crtr push`":`\`kind\` must be one of ${H.join(", ")}`);const c=ee(e,t);e.cwd!==void 0&&typeof e.cwd!="string"&&t.push("`cwd` must be a string");const s=e.schema_type;s!==void 0&&typeof s!="string"&&t.push("`schema_type` must be a string");const f=e.relations;f!==void 0&&(!Array.isArray(f)||f.some(i=>typeof i!="object"||i===null||typeof i.relation!="string"||typeof i.subject!="string"))&&t.push("`relations` must be a list of {relation, subject} strings"),te(t);const a=e;await O(n.principal,a.cwd);const{dto:d,change:m}=await E(async i=>{const p=await $(i,n);a.schema_type!==void 0?await Ce(i,Y(n).app,a.schema_type):a.relations!==void 0&&Y(n);const h=a.owner==="user"&&p.class==="app"&&p.sub!==void 0?await $e(i,p.sub.startsWith("user:")?p.sub:`user:${p.sub}`):await q(i,p,a.owner,a.cwd);try{const{object:w,unresolved:v}=await ve(i,p,{...c,owner:h,name:a.name,kind:r,preview:c.preview,privacy:c.privacy??"public",...a.schema_type!==void 0?{schemaType:a.schema_type}:{},...a.relations!==void 0?{relations:a.relations}:{}},a.body,await T(i));return{dto:{id:w.id,name:await D(i,w),...await ne(i,p,a.body,v)},change:await F(i,p,[w],a.cwd)}}catch(w){I(w)}});return await L(m),d}u(Me,"write");function We(n,e){let t=n;e.forEach(({old:o,new:r},c)=>{const s=o===""?-1:t.indexOf(o);if(s<0)throw y(422,"replace_no_match",`replace[${c}]: old text not found in the revision you read`,"Copy the old text exactly from your last read and rerun.",{index:c});if(t.indexOf(o,s+1)>=0)throw y(422,"replace_ambiguous",`replace[${c}]: old text occurs more than once`,"Include more surrounding text so the old text matches once, and rerun.",{index:c});t=t.slice(0,s)+r+t.slice(s+o.length)})}u(We,"checkReplacements");async function Be(n){const e=S(n),t=[],o=e.body!==void 0,r=e.replace!==void 0;o&&r&&t.push("give `body` or `replace`, not both"),o&&typeof e.body!="string"&&t.push("`body` must be a string");const c=e.replace;r&&(!Array.isArray(c)||c.length===0||c.some(i=>typeof i!="object"||i===null||typeof i.old!="string"||typeof i.new!="string"))&&t.push("`replace` must be a non-empty list of {old, new} strings"),(typeof e.rationale!="string"||e.rationale.trim()==="")&&t.push("`rationale` is required"),e.verbatim!==void 0&&typeof e.verbatim!="boolean"&&t.push("`verbatim` must be a boolean"),e.base!==void 0&&(typeof e.base!="string"||e.base==="")&&t.push("`base` must be a non-empty heads key"),e.base!==void 0&&!o&&!r&&t.push("`base` applies only to a `body` or `replace` edit");const s=ee(e,t),f=e.unset??[];if(!Array.isArray(f)||f.some(i=>!U.includes(i)))t.push(`\`unset\` takes only ${U.join(", ")}`);else for(const i of f)i==="summary"&&(s.summary=null),i==="why_it_exists"&&(s.whyItExists=null),i==="unlisted"&&(s.unlisted=!1),i==="lint_ignore"&&(s.lintIgnore=null),i==="extensions"&&(s.extensions=null),i==="rules"&&(s.rules=[]);t.length===0&&!o&&!r&&Object.keys(s).length===0&&t.push("nothing to change: give `body`, `replace`, or a field"),te(t);const a=e;await Z(n),await O(n.principal);const{dto:d,synced:m}=await E(async i=>{const p=await $(i,n),h=await P(i,p,n.params.ref),w=await D(i,h),v=await F(i,p,[h]);try{if(!o&&!r)return await ye(i,p,h,s,a.rationale),{dto:{id:h.id,name:w,revision:(await M(i,h)).revision},synced:v};const g=a.base??await we(i,p,h);if(r&&g!==null){const _=await M(i,h),N=await W(i,h);_.source_path===null&&_.heads_key===g&&N.length===1&&We(await ge(i,h,await x(i,N[0].body_hash)),a.replace)}const R=a.verbatim===!0,C=o?{body:a.body,verbatim:R}:{replace:a.replace,verbatim:R},b=await pe(i,p,h,C,a.rationale,g,s,await T(i));if("stale"in b)throw y(409,"stale_revision",`${w} changed after your read; it is now at revision ${b.stale.revision}`,`Read it again with \`crtr canvas read ${w}\`, then redo your change on the current text.`,{stale:b.stale});const l=await x(i,(await W(i,h))[0].body_hash);return{dto:{id:h.id,name:w,revision:b.revision,...await ne(i,p,l,b.unresolved)},synced:v}}catch(g){I(g,h)}});return await L(m),d}u(Be,"edit");async function qe(n){const e=S(n);if(e.new_name===void 0&&e.owner===void 0)throw k("give a new name, an owner, or both");if(e.new_name!==void 0&&(typeof e.new_name!="string"||!Q.test(e.new_name)))throw k("`new_name` must be segments of letters, digits, `_` or `-` joined by `/`");await O(n.principal,e.cwd);const{dto:t,change:o}=await E(async r=>{const c=await $(r,n),s=await P(r,c,n.params.ref),f=e.owner===void 0?void 0:await q(r,c,e.owner,e.cwd);try{const a=await he(r,c,s,{...e.new_name!==void 0?{name:e.new_name}:{},...f?{owner:f}:{}},await T(r));return{dto:{id:a.id,name:await D(r,a)},change:await F(r,c,[s,a],e.cwd)}}catch(a){I(a,s)}});return await L(o),t}u(qe,"move");async function Pe(n){await O(n.principal);const{dto:e,change:t}=await E(async o=>{const r=await $(o,n),c=await P(o,r,n.params.ref),s=await D(o,c),f=await F(o,r,[c]);try{await me(o,r,c,await T(o))}catch(a){I(a,c)}return{dto:{id:c.id,name:s},change:f}});return await L(t),e}u(Pe,"remove");async function He(n){const e=n.query.get("diff"),t=e===null?void 0:Number(e);if(t!==void 0&&!(Number.isInteger(t)&&t>0))throw k("`diff` must be a revision number");return await Z(n),await O(n.principal),await E(async o=>{const r=await $(o,n),c=await z(o,r,decodeURIComponent(n.params.ref));if("remote"in c)throw y(404,"not_found",J(c.remote),"Check the name with `crtr canvas search`.");const s="deleted"in c?c.deleted:c;if(s.type!=="document")throw k(`${s.name} is a ${s.type}, not a document`);const f=await le(o,r,s),a={object:await Ie(o,s),revisions:f.map(d=>({rev_id:d.rev_id,number:d.number,author_id:d.author,rationale:d.rationale,verbatim:d.verbatim,created_at:d.created_at,parents:d.parents,acting_app:d.acting_app,acting_for:d.acting_for}))};if(t!==void 0){const d=f.find(p=>p.number===t);if(!d)throw new A(404,"not_found",`${s.name} has no revision ${t}`);const m=d.parents[0]===void 0?void 0:f.find(p=>p.rev_id===d.parents[0]),i=await K(o,m?[m.body_hash,d.body_hash]:[d.body_hash]);a.diff=Ee(m?i.get(m.body_hash):"",i.get(d.body_hash))}return a})}u(He,"revisionHistory");const oe=1e3,ie=1e3,ae=3e3,Ue=/^When\b.+\bbecause\b.+/,Ve=u(n=>{const e=n.trim();return e===""?0:e.split(/\s+/).length},"words");function xe(n,e){if(n.some(o=>o.on==="boot"&&o.deliver==="content")&&e>oe)return`body is ${e} words, over the ${oe}-word cap for a boot rule at content (the whole body enters every node's context). Keep the core here and move separate subjects into [[linked]] documents.`;const t=[...new Set(n.filter(o=>o.on!=="boot"&&o.deliver==="content").map(o=>o.on))];return t.length>0&&e>ie?`body is ${e} words, over the ${ie}-word cap for a content rule on ${t.join("/")}. Keep the core here and move separate subjects into [[linked]] documents.`:n.some(o=>o.on==="boot"&&o.deliver==="preview")&&e>ae?`body is ${e} words, over the ${ae}-word cap for a boot rule at preview. Keep the core here and move separate subjects into [[linked]] documents.`:null}u(xe,"lengthFinding");async function Ke(n){const e=S(n);return await O(n.principal,e.cwd),await E(async t=>{const o=await $(t,n),r=e.owner===void 0?null:await q(t,o,e.owner,e.cwd),c=await t.prepare("SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND home_machine IS NULL AND kind != 'report' ORDER BY name").all(),s=[],f=new Map,a=[];for(const m of c)r&&m.owner!==r.id||await ue(t,o,m)&&a.push({doc:m,head:(await W(t,m))[0]});const d=await K(t,a.flatMap(m=>m.head?[m.head.body_hash]:[]));for(const{doc:m,head:i}of a){const p=await M(t,m),h=new Set(p.lint_ignore??[]),w=await D(t,m),v=u((l,_,N)=>{h.has(l)||s.push({name:w,severity:_,rule:l,message:N})},"add"),g=await Ae(t,m),R=i?d.get(i.body_hash):"",C=xe(g,Ve(R));C&&v("length","error",C),g.some(l=>l.deliver==="preview")&&!Ue.test(m.preview??"")&&v("preview-shape","error",`a rule delivers this document at preview, so the preview must read "When <situation in the reader's work>, read this because <what the reader gains>"`);const b=new Set((await t.prepare("SELECT written_as FROM edges WHERE type = 'links-to' AND src = ?").all(m.id)).map(l=>l.written_as));for(const l of G(R)){if(b.has(l))continue;const _=await X(t,o,l);"remote"in _||"object"in _||"deleted"in _||v("links","warning",`[[${l}]] names no object in your view`)}if(g.some(l=>l.on==="workspace-open"&&l.deliver==="content")){const l=await fe(t,m);l.type==="repo"&&!h.has("workspace-open")&&f.set(l.name,[...f.get(l.name)??[],w])}}for(const[m,i]of f)if(!(i.length<2))for(const p of i)s.push({name:p,severity:"warning",rule:"workspace-open",message:`repo ${m} has ${i.length} documents delivered at content on workspace-open (${i.join(", ")}); keep one and link the rest from it`});return{findings:s}})}u(Ke,"lint");const j=u((n,e=200)=>async t=>({status:e,body:await n(t)}),"ok"),wt=[{method:"POST",pattern:"/v1/docs",handler:j(Me,201)},{method:"PATCH",pattern:"/v1/docs/:ref",handler:j(Be)},{method:"POST",pattern:"/v1/docs/:ref/move",handler:j(qe)},{method:"DELETE",pattern:"/v1/docs/:ref",handler:j(Pe)},{method:"GET",pattern:"/v1/docs/:ref/history",handler:j(He)},{method:"POST",pattern:"/v1/docs/lint",handler:j(Ke)}];export{D as canonicalName,wt as docRoutes,F as repoChange,L as syncChanged};