@north-light/cli 0.3.581 → 0.3.583

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 (85) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/health.d.ts +2 -0
  4. package/dist/api/error-codes.d.ts +5 -0
  5. package/dist/api/error-codes.js +1 -1
  6. package/dist/api/errors.js +1 -1
  7. package/dist/build-root.d.ts +11 -4
  8. package/dist/build-root.js +1 -1
  9. package/dist/cli.js +7 -7
  10. package/dist/clients/attach/viewer.js +428 -428
  11. package/dist/commands/api-client.d.ts +5 -0
  12. package/dist/commands/api-client.js +3 -3
  13. package/dist/commands/sys/desktop.d.ts +5 -0
  14. package/dist/commands/sys/desktop.js +1 -0
  15. package/dist/commands/sys/import/move.d.ts +55 -0
  16. package/dist/commands/sys/import/move.js +8 -0
  17. package/dist/commands/sys/import/resume.js +1 -1
  18. package/dist/commands/sys/import/start.d.ts +3 -4
  19. package/dist/commands/sys/import/start.js +1 -4
  20. package/dist/commands/sys/signin.js +2 -2
  21. package/dist/commands/sys.js +1 -1
  22. package/dist/core/canvas/pid.d.ts +6 -0
  23. package/dist/core/canvas/pid.js +5 -5
  24. package/dist/core/command-plugins/compose.js +1 -1
  25. package/dist/core/command.d.ts +17 -8
  26. package/dist/core/command.js +9 -9
  27. package/dist/core/directory/issuer.d.ts +3 -0
  28. package/dist/core/directory/issuer.js +1 -1
  29. package/dist/core/directory/person-credential.d.ts +28 -3
  30. package/dist/core/directory/person-credential.js +1 -1
  31. package/dist/core/directory/reach.d.ts +3 -0
  32. package/dist/core/directory/reach.js +1 -0
  33. package/dist/core/grants/mirror.d.ts +3 -0
  34. package/dist/core/grants/mirror.js +2 -2
  35. package/dist/core/graph/bodies-boot-copy.d.ts +16 -2
  36. package/dist/core/graph/bodies-boot-copy.js +1 -1
  37. package/dist/core/graph/bodies.d.ts +13 -4
  38. package/dist/core/graph/bodies.js +1 -1
  39. package/dist/core/graph/documents.js +10 -10
  40. package/dist/core/graph/repo-sync/exchange.js +2 -2
  41. package/dist/core/graph/repo-sync/ref-format.d.ts +7 -2
  42. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  43. package/dist/core/import/bundle/layout.d.ts +2 -1
  44. package/dist/core/import/export/cloud.d.ts +4 -2
  45. package/dist/core/import/export/cloud.js +1 -1
  46. package/dist/core/import/export/dump.js +3 -3
  47. package/dist/core/import/load/copy.js +5 -5
  48. package/dist/core/import/load/rewrite.js +9 -5
  49. package/dist/core/import/load/stage.js +1 -1
  50. package/dist/core/import/load/verify.js +8 -7
  51. package/dist/core/io.js +4 -4
  52. package/dist/core/northlight/previews.d.ts +22 -0
  53. package/dist/core/northlight/previews.js +1 -0
  54. package/dist/core/providers/call.js +1 -1
  55. package/dist/core/providers/device.js +1 -1
  56. package/dist/core/providers/files.d.ts +2 -1
  57. package/dist/core/providers/files.js +1 -1
  58. package/dist/core/runtime/broker/cli-relay.js +1 -1
  59. package/dist/core/runtime/broker/model-adapter.js +3 -3
  60. package/dist/core/runtime/broker-ownership.d.ts +1 -0
  61. package/dist/core/runtime/broker-ownership.js +1 -0
  62. package/dist/core/runtime/first-run-offer.d.ts +7 -2
  63. package/dist/core/runtime/first-run-offer.js +10 -2
  64. package/dist/core/runtime/front-door.js +4 -4
  65. package/dist/core/runtime/runner/machine/executor.d.ts +7 -1
  66. package/dist/core/runtime/runner/machine/executor.js +1 -1
  67. package/dist/core/worktree-mutation-async.js +1 -1
  68. package/dist/daemon/api/handlers/daemon.js +1 -1
  69. package/dist/daemon/api/handlers/providers.js +1 -1
  70. package/dist/daemon/api/router.js +1 -1
  71. package/dist/daemon/boot-migrations.js +1 -1
  72. package/dist/daemon/cron/machine-exec.d.ts +6 -0
  73. package/dist/daemon/cron/machine-exec.js +1 -1
  74. package/dist/daemon/crtrd.d.ts +1 -1
  75. package/dist/daemon/crtrd.js +14 -14
  76. package/dist/daemon/machine-daemon.d.ts +7 -4
  77. package/dist/daemon/machine-daemon.js +1 -1
  78. package/dist/daemon/machine-link/config.d.ts +32 -1
  79. package/dist/daemon/machine-link/config.js +3 -3
  80. package/dist/daemon/machine-link/move-notify.d.ts +4 -0
  81. package/dist/daemon/machine-link/move-notify.js +1 -0
  82. package/package.json +2 -2
  83. package/packages/crouter-identity/package.json +1 -1
  84. package/runtime.lock.json +13 -13
  85. package/scripts/sandbox-bootstrap.sh +2 -1
@@ -1,10 +1,10 @@
1
- var z=Object.defineProperty;var f=(e,i)=>z(e,"name",{value:i,configurable:!0});import{renderRoot as F,renderBranch as V,renderLeafArgv as T,withAsyncHelpState as G,collapseWideChildren as L,rootPromotedChildren as B}from"./help.js";import{beginPreview as K,publishPreviewResult as D,readStdinRaw as J,peekStdinRaw as Y,emit as Q,handle as X,setJsonOutput as Z,isJsonOutput as ee}from"./io.js";import{renderResult as ne}from"./render.js";import{CrtrError as H}from"./errors.js";import{validateValue as te}from"../api/command-manifest/result.js";import{isManifestStaleError as ie}from"./manifest-stale.js";import{operationIdContext as re}from"./events/operation-id.js";import{ExitCode as C}from"../types.js";import{envNodeId as ae,providerProjectionOverBrokerRelay as oe}from"../shared/env.js";import{readFileSync as se}from"node:fs";import{spawnSync as le}from"node:child_process";import{constants as de}from"node:os";import{resolve as fe}from"node:path";function W(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))W(i);return Object.freeze(e)}f(W,"freezeCommandMetadata");function Ve(e){const i=W(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>G(i,r,n)),slash:e.slash===void 0?void 0:W(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(Ve,"defineLeaf");function A(e){return e.kind==="branch"&&e.children.length>0&&I(e)===0?"hidden":e.tier??"normal"}f(A,"effectiveTier");function I(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(I,"visibleSubCount");function Be(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=I(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:A(i),kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:e.children}}f(Be,"defineBranch");function Je(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Je,"collectSlashSpecs");function Ie(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0&&A(n)!=="hidden").map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),d=B(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:d.length>0?d:void 0,otherSubcommandCount:a.length-d.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(Ie,"defineRoot");function ce(e){if(e.kind==="root")return e.subtrees.filter(i=>A(i)!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=L(e.children.filter(n=>A(n)!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(ce,"childNames");function ue(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const d=i[a];if(d==="-h"||d==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(ue,"walk");function he(e){return e.kind==="root"?F(e.help):e.kind==="branch"?V(e.help):T(e.help)}f(he,"renderNode");async function M(e,i,r){if(r!==void 0&&e.kind==="leaf")return T(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?T(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?V(await e.effectiveHelp()):he(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:d},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:k}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),v=await d(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:k});return n+v.map(E=>`
1
+ var z=Object.defineProperty;var f=(e,i)=>z(e,"name",{value:i,configurable:!0});import{renderRoot as q,renderBranch as V,renderLeafArgv as T,withAsyncHelpState as G,collapseWideChildren as L,rootPromotedChildren as B}from"./help.js";import{beginPreview as K,publishPreviewResult as D,readStdinRaw as J,peekStdinRaw as Q,emit as X,handle as Y,setJsonOutput as Z,isJsonOutput as ee}from"./io.js";import{renderResult as ne}from"./render.js";import{CrtrError as R}from"./errors.js";import{validateValue as te}from"../api/command-manifest/result.js";import{isManifestStaleError as ie}from"./manifest-stale.js";import{operationIdContext as re}from"./events/operation-id.js";import{ExitCode as _}from"../types.js";import{envNodeId as ae,providerProjectionOverBrokerRelay as oe}from"../shared/env.js";import{readFileSync as se}from"node:fs";import{spawnSync as de}from"node:child_process";import{constants as le}from"node:os";import{resolve as fe}from"node:path";function F(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))F(i);return Object.freeze(e)}f(F,"freezeCommandMetadata");function qe(e){const i=F(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>G(i,r,n)),slash:e.slash===void 0?void 0:F(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(qe,"defineLeaf");function A(e){return e.kind==="branch"&&e.children.length>0&&M(e)===0?"hidden":e.tier??"normal"}f(A,"effectiveTier");function M(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(M,"visibleSubCount");function Ve(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=M(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:A(i),kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,refusal:e.refusal,children:e.children}}f(Ve,"defineBranch");function Be(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Be,"collectSlashSpecs");function Je(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0&&A(n)!=="hidden").map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),l=B(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:l.length>0?l:void 0,otherSubcommandCount:a.length-l.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(Je,"defineRoot");function ce(e){if(e.kind==="root")return e.subtrees.filter(i=>A(i)!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=L(e.children.filter(n=>A(n)!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(ce,"childNames");function ue(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const l=i[a];if(l==="-h"||l==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(ue,"walk");function he(e){return e.kind==="root"?q(e.help):e.kind==="branch"?V(e.help):T(e.help)}f(he,"renderNode");async function I(e,i,r){if(r!==void 0&&e.kind==="leaf")return T(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?T(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?V(await e.effectiveHelp()):he(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:l},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:k}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),v=await l(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:k});return n+v.map(C=>`
2
2
 
3
- <plugin-help plugin="${E.plugin}">
4
- ${E.text}
5
- </plugin-help>`).join("")}f(M,"renderNodeWithAddenda");function me(e){return e.some(i=>i==="-h"||i==="--help")}f(me,"helpRequested");function pe(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(pe,"focusedHelpFlag");function ge(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(ge,"rawArgsAfterPath");function we(e,i){const r=le(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new H("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,C.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=de.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(we,"runPassthrough");function ve(e,i,r){const n=ce(e),a=n.length>0?n.join(", "):"(none)",d=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new H("unknown_path",`unknown subcommand: ${r}`,C.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${d}\` for how to find the rest.`:`Valid children: ${a}. Run \`${d}\` for the full list.`})}f(ve,"unknownPathError");const be="https://account.northlight.ai";function xe(e){const i=be,r=`open ${i}/account/machines, choose Create join token, then run \`crtr sys join --directory ${i} --token <token>\``;return new H("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from Northlight, this machine isn't joined to your account yet: ${r}.`,C.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}f(xe,"notJoinedError");async function ke(){if(oe())return!1;try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}f(ke,"runtimeUnjoined");function p(e,i,r,n,a){return new H(e,i,C.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(p,"parseArgvError");function $(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f($,"flagNameToKey");async function ye(e,i,r){const n={},a=new Set,d=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),k=e.find(t=>t.kind==="context-file"),v=e.filter(t=>t.kind==="flag"),E=new Map(v.map(t=>[t.name,t])),S=v.some(t=>t.sensitive===!0);for(const t of v)t.type==="bool"?n[$(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[$(t.name)]=t.default);const w=[];let b=0,U=!1,u=0;for(;u<i.length;){const t=i[u];if(t==="--"&&!U){U=!0,u++;continue}if(!U&&t.startsWith("--")){const h=t.indexOf("=");let l,j;if(h!==-1?(l=t.slice(2,h),j=t.slice(h+1)):l=t.slice(2),l==="context-file"){if(k===void 0)throw p("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const c=j!==void 0?j:i[++u];if(c===void 0)throw p("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let x;try{x=se(c,"utf8")}catch{throw p("invalid_type",`--context-file: cannot read file: ${c}`,c,"context-file","Provide a readable file path.")}let q;try{q=JSON.parse(x)}catch{throw p("invalid_type",`--context-file: file is not valid JSON: ${c}`,c,"context-file","Ensure the file contains a valid JSON object.")}const O=$(k.name);n[O]=q,d.set(O,fe(c)),a.add(O),u++;continue}const P=E.get(l);if(P===void 0)throw p("unknown_flag",`unknown flag: --${l}`,`--${l}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const R=$(l);if(a.add(R),P.type==="bool"){if(j!==void 0)throw p("invalid_type",`boolean flag --${l} takes no value`,`--${l}=${j}`,l,`Use --${l} (presence = true) with no value.`);n[R]=!0,u++;continue}const y=j!==void 0?j:i[++u];if(y===void 0||j===void 0&&y.startsWith("--")){const c=P.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${l} -h\` and read the focused value contract before retrying.`:void 0;throw p("missing_parameter",`--${l} requires a value`,`--${l}`,l,c)}let _;if(P.type==="int"){const c=Number(y);if(!Number.isInteger(c))throw p("invalid_type",`--${l} must be an integer`,y,l,`Provide an integer value for --${l}.`);_=c}else if(P.type==="enum"){const c=P.choices;if(c!==void 0&&!c.includes(y))throw p("invalid_type",`--${l} must be one of: ${c.join(", ")}`,y,l,`Retry with one of: ${c.join(", ")}.`);_=y}else if(P.type==="json"){let c;try{c=JSON.parse(y)}catch{throw p("invalid_type",`--${l} is not valid JSON`,y,l,`Pass one JSON value for --${l}; run -h for its shape.`)}const x=P.shape===void 0?null:te(P.shape,c,`--${l}`);if(x!==null)throw p("invalid_type",`${x.path} must be ${x.expected}, got ${x.receivedType}`,y,x.path,`Fix ${x.path}; run -h for the shape of --${l}.`);_=c}else _=y;if(P.repeatable===!0){const c=n[R],x=Array.isArray(c)?c:[];x.push(_),n[R]=x}else n[R]=_;u++;continue}const m=s[b];if(m!==void 0){const h=$(m.name);if(m.repeatable===!0){const l=n[h]??[];l.push(t),n[h]=l}else n[h]=t,b++;w.push(t),a.add(h),u++;continue}if(w.length>0){if(o!==void 0&&s.length===0){w.push(t),u++;continue}throw o!==void 0?p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):p("bad_invocation",S?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,S?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw p("bad_invocation",S?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,S?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");w.push(t),u++}let g=!1;if(o!==void 0){if(w.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?p("bad_invocation",t.message,void 0,t.field,t.next):p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(w.length>0&&s.length===0&&o.allowPositional!==!1){const t=w[0];if(t==="-"){const m=await J();(m.trim()!==""||!o.required)&&(n[$(o.name)]=m),g=!0}else{if((await Y()).trim()!==""){const h=o.positionalStdinConflict?.(n);throw p("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,h?.field??o.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[$(o.name)]=t,g=!0}}else{const t=await J();t!==""&&(t.trim()!==""||!o.required)&&(n[$(o.name)]=t,g=!0)}}g&&o!==void 0&&a.add($(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const m=$(t.name);if(a.has(m))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[m]=t.kind==="positional"&&t.repeatable===!0?[h]:h,a.add(m))}const N=[];for(const t of e){const m=$(t.name),h=t.kind==="flag"&&t.type==="bool"&&!a.has(m);if(t.required&&(h||n[m]===void 0||n[m]===null)){const l=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";N.push({display:l,name:t.name})}}if(N.length>0){const t=N.map(l=>l.display).join(", "),m=r?.leafPath,h=m!==void 0&&m.length>0?`\`crtr ${m.join(" ")} -h\``:"this command with -h";throw p("missing_parameter",N.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,N.map(l=>l.name).join(", "),`Run ${h} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(d),n}f(ye,"parseArgv");function $e(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const d=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&d===void 0){i.push(a);continue}const s=d??e[n+1];if(s===void 0||s===""||d===void 0&&s.startsWith("-"))throw p("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");d===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f($e,"takeMachineSelector");const Se=new Set(["--json","--no-autostart"]);async function Me(e,i){const r=i.slice(2);r.includes("--json")&&Z(!0);const n=r.filter(o=>!Se.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=ae()?{...e.help,commands:await Promise.all(e.help.commands.map(async k=>{const v=e.subtrees.find(b=>b.name===k.name);if(v?.effectiveHelp===void 0)return k;const S=((await v.effectiveHelp()).listing??[]).filter(b=>b.tier!=="hidden"),w=B(S).map(b=>({path:`${v.name} ${b.name}`,desc:b.tier==="important"?b.description:void 0}));return{...k,subcommands:w.length?w:void 0,otherSubcommandCount:S.length-w.length}}))}:e.help;process.stdout.write(F(o)+`
6
- `),process.exitCode=C.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:o}=await import("../commands/sys/shared.js");process.stdout.write(o()+`
7
- `),process.exitCode=C.SUCCESS;return}let{node:a,path:d,remaining:s}=ue(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&we(a.passthrough,ge(r,d)),a.kind==="branch"&&a.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new H("provider_unavailable",a.unavailable,C.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&await ke()?xe(s[0]):ve(a,d,s[0]);if(me(s)){process.stdout.write(await M(a,d,pe(a,s))+`
8
- `),process.exitCode=C.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await M(a,d)+`
9
- `),process.exitCode=C.SUCCESS;return}const o=a;K(d.join(" "));const k=o.help.params!==void 0?o.help.params:[];let v=new Set,E=new Map,S=s,w;if(o.machineSelector===!0&&!k.some(g=>g.kind==="flag"&&g.name==="machine")){const g=$e(s);S=g.rest,w=g.machine}const b=await ye(k,S,{leafPath:d,onProvidedParams:f(g=>{v=g},"onProvidedParams"),onContextFilePaths:f(g=>{E=g},"onContextFilePaths")}),U={providedParams:v,contextFilePaths:E,...w===void 0?{}:{machine:w}},u=await re.fresh(()=>o.run(b,U));if(D(u==null?u:o.preview?.(u)??u),u!=null)if(ee())Q(u);else{const g=o.render!==void 0?o.render(u):ne(u,o.help);process.stdout.write(g+(o.stdoutExact?.(u)?"":`
10
- `))}}catch(o){if(ie(o))throw o;X(o)}}f(Me,"runCli");export{be as DEFAULT_DIRECTORY_ISSUER,Se as GLOBAL_TOKENS,Je as collectSlashSpecs,Be as defineBranch,Ve as defineLeaf,Ie as defineRoot,xe as notJoinedError,ye as parseArgv,he as renderNode,Me as runCli,$e as takeMachineSelector,ve as unknownPathError,ue as walk};
3
+ <plugin-help plugin="${C.plugin}">
4
+ ${C.text}
5
+ </plugin-help>`).join("")}f(I,"renderNodeWithAddenda");function me(e){return e.some(i=>i==="-h"||i==="--help")}f(me,"helpRequested");function pe(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(pe,"focusedHelpFlag");function ge(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(ge,"rawArgsAfterPath");function we(e,i){const r=de(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new R("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,_.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=le.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(we,"runPassthrough");function ve(e,i,r){const n=ce(e),a=n.length>0?n.join(", "):"(none)",l=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new R("unknown_path",`unknown subcommand: ${r}`,_.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${l}\` for how to find the rest.`:`Valid children: ${a}. Run \`${l}\` for the full list.`})}f(ve,"unknownPathError");function be(e){const i="If it comes from Northlight, it needs a Northlight account: `crtr sys signin`.";return new R("not_joined",`\`crtr ${e}\` isn't a command here. ${i}`,_.USAGE,{received:e,next:i})}f(be,"notJoinedError");async function xe(){if(oe())return!1;try{const{readConfig:e}=await import("./config.js"),{readMachineConfig:i}=await import("../daemon/machine-link/config.js");return i()===void 0&&!e("user").runtime?.issuer}catch{return!1}}f(xe,"runtimeUnjoined");function p(e,i,r,n,a){return new R(e,i,_.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(p,"parseArgvError");function y(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f(y,"flagNameToKey");async function ke(e,i,r){const n={},a=new Set,l=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),k=e.find(t=>t.kind==="context-file"),v=e.filter(t=>t.kind==="flag"),C=new Map(v.map(t=>[t.name,t])),S=v.some(t=>t.sensitive===!0);for(const t of v)t.type==="bool"?n[y(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[y(t.name)]=t.default);const w=[];let b=0,N=!1,u=0;for(;u<i.length;){const t=i[u];if(t==="--"&&!N){N=!0,u++;continue}if(!N&&t.startsWith("--")){const h=t.indexOf("=");let d,E;if(h!==-1?(d=t.slice(2,h),E=t.slice(h+1)):d=t.slice(2),d==="context-file"){if(k===void 0)throw p("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const c=E!==void 0?E:i[++u];if(c===void 0)throw p("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let x;try{x=se(c,"utf8")}catch{throw p("invalid_type",`--context-file: cannot read file: ${c}`,c,"context-file","Provide a readable file path.")}let W;try{W=JSON.parse(x)}catch{throw p("invalid_type",`--context-file: file is not valid JSON: ${c}`,c,"context-file","Ensure the file contains a valid JSON object.")}const O=y(k.name);n[O]=W,l.set(O,fe(c)),a.add(O),u++;continue}const P=C.get(d);if(P===void 0)throw p("unknown_flag",`unknown flag: --${d}`,`--${d}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const H=y(d);if(a.add(H),P.type==="bool"){if(E!==void 0)throw p("invalid_type",`boolean flag --${d} takes no value`,`--${d}=${E}`,d,`Use --${d} (presence = true) with no value.`);n[H]=!0,u++;continue}const $=E!==void 0?E:i[++u];if($===void 0||E===void 0&&$.startsWith("--")){const c=P.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${d} -h\` and read the focused value contract before retrying.`:void 0;throw p("missing_parameter",`--${d} requires a value`,`--${d}`,d,c)}let j;if(P.type==="int"){const c=Number($);if(!Number.isInteger(c))throw p("invalid_type",`--${d} must be an integer`,$,d,`Provide an integer value for --${d}.`);j=c}else if(P.type==="enum"){const c=P.choices;if(c!==void 0&&!c.includes($))throw p("invalid_type",`--${d} must be one of: ${c.join(", ")}`,$,d,`Retry with one of: ${c.join(", ")}.`);j=$}else if(P.type==="json"){let c;try{c=JSON.parse($)}catch{throw p("invalid_type",`--${d} is not valid JSON`,$,d,`Pass one JSON value for --${d}; run -h for its shape.`)}const x=P.shape===void 0?null:te(P.shape,c,`--${d}`);if(x!==null)throw p("invalid_type",`${x.path} must be ${x.expected}, got ${x.receivedType}`,$,x.path,`Fix ${x.path}; run -h for the shape of --${d}.`);j=c}else j=$;if(P.repeatable===!0){const c=n[H],x=Array.isArray(c)?c:[];x.push(j),n[H]=x}else n[H]=j;u++;continue}const m=s[b];if(m!==void 0){const h=y(m.name);if(m.repeatable===!0){const d=n[h]??[];d.push(t),n[h]=d}else n[h]=t,b++;w.push(t),a.add(h),u++;continue}if(w.length>0){if(o!==void 0&&s.length===0){w.push(t),u++;continue}throw o!==void 0?p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):p("bad_invocation",S?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,S?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw p("bad_invocation",S?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,S?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");w.push(t),u++}let g=!1;if(o!==void 0){if(w.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?p("bad_invocation",t.message,void 0,t.field,t.next):p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(w.length>0&&s.length===0&&o.allowPositional!==!1){const t=w[0];if(t==="-"){const m=await J();(m.trim()!==""||!o.required)&&(n[y(o.name)]=m),g=!0}else{if((await Q()).trim()!==""){const h=o.positionalStdinConflict?.(n);throw p("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,h?.field??o.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[y(o.name)]=t,g=!0}}else{const t=await J();t!==""&&(t.trim()!==""||!o.required)&&(n[y(o.name)]=t,g=!0)}}g&&o!==void 0&&a.add(y(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const m=y(t.name);if(a.has(m))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[m]=t.kind==="positional"&&t.repeatable===!0?[h]:h,a.add(m))}const U=[];for(const t of e){const m=y(t.name),h=t.kind==="flag"&&t.type==="bool"&&!a.has(m);if(t.required&&(h||n[m]===void 0||n[m]===null)){const d=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";U.push({display:d,name:t.name})}}if(U.length>0){const t=U.map(d=>d.display).join(", "),m=r?.leafPath,h=m!==void 0&&m.length>0?`\`crtr ${m.join(" ")} -h\``:"this command with -h";throw p("missing_parameter",U.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,U.map(d=>d.name).join(", "),`Run ${h} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(l),n}f(ke,"parseArgv");function $e(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const l=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&l===void 0){i.push(a);continue}const s=l??e[n+1];if(s===void 0||s===""||l===void 0&&s.startsWith("-"))throw p("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");l===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f($e,"takeMachineSelector");const ye=new Set(["--json","--no-autostart"]);async function Me(e,i){const r=i.slice(2);r.includes("--json")&&Z(!0);const n=r.filter(o=>!ye.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=ae()?{...e.help,commands:await Promise.all(e.help.commands.map(async k=>{const v=e.subtrees.find(b=>b.name===k.name);if(v?.effectiveHelp===void 0)return k;const S=((await v.effectiveHelp()).listing??[]).filter(b=>b.tier!=="hidden"),w=B(S).map(b=>({path:`${v.name} ${b.name}`,desc:b.tier==="important"?b.description:void 0}));return{...k,subcommands:w.length?w:void 0,otherSubcommandCount:S.length-w.length}}))}:e.help;process.stdout.write(q(o)+`
6
+ `),process.exitCode=_.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:o}=await import("../commands/sys/shared.js");process.stdout.write(o()+`
7
+ `),process.exitCode=_.SUCCESS;return}let{node:a,path:l,remaining:s}=ue(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&we(a.passthrough,ge(r,l)),a.kind==="branch"&&a.refusal!==void 0&&s[0]!=="-h"&&s[0]!=="--help")throw new R(a.refusal.code,a.refusal.message,a.refusal.exit,{next:a.refusal.next});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&a.providerCatalogFailure!==void 0?a.providerCatalogFailure:a.kind==="root"&&!s[0].startsWith("-")&&await xe()?be(s[0]):ve(a,l,s[0]);if(me(s)){process.stdout.write(await I(a,l,pe(a,s))+`
8
+ `),process.exitCode=_.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await I(a,l)+`
9
+ `),process.exitCode=_.SUCCESS;return}const o=a;K(l.join(" "));const k=o.help.params!==void 0?o.help.params:[];let v=new Set,C=new Map,S=s,w;if(o.machineSelector===!0&&!k.some(g=>g.kind==="flag"&&g.name==="machine")){const g=$e(s);S=g.rest,w=g.machine}const b=await ke(k,S,{leafPath:l,onProvidedParams:f(g=>{v=g},"onProvidedParams"),onContextFilePaths:f(g=>{C=g},"onContextFilePaths")}),N={providedParams:v,contextFilePaths:C,...w===void 0?{}:{machine:w}},u=await re.fresh(()=>o.run(b,N));if(D(u==null?u:o.preview?.(u)??u),u!=null)if(ee())X(u);else{const g=o.render!==void 0?o.render(u):ne(u,o.help);process.stdout.write(g+(o.stdoutExact?.(u)?"":`
10
+ `))}}catch(o){if(ie(o))throw o;Y(o)}}f(Me,"runCli");export{ye as GLOBAL_TOKENS,Be as collectSlashSpecs,Ve as defineBranch,qe as defineLeaf,Je as defineRoot,be as notJoinedError,ke as parseArgv,he as renderNode,Me as runCli,$e as takeMachineSelector,ve as unknownPathError,ue as walk};
@@ -3,3 +3,6 @@
3
3
  * deployment's `CRTR_DIRECTORY_URL` (the daemon refuses to boot without it), never from one person's settings. Anything else (a
4
4
  * Mac or a legacy single-person install) reads the user config's `runtime.issuer`, null while the runtime has not joined. */
5
5
  export declare function directoryIssuer(): string | null;
6
+ /** Northlight's directory: where a person signs in or creates an account, and the default for `crtr sys signin` and
7
+ * `crtr sys import start` when nothing names another (`--directory-url`, `runtime/machine.json`, the stored sign-in). */
8
+ export declare const NORTHLIGHT_DIRECTORY_URL = "https://account.northlight.ai";
@@ -1 +1 @@
1
- var s=Object.defineProperty;var e=(r,o)=>s(r,"name",{value:o,configurable:!0});import{isCloudRole as u}from"../canvas/person-state.js";import{readConfig as i}from"../config.js";function R(){const r=u()?process.env.CRTR_DIRECTORY_URL:i("user").runtime?.issuer;return r?r.replace(/\/+$/,""):null}e(R,"directoryIssuer");export{R as directoryIssuer};
1
+ var e=Object.defineProperty;var o=(r,t)=>e(r,"name",{value:t,configurable:!0});import{isCloudRole as n}from"../canvas/person-state.js";import{readConfig as s}from"../config.js";function c(){const r=n()?process.env.CRTR_DIRECTORY_URL:s("user").runtime?.issuer;return r?r.replace(/\/+$/,""):null}o(c,"directoryIssuer");const l="https://account.northlight.ai";export{l as NORTHLIGHT_DIRECTORY_URL,c as directoryIssuer};
@@ -12,19 +12,44 @@ export interface SignInPrompt {
12
12
  verificationUri: string;
13
13
  userCode: string;
14
14
  }
15
+ /** What the directory approved: the account, and a bearer the new secret was just exchanged for (not cached until `signIn` writes it). */
16
+ export interface ApprovedSignIn {
17
+ account: {
18
+ id: string;
19
+ display: string;
20
+ };
21
+ bearer: string;
22
+ }
23
+ export interface SignInOptions {
24
+ sleep?: (ms: number) => Promise<void>;
25
+ /** Called after approval and before anything is written. A returned error refuses the sign-in: the new credential is revoked at the directory,
26
+ * nothing is written, and `signIn` throws that error. */
27
+ accept?: (approved: ApprovedSignIn) => Promise<CrtrError | undefined>;
28
+ }
15
29
  /** Pair this computer with the directory: start a developer login of kind `crtr`, show the code, poll until the person allows it, then store the secret.
16
30
  * Nothing durable is written until the directory has approved, so an abandoned sign-in leaves the previous state untouched. */
17
31
  export declare function signIn(directoryUrl: string, show: (prompt: SignInPrompt & {
18
32
  complete: string;
19
- }) => void, opts?: {
20
- sleep?: (ms: number) => Promise<void>;
21
- }): Promise<{
33
+ }) => void, opts?: SignInOptions): Promise<{
22
34
  account: string;
23
35
  credentialId: string;
24
36
  }>;
25
37
  /** A person bearer for the control plane: the cached one while it has more than 30 seconds left, else a fresh exchange of the stored secret.
26
38
  * A revoked, expired or unknown credential is `not_signed_in`. */
27
39
  export declare function personBearer(directoryUrl?: string): Promise<string>;
40
+ /** A `fetch` for the control plane at `apiUrl`: each request carries the person's bearer for `directoryUrl` (fetched per request, so a long command never sends
41
+ * an ended one), a sign-in refusal crosses as its own `CrtrError`, and a request that never got an HTTP answer is `northlight_unreachable` (a caller's own abort
42
+ * stays what it was). */
43
+ export declare function northlightFetch(directoryUrl: string, apiUrl: string): typeof fetch;
44
+ /** The account the stored sign-in acts as: a fresh exchange (the token answer names it), whose bearer is cached like `personBearer`'s. */
45
+ export declare function personAccount(directoryUrl?: string): Promise<{
46
+ id: string;
47
+ display: string;
48
+ }>;
49
+ /** The control plane a person bearer is good for: its `aud`, which the directory signs as its `CRTR_PUBLIC_API_URL` (design D6). */
50
+ export declare function controlPlaneUrl(directoryUrl?: string): Promise<string>;
51
+ /** Delete the stored credential and cached bearer without calling the directory. */
52
+ export declare function forgetSignIn(): void;
28
53
  /** Revoke the stored credential at the directory (best effort when it is already gone) and delete both files. Returns whether one was stored. */
29
54
  export declare function signOut(): Promise<{
30
55
  signedOut: boolean;
@@ -1 +1 @@
1
- var B=Object.defineProperty;var o=(e,r)=>B(e,"name",{value:r,configurable:!0});import{createHash as C,randomBytes as H}from"node:crypto";import{renameSync as I,rmSync as h}from"node:fs";import{hostname as R}from"node:os";import{join as w}from"node:path";import{CrtrError as J,general as p,network as N}from"../errors.js";import{atomicWriteJson as S,readJsonOrNull as $}from"../fs-utils.js";import{secretsDir as _}from"../layout.js";import{ExitCode as A}from"../../types.js";const x=o(()=>w(_(),"person-credential"),"personCredentialPath"),y=o(()=>w(_(),"person-bearer.json"),"personBearerPath"),O=o(()=>w(_(),"person-credential.pending"),"pendingPath"),M=3e4,z=o(e=>C("sha256").update(e).digest("hex"),"sha256Hex"),b=o(e=>e.replace(/\/+$/,""),"trimUrl"),U=o(e=>new J("not_signed_in",e,A.USAGE,{next:"Run `crtr sys signin`, then retry."}),"notSignedIn");function v(){const e=$(x());if(!(e===null||typeof e.secret!="string"||typeof e.directoryUrl!="string"))return{directoryUrl:e.directoryUrl,secret:e.secret}}o(v,"readPersonCredential");function f(e,r){const n=e!==null&&typeof e=="object"?e:{},t=n.error;if(t!==null&&typeof t=="object"){const s=t;return{code:String(s.code??"error"),message:String(s.message??`HTTP ${r}`)}}return typeof t=="string"?{code:t,message:String(n.error_description??n.message??t)}:{code:"error",message:`the directory answered HTTP ${r}`}}o(f,"errorOf");async function m(e,r,n,t){let s;try{s=await fetch(`${b(e)}${r}`,{method:"POST",headers:{"content-type":"application/json",...t===void 0?{}:{authorization:`Bearer ${t}`}},body:JSON.stringify(n),signal:AbortSignal.timeout(3e4)})}catch(c){const l=c.cause;throw N(`could not reach the directory at ${e} (${l?.code??l?.message??c.message})`)}const u=await s.text();let a={};try{a=u===""?{}:JSON.parse(u)}catch{}return{status:s.status,body:a}}o(m,"call");const g=o((e,r)=>{const n=f(r,e);throw p(`the directory refused: ${n.message}`,{code:n.code})},"refuse");async function G(e,r,n={}){const t=b(e),s=n.sleep??(i=>new Promise(d=>setTimeout(d,i))),u=`nld_${H(32).toString("base64url")}`,a=await m(t,"/v1/developer/logins",{credential_hash:z(u),name:R().slice(0,64),platform:process.platform==="darwin"||process.platform==="win32"?process.platform:"linux",sdk_version:"crtr",kind:"crtr"});a.status!==200&&a.status!==201&&g(a.status,a.body);const c=a.body;r({verificationUri:c.verification_uri,userCode:c.user_code,complete:c.verification_uri_complete});let l=Math.max(1,c.interval)*1e3;const T=Date.now()+c.expires_in*1e3;for(;;){if(Date.now()>T)throw p("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:"expired_token"});await s(l);const i=await m(t,"/v1/developer/logins/poll",{device_code:c.device_code});if(i.status===200){const k=i.body,j=await E(t,u);return S(O(),{directoryUrl:t,secret:u},{mode:384}),I(O(),x()),D(t,j),{account:k.account.display,credentialId:k.credential_id}}const d=f(i.body,i.status).code;if(!(i.status===400&&d==="authorization_pending")){if(i.status===400&&d==="slow_down"){l+=5e3;continue}if(i.status===400&&d==="access_denied")throw p("the sign-in was declined in the browser",{code:d});if(i.status===400&&d==="expired_token")throw p("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:d});g(i.status,i.body)}}}o(G,"signIn");const P=new Set(["credential_revoked","credential_expired","credential_unknown"]);async function E(e,r,n=!1){const t=await m(e,"/v1/developer/token",{},r);if(t.status===401&&P.has(f(t.body,401).code))throw n&&h(y(),{force:!0}),U(`the sign-in on this computer is no longer valid (${f(t.body,401).code})`);t.status!==200&&g(t.status,t.body);const s=t.body;if(typeof s.access_token!="string"||typeof s.expires_in!="number")throw p("the directory answered without a bearer token");return{access_token:s.access_token,expires_in:s.expires_in}}o(E,"exchange");function D(e,r){S(y(),{directoryUrl:e,access_token:r.access_token,expires_at_ms:Date.now()+r.expires_in*1e3},{mode:384})}o(D,"writeBearer");async function F(e){const r=v();if(r===void 0)throw U("this computer is not signed in");const n=b(e??r.directoryUrl),t=$(y());if(t!==null&&t.directoryUrl===n&&typeof t.access_token=="string"&&t.expires_at_ms-Date.now()>M)return t.access_token;const s=await E(n,r.secret,!0);return D(n,s),s.access_token}o(F,"personBearer");async function ee(){const e=v();if(e===void 0)return{signedOut:!1,revoked:!1};let r=!1;const n=await m(e.directoryUrl,"/v1/developer/token/revoke",{},e.secret);return n.status===200?r=!0:n.status===401&&P.has(f(n.body,401).code)||g(n.status,n.body),h(x(),{force:!0}),h(y(),{force:!0}),{signedOut:!0,revoked:r}}o(ee,"signOut");async function te(e,r){return v()===void 0&&await G(e,r),F(e)}o(te,"ensureSignedIn");export{te as ensureSignedIn,U as notSignedIn,F as personBearer,y as personBearerPath,x as personCredentialPath,v as readPersonCredential,G as signIn,ee as signOut};
1
+ var C=Object.defineProperty;var o=(e,n)=>C(e,"name",{value:n,configurable:!0});import{createHash as J,randomBytes as N}from"node:crypto";import{renameSync as R,rmSync as _}from"node:fs";import{hostname as z}from"node:os";import{join as x}from"node:path";import{CrtrError as M,general as f}from"../errors.js";import{atomicWriteJson as E,readJsonOrNull as A}from"../fs-utils.js";import{secretsDir as b}from"../layout.js";import{ExitCode as F}from"../../types.js";import{northlightUnreachable as D}from"./reach.js";const k=o(()=>x(b(),"person-credential"),"personCredentialPath"),y=o(()=>x(b(),"person-bearer.json"),"personBearerPath"),H=o(()=>x(b(),"person-credential.pending"),"pendingPath"),G=3e4,K=o(e=>J("sha256").update(e).digest("hex"),"sha256Hex"),l=o(e=>e.replace(/\/+$/,""),"trimUrl"),v=o(e=>new M("not_signed_in",e,F.USAGE,{next:"Run `crtr sys signin`, then retry."}),"notSignedIn");function h(){const e=A(k());if(!(e===null||typeof e.secret!="string"||typeof e.directoryUrl!="string"))return{directoryUrl:e.directoryUrl,secret:e.secret}}o(h,"readPersonCredential");function p(e,n){const r=e!==null&&typeof e=="object"?e:{},t=r.error;if(t!==null&&typeof t=="object"){const s=t;return{code:String(s.code??"error"),message:String(s.message??`HTTP ${n}`)}}return typeof t=="string"?{code:t,message:String(r.error_description??r.message??t)}:{code:"error",message:`the directory answered HTTP ${n}`}}o(p,"errorOf");async function w(e,n,r,t){let s;try{s=await fetch(`${l(e)}${n}`,{method:"POST",headers:{"content-type":"application/json",...t===void 0?{}:{authorization:`Bearer ${t}`}},body:JSON.stringify(r),signal:AbortSignal.timeout(3e4)})}catch(d){throw D(d,e)}const i=await s.text();let c={};try{c=i===""?{}:JSON.parse(i)}catch{}return{status:s.status,body:c}}o(w,"call");const g=o((e,n)=>{const r=p(n,e);throw f(`the directory refused: ${r.message}`,{code:r.code})},"refuse");async function V(e,n,r={}){const t=l(e),s=r.sleep??(a=>new Promise(u=>setTimeout(u,a))),i=`nld_${N(32).toString("base64url")}`,c=await w(t,"/v1/developer/logins",{credential_hash:K(i),name:z().slice(0,64),platform:process.platform==="darwin"||process.platform==="win32"?process.platform:"linux",sdk_version:"crtr",kind:"crtr"});c.status!==200&&c.status!==201&&g(c.status,c.body);const d=c.body;n({verificationUri:d.verification_uri,userCode:d.user_code,complete:d.verification_uri_complete});let $=Math.max(1,d.interval)*1e3;const j=Date.now()+d.expires_in*1e3;for(;;){if(Date.now()>j)throw f("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:"expired_token"});await s($);const a=await w(t,"/v1/developer/logins/poll",{device_code:d.device_code});if(a.status===200){const m=a.body,P=await S(t,i),B=await r.accept?.({account:m.account,bearer:P.access_token});if(B!==void 0)throw await T(t,i),B;return E(H(),{directoryUrl:t,secret:i},{mode:384}),R(H(),k()),U(t,P),{account:m.account.display,credentialId:m.credential_id}}const u=p(a.body,a.status).code;if(!(a.status===400&&u==="authorization_pending")){if(a.status===400&&u==="slow_down"){$+=5e3;continue}if(a.status===400&&u==="access_denied")throw f("the sign-in was declined in the browser",{code:u});if(a.status===400&&u==="expired_token")throw f("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:u});g(a.status,a.body)}}}o(V,"signIn");const I=new Set(["credential_revoked","credential_expired","credential_unknown"]);async function S(e,n,r=!1){const t=await w(e,"/v1/developer/token",{},n);if(t.status===401&&I.has(p(t.body,401).code))throw r&&_(y(),{force:!0}),v(`the sign-in on this computer is no longer valid (${p(t.body,401).code})`);t.status!==200&&g(t.status,t.body);const s=t.body;if(typeof s.access_token!="string"||typeof s.expires_in!="number")throw f("the directory answered without a bearer token");const i=s.account;return{access_token:s.access_token,expires_in:s.expires_in,...typeof i?.id=="string"&&typeof i.display=="string"?{account:{id:i.id,display:i.display}}:{}}}o(S,"exchange");function U(e,n){E(y(),{directoryUrl:e,access_token:n.access_token,expires_at_ms:Date.now()+n.expires_in*1e3},{mode:384})}o(U,"writeBearer");async function O(e){const n=h();if(n===void 0)throw v("this computer is not signed in");const r=l(e??n.directoryUrl),t=A(y());if(t!==null&&t.directoryUrl===r&&typeof t.access_token=="string"&&t.expires_at_ms-Date.now()>G)return t.access_token;const s=await S(r,n.secret,!0);return U(r,s),s.access_token}o(O,"personBearer");function oe(e,n){return async(r,t)=>{const s=new Headers(t?.headers);s.set("authorization",`Bearer ${await O(e)}`);try{return await fetch(r,{...t,headers:s})}catch(i){throw t?.signal?.aborted===!0?i:D(i,n)}}}o(oe,"northlightFetch");async function se(e){const n=h();if(n===void 0)throw v("this computer is not signed in");const r=l(e??n.directoryUrl),t=await S(r,n.secret,!0);if(U(r,t),t.account===void 0)throw f("the directory's token answer names no account");return t.account}o(se,"personAccount");async function ie(e){const n=await O(e);let r;try{r=JSON.parse(Buffer.from(n.split(".")[1]??"","base64url").toString("utf8")).aud}catch{r=void 0}const t=Array.isArray(r)?r[0]:r;if(typeof t!="string"||!/^https?:\/\//.test(t))throw f("the directory's sign-in token names no control plane (no http(s) `aud`)");return l(t)}o(ie,"controlPlaneUrl");async function T(e,n){const r=await w(e,"/v1/developer/token/revoke",{},n);return r.status===200?!0:r.status===401&&I.has(p(r.body,401).code)?!1:g(r.status,r.body)}o(T,"revoke");function W(){_(k(),{force:!0}),_(y(),{force:!0})}o(W,"forgetSignIn");async function ae(){const e=h();if(e===void 0)return{signedOut:!1,revoked:!1};const n=await T(e.directoryUrl,e.secret);return W(),{signedOut:!0,revoked:n}}o(ae,"signOut");async function ce(e,n){return h()===void 0&&await V(e,n),O(e)}o(ce,"ensureSignedIn");export{ie as controlPlaneUrl,ce as ensureSignedIn,W as forgetSignIn,oe as northlightFetch,v as notSignedIn,se as personAccount,O as personBearer,y as personBearerPath,k as personCredentialPath,h as readPersonCredential,V as signIn,ae as signOut};
@@ -0,0 +1,3 @@
1
+ import { CrtrError } from '../errors.js';
2
+ export declare const NORTHLIGHT_UNREACHABLE = "northlight_unreachable";
3
+ export declare function northlightUnreachable(cause: unknown, url: string): CrtrError;
@@ -0,0 +1 @@
1
+ var c=Object.defineProperty;var o=(t,n)=>c(t,"name",{value:n,configurable:!0});import{CrtrError as i}from"../errors.js";import{ExitCode as s}from"../../types.js";const h="northlight_unreachable",a="Check this computer's internet connection, then retry.";function f(t){const n=t?.cause,e=t;for(const r of[n?.code,n?.message,e?.code,e?.message])if(typeof r=="string"&&r!=="")return r;return String(t)}o(f,"causeText");function p(t,n){const e=`crtr cannot reach Northlight (${new URL(n).host}): ${f(t)}.`;return new i(h,e,s.NETWORK,{next:a})}o(p,"northlightUnreachable");export{h as NORTHLIGHT_UNREACHABLE,p as northlightUnreachable};
@@ -68,6 +68,9 @@ export declare function isProviderBundleRow(row: {
68
68
  source_marketplace: string | null;
69
69
  source_path: string | null;
70
70
  }, granted: ReadonlySet<string>): boolean;
71
+ /** `person`'s (`user:<id>`) active, unexpired grants on this runtime, every grantee: what a cloud role reads for one request's person, where `localGrants()`
72
+ * would hand back every person the fleet serves. */
73
+ export declare function activeGrantsOfPerson(person: string): Promise<ActiveGrant[]>;
71
74
  /** Every mirrored grant, whatever its status. */
72
75
  export declare function allGrants(): Promise<GrantRecord[]>;
73
76
  /** Whether a grant's `expires_at` has passed. An unparsable time reads as expired (fail closed). */
@@ -1,4 +1,4 @@
1
- var w=Object.defineProperty;var t=(e,r)=>w(e,"name",{value:r,configurable:!0});import{createHash as O}from"node:crypto";import{parseScope as h}from"@north-light/identity";import{openDb as d,withCanvasWrite as m}from"../canvas/db.js";import{isUnsandboxedMachine as j}from"../runtime/unsandboxed.js";import{replaceWrittenBy as A,userSubject as b}from"../permissions/facts.js";import{GROUP_TYPE as R}from"../permissions/builtin.js";const D=["not_started","pending","ready","failed","unavailable"],k=["ultra","strong","medium","light"];function I(){const e=process.env.CRTR_RUNTIME;return e?{sql:" AND runtime = ?",args:[e]}:{sql:"",args:[]}}t(I,"runtimeScope");async function u(e,r,n=[]){const s=I();return await(await d()).prepare(`SELECT ${e} FROM grants WHERE ${r}${s.sql}`).all(...n,...s.args)}t(u,"grantRows");async function c(e,r,n=[]){return(await u(e,r,n))[0]}t(c,"grantRow");function l(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(l,"entries");function L(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:n,next:s,step:a}=e;return typeof r!="string"||!D.includes(r)||n!==void 0&&typeof n!="string"||a!==void 0&&typeof a!="string"||typeof s!="string"||!s?null:{state:r,...n===void 0?{}:{message:n},next:s,...a===void 0?{}:{step:a}}}t(L,"dependencyOf");function E(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[n,s]of Object.entries(e)){const a=L(s);a&&(r[n]=a)}return r}t(E,"dependenciesOf");function C(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:n}=e;if(typeof r!="string"||!r||!n||typeof n!="object"||Array.isArray(n))return null;const s={};for(const a of k){const o=n[a];typeof o=="string"&&o&&(s[a]=o)}return Object.keys(s).length?{base_url:r.replace(/\/+$/,""),rungs:s}:null}t(C,"inferenceEndpointOf");function _(e){if(e.computed_rev!==e.rev||e.scopes_json===null||e.resolved_scopes_json===null)throw new TypeError("grant columns are not computed for this revision");return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:l(JSON.parse(e.scopes_json)),resolved_scopes:l(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:E(JSON.parse(e.dependencies_json??"{}")),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?C(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at??""}}t(_,"record");function p(e){if(!e||e.status!=="active")return null;try{return G(_(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(p,"active");function g(e){return e.grantee!=="app:terminal"&&j()}t(g,"mirrorOnly");async function Y(){return(await U()).filter(e=>!g(e))}t(Y,"localGrants");const x="[A-Za-z0-9_-]{1,64}",F=new RegExp(`^(${x}):(${x})$`);function M(e){const r=new Set;for(const n of e)if(n.status==="active")for(const{scope:s}of n.resolved_scopes){const a=F.exec(s);a&&a[1]!=="crtr"&&r.add(a[1])}return[...r].sort()}t(M,"providerIdsFromGrants");async function Z(e){const r=await u("*","sub = ? AND status = 'active'",[e]);return new Set(M(r.flatMap(n=>{try{return[_(n)]}catch(s){if(s instanceof TypeError||s instanceof SyntaxError)return[];throw s}})))}t(Z,"grantedProviderIds");function K(e,r){return e.source_marketplace===null&&e.source_path===null&&r.has(e.name)}t(K,"isProviderBundleRow");async function U(){return(await u("*","true")).flatMap(e=>{try{return[_(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(U,"allGrants");function $(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t($,"grantExpired");function G(e){if(e.status!=="active"||$(e))return null;try{const r=e.resolved_scopes.map(({scope:n})=>h(n));return{...e,resolved:e.resolved_scopes.flatMap(({mode:n},s)=>n==="always"?[r[s]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(G,"activeRecord");function v(e){return Array.isArray(e)?`[${e.map(v).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(v,"canonicalJson");function Q(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return O("sha256").update(v(r)).digest("base64url").slice(0,22)}t(Q,"grantVersion");async function ee(e){return!!await c("1","id = ?",[e])}t(ee,"hasGrant");async function re(e){return await c("rev, status","id = ?",[e])??null}t(re,"grantSyncState");async function ne(e,r){return await c("rev, status","grantee = ? AND sub = ?",[e,r])??null}t(ne,"grantSyncStateFor");async function H(e){const r=await c("*","id = ?",[e]);if(!r)return null;try{return _(r)}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}t(H,"grantRecord");async function te(e){const r=await c("*","id = ?",[e]);if(!r)return{standing:"inactive",sub:null,grantee:null,status:null,removal_done_at:null,record:null};const n={sub:r.sub,grantee:r.grantee,status:r.status,removal_done_at:r.removal_done_at??null};if(r.status!=="active")return{...n,standing:"inactive",record:null};if(r.computed_rev!==r.rev)return{...n,standing:"lagging",record:null};const s=p(r);return s===null?{...n,standing:"inactive",record:null}:{...n,standing:"active",record:s}}t(te,"grantStanding");async function se(e){return(await u("id",e)).map(r=>r.id)}t(se,"grantIdsWhere");async function ae(e){return(e?(await H(e))?.payer:null)??"user"}t(ae,"payerOfGrant");async function W(e){return p(await c("*","id = ?",[e]))}t(W,"activeGrant");async function oe(e,r){return p(await c("*","grantee = ? AND sub = ?",[e,r]))}t(oe,"grantFor");async function ie(e){if(g({grantee:e}))return null;const r=await u("*","grantee = ? AND status = 'active'",[e]);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return p(r[0])}t(ie,"activeGrantForApp");async function ce(e){const r=await W(e);return r&&r.run_notice_url&&!g(r)?{url:r.run_notice_url,sub:r.sub,grantee:r.grantee}:null}t(ce,"runNoticeTarget");async function ue(e){const r=await u("*","grantee = 'app:terminal' AND sub = ? AND status = 'active'",[e]);if(r.length>1)throw new Error(`multiple active terminal grants for ${e}`);return p(r[0])}t(ue,"terminalGrant");async function pe(){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}t(pe,"grantsSource");async function de(e){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}t(de,"hasSyncedGrants");async function le(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}t(le,"dropLocalGrantMirror");async function _e(){return await m(async e=>Number((await e.prepare("UPDATE grants SET computed_rev = rev WHERE computed_rev IS NULL AND scopes_json IS NOT NULL AND resolved_scopes_json IS NOT NULL").run()).changes))}t(_e,"backfillComputedRev");function fe(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(r=>{const{team:n,members:s}=r??{};if(typeof n!="string"||!/^team:\S+$/.test(n)||!Array.isArray(s)||s.some(a=>typeof a!="string"||!/^user:[^\s*]+$/.test(a)))throw new TypeError("invalid grant sync response");return{team:n,members:s}})}t(fe,"teamsOf");async function ye(e,r,n,s=[]){const a=new Set;for(const o of e){if(a.has(o.id)||!o.id||!o.runtime||!o.sub||!o.grantee||!Number.isInteger(o.rev)||!["active","revoked","removed"].includes(o.status))throw new TypeError("invalid grant sync response");a.add(o.id),l(o.scopes),l(o.resolved_scopes)}await m(async o=>{let f=0;const y=[...a];f+=Number((await(y.length?o.prepareUncached(`DELETE FROM grants WHERE id NOT IN (${y.map(()=>"?").join(",")})`).run(...y):o.prepareUncached("DELETE FROM grants").run())).changes);const S=o.prepare(`INSERT INTO grants
1
+ var w=Object.defineProperty;var t=(e,r)=>w(e,"name",{value:r,configurable:!0});import{createHash as O}from"node:crypto";import{parseScope as h}from"@north-light/identity";import{openDb as d,withCanvasWrite as m}from"../canvas/db.js";import{isUnsandboxedMachine as j}from"../runtime/unsandboxed.js";import{replaceWrittenBy as A,userSubject as b}from"../permissions/facts.js";import{GROUP_TYPE as R}from"../permissions/builtin.js";const D=["not_started","pending","ready","failed","unavailable"],k=["ultra","strong","medium","light"];function I(){const e=process.env.CRTR_RUNTIME;return e?{sql:" AND runtime = ?",args:[e]}:{sql:"",args:[]}}t(I,"runtimeScope");async function i(e,r,n=[]){const s=I();return await(await d()).prepare(`SELECT ${e} FROM grants WHERE ${r}${s.sql}`).all(...n,...s.args)}t(i,"grantRows");async function u(e,r,n=[]){return(await i(e,r,n))[0]}t(u,"grantRow");function l(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(l,"entries");function L(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:n,next:s,step:a}=e;return typeof r!="string"||!D.includes(r)||n!==void 0&&typeof n!="string"||a!==void 0&&typeof a!="string"||typeof s!="string"||!s?null:{state:r,...n===void 0?{}:{message:n},next:s,...a===void 0?{}:{step:a}}}t(L,"dependencyOf");function E(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[n,s]of Object.entries(e)){const a=L(s);a&&(r[n]=a)}return r}t(E,"dependenciesOf");function C(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:n}=e;if(typeof r!="string"||!r||!n||typeof n!="object"||Array.isArray(n))return null;const s={};for(const a of k){const o=n[a];typeof o=="string"&&o&&(s[a]=o)}return Object.keys(s).length?{base_url:r.replace(/\/+$/,""),rungs:s}:null}t(C,"inferenceEndpointOf");function _(e){if(e.computed_rev!==e.rev||e.scopes_json===null||e.resolved_scopes_json===null)throw new TypeError("grant columns are not computed for this revision");return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:l(JSON.parse(e.scopes_json)),resolved_scopes:l(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:E(JSON.parse(e.dependencies_json??"{}")),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?C(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at??""}}t(_,"record");function p(e){if(!e||e.status!=="active")return null;try{return $(_(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(p,"active");function g(e){return e.grantee!=="app:terminal"&&j()}t(g,"mirrorOnly");async function Y(){return(await G()).filter(e=>!g(e))}t(Y,"localGrants");const x="[A-Za-z0-9_-]{1,64}",M=new RegExp(`^(${x}):(${x})$`);function F(e){const r=new Set;for(const n of e)if(n.status==="active")for(const{scope:s}of n.resolved_scopes){const a=M.exec(s);a&&a[1]!=="crtr"&&r.add(a[1])}return[...r].sort()}t(F,"providerIdsFromGrants");async function Z(e){const r=await i("*","sub = ? AND status = 'active'",[e]);return new Set(F(r.flatMap(n=>{try{return[_(n)]}catch(s){if(s instanceof TypeError||s instanceof SyntaxError)return[];throw s}})))}t(Z,"grantedProviderIds");function K(e,r){return e.source_marketplace===null&&e.source_path===null&&r.has(e.name)}t(K,"isProviderBundleRow");async function Q(e){return(await i("*","sub = ? AND status = 'active'",[e])).flatMap(n=>{const s=p(n);return s===null?[]:[s]})}t(Q,"activeGrantsOfPerson");async function G(){return(await i("*","true")).flatMap(e=>{try{return[_(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(G,"allGrants");function U(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t(U,"grantExpired");function $(e){if(e.status!=="active"||U(e))return null;try{const r=e.resolved_scopes.map(({scope:n})=>h(n));return{...e,resolved:e.resolved_scopes.flatMap(({mode:n},s)=>n==="always"?[r[s]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t($,"activeRecord");function v(e){return Array.isArray(e)?`[${e.map(v).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(v,"canonicalJson");function ee(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return O("sha256").update(v(r)).digest("base64url").slice(0,22)}t(ee,"grantVersion");async function re(e){return!!await u("1","id = ?",[e])}t(re,"hasGrant");async function ne(e){return await u("rev, status","id = ?",[e])??null}t(ne,"grantSyncState");async function te(e,r){return await u("rev, status","grantee = ? AND sub = ?",[e,r])??null}t(te,"grantSyncStateFor");async function H(e){const r=await u("*","id = ?",[e]);if(!r)return null;try{return _(r)}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}t(H,"grantRecord");async function se(e){const r=await u("*","id = ?",[e]);if(!r)return{standing:"inactive",sub:null,grantee:null,status:null,removal_done_at:null,record:null};const n={sub:r.sub,grantee:r.grantee,status:r.status,removal_done_at:r.removal_done_at??null};if(r.status!=="active")return{...n,standing:"inactive",record:null};if(r.computed_rev!==r.rev)return{...n,standing:"lagging",record:null};const s=p(r);return s===null?{...n,standing:"inactive",record:null}:{...n,standing:"active",record:s}}t(se,"grantStanding");async function ae(e){return(await i("id",e)).map(r=>r.id)}t(ae,"grantIdsWhere");async function oe(e){return(e?(await H(e))?.payer:null)??"user"}t(oe,"payerOfGrant");async function W(e){return p(await u("*","id = ?",[e]))}t(W,"activeGrant");async function ce(e,r){return p(await u("*","grantee = ? AND sub = ?",[e,r]))}t(ce,"grantFor");async function ie(e){if(g({grantee:e}))return null;const r=await i("*","grantee = ? AND status = 'active'",[e]);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return p(r[0])}t(ie,"activeGrantForApp");async function ue(e){const r=await W(e);return r&&r.run_notice_url&&!g(r)?{url:r.run_notice_url,sub:r.sub,grantee:r.grantee}:null}t(ue,"runNoticeTarget");async function pe(e){const r=await i("*","grantee = 'app:terminal' AND sub = ? AND status = 'active'",[e]);if(r.length>1)throw new Error(`multiple active terminal grants for ${e}`);return p(r[0])}t(pe,"terminalGrant");async function de(){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}t(de,"grantsSource");async function le(e){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}t(le,"hasSyncedGrants");async function _e(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}t(_e,"dropLocalGrantMirror");async function fe(){return await m(async e=>Number((await e.prepare("UPDATE grants SET computed_rev = rev WHERE computed_rev IS NULL AND scopes_json IS NOT NULL AND resolved_scopes_json IS NOT NULL").run()).changes))}t(fe,"backfillComputedRev");function ye(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(r=>{const{team:n,members:s}=r??{};if(typeof n!="string"||!/^team:\S+$/.test(n)||!Array.isArray(s)||s.some(a=>typeof a!="string"||!/^user:[^\s*]+$/.test(a)))throw new TypeError("invalid grant sync response");return{team:n,members:s}})}t(ye,"teamsOf");async function ge(e,r,n,s=[]){const a=new Set;for(const o of e){if(a.has(o.id)||!o.id||!o.runtime||!o.sub||!o.grantee||!Number.isInteger(o.rev)||!["active","revoked","removed"].includes(o.status))throw new TypeError("invalid grant sync response");a.add(o.id),l(o.scopes),l(o.resolved_scopes)}await m(async o=>{let f=0;const y=[...a];f+=Number((await(y.length?o.prepareUncached(`DELETE FROM grants WHERE id NOT IN (${y.map(()=>"?").join(",")})`).run(...y):o.prepareUncached("DELETE FROM grants").run())).changes);const S=o.prepare(`INSERT INTO grants
2
2
  (id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
3
3
  status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, inference_endpoint_json, synced_at, computed_rev)
4
4
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -13,4 +13,4 @@ var w=Object.defineProperty;var t=(e,r)=>w(e,"name",{value:r,configurable:!0});i
13
13
  grants.run_notice_url, grants.inference_endpoint_json, grants.computed_rev)
14
14
  IS DISTINCT FROM (excluded.runtime, excluded.sub, excluded.grantee, excluded.rev, excluded.scopes_json, excluded.resolved_scopes_json,
15
15
  excluded.spend_cap, excluded.expires_at, excluded.status, excluded.revoked_at, excluded.approved_at, excluded.payer,
16
- excluded.dependencies_json, excluded.run_notice_url, excluded.inference_endpoint_json, excluded.computed_rev)`);for(const i of e)f+=Number((await S.run(i.id,i.runtime,i.sub,i.grantee,i.rev,JSON.stringify(i.scopes),JSON.stringify(i.resolved_scopes),i.spend_cap,i.expires_at,i.status,i.revoked_at,i.approved_at,i.payer,JSON.stringify(E(i.dependencies)),i.run_notice_url??null,i.inference_endpoint?JSON.stringify(i.inference_endpoint):null,i.synced_at,i.rev)).changes);await A(o,"directory",R,"member",s.flatMap(({team:i,members:T})=>T.map(N=>({resourceId:i,subject:b(N)})))),await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n),f>0&&await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}t(ye,"replaceGrants");async function ge(){const e=await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(ge,"grantsGeneration");export{W as activeGrant,ie as activeGrantForApp,G as activeRecord,U as allGrants,_e as backfillComputedRev,E as dependenciesOf,le as dropLocalGrantMirror,$ as grantExpired,oe as grantFor,se as grantIdsWhere,H as grantRecord,te as grantStanding,re as grantSyncState,ne as grantSyncStateFor,Q as grantVersion,Z as grantedProviderIds,ge as grantsGeneration,pe as grantsSource,ee as hasGrant,de as hasSyncedGrants,C as inferenceEndpointOf,K as isProviderBundleRow,Y as localGrants,g as mirrorOnly,ae as payerOfGrant,M as providerIdsFromGrants,ye as replaceGrants,ce as runNoticeTarget,fe as teamsOf,ue as terminalGrant};
16
+ excluded.dependencies_json, excluded.run_notice_url, excluded.inference_endpoint_json, excluded.computed_rev)`);for(const c of e)f+=Number((await S.run(c.id,c.runtime,c.sub,c.grantee,c.rev,JSON.stringify(c.scopes),JSON.stringify(c.resolved_scopes),c.spend_cap,c.expires_at,c.status,c.revoked_at,c.approved_at,c.payer,JSON.stringify(E(c.dependencies)),c.run_notice_url??null,c.inference_endpoint?JSON.stringify(c.inference_endpoint):null,c.synced_at,c.rev)).changes);await A(o,"directory",R,"member",s.flatMap(({team:c,members:T})=>T.map(N=>({resourceId:c,subject:b(N)})))),await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n),f>0&&await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}t(ge,"replaceGrants");async function ve(){const e=await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(ve,"grantsGeneration");export{W as activeGrant,ie as activeGrantForApp,Q as activeGrantsOfPerson,$ as activeRecord,G as allGrants,fe as backfillComputedRev,E as dependenciesOf,_e as dropLocalGrantMirror,U as grantExpired,ce as grantFor,ae as grantIdsWhere,H as grantRecord,se as grantStanding,ne as grantSyncState,te as grantSyncStateFor,ee as grantVersion,Z as grantedProviderIds,ve as grantsGeneration,de as grantsSource,re as hasGrant,le as hasSyncedGrants,C as inferenceEndpointOf,K as isProviderBundleRow,Y as localGrants,g as mirrorOnly,oe as payerOfGrant,F as providerIdsFromGrants,ge as replaceGrants,ue as runNoticeTarget,ye as teamsOf,pe as terminalGrant};
@@ -1,6 +1,7 @@
1
1
  /** D106 first boot on a Mac: copy every document text still in the old `<canvas home>/bodies/<hh>/<hash>` files into the `bodies` table.
2
- * A file is copied only when the sha256 of its text equals its name; any other is skipped and reported. Rows go in with
3
- * `ON CONFLICT DO NOTHING` in batches of one transaction each, so an interrupted run resumes and a finished one changes nothing.
2
+ * A file is copied only when the sha256 of its text equals its name; any other is skipped and reported. A text with NULs is stored as `canonical`
3
+ * makes it (NUL → U+FFFD, under the hash of that text) and every revision naming the file's hash is rekeyed to the canonical hash in the same transaction.
4
+ * Rows go in with `ON CONFLICT DO NOTHING` in batches of one transaction each, so an interrupted run resumes and a finished one changes nothing.
4
5
  * The files stay in place for a rollback to the previous build. Cloud roles never call this: their text was never on a disk that survives. */
5
6
  export interface BodiesBootCopyReport {
6
7
  scanned: number;
@@ -12,3 +13,16 @@ export interface BodiesBootCopyReport {
12
13
  }>;
13
14
  }
14
15
  export declare function copyBodyFilesToTable(root?: string): Promise<BodiesBootCopyReport>;
16
+ /** Rows #822 stored under the hash of the text before its NULs became U+FFFD. */
17
+ export interface BodyHashRepairReport {
18
+ rekeyed: number;
19
+ removed: number;
20
+ unresolved: string[];
21
+ }
22
+ /** D106 correction, every role, before readers serve: a `bodies` row whose key is not the sha256 of its text is rekeyed to the canonical hash.
23
+ * Rows are found by the database hashing every text and paged by key. A mismatch is repaired only when an intact original — the retained
24
+ * `<home>/bodies/<hh>/<hash>` file, else a repo ref's body that hashes to its own `body_hash` — becomes exactly the stored text under `storable`.
25
+ * Each page is one transaction: the canonical row is stored (or confirmed to hold the same text), every revision naming the old hash is rekeyed,
26
+ * and the invalid old-key row is deleted — the one place a body row is removed, as correction of an invalid duplicate, because leaving it would
27
+ * keep every future dump invalid. A mismatch with no such original is never rehashed: it is returned in `unresolved`. A re-run changes nothing. */
28
+ export declare function repairBodyHashes(root?: string): Promise<BodyHashRepairReport>;
@@ -1 +1 @@
1
- var m=Object.defineProperty;var r=(n,a)=>m(n,"name",{value:a,configurable:!0});import{existsSync as l,readdirSync as f,readFileSync as d}from"node:fs";import{join as c}from"node:path";import{openDb as u,withCanvasWrite as w}from"../canvas/db.js";import{crtrHome as y}from"../canvas/paths.js";import{hashOf as T,storable as b}from"./bodies.js";const O=200;async function E(n=c(y(),"bodies")){const a={scanned:0,copied:0,mismatched:[],nulReplaced:[]};if(!l(n))return a;const p=new Set((await(await u()).prepare("SELECT hash FROM bodies").all()).map(e=>e.hash));let i=[];const h=r(async()=>{if(i.length===0)return;const e=i;i=[],await w(async t=>{const s=t.prepare("INSERT INTO bodies (hash, text) VALUES (?, ?) ON CONFLICT (hash) DO NOTHING");for(const o of e)a.copied+=(await s.run(o.hash,o.text)).changes})},"flush");for(const e of f(n,{withFileTypes:!0}))if(!(!e.isDirectory()||!/^[0-9a-f]{2}$/.test(e.name)))for(const t of f(c(n,e.name))){if(!/^[0-9a-f]{64}$/.test(t)||(a.scanned+=1,p.has(t)))continue;const s=d(c(n,e.name,t),"utf8");if(T(s)!==t||!t.startsWith(e.name)){a.mismatched.push(t);continue}const o=b(s);o!==s&&a.nulReplaced.push({hash:t,replaced:s.split("\0").length-1}),i.push({hash:t,text:o}),i.length>=O&&await h()}return await h(),a}r(E,"copyBodyFilesToTable");export{E as copyBodyFilesToTable};
1
+ var S=Object.defineProperty;var d=(n,s)=>S(n,"name",{value:s,configurable:!0});import{existsSync as u,readdirSync as E,readFileSync as b}from"node:fs";import{join as f}from"node:path";import{openDb as w,withCanvasWrite as R}from"../canvas/db.js";import{crtrHome as m}from"../canvas/paths.js";import{canonical as x,hashOf as y,store as g,storable as v}from"./bodies.js";import{readTree as D}from"./repo-sync/mirror.js";import{parseDoc as F}from"./repo-sync/ref-format.js";const O=200;async function T(n,s,p){return(await n.prepare("UPDATE revisions SET body_hash = ? WHERE body_hash = ?").run(p,s)).changes}d(T,"rekeyRevisions");async function P(n=f(m(),"bodies")){const s={scanned:0,copied:0,mismatched:[],nulReplaced:[]};if(!u(n))return s;const p=new Set((await(await w()).prepare("SELECT hash FROM bodies").all()).map(o=>o.hash));let r=[];const h=d(async()=>{if(r.length===0)return;const o=r;r=[],await R(async t=>{for(const a of o)await g(t,a.hash,a.text)&&(s.copied+=1),a.file!==a.hash&&await T(t,a.file,a.hash)})},"flush");for(const o of E(n,{withFileTypes:!0}))if(!(!o.isDirectory()||!/^[0-9a-f]{2}$/.test(o.name)))for(const t of E(f(n,o.name))){if(!/^[0-9a-f]{64}$/.test(t)||(s.scanned+=1,p.has(t)))continue;const a=b(f(n,o.name,t),"utf8");if(y(a)!==t||!t.startsWith(o.name)){s.mismatched.push(t);continue}const{text:c,hash:e}=x(a,t);c!==a&&s.nulReplaced.push({hash:t,replaced:a.split("\0").length-1}),r.push({file:t,hash:e,text:c}),r.length>=O&&await h()}return await h(),s}d(P,"copyBodyFilesToTable");const H=["refs/crtr/docs","refs/crtr/remote-docs","refs/crtr/imported"];async function M(n){const s=new Map,p=await(await w()).prepare("SELECT id FROM objects WHERE type = 'repo' AND owner IS NULL").all();for(const r of p){const h=f(m(),"repo-sync",r.id);if(u(f(h,"HEAD")))for(const o of H)for(const[t,a]of await D({dir:h},o)??[]){if(!F(t,a).ok)continue;const c=JSON.parse(a);for(const e of c.revisions)n.has(e.body_hash)&&s.set(e.body_hash,e.body)}}return s}d(M,"peerOriginals");const A=50;async function U(n=f(m(),"bodies")){const s={rekeyed:0,removed:0,unresolved:[]},r=(await w()).prepare(`SELECT hash, text FROM bodies WHERE hash > ? AND encode(sha256(convert_to(text, 'UTF8')), 'hex') <> hash ORDER BY hash LIMIT ${A}`);for(let h="";;){const o=await r.all(h);if(o.length===0)break;h=o[o.length-1].hash;const t=new Map;for(const e of o){const i=f(n,e.hash.slice(0,2),e.hash);if(!u(i))continue;const l=b(i,"utf8");y(l)===e.hash&&t.set(e.hash,l)}const a=new Set(o.filter(e=>!t.has(e.hash)).map(e=>e.hash));if(a.size>0)for(const[e,i]of await M(a))t.set(e,i);const c=o.filter(e=>t.has(e.hash)&&v(t.get(e.hash))===e.text);for(const e of o)c.includes(e)||s.unresolved.push(e.hash);c.length!==0&&await R(async e=>{for(const i of c){const l=y(i.text);await g(e,l,i.text),s.rekeyed+=await T(e,i.hash,l),s.removed+=(await e.prepare("DELETE FROM bodies WHERE hash = ?").run(i.hash)).changes}})}return s}d(U,"repairBodyHashes");export{P as copyBodyFilesToTable,U as repairBodyHashes};
@@ -1,11 +1,20 @@
1
1
  import type { CanvasDb } from '../canvas/db.js';
2
2
  export declare function hashOf(body: string): string;
3
- /** Postgres `text` cannot hold U+0000, so each NUL is stored as U+FFFD. The one rule for a new write and for the Mac boot copy. */
3
+ /** Postgres `text` cannot hold U+0000, so each NUL is stored as U+FFFD. The one rule for a new write, the Mac boot copy and repo sync. */
4
4
  export declare function storable(text: string): string;
5
- /** Store a body and return its hash, which is the hash of the text as stored (see `storable`). Call inside the transaction that writes the
5
+ /** The pair a body is stored and exchanged as: the text `storable` makes of `original` and the hash of that text.
6
+ * A `suppliedHash` is the hash `original` arrived with (a repo ref, a retained file); it is checked against `original`, never used as the key. */
7
+ export declare function canonical(original: string, suppliedHash?: string): {
8
+ text: string;
9
+ hash: string;
10
+ };
11
+ /** Insert `text` under `hash`, or confirm the row already there holds exactly that text: a different text under a key is corruption, not a duplicate.
12
+ * Returns whether a row was added. */
13
+ export declare function store(db: CanvasDb, hash: string, text: string): Promise<boolean>;
14
+ /** Store a body and return its hash, which is the hash of the text as stored (see `canonical`). Call inside the transaction that writes the
6
15
  * revision naming it, so a rolled-back write leaves neither.
7
- * `as` is for text that arrives with the hash its revision already names (repo sync): the row is stored under that hash even when `storable` changed the text. */
8
- export declare function put(db: CanvasDb, body: string, as?: string): Promise<string>;
16
+ * `suppliedHash` is for text that arrives with the hash its source named (repo sync): it must be the hash of `body` as given; the row is keyed by the hash of the stored text. */
17
+ export declare function put(db: CanvasDb, body: string, suppliedHash?: string): Promise<string>;
9
18
  export declare function get(db: CanvasDb, hash: string): Promise<string>;
10
19
  /** The bodies that exist among `hashes`, in one query. A hash with no row is absent from the map. */
11
20
  export declare function findMany(db: CanvasDb, hashes: readonly string[]): Promise<Map<string, string>>;
@@ -1 +1 @@
1
- var c=Object.defineProperty;var r=(t,e)=>c(t,"name",{value:e,configurable:!0});import{createHash as f}from"node:crypto";import{notFound as u}from"../errors.js";const h=/^[0-9a-f]{64}$/;function s(t){if(!h.test(t))throw new Error(`invalid body hash: ${t}`);return t}r(s,"checked");function p(t){return f("sha256").update(t,"utf8").digest("hex")}r(p,"hashOf");function d(t){return t.replaceAll("\0","\uFFFD")}r(d,"storable");async function l(t,e,n){const o=d(e),a=n===void 0?p(o):s(n);return await t.prepare("INSERT INTO bodies (hash, text) VALUES (?, ?) ON CONFLICT (hash) DO NOTHING").run(a,o),a}r(l,"put");async function O(t,e){const n=await t.prepare("SELECT text FROM bodies WHERE hash = ?").get(s(e));if(n===void 0)throw u(`no body ${e}`);return n.text}r(O,"get");async function w(t,e){const n=[...new Set(e)].map(s),o=new Map;if(n.length===0)return o;const a=await t.prepare("SELECT hash, text FROM bodies WHERE hash = ANY(?::text[])").all(n);for(const i of a)o.set(i.hash,i.text);return o}r(w,"findMany");async function b(t,e){const n=await w(t,e);for(const o of e)if(!n.has(o))throw u(`no body ${o}`);return n}r(b,"getMany");export{w as findMany,O as get,b as getMany,p as hashOf,l as put,d as storable};
1
+ var u=Object.defineProperty;var o=(t,e)=>u(t,"name",{value:e,configurable:!0});import{createHash as h}from"node:crypto";import{notFound as c}from"../errors.js";const d=/^[0-9a-f]{64}$/;function s(t){if(!d.test(t))throw new Error(`invalid body hash: ${t}`);return t}o(s,"checked");function f(t){return h("sha256").update(t,"utf8").digest("hex")}o(f,"hashOf");function w(t){return t.replaceAll("\0","\uFFFD")}o(w,"storable");function p(t,e){if(e!==void 0&&f(t)!==s(e))throw new Error(`body does not match its hash ${e}`);const n=w(t);return{text:n,hash:f(n)}}o(p,"canonical");async function x(t,e,n){if((await t.prepare("INSERT INTO bodies (hash, text) VALUES (?, ?) ON CONFLICT (hash) DO NOTHING").run(s(e),n)).changes===1)return!0;const r=await t.prepare("SELECT text FROM bodies WHERE hash = ?").get(e);if(r===void 0||r.text!==n)throw new Error(`body ${e} already holds different text`);return!1}o(x,"store");async function O(t,e,n){const{text:r,hash:a}=p(e,n);return await x(t,a,r),a}o(O,"put");async function F(t,e){const n=await t.prepare("SELECT text FROM bodies WHERE hash = ?").get(s(e));if(n===void 0)throw c(`no body ${e}`);return n.text}o(F,"get");async function E(t,e){const n=[...new Set(e)].map(s),r=new Map;if(n.length===0)return r;const a=await t.prepare("SELECT hash, text FROM bodies WHERE hash = ANY(?::text[])").all(n);for(const i of a)r.set(i.hash,i.text);return r}o(E,"findMany");async function N(t,e){const n=await E(t,e);for(const r of e)if(!n.has(r))throw c(`no body ${r}`);return n}o(N,"getMany");export{p as canonical,E as findMany,F as get,N as getMany,f as hashOf,O as put,w as storable,x as store};
@@ -1,16 +1,16 @@
1
- var se=Object.defineProperty;var s=(t,n)=>se(t,"name",{value:n,configurable:!0});import{randomUUID as ce}from"node:crypto";import{CrtrError as R,notFound as N,usage as l}from"../errors.js";import{ExitCode as g}from"../../types.js";import{get as ue,getMany as we,put as B}from"./bodies.js";import{dropUnresolved as pe,linksOf as le,recordActedOn as O,setLinks as G,setUnresolved as de,unresolvedLinksCouldName as me}from"./edges.js";import{publish as j}from"./events.js";import{readableOrDeleted as fe,resolveName as ye}from"./names.js";import{createObject as he,getById as m}from"./objects.js";import{liveLookups as _e,readerForNode as Ee,readerForObject as ve}from"./reader.js";import{replaceRules as q,setRulesOwner as Re}from"./rules.js";import{recordWatch as x}from"./watches.js";import{withCanvasWrite as y}from"../canvas/db.js";import{PURGE_RETENTION_MS as Ne}from"../storage/purge.js";import{filterAsync as ge,forEachAsync as Oe,mapAsync as A}from"../async-array.js";import{check as L,resolveReader as Se,userOf as K}from"../permissions/evaluator.js";import{missingMemoryScope as Y}from"../permissions/ceiling.js";import{deleteFact as De,parseSubject as Te,writeActsFor as ke,writeFact as je}from"../permissions/facts.js";import{PermissionError as xe}from"../permissions/errors.js";function _(t){return t.class==="node"?t.node:t.class==="app"?t.app:t.class==="object"?t.object:t.person}s(_,"actorOf");async function M(t,n){const e=`user:${await K(t,n)}`;return n.class==="person"?{app:null,for:e}:{app:(await t.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(n.app.id))?.external_id??null,for:e}}s(M,"actingOf");function Ae(t,n){return new R("conflict",t,g.GENERAL,n)}s(Ae,"conflict");function h(t){if(t.type!=="document")throw l(`${t.type} ${t.id} is not a document`)}s(h,"assertDocument");async function V(t,n){return(await t.prepare(`WITH RECURSIVE chain(id, owner, type, depth) AS (
1
+ var se=Object.defineProperty;var s=(e,n)=>se(e,"name",{value:n,configurable:!0});import{randomUUID as ce}from"node:crypto";import{CrtrError as R,notFound as N,usage as d}from"../errors.js";import{ExitCode as g}from"../../types.js";import{get as ue,getMany as we,put as B}from"./bodies.js";import{dropUnresolved as pe,linksOf as le,recordActedOn as O,setLinks as G,setUnresolved as de,unresolvedLinksCouldName as me}from"./edges.js";import{publish as k}from"./events.js";import{readableOrDeleted as fe,resolveName as he}from"./names.js";import{createObject as ye,getById as f}from"./objects.js";import{liveLookups as _e,readerForNode as Ee,readerForObject as ve}from"./reader.js";import{replaceRules as q,setRulesOwner as Re}from"./rules.js";import{recordWatch as x}from"./watches.js";import{withCanvasWrite as h}from"../canvas/db.js";import{PURGE_RETENTION_MS as Ne}from"../storage/purge.js";import{filterAsync as ge,forEachAsync as Oe,mapAsync as L}from"../async-array.js";import{check as A,resolveReader as Se,userOf as K}from"../permissions/evaluator.js";import{missingMemoryScope as Y}from"../permissions/ceiling.js";import{deleteFact as Te,parseSubject as De,writeActsFor as je,writeFact as ke}from"../permissions/facts.js";import{PermissionError as xe}from"../permissions/errors.js";function _(e){return e.class==="node"?e.node:e.class==="app"?e.app:e.class==="object"?e.object:e.person}s(_,"actorOf");async function M(e,n){const t=`user:${await K(e,n)}`;return n.class==="person"?{app:null,for:t}:{app:(await e.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(n.app.id))?.external_id??null,for:t}}s(M,"actingOf");function Le(e,n){return new R("conflict",e,g.GENERAL,n)}s(Le,"conflict");function y(e){if(e.type!=="document")throw d(`${e.type} ${e.id} is not a document`)}s(y,"assertDocument");async function V(e,n){return(await e.prepare(`WITH RECURSIVE chain(id, owner, type, depth) AS (
2
2
  SELECT id, owner, type, 0 FROM objects WHERE id = ?
3
3
  UNION ALL
4
4
  SELECT o.id, o.owner, o.type, c.depth + 1 FROM objects o JOIN chain c ON o.id = c.owner WHERE c.depth < 12
5
- ) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function C(t,n,e){if(e!=="public"&&await V(t,n)==="repo")throw l(`a repo owns only public documents; ${n.name} cannot own a ${e} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(C,"assertPublicInRepo");async function U(t,n,e,a){if(e.trim()===""||e!==e.trim())throw l(`invalid document name ${JSON.stringify(e)}`);const i=await t.prepare("SELECT id FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.id,e);if(i&&i.id!==a?.id)throw Ae(`${n.name} already has a document named ${e}`,{name:e,holder:i.id})}s(U,"assertNameFree");async function W(t,n,e){if(!await L(t,n,"read",e.id))throw N(`no document ${e.id}`)}s(W,"readable");function X(t){return new R("scope_missing",`missing scope ${t}`,g.GENERAL,{scope:t})}s(X,"scopeMissing");async function S(t,n,e){await W(t,n,e);const{source_path:a}=await v(t,e);if(a!==null)throw new R("forbidden",`${e.name} is a built-in document shipped by a package; \`crtr doc\` cannot change it`,g.GENERAL,{id:e.id,next:"Edit it in the source repository of the package that ships it (for crtr's own documents, the crouter repo), not with `crtr doc`."});const i=await Y(t,n,e.id,"rw");if(i!==null)throw X(i);if(!await L(t,n,"edit",e.id))throw new R("forbidden",`you cannot change ${e.name}`,g.GENERAL,{id:e.id})}s(S,"changeable");async function z(t,n){const e=await t.prepare(`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent
6
- WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rev_id);return{rev_id:n.rev_id,seq:n.seq,number:n.number,body_hash:n.body_hash,author:n.author,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,acting_app:n.acting_app,acting_for:n.acting_for,parents:e.map(a=>a.rev_id)}}s(z,"toRevision");async function I(t,n){const e=await t.prepare(`SELECT * FROM revisions r WHERE r.document = ?
7
- AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await A(e,async a=>await z(t,a))}s(I,"heads");function Le(t){return t.map(n=>n.rev_id).sort().join(",")}s(Le,"headsKeyOf");async function v(t,n){const e=await t.prepare("SELECT * FROM documents WHERE object = ?").get(n.id);if(!e)throw N(`no document record for ${n.id}`);return{summary:e.summary,heads_key:e.heads_key,revision:e.revision,why_it_exists:e.why_it_exists,unlisted:e.unlisted===1,lint_ignore:e.lint_ignore_json===null?null:JSON.parse(e.lint_ignore_json),extensions:e.extensions_json===null?null:JSON.parse(e.extensions_json),source_path:e.source_path}}s(v,"documentRecord");async function D(t,n,e){const a=await t.prepare(`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at, acting_app, acting_for)
8
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,e.rev_id??ce(),e.number,e.body_hash,e.author?.id??null,e.rationale,e.verbatim?1:0,e.created_at??new Date().toISOString(),e.acting?.app??null,e.acting?.for??null),i=t.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const r of e.parents)await i.run(a.rev_id,r);return a}s(D,"insertRevision");function Ie(t){const n=new Set;for(const e of t.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(e[1].trim());return[...n]}s(Ie,"linksIn");async function Q(t,n,e){const a=[],i=[];for(const r of e){const o=await ye(t,n,r);"object"in o?a.push({written_as:r,dst:o.object}):"deleted"in o?a.push({written_as:r,dst:o.deleted}):"remote"in o||i.push(r)}return{links:a,unresolved:i}}s(Q,"resolveLinks");async function Z(t,n,e,a){const{links:i,unresolved:r}=await Q(t,n,Ie(a));return await ie(t,await G(t,e,i)),await de(t,e,r),r}s(Z,"setRevisionLinks");async function F(t,n,e){const a=new Map;for(const{src:i,name:r}of await me(t,n.name,n.id))a.set(i,[...a.get(i)??[],r]);for(const[i,r]of a){const o=await m(t,i),c=o?.owner==null?null:await m(t,o.owner);if(!o||o.deleted_at||o.home_machine!==null||!c||c.deleted_at)continue;const p=c.type==="node"?await Ee(t,c.id,e??(async()=>[]),_e(t),!0):await ve(t,o,e),u=(await Q(t,p,r)).links.filter(d=>d.dst.id===n.id);u.length!==0&&(await G(t,o,[...await le(t,o),...u]),await pe(t,o,u.map(d=>d.written_as)))}}s(F,"linkNewlyNamed");async function $e(t,n,e){return n.type==="node"?n.id:await t.prepare(`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.id
5
+ ) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function C(e,n,t){if(t!=="public"&&await V(e,n)==="repo")throw d(`a repo owns only public documents; ${n.name} cannot own a ${t} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(C,"assertPublicInRepo");async function W(e,n,t,a){if(t.trim()===""||t!==t.trim())throw d(`invalid document name ${JSON.stringify(t)}`);const r=await e.prepare("SELECT id FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.id,t);if(r&&r.id!==a?.id)throw Le(`${n.name} already has a document named ${t}`,{name:t,holder:r.id})}s(W,"assertNameFree");async function U(e,n,t){if(!await A(e,n,"read",t.id))throw N(`no document ${t.id}`)}s(U,"readable");function X(e){return new R("scope_missing",`missing scope ${e}`,g.GENERAL,{scope:e})}s(X,"scopeMissing");async function S(e,n,t){await U(e,n,t);const{source_path:a}=await v(e,t);if(a!==null)throw new R("forbidden",`${t.name} is a built-in document shipped by a package; \`crtr doc\` cannot change it`,g.GENERAL,{id:t.id,next:"Edit it in the source repository of the package that ships it (for crtr's own documents, the crouter repo), not with `crtr doc`."});const r=await Y(e,n,t.id,"rw");if(r!==null)throw X(r);if(!await A(e,n,"edit",t.id))throw new R("forbidden",`you cannot change ${t.name}`,g.GENERAL,{id:t.id})}s(S,"changeable");async function z(e,n){const t=await e.prepare(`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent
6
+ WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rev_id);return{rev_id:n.rev_id,seq:n.seq,number:n.number,body_hash:n.body_hash,author:n.author,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,acting_app:n.acting_app,acting_for:n.acting_for,parents:t.map(a=>a.rev_id)}}s(z,"toRevision");async function I(e,n){const t=await e.prepare(`SELECT * FROM revisions r WHERE r.document = ?
7
+ AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await L(t,async a=>await z(e,a))}s(I,"heads");function Ae(e){return e.map(n=>n.rev_id).sort().join(",")}s(Ae,"headsKeyOf");async function v(e,n){const t=await e.prepare("SELECT * FROM documents WHERE object = ?").get(n.id);if(!t)throw N(`no document record for ${n.id}`);return{summary:t.summary,heads_key:t.heads_key,revision:t.revision,why_it_exists:t.why_it_exists,unlisted:t.unlisted===1,lint_ignore:t.lint_ignore_json===null?null:JSON.parse(t.lint_ignore_json),extensions:t.extensions_json===null?null:JSON.parse(t.extensions_json),source_path:t.source_path}}s(v,"documentRecord");async function T(e,n,t){const a=await e.prepare(`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at, acting_app, acting_for)
8
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,t.rev_id??ce(),t.number,t.body_hash,t.author?.id??null,t.rationale,t.verbatim?1:0,t.created_at??new Date().toISOString(),t.acting?.app??null,t.acting?.for??null),r=e.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const i of t.parents)await r.run(a.rev_id,i);return a}s(T,"insertRevision");function Ie(e){const n=new Set;for(const t of e.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(t[1].trim());return[...n]}s(Ie,"linksIn");async function Q(e,n,t){const a=[],r=[];for(const i of t){const o=await he(e,n,i);"object"in o?a.push({written_as:i,dst:o.object}):"deleted"in o?a.push({written_as:i,dst:o.deleted}):"remote"in o||r.push(i)}return{links:a,unresolved:r}}s(Q,"resolveLinks");async function Z(e,n,t,a){const{links:r,unresolved:i}=await Q(e,n,Ie(a));return await ie(e,await G(e,t,r)),await de(e,t,i),i}s(Z,"setRevisionLinks");async function F(e,n,t){const a=new Map;for(const{src:r,name:i}of await me(e,n.name,n.id))a.set(r,[...a.get(r)??[],i]);for(const[r,i]of a){const o=await f(e,r),u=o?.owner==null?null:await f(e,o.owner);if(!o||o.deleted_at||o.home_machine!==null||!u||u.deleted_at)continue;const w=u.type==="node"?await Ee(e,u.id,t??(async()=>[]),_e(e),!0):await ve(e,o,t),c=(await Q(e,w,i)).links.filter(m=>m.dst.id===n.id);c.length!==0&&(await G(e,o,[...await le(e,o),...c]),await pe(e,o,c.map(m=>m.written_as)))}}s(F,"linkNewlyNamed");async function $e(e,n,t){return n.type==="node"?n.id:await e.prepare(`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.id
9
9
  WHERE o.name = ? AND o.type <> ? AND o.type IN ('person','app','repo','profile')
10
- AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,e)?`${n.type}:${n.name}`:n.name}s($e,"ownerPrefix");async function b(t,n,e){const a=await t.prepare(`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
11
- WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(a.length===0)return e;const i=new Map;for(const r of a){if(r.deleted_at||r.written_as===r.id)continue;const o=!r.written_as.includes("/");if(o&&r.name===r.written_as)continue;const c=r.owner===null?null:await m(t,r.owner),p=c?`${await $e(t,c,r.name)}/${r.name}`:r.name,u=o&&r.owner===n.owner?r.name:p;u!==r.written_as&&i.set(r.written_as,u)}return i.size===0?e:e.replace(/\[\[([^[\]\n]+?)\]\]/g,(r,o)=>{const c=i.get(o.trim());return c===void 0?r:`[[${c}]]`})}s(b,"withCurrentNames");async function J(t,n,e){if(e.preview!==void 0){if(e.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await t.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(e.preview,n.id)}e.privacy!==void 0&&(await C(t,await m(t,n.owner),e.privacy),await t.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(e.privacy,n.id));const a=s(async(r,o)=>await t.prepareUncached(`UPDATE documents SET ${r} = ? WHERE object = ?`).run(o,n.id),"set");e.summary!==void 0&&await a("summary",e.summary),e.whyItExists!==void 0&&await a("why_it_exists",e.whyItExists),e.unlisted!==void 0&&await a("unlisted",e.unlisted?1:0),e.lintIgnore!==void 0&&await a("lint_ignore_json",e.lintIgnore===null?null:JSON.stringify(e.lintIgnore)),e.extensions!==void 0&&await a("extensions_json",e.extensions===null?null:JSON.stringify(e.extensions));const i=await m(t,n.id);return e.rules!==void 0&&await q(t,i,e.rules),i}s(J,"applyDocumentFields");async function ee(t,n,e,a="crtr"){if(n.owner.deleted_at)throw l(`${n.owner.name} is deleted`);if(n.relations?.length&&n.schemaType===void 0)throw new xe(400,"schema_type_required","relations need a schemaType: they are facts about a typed resource",{field:"relations"});if(n.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await C(t,n.owner,n.privacy),await U(t,n.owner,n.name);const i=await he(t,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.id,privacy:n.privacy,name:n.name,preview:n.preview,...n.schemaType!==void 0?{schema_type:n.schemaType}:{}});for(const{relation:o,subject:c}of n.relations??[])await je(t,{resourceType:n.schemaType,resourceId:i.id,relation:o,subject:Te(c),writtenBy:a});await t.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(i.id);const r=await J(t,i,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await F(t,r,e),r}s(ee,"createDocument");async function $(t,n){const e=await I(t,n),a=Le(e),i=Math.max(0,...e.map(r=>r.number));return await t.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(a,i,n.id),{heads_key:a,revision:i,headCount:e.length}}s($,"setHeads");async function rt(t,n,e,a,i){return await y(async()=>{const r=await B(t,a),o=_(n),c=await Y(t,n,e.owner.id,"rw",!0);if(c!==null)throw X(c);const p=await M(t,n),u=await ee(t,e,i,p.app??p.for);e.owner.type==="app"&&e.schemaType===void 0&&await ke(t,u.id,await K(t,n));const d=await D(t,u,{number:1,body_hash:r,author:o,rationale:null,parents:[],acting:p});if(!await L(t,n,"edit",u.id))throw N(`no owner ${e.owner.name} you can write to`);const{heads_key:w}=await $(t,u),f=await Z(t,n,u,a);return await O(t,o,u),await x(t,o,u,"write",{readHeadsKey:w}),await j(t,{event:"created",object:u,actor:o,attrs:{revision:1},pointer:d.rev_id},i),{object:u,revision:1,unresolved:f}},{savepoint:!0})}s(rt,"writeDocument");async function He(t,n,e){return(await t.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(_(n).id,e.id))?.read_heads_key??null}s(He,"readBase");function Me(t,n){let e=t;for(const[a,{old:i,new:r}]of n.entries()){if(i==="")throw l(`block ${a+1}: a replacement needs non-empty old text`,{field:"replace"});const o=e.indexOf(i);if(o<0)throw l(`block ${a+1}: old text not found: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});if(e.indexOf(i,o+1)>=0)throw l(`block ${a+1}: old text occurs more than once: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});e=e.slice(0,o)+r+e.slice(o+i.length)}return e}s(Me,"applyReplacements");async function ot(t,n,e,a,i,r,o={},c){if(h(e),await S(t,n,e),e.deleted_at)throw l(`${e.name} is deleted`);if(r===null)throw new R("read_first",`read ${e.name} before editing it`,g.GENERAL,{id:e.id});if(i.trim()==="")throw l("an edit needs a rationale",{field:"rationale"});const p=await v(t,e);if(p.heads_key!==r)return{stale:{heads_key:p.heads_key,revision:p.revision}};const u=await I(t,e);let d;if("body"in a)d=a.body;else{if(u.length!==1)throw l(`${e.name} has ${u.length} heads and needs a merge; pass the whole merged body`,{heads:u.length});d=Me(await b(t,e,await ue(t,u[0].body_hash)),a.replace)}try{return await y(async()=>{const w=await B(t,d),f=_(n),T=1+Math.max(0,...u.map(E=>E.number)),P=await D(t,e,{number:T,body_hash:w,author:f,rationale:i,verbatim:a.verbatim===!0,parents:u.map(E=>E.rev_id),acting:await M(t,n)}),H=P.rev_id,re=await t.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(H,T,e.id,r);if(Number(re.changes)===0){const E=await v(t,e);throw new te(E.heads_key,E.revision)}const k=await J(t,e,o),oe=await Z(t,n,k,d);return await O(t,f,k),await x(t,f,k,"write",{readHeadsKey:H}),await j(t,{event:"edited",object:k,actor:f,pointer:P.rev_id,attrs:{revision:T,from_revision:p.revision,rationale:i}},c),{revision:T,heads_key:H,unresolved:oe}},{savepoint:!0})}catch(w){if(w instanceof te)return{stale:{heads_key:w.heads_key,revision:w.revision}};throw w}}s(ot,"editDocument");class te extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,e){super("stale"),this.heads_key=n,this.revision=e}}async function st(t,n,e,a,i){if(h(e),n&&(await S(t,n,e),i===void 0||i.trim()===""))throw l("an edit needs a rationale",{field:"rationale"});return await y(async()=>{const r=await J(t,e,a);if(n){const o=_(n),c=await v(t,e),p=await He(t,n,e),u=await I(t,e),d=u[0];await D(t,e,{number:1+Math.max(...u.map(f=>f.number)),body_hash:d.body_hash,author:o,rationale:i,parents:[d.rev_id],acting:await M(t,n)});const{heads_key:w}=await $(t,e);await O(t,o,r),await x(t,o,e,"write",p===c.heads_key?{readHeadsKey:w}:{})}return r},{savepoint:!0})}s(st,"setDocumentRecord");async function Ce(t,n,e,a){const i=_(n),{heads_key:r}=await v(t,e);await x(t,i,e,"read",{readHeadsKey:r}),await j(t,{event:"read",object:e,actor:i,attrs:{}},a)}s(Ce,"recordRead");async function ct(t,n,e,a){return h(e),await W(t,n,e),await y(async()=>{const i=await v(t,e),r=await I(t,e),o=await we(t,r.map(w=>w.body_hash)),c=await A(r,async w=>({...w,body:await b(t,e,o.get(w.body_hash))})),p=await fe(t,await Se(t,n),"o"),d=(await t.prepareUncached(`SELECT e.written_as AS _written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
12
- WHERE e.type = 'links-to' AND e.src = ? AND (${p.sql}) ORDER BY e.written_as`).all(e.id,...p.params)).map(({_written_as:w,...f})=>({written_as:w??"",object:f,deleted:f.deleted_at!==null}));return e.deleted_at||await Ce(t,n,e,a),{object:e,record:i,heads:c,needsMerge:c.length>1,links:d}},{savepoint:!0})}s(ct,"readDocument");async function ut(t,n,e){h(e),await W(t,n,e);const a=await t.prepare("SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, seq DESC").all(e.id);return await A(a,async i=>await z(t,i))}s(ut,"history");async function wt(t,n,e,a,i){if(h(e),e.deleted_at)throw l(`${e.name} is deleted`);return n&&await S(t,n,e),await y(async()=>{const r=a.owner??await m(t,e.owner),o=a.name??e.name;await U(t,r,o,e),await C(t,r,e.privacy),await t.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,r.id,e.id);const c=await m(t,e.id);if(r.id!==e.owner&&(await Re(t,c),n&&!await L(t,n,"edit",c.id)))throw N(`no owner ${r.name} you can move documents to`);return n&&await O(t,_(n),c),(o!==e.name||r.id!==e.owner)&&await F(t,c,i),c},{savepoint:!0})}s(wt,"moveDocument");async function pt(t,n,e,a,i){return h(e),e.deleted_at?e:(n&&await S(t,n,e),await y(async()=>{const r=await ne(t,n,e,a,i);return await Ue(t,e),await ie(t,await ae(t,e.id),a),r},{savepoint:!0}))}s(pt,"deleteDocument");async function Ue(t,n){if(n.schema_type==null)return;const e=await t.prepare(`SELECT resource_type, resource_id, relation, subject_type, subject_id, subject_relation FROM relations
13
- WHERE (resource_type = ? AND resource_id = ?) OR (subject_type = ? AND subject_id = ?)`).all(n.schema_type,n.id,n.schema_type,n.id);for(const a of e)await De(t,{resourceType:a.resource_type,resourceId:a.resource_id,relation:a.relation,subject:{type:a.subject_type,id:a.subject_id,relation:a.subject_relation}})}s(Ue,"deleteFactsOf");async function We(t,n,e,a){return h(n),n.deleted_at?await y(async()=>{await U(t,await m(t,n.owner),e,n),await t.prepare("UPDATE objects SET deleted_at = NULL, name = ? WHERE id = ?").run(e,n.id);const i=await m(t,n.id);return await F(t,i,a),i},{savepoint:!0}):n}s(We,"restoreDocument");async function lt(t,n,e,a=Date.now()){if(h(e),!e.deleted_at)return e;if(Date.parse(e.deleted_at)<a-Ne)throw l(`${e.name} was deleted over 30 days ago and can no longer be undeleted`,{id:e.id,deleted_at:e.deleted_at});const i=e.owner===null?null:await m(t,e.owner);if(i===null||i.deleted_at)throw l(`${e.name} cannot be undeleted while its owner is deleted`,{id:e.id});return await y(async()=>{const r=await We(t,e,e.name);return n&&await S(t,n,r),r},{savepoint:!0})}s(lt,"undeleteDocument");async function ne(t,n,e,a,i){await t.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i??new Date().toISOString(),e.id);const r=await m(t,e.id),o=n?_(n):null;return o&&await O(t,o,r),await j(t,{event:"deleted",object:r,actor:o,attrs:{}},a),await t.prepare("DELETE FROM watches WHERE object = ?").run(e.id),await q(t,r,[]),r}s(ne,"markDeleted");async function ae(t,n){return(await t.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n)).map(({dst:e})=>e)}s(ae,"linkTargets");async function ie(t,n,e){const a=t.prepare(`SELECT 1 FROM objects d JOIN objects w ON w.id = d.owner
10
+ AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,t)?`${n.type}:${n.name}`:n.name}s($e,"ownerPrefix");async function b(e,n,t){const a=await e.prepare(`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
11
+ WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(a.length===0)return t;const r=new Map;for(const i of a){if(i.deleted_at||i.written_as===i.id)continue;const o=!i.written_as.includes("/");if(o&&i.name===i.written_as)continue;const u=i.owner===null?null:await f(e,i.owner),w=u?`${await $e(e,u,i.name)}/${i.name}`:i.name,c=o&&i.owner===n.owner?i.name:w;c!==i.written_as&&r.set(i.written_as,c)}return r.size===0?t:t.replace(/\[\[([^[\]\n]+?)\]\]/g,(i,o)=>{const u=r.get(o.trim());return u===void 0?i:`[[${u}]]`})}s(b,"withCurrentNames");async function J(e,n,t){if(t.preview!==void 0){if(t.preview.trim()==="")throw d("a document needs a preview",{field:"preview"});await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(t.preview,n.id)}t.privacy!==void 0&&(await C(e,await f(e,n.owner),t.privacy),await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(t.privacy,n.id));const a=s(async(i,o)=>await e.prepareUncached(`UPDATE documents SET ${i} = ? WHERE object = ?`).run(o,n.id),"set");t.summary!==void 0&&await a("summary",t.summary),t.whyItExists!==void 0&&await a("why_it_exists",t.whyItExists),t.unlisted!==void 0&&await a("unlisted",t.unlisted?1:0),t.lintIgnore!==void 0&&await a("lint_ignore_json",t.lintIgnore===null?null:JSON.stringify(t.lintIgnore)),t.extensions!==void 0&&await a("extensions_json",t.extensions===null?null:JSON.stringify(t.extensions));const r=await f(e,n.id);return t.rules!==void 0&&await q(e,r,t.rules),r}s(J,"applyDocumentFields");async function ee(e,n,t,a="crtr"){if(n.owner.deleted_at)throw d(`${n.owner.name} is deleted`);if(n.relations?.length&&n.schemaType===void 0)throw new xe(400,"schema_type_required","relations need a schemaType: they are facts about a typed resource",{field:"relations"});if(n.preview.trim()==="")throw d("a document needs a preview",{field:"preview"});await C(e,n.owner,n.privacy),await W(e,n.owner,n.name);const r=await ye(e,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.id,privacy:n.privacy,name:n.name,preview:n.preview,...n.schemaType!==void 0?{schema_type:n.schemaType}:{}});for(const{relation:o,subject:u}of n.relations??[])await ke(e,{resourceType:n.schemaType,resourceId:r.id,relation:o,subject:De(u),writtenBy:a});await e.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(r.id);const i=await J(e,r,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await F(e,i,t),i}s(ee,"createDocument");async function $(e,n){const t=await I(e,n),a=Ae(t),r=Math.max(0,...t.map(i=>i.number));return await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(a,r,n.id),{heads_key:a,revision:r,headCount:t.length}}s($,"setHeads");async function rt(e,n,t,a,r){return await h(async()=>{const i=await B(e,a),o=_(n),u=await Y(e,n,t.owner.id,"rw",!0);if(u!==null)throw X(u);const w=await M(e,n),c=await ee(e,t,r,w.app??w.for);t.owner.type==="app"&&t.schemaType===void 0&&await je(e,c.id,await K(e,n));const m=await T(e,c,{number:1,body_hash:i,author:o,rationale:null,parents:[],acting:w});if(!await A(e,n,"edit",c.id))throw N(`no owner ${t.owner.name} you can write to`);const{heads_key:p}=await $(e,c),l=await Z(e,n,c,a);return await O(e,o,c),await x(e,o,c,"write",{readHeadsKey:p}),await k(e,{event:"created",object:c,actor:o,attrs:{revision:1},pointer:m.rev_id},r),{object:c,revision:1,unresolved:l}},{savepoint:!0})}s(rt,"writeDocument");async function He(e,n,t){return(await e.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(_(n).id,t.id))?.read_heads_key??null}s(He,"readBase");function Me(e,n){let t=e;for(const[a,{old:r,new:i}]of n.entries()){if(r==="")throw d(`block ${a+1}: a replacement needs non-empty old text`,{field:"replace"});const o=t.indexOf(r);if(o<0)throw d(`block ${a+1}: old text not found: ${JSON.stringify(r.slice(0,120))}`,{field:"replace",old:r});if(t.indexOf(r,o+1)>=0)throw d(`block ${a+1}: old text occurs more than once: ${JSON.stringify(r.slice(0,120))}`,{field:"replace",old:r});t=t.slice(0,o)+i+t.slice(o+r.length)}return t}s(Me,"applyReplacements");async function ot(e,n,t,a,r,i,o={},u){if(y(t),await S(e,n,t),t.deleted_at)throw d(`${t.name} is deleted`);if(i===null)throw new R("read_first",`read ${t.name} before editing it`,g.GENERAL,{id:t.id});if(r.trim()==="")throw d("an edit needs a rationale",{field:"rationale"});const w=await v(e,t);if(w.heads_key!==i)return{stale:{heads_key:w.heads_key,revision:w.revision}};const c=await I(e,t);let m;if("body"in a)m=a.body;else{if(c.length!==1)throw d(`${t.name} has ${c.length} heads and needs a merge; pass the whole merged body`,{heads:c.length});m=Me(await b(e,t,await ue(e,c[0].body_hash)),a.replace)}try{return await h(async()=>{const p=await B(e,m),l=_(n),D=1+Math.max(0,...c.map(E=>E.number)),P=await T(e,t,{number:D,body_hash:p,author:l,rationale:r,verbatim:a.verbatim===!0,parents:c.map(E=>E.rev_id),acting:await M(e,n)}),H=P.rev_id,re=await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(H,D,t.id,i);if(Number(re.changes)===0){const E=await v(e,t);throw new te(E.heads_key,E.revision)}const j=await J(e,t,o),oe=await Z(e,n,j,m);return await O(e,l,j),await x(e,l,j,"write",{readHeadsKey:H}),await k(e,{event:"edited",object:j,actor:l,pointer:P.rev_id,attrs:{revision:D,from_revision:w.revision,rationale:r}},u),{revision:D,heads_key:H,unresolved:oe}},{savepoint:!0})}catch(p){if(p instanceof te)return{stale:{heads_key:p.heads_key,revision:p.revision}};throw p}}s(ot,"editDocument");class te extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,t){super("stale"),this.heads_key=n,this.revision=t}}async function st(e,n,t,a,r){if(y(t),n&&(await S(e,n,t),r===void 0||r.trim()===""))throw d("an edit needs a rationale",{field:"rationale"});return await h(async()=>{const i=await J(e,t,a);if(n){const o=_(n),u=await v(e,t),w=await He(e,n,t),c=await I(e,t),m=c[0];await T(e,t,{number:1+Math.max(...c.map(l=>l.number)),body_hash:m.body_hash,author:o,rationale:r,parents:[m.rev_id],acting:await M(e,n)});const{heads_key:p}=await $(e,t);await O(e,o,i),await x(e,o,t,"write",w===u.heads_key?{readHeadsKey:p}:{})}return i},{savepoint:!0})}s(st,"setDocumentRecord");async function Ce(e,n,t,a){const r=_(n),{heads_key:i}=await v(e,t);await x(e,r,t,"read",{readHeadsKey:i}),await k(e,{event:"read",object:t,actor:r,attrs:{}},a)}s(Ce,"recordRead");async function ct(e,n,t,a){return y(t),await U(e,n,t),await h(async()=>{const r=await v(e,t),i=await I(e,t),o=await we(e,i.map(p=>p.body_hash)),u=await L(i,async p=>({...p,body:await b(e,t,o.get(p.body_hash))})),w=await fe(e,await Se(e,n),"o"),m=(await e.prepareUncached(`SELECT e.written_as AS _written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
12
+ WHERE e.type = 'links-to' AND e.src = ? AND (${w.sql}) ORDER BY e.written_as`).all(t.id,...w.params)).map(({_written_as:p,...l})=>({written_as:p??"",object:l,deleted:l.deleted_at!==null}));return t.deleted_at||await Ce(e,n,t,a),{object:t,record:r,heads:u,needsMerge:u.length>1,links:m}},{savepoint:!0})}s(ct,"readDocument");async function ut(e,n,t){y(t),await U(e,n,t);const a=await e.prepare("SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, seq DESC").all(t.id);return await L(a,async r=>await z(e,r))}s(ut,"history");async function wt(e,n,t,a,r){if(y(t),t.deleted_at)throw d(`${t.name} is deleted`);return n&&await S(e,n,t),await h(async()=>{const i=a.owner??await f(e,t.owner),o=a.name??t.name;await W(e,i,o,t),await C(e,i,t.privacy),await e.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,i.id,t.id);const u=await f(e,t.id);if(i.id!==t.owner&&(await Re(e,u),n&&!await A(e,n,"edit",u.id)))throw N(`no owner ${i.name} you can move documents to`);return n&&await O(e,_(n),u),(o!==t.name||i.id!==t.owner)&&await F(e,u,r),u},{savepoint:!0})}s(wt,"moveDocument");async function pt(e,n,t,a,r){return y(t),t.deleted_at?t:(n&&await S(e,n,t),await h(async()=>{const i=await ne(e,n,t,a,r);return await We(e,t),await ie(e,await ae(e,t.id),a),i},{savepoint:!0}))}s(pt,"deleteDocument");async function We(e,n){if(n.schema_type==null)return;const t=await e.prepare(`SELECT resource_type, resource_id, relation, subject_type, subject_id, subject_relation FROM relations
13
+ WHERE (resource_type = ? AND resource_id = ?) OR (subject_type = ? AND subject_id = ?)`).all(n.schema_type,n.id,n.schema_type,n.id);for(const a of t)await Te(e,{resourceType:a.resource_type,resourceId:a.resource_id,relation:a.relation,subject:{type:a.subject_type,id:a.subject_id,relation:a.subject_relation}})}s(We,"deleteFactsOf");async function Ue(e,n,t,a){return y(n),n.deleted_at?await h(async()=>{await W(e,await f(e,n.owner),t,n),await e.prepare("UPDATE objects SET deleted_at = NULL, name = ? WHERE id = ?").run(t,n.id);const r=await f(e,n.id);return await F(e,r,a),r},{savepoint:!0}):n}s(Ue,"restoreDocument");async function lt(e,n,t,a=Date.now()){if(y(t),!t.deleted_at)return t;if(Date.parse(t.deleted_at)<a-Ne)throw d(`${t.name} was deleted over 30 days ago and can no longer be undeleted`,{id:t.id,deleted_at:t.deleted_at});const r=t.owner===null?null:await f(e,t.owner);if(r===null||r.deleted_at)throw d(`${t.name} cannot be undeleted while its owner is deleted`,{id:t.id});return await h(async()=>{const i=await Ue(e,t,t.name);return n&&await S(e,n,i),i},{savepoint:!0})}s(lt,"undeleteDocument");async function ne(e,n,t,a,r){await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(r??new Date().toISOString(),t.id);const i=await f(e,t.id),o=n?_(n):null;return o&&await O(e,o,i),await k(e,{event:"deleted",object:i,actor:o,attrs:{}},a),await e.prepare("DELETE FROM watches WHERE object = ?").run(t.id),await q(e,i,[]),i}s(ne,"markDeleted");async function ae(e,n){return(await e.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n)).map(({dst:t})=>t)}s(ae,"linkTargets");async function ie(e,n,t){const a=e.prepare(`SELECT 1 FROM objects d JOIN objects w ON w.id = d.owner
14
14
  WHERE d.id = ? AND d.type = 'document' AND d.deleted_at IS NULL AND w.deleted_at IS NOT NULL
15
15
  AND NOT EXISTS (SELECT 1 FROM edges e JOIN objects s ON s.id = e.src
16
- WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),i=[...n];for(;i.length>0;){const r=i.pop();await a.get(r)!==void 0&&(await ne(t,null,await m(t,r),e),i.push(...await ae(t,r)))}}s(ie,"collectUnlinked");async function dt(t,n,e){if(n.revisions.length===0)throw l(`import of ${n.name}: no revisions`);return await y(async()=>{const a=await ee(t,n,e);await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.revisions[0].created_at,a.id);let i=null;return await Oe(n.revisions,async(r,o)=>{i=(await D(t,a,{...r,number:o+1,parents:i===null?[]:[i]})).rev_id}),await $(t,a),await m(t,a.id)},{savepoint:!0})}s(dt,"importDocument");async function mt(t,n,e){if(h(n),n.privacy!=="public"&&await V(t,n)==="repo")throw l(`a repo owns only public documents; refusing revisions of ${n.name}`);return await y(async()=>{const a=t.prepare("SELECT rev_id, number, document FROM revisions WHERE rev_id = ?");let i=await ge(e,async c=>!await a.get(c.rev_id));for(;i.length>0;){const c=[];for(const p of i){const u=await A(p.parents,async w=>await a.get(w));if(u.some(w=>w===void 0)){c.push(p);continue}const d=u;if(d.some(w=>w.document!==n.id))throw l(`revision ${p.rev_id} names a parent of another document`);await D(t,n,{...p,author:null,number:1+Math.max(0,...d.map(w=>w.number)),parents:d.map(w=>w.rev_id)})}if(c.length===i.length)throw l(`revisions of ${n.name} name unknown parents: ${c.map(p=>p.rev_id).join(", ")}`);i=c}const{heads_key:r,headCount:o}=await $(t,n);return{heads_key:r,headCount:o}},{savepoint:!0})}s(mt,"insertImportedRevisions");async function ft(t,n){const e=await m(t,n);if(!e||e.type!=="document")throw N(`no document ${n}`);return e}s(ft,"documentById");export{_ as actorOf,J as applyDocumentFields,Me as applyReplacements,ie as collectUnlinked,pt as deleteDocument,ft as documentById,v as documentRecord,ot as editDocument,I as heads,ut as history,dt as importDocument,mt as insertImportedRevisions,Ie as linksIn,wt as moveDocument,He as readBase,ct as readDocument,Ce as recordRead,We as restoreDocument,st as setDocumentRecord,Z as setRevisionLinks,lt as undeleteDocument,b as withCurrentNames,rt as writeDocument};
16
+ WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),r=[...n];for(;r.length>0;){const i=r.pop();await a.get(i)!==void 0&&(await ne(e,null,await f(e,i),t),r.push(...await ae(e,i)))}}s(ie,"collectUnlinked");async function dt(e,n,t){if(n.revisions.length===0)throw d(`import of ${n.name}: no revisions`);return await h(async()=>{const a=await ee(e,n,t);await e.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.revisions[0].created_at,a.id);let r=null;return await Oe(n.revisions,async(i,o)=>{r=(await T(e,a,{...i,number:o+1,parents:r===null?[]:[r]})).rev_id}),await $(e,a),await f(e,a.id)},{savepoint:!0})}s(dt,"importDocument");async function mt(e,n,t){if(y(n),n.privacy!=="public"&&await V(e,n)==="repo")throw d(`a repo owns only public documents; refusing revisions of ${n.name}`);return await h(async()=>{const a=e.prepare("SELECT rev_id, number, document, body_hash FROM revisions WHERE rev_id = ?"),r=e.prepare("SELECT parent FROM revision_parents WHERE child = ?");let i=await ge(t,async w=>{const c=await a.get(w.rev_id);if(c===void 0)return!0;const m=(await r.all(w.rev_id)).map(p=>p.parent).sort().join(",");if(c.document!==n.id||c.body_hash!==w.body_hash||m!==[...w.parents].sort().join(","))throw d(`revision ${w.rev_id} already exists with different content (document, body hash or parents); refusing to overwrite it`);return!1});for(;i.length>0;){const w=[];for(const c of i){const m=await L(c.parents,async l=>await a.get(l));if(m.some(l=>l===void 0)){w.push(c);continue}const p=m;if(p.some(l=>l.document!==n.id))throw d(`revision ${c.rev_id} names a parent of another document`);await T(e,n,{...c,author:null,number:1+Math.max(0,...p.map(l=>l.number)),parents:p.map(l=>l.rev_id)})}if(w.length===i.length)throw d(`revisions of ${n.name} name unknown parents: ${w.map(c=>c.rev_id).join(", ")}`);i=w}const{heads_key:o,headCount:u}=await $(e,n);return{heads_key:o,headCount:u}},{savepoint:!0})}s(mt,"insertImportedRevisions");async function ft(e,n){const t=await f(e,n);if(!t||t.type!=="document")throw N(`no document ${n}`);return t}s(ft,"documentById");export{_ as actorOf,J as applyDocumentFields,Me as applyReplacements,ie as collectUnlinked,pt as deleteDocument,ft as documentById,v as documentRecord,ot as editDocument,I as heads,ut as history,dt as importDocument,mt as insertImportedRevisions,Ie as linksIn,wt as moveDocument,He as readBase,ct as readDocument,Ce as recordRead,Ue as restoreDocument,st as setDocumentRecord,Z as setRevisionLinks,lt as undeleteDocument,b as withCurrentNames,rt as writeDocument};