@north-light/cli 0.3.462 → 0.3.463

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 (49) hide show
  1. package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +2 -2
  2. package/dist/builtin-memory/crouter-plugin/bundles-and-memory.md +22 -1
  3. package/dist/builtin-memory/internal/agent-shaping.md +2 -2
  4. package/dist/builtin-memory/internal/memory-loading.md +5 -5
  5. package/dist/clients/attach/viewer.js +548 -548
  6. package/dist/commands/doc/shared.js +2 -2
  7. package/dist/commands/profile/list.js +1 -1
  8. package/dist/commands/profile/new.js +1 -1
  9. package/dist/commands/profile/project.js +1 -1
  10. package/dist/commands/profile/show.js +1 -1
  11. package/dist/core/feed/feed.js +3 -3
  12. package/dist/core/graph/access.d.ts +4 -0
  13. package/dist/core/graph/access.js +3 -3
  14. package/dist/core/graph/documents.d.ts +5 -2
  15. package/dist/core/graph/documents.js +7 -7
  16. package/dist/core/graph/names.d.ts +1 -1
  17. package/dist/core/graph/names.js +3 -3
  18. package/dist/core/graph/reader.d.ts +8 -0
  19. package/dist/core/graph/reader.js +3 -3
  20. package/dist/core/graph/repo-sync/exchange.d.ts +8 -2
  21. package/dist/core/graph/repo-sync/exchange.js +2 -2
  22. package/dist/core/graph/repo-sync/ref-format.d.ts +2 -2
  23. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  24. package/dist/core/graph/repo-sync/sync.js +2 -2
  25. package/dist/core/graph/spaces/app.js +1 -1
  26. package/dist/core/graph/spaces/own-memory.d.ts +18 -0
  27. package/dist/core/graph/spaces/own-memory.js +1 -0
  28. package/dist/core/graph/spaces/repo.d.ts +2 -1
  29. package/dist/core/graph/spaces/repo.js +1 -1
  30. package/dist/core/graph/types.d.ts +10 -1
  31. package/dist/core/runtime/nodes.js +1 -1
  32. package/dist/core/runtime/roadmap.js +3 -3
  33. package/dist/core/scopes.d.ts +3 -0
  34. package/dist/core/scopes.js +1 -1
  35. package/dist/core/substrate/delivery/corpus.d.ts +1 -1
  36. package/dist/core/substrate/delivery/corpus.js +1 -1
  37. package/dist/core/substrate/delivery/deliver.js +3 -3
  38. package/dist/core/substrate/subject-fields.d.ts +4 -2
  39. package/dist/core/substrate/subject.js +1 -1
  40. package/dist/daemon/api/handlers/docs.js +1 -1
  41. package/dist/daemon/api/handlers/profiles.d.ts +3 -0
  42. package/dist/daemon/api/handlers/profiles.js +1 -1
  43. package/dist/daemon/api/map.js +2 -2
  44. package/dist/daemon/cron/authority.js +1 -1
  45. package/docs/concepts/scopes-and-trust.md +2 -2
  46. package/docs/plugin/bundles-and-memory.md +23 -1
  47. package/package.json +2 -2
  48. package/packages/crouter-identity/package.json +1 -1
  49. package/runtime.lock.json +12 -12
@@ -1,2 +1,2 @@
1
- var f=Object.defineProperty;var o=(e,t)=>f(e,"name",{value:t,configurable:!0});import{usage as m}from"../../core/errors.js";import{ApiError as w}from"../../api/errors.js";import{rethrowAsCliError as c}from"../api-client.js";import{RULE_ON as v,RULE_DELIVER as y,DOC_KINDS as g}from"../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as b}from"../../api/dto/objects.js";const O="A document name (with or without [[ ]]) or a raw object id.",p="node | user | profile | profile:<name> | app | repo | repo:<handle>";function j(e){return{kind:"flag",name:"owner",type:"string",required:!1,constraint:e,focusedHelp:{whenToUse:"Omit it for a document only this node and the nodes it points at need: the writing node owns it and it is deleted with the node, unless a kept document links to it. Give another owner when the document must outlive this node or reach other nodes by itself. A document worth another owner carries reusable current truth: a non-obvious procedure, a gotcha, a lasting preference, a convention across repos. Task notes, conversation summaries and facts the repo already records stay with the node or are not written. Before writing one, search for a document that already covers it (`crtr canvas search`) and revise that one with `crtr doc edit` instead of adding a near copy. When one document grows long, keep the most important content in it and move separate subjects into documents named under it (`topic/part`), each pointed at with a `[[link]]`. Choose the narrowest owner that still reaches the next agent who needs it.",value:`One of ${p}. \`node\` \u2014 the writing node; only this node loads it by rule, other nodes read it when pointed at it; deleted with the node. \`profile\` \u2014 the node's profile, or \`profile:<name>\`; loads for nodes running in that profile; stays on this runtime. \`app\` \u2014 the app that runs the node; kept until deleted; readable by that app's nodes. \`repo\` \u2014 the repo the node works in (from its working directory), or \`repo:<handle>\`; every clone and worktree of the repo shares it, and it syncs to collaborators over the repo's \`refs/crtr/docs\`. A repo owns only public documents: a private or friends document that should load in one repo is owned by \`user\` and carries a rule with \`if: {"repo": "<handle>"}\`. \`user\` \u2014 you, the person; loads for every node by its rules; readable by apps by privacy level. Default: the writing node; a person's own run (not a node) defaults to \`user\`. The owner becomes the start of the document's full name (\`<owner-handle>/<name>\`); \`crtr doc move --owner\` changes it later and keeps the id and every link.`,effects:["Decides who may read and change the document, where it syncs, and what deleting takes it with."]}}}o(j,"ownerParam");const k="`if` is a condition on the receiving node. Fields: `kind`, `mode`, `lifecycle`, `cwd`, `repo` (the handle of a repo the node works in), `profile`, `depth`. A field map is ANDed across fields; dotted fields resolve nested values. A scalar matches by equality (a list value matches when any element is equal); a list matcher matches by membership or overlap; an object matcher takes `eq`, `ne`, `in`, `nin`, `exists`, `contains`, `containsAll`, `containsAny`, `matches`, `imatches`, `gt`, `gte`, `lt`, `lte`. `all`, `any` and `not` combine conditions; field matchers beside them are ANDed in. An empty condition always matches; an unknown operator never matches. `match_frontmatter` uses the same language against the read file's YAML frontmatter.";function S(e){return{kind:"flag",name:"rule",type:"string",required:!1,repeatable:!0,constraint:e,focusedHelp:{whenToUse:"Omit it for most documents: a document with no rule is read when a node is pointed at it (`[[name]]`), finds it with `crtr canvas search`, or sees it in a listing. Add a rule only when the document must load on its own because of what a node is doing; every rule costs context in every node it fires on. Prefer the narrowest trigger: a `file-read` rule with a real glob over `boot` for knowledge about specific code; `boot` for knowledge about a person or process with no file boundary. When the whole document is one sentence, deliver `content` \u2014 a preview line would be longer than the document.",value:`One JSON object per occurrence: \`{"on": \u2026, "match"?: [globs], "match_frontmatter"?: {\u2026}, "if"?: {\u2026}, "deliver": \u2026}\`. \`on\` is the node's activity: ${v.join(" | ")}. \`boot\` \u2014 every session start of the node. \`workspace-open\` \u2014 the node's first message when it works in the document's repo. \`file-read\` \u2014 the node reads a file whose absolute path or basename matches a \`match\` glob, or whose frontmatter matches \`match_frontmatter\`. \`document-read\` \u2014 another document whose name matches is delivered at content, including an explicit read. \`command\` \u2014 a shell segment of a bash command matches (segments split on \`&&\`, \`||\`, \`;\`, \`|\` and newlines outside quotes, leading \`VAR=value\` dropped; \`*\` spans whole whitespace-separated tokens, \`?\` one character, quoted text is opaque). \`pre-command\` \u2014 as \`command\`, but runs before the command: when the node has not read the document, the command does not run and the document is returned instead; the node re-issues the command. \`slash-command\` \u2014 the document is a \`/<name>\` slash command whose body is the injected prompt. \`match\` is required on \`file-read\` (unless \`match_frontmatter\` is given), \`document-read\`, \`command\` and \`pre-command\`, and not allowed on \`boot\`, \`workspace-open\` and \`slash-command\`. `+k+` \`deliver\` is ${y.join(" | ")}: the bare name, the preview line, or the whole body. The flag set replaces the document's whole rule list; on \`crtr doc edit\`, \`--unset rules\` removes every rule.`,effects:["When several rules deliver one document to a node, the highest delivery wins: content over preview over name; content already in the node's context is not delivered again.","A document delivered at content is a read: it records the node as a watcher, so the node is pushed later edits.","The profile's delivery limit per owner caps what `boot` and `workspace-open` rules deliver for that owner.",'A rule that delivers at `preview` requires the preview in the shape "When <situation in the reader\'s work>, read this because <what the reader gains>"; `crtr doc lint` checks it. There the line alone decides whether a node that did not ask for the document opens it.']}}}o(S,"ruleParam");const N={preview:o(e=>({kind:"flag",name:"preview",type:"string",required:!1,constraint:e}),"preview"),kind:{kind:"flag",name:"kind",type:"enum",choices:[...g],required:!1,constraint:"knowledge is consulted for facts or procedures; preference directs behavior. Default knowledge."},summary:{kind:"flag",name:"summary",type:"string",required:!1,constraint:"A very short version of the content, shown in `crtr canvas list`."},privacy:o(e=>({kind:"flag",name:"privacy",type:"enum",choices:[...b],required:!1,constraint:e}),"privacy"),whyItExists:{kind:"flag",name:"why-it-exists",type:"string",required:!1,constraint:"The observed failure this document exists to prevent; shown only with `crtr canvas read --fields`."},unlisted:o(e=>({kind:"flag",name:"unlisted",type:"bool",required:!1,constraint:e}),"unlisted"),lintIgnore:{kind:"flag",name:"lint-ignore",type:"string",required:!1,repeatable:!0,constraint:"A `crtr doc lint` rule name to skip for this document."},extension:{kind:"flag",name:"extension",type:"string",required:!1,repeatable:!0,constraint:"One declared plugin field as `<plugin>.<field>=<json value>`."}};function h(e){return e===void 0?[]:Array.isArray(e)?e.map(String):[String(e)]}o(h,"list");function T(e){const t=[],n={};for(const[r,a]of[["preview","preview"],["kind","kind"],["summary","summary"],["privacy","privacy"],["whyItExists","why_it_exists"]])typeof e[r]=="string"&&(n[a]=e[r]);typeof n.preview=="string"&&/[\r\n]/.test(n.preview)&&t.push("--preview must be one line"),e.unlisted===!0&&(n.unlisted=!0);const s=h(e.lintIgnore);s.length>0&&(n.lint_ignore=s);const i=h(e.rule);if(i.length>0){const r=[];i.forEach((a,u)=>{try{const d=JSON.parse(a);typeof d!="object"||d===null||Array.isArray(d)?t.push(`--rule #${u+1} must be a JSON object`):r.push(d)}catch{t.push(`--rule #${u+1} is not valid JSON: ${a}`)}}),n.rules=r}const l={};for(const r of h(e.extension)){const a=/^([^.=]+)\.([^=]+)=(.*)$/s.exec(r);if(!a){t.push(`--extension ${r} is not <plugin>.<field>=<json value>`);continue}try{(l[a[1]]??={})[a[2]]=JSON.parse(a[3])}catch{t.push(`--extension ${r}: the value after = is not valid JSON`)}}if(Object.keys(l).length>0&&(n.extensions=l),t.length>0)throw m(t.join("; "),{next:"Fix every value above and rerun; `--rule -h` gives the rule grammar."});return n}o(T,"fieldsFromInput");function D(e){const t=e.owner;if(t===void 0)return{};const n=String(t);if(!/^(node|user|profile|app|repo)$|^(profile|repo):.+$/.test(n))throw m(`invalid --owner ${n}`,{field:"owner",next:`Pass one of ${p}.`});return{owner:n}}o(D,"ownerFromInput");function I(e,t){e instanceof w&&(e.code==="read_first"&&c(e,`Read it first with \`crtr canvas read ${t}\`, then rerun the edit.`),e.code==="stale_revision"&&c(e,`Someone edited it after your read. Read it again with \`crtr canvas read ${t}\`, make your change on the current text, and rerun the edit.`)),c(e)}o(I,"rethrowDocError");function R(e){const t=[],n=e.unresolved_links,s=e.remote_links;return n?.length&&t.push(`Unresolved links (no document by these names yet): ${n.map(i=>`[[${i}]]`).join(", ")}`),s?.length&&t.push(`Not on this runtime: ${s.map(i=>`[[${i}]]`).join(", ")} \u2014 each may be on another runtime, or the name may be misspelled.`),t.map(i=>`
2
- ${i}`).join("")}o(R,"renderLinkReport");export{N as FIELD_FLAGS,p as OWNER_VALUES,O as REF_CONSTRAINT,T as fieldsFromInput,D as ownerFromInput,j as ownerParam,R as renderLinkReport,I as rethrowDocError,S as ruleParam};
1
+ var f=Object.defineProperty;var o=(e,t)=>f(e,"name",{value:t,configurable:!0});import{usage as m}from"../../core/errors.js";import{ApiError as w}from"../../api/errors.js";import{rethrowAsCliError as c}from"../api-client.js";import{RULE_ON as g,RULE_DELIVER as v,DOC_KINDS as y}from"../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as b}from"../../api/dto/objects.js";const j="A document name (with or without [[ ]]) or a raw object id.",p="node | user | profile | profile:<name> | app | repo | repo:<handle>";function O(e){return{kind:"flag",name:"owner",type:"string",required:!1,constraint:e,focusedHelp:{whenToUse:"Omit it for a document only this node and the nodes it points at need: the writing node owns it and it is deleted with the node, unless a kept document links to it. Give another owner when the document must outlive this node or reach other nodes by itself. A document worth another owner carries reusable current truth: a non-obvious procedure, a gotcha, a lasting preference, a convention across repos. Task notes, conversation summaries and facts the repo already records stay with the node or are not written. Before writing one, search for a document that already covers it (`crtr canvas search`) and revise that one with `crtr doc edit` instead of adding a near copy. When one document grows long, keep the most important content in it and move separate subjects into documents named under it (`topic/part`), each pointed at with a `[[link]]`. Choose the narrowest owner that still reaches the next agent who needs it.",value:`One of ${p}. \`node\` \u2014 the writing node; only this node loads it by rule, other nodes read it when pointed at it; deleted with the node. \`profile\` \u2014 the node's profile, or \`profile:<name>\`; loads for nodes running in that profile; stays on this runtime. \`app\` \u2014 the app that runs the node; kept until deleted; readable by that app's nodes. \`repo\` \u2014 the repo the node works in (from its working directory), or \`repo:<handle>\`; every clone and worktree of the repo shares it, and it syncs to collaborators over the repo's \`refs/crtr/docs\`. A repo owns only public documents: a private or friends document that should load in one repo is owned by \`user\` and carries a rule with \`if: {"repo": "<handle>"}\`. \`user\` \u2014 you, the person; loads for every node by its rules; readable by apps by privacy level. Default: the writing node; a person's own run (not a node) defaults to \`user\`. The owner becomes the start of the document's full name (\`<owner-handle>/<name>\`); \`crtr doc move --owner\` changes it later and keeps the id and every link.`,effects:["Decides who may read and change the document, where it syncs, and what deleting takes it with."]}}}o(O,"ownerParam");const k="`if` is a condition on the receiving node. Fields: `kind`, `mode`, `lifecycle`, `cwd`, `repo` (the handle of a repo the node is eligible for: those enclosing its cwd and those of its profile's projects; use `cwd` for where it works), `profile`, `depth`. A field map is ANDed across fields; dotted fields resolve nested values. A scalar matches by equality (a list value matches when any element is equal); a list matcher matches by membership or overlap; an object matcher takes `eq`, `ne`, `in`, `nin`, `exists`, `contains`, `containsAll`, `containsAny`, `matches`, `imatches`, `gt`, `gte`, `lt`, `lte`. `all`, `any` and `not` combine conditions; field matchers beside them are ANDed in. An empty condition always matches; an unknown operator never matches. `match_frontmatter` uses the same language against the read file's YAML frontmatter.";function S(e){return{kind:"flag",name:"rule",type:"string",required:!1,repeatable:!0,constraint:e,focusedHelp:{whenToUse:"Omit it for most documents: a document with no rule is read when a node is pointed at it (`[[name]]`), finds it with `crtr canvas search`, or sees it in a listing. Add a rule only when the document must load on its own because of what a node is doing; every rule costs context in every node it fires on. Prefer the narrowest trigger: a `file-read` rule with a real glob over `boot` for knowledge about specific code; `boot` for knowledge about a person or process with no file boundary. When the whole document is one sentence, deliver `content` \u2014 a preview line would be longer than the document.",value:`One JSON object per occurrence: \`{"on": \u2026, "match"?: [globs], "match_frontmatter"?: {\u2026}, "if"?: {\u2026}, "deliver": \u2026}\`. \`on\` is the node's activity: ${g.join(" | ")}. \`boot\` \u2014 every session start of the node. \`workspace-open\` \u2014 the node's first message when it works in the document's repo. \`file-read\` \u2014 the node reads a file whose absolute path or basename matches a \`match\` glob, or whose frontmatter matches \`match_frontmatter\`. \`document-read\` \u2014 another document whose name matches is delivered at content, including an explicit read. \`command\` \u2014 a shell segment of a bash command matches (segments split on \`&&\`, \`||\`, \`;\`, \`|\` and newlines outside quotes, leading \`VAR=value\` dropped; \`*\` spans whole whitespace-separated tokens, \`?\` one character, quoted text is opaque). \`pre-command\` \u2014 as \`command\`, but runs before the command: when the node has not read the document, the command does not run and the document is returned instead; the node re-issues the command. \`slash-command\` \u2014 the document is a \`/<name>\` slash command whose body is the injected prompt. \`match\` is required on \`file-read\` (unless \`match_frontmatter\` is given), \`document-read\`, \`command\` and \`pre-command\`, and not allowed on \`boot\`, \`workspace-open\` and \`slash-command\`. `+k+` \`deliver\` is ${v.join(" | ")}: the bare name, the preview line, or the whole body. The flag set replaces the document's whole rule list; on \`crtr doc edit\`, \`--unset rules\` removes every rule.`,effects:["When several rules deliver one document to a node, the highest delivery wins: content over preview over name; content already in the node's context is not delivered again.","A document delivered at content is a read: it records the node as a watcher, so the node is pushed later edits.","The profile's delivery limit per owner caps what `boot` and `workspace-open` rules deliver for that owner.",'A rule that delivers at `preview` requires the preview in the shape "When <situation in the reader\'s work>, read this because <what the reader gains>"; `crtr doc lint` checks it. There the line alone decides whether a node that did not ask for the document opens it.']}}}o(S,"ruleParam");const N={preview:o(e=>({kind:"flag",name:"preview",type:"string",required:!1,constraint:e}),"preview"),kind:{kind:"flag",name:"kind",type:"enum",choices:[...y],required:!1,constraint:"knowledge is consulted for facts or procedures; preference directs behavior. Default knowledge."},summary:{kind:"flag",name:"summary",type:"string",required:!1,constraint:"A very short version of the content, shown in `crtr canvas list`."},privacy:o(e=>({kind:"flag",name:"privacy",type:"enum",choices:[...b],required:!1,constraint:e}),"privacy"),whyItExists:{kind:"flag",name:"why-it-exists",type:"string",required:!1,constraint:"The observed failure this document exists to prevent; shown only with `crtr canvas read --fields`."},unlisted:o(e=>({kind:"flag",name:"unlisted",type:"bool",required:!1,constraint:e}),"unlisted"),lintIgnore:{kind:"flag",name:"lint-ignore",type:"string",required:!1,repeatable:!0,constraint:"A `crtr doc lint` rule name to skip for this document."},extension:{kind:"flag",name:"extension",type:"string",required:!1,repeatable:!0,constraint:"One declared plugin field as `<plugin>.<field>=<json value>`."}};function h(e){return e===void 0?[]:Array.isArray(e)?e.map(String):[String(e)]}o(h,"list");function T(e){const t=[],n={};for(const[r,a]of[["preview","preview"],["kind","kind"],["summary","summary"],["privacy","privacy"],["whyItExists","why_it_exists"]])typeof e[r]=="string"&&(n[a]=e[r]);typeof n.preview=="string"&&/[\r\n]/.test(n.preview)&&t.push("--preview must be one line"),e.unlisted===!0&&(n.unlisted=!0);const i=h(e.lintIgnore);i.length>0&&(n.lint_ignore=i);const s=h(e.rule);if(s.length>0){const r=[];s.forEach((a,u)=>{try{const d=JSON.parse(a);typeof d!="object"||d===null||Array.isArray(d)?t.push(`--rule #${u+1} must be a JSON object`):r.push(d)}catch{t.push(`--rule #${u+1} is not valid JSON: ${a}`)}}),n.rules=r}const l={};for(const r of h(e.extension)){const a=/^([^.=]+)\.([^=]+)=(.*)$/s.exec(r);if(!a){t.push(`--extension ${r} is not <plugin>.<field>=<json value>`);continue}try{(l[a[1]]??={})[a[2]]=JSON.parse(a[3])}catch{t.push(`--extension ${r}: the value after = is not valid JSON`)}}if(Object.keys(l).length>0&&(n.extensions=l),t.length>0)throw m(t.join("; "),{next:"Fix every value above and rerun; `--rule -h` gives the rule grammar."});return n}o(T,"fieldsFromInput");function D(e){const t=e.owner;if(t===void 0)return{};const n=String(t);if(!/^(node|user|profile|app|repo)$|^(profile|repo):.+$/.test(n))throw m(`invalid --owner ${n}`,{field:"owner",next:`Pass one of ${p}.`});return{owner:n}}o(D,"ownerFromInput");function I(e,t){e instanceof w&&(e.code==="read_first"&&c(e,`Read it first with \`crtr canvas read ${t}\`, then rerun the edit.`),e.code==="stale_revision"&&c(e,`Someone edited it after your read. Read it again with \`crtr canvas read ${t}\`, make your change on the current text, and rerun the edit.`)),c(e)}o(I,"rethrowDocError");function R(e){const t=[],n=e.unresolved_links,i=e.remote_links;return n?.length&&t.push(`Unresolved links (no document by these names yet): ${n.map(s=>`[[${s}]]`).join(", ")}`),i?.length&&t.push(`Not on this runtime: ${i.map(s=>`[[${s}]]`).join(", ")} \u2014 each may be on another runtime, or the name may be misspelled.`),t.map(s=>`
2
+ ${s}`).join("")}o(R,"renderLinkReport");export{N as FIELD_FLAGS,p as OWNER_VALUES,j as REF_CONSTRAINT,T as fieldsFromInput,D as ownerFromInput,O as ownerParam,R as renderLinkReport,I as rethrowDocError,S as ruleParam};
@@ -1 +1 @@
1
- var a=Object.defineProperty;var n=(t,e)=>a(t,"name",{value:e,configurable:!0});import{defineLeaf as s}from"../../core/command.js";import{emitLine as o}from"../../core/io.js";import{cliClient as p}from"../api-client.js";const f=s({name:"list",description:"inventory every profile visible to you and its project relationships",whenToUse:"browse every profile that exists \u2014 one line each with its id, name, explicit project relationships, owning app, paused state, and last-used time. Reach for `profile show` once you have picked one, for its full manifest.",help:{name:"profile list",summary:"every profile the daemon shows the caller (its own app's, plus any another app shared) as JSONL, sorted by name then profile_id",output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"name",type:"string",required:!0,constraint:"Manifest name."},{name:"projects_count",type:"int",required:!0,constraint:"Number of `{path}` project relationships on the manifest."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` relationships in manifest order. `path` defines profile purview."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"paused_at",type:"string | null",required:!0,constraint:"ISO 8601 pause timestamp, or null when the profile is active."},{name:"default_kind",type:"string",required:!0,constraint:"Default node kind for creates that omit --kind; `general` when none is stored."},{name:"last_used_at",type:"string | null",required:!0,constraint:"ISO 8601 timestamp of the most recent selection, or null if never selected."}],outputKind:"jsonl",effects:["None. Read-only."]},run:n(async()=>{const t=(await p().listProfiles()).sort((e,i)=>{const r=e.name.localeCompare(i.name);return r!==0?r:e.id.localeCompare(i.id)});for(const e of t)o({profile_id:e.id,name:e.name,projects_count:e.projects.length,projects:e.projects,app:e.app,paused_at:e.paused_at,default_kind:e.default_kind,last_used_at:e.last_used_at});o({follow_up:"Show one in full with `crtr profile show <profile>`."})},"run")});export{f as listLeaf};
1
+ var a=Object.defineProperty;var n=(t,e)=>a(t,"name",{value:e,configurable:!0});import{defineLeaf as s}from"../../core/command.js";import{emitLine as o}from"../../core/io.js";import{cliClient as p}from"../api-client.js";const f=s({name:"list",description:"inventory every profile visible to you and its project relationships",whenToUse:"browse every profile that exists \u2014 one line each with its id, name, explicit project relationships, owning app, paused state, and last-used time. Reach for `profile show` once you have picked one, for its full manifest.",help:{name:"profile list",summary:"every profile the daemon shows the caller (its own app's, plus any another app shared) as JSONL, sorted by name then profile_id",output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"name",type:"string",required:!0,constraint:"Manifest name."},{name:"projects_count",type:"int",required:!0,constraint:"Number of `{path}` project relationships on the manifest."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` relationships in manifest order. `path` defines profile purview; the repos enclosing each path are in the view of every node of the profile."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"paused_at",type:"string | null",required:!0,constraint:"ISO 8601 pause timestamp, or null when the profile is active."},{name:"default_kind",type:"string",required:!0,constraint:"Default node kind for creates that omit --kind; `general` when none is stored."},{name:"last_used_at",type:"string | null",required:!0,constraint:"ISO 8601 timestamp of the most recent selection, or null if never selected."}],outputKind:"jsonl",effects:["None. Read-only."]},run:n(async()=>{const t=(await p().listProfiles()).sort((e,i)=>{const r=e.name.localeCompare(i.name);return r!==0?r:e.id.localeCompare(i.id)});for(const e of t)o({profile_id:e.id,name:e.name,projects_count:e.projects.length,projects:e.projects,app:e.app,paused_at:e.paused_at,default_kind:e.default_kind,last_used_at:e.last_used_at});o({follow_up:"Show one in full with `crtr profile show <profile>`."})},"run")});export{f as listLeaf};
@@ -1 +1 @@
1
- var l=Object.defineProperty;var i=(e,t)=>l(e,"name",{value:t,configurable:!0});import{defineLeaf as p}from"../../core/command.js";import{usage as n}from"../../core/errors.js";import{resolve as f}from"node:path";import{cliClient as s}from"../api-client.js";import{resolveMetaPairs as c}from"./meta.js";const k=p({name:"new",description:"create a new profile",whenToUse:"you are establishing a new agent identity \u2014 a stable id, a display name, and its own documents (a profile owns documents like any other owner). Pass at most one initial project directory here (the parser has no repeatable-flag support); use `profile project add` afterward for every additional one. Use `--root` (no name) to provision the canonical stable root profile \u2014 the default identity a home falls back to \u2014 idempotently, without spawning a node.",help:{name:"profile new",summary:"create a profile of the caller's app in the scoped store, with its own app directory",params:[{kind:"flag",name:"name",type:"string",required:!1,constraint:"Display name for the profile. Also slugified into the stable profile id \u2014 the id never changes on later rename. Required unless `--root` is given; mutually exclusive with `--root`."},{kind:"flag",name:"root",type:"bool",required:!1,constraint:"Provision the canonical stable root profile (fixed id `root-00000000`, name `root`) \u2014 the fallback default identity for a home \u2014 instead of a fresh named one. Idempotent: re-running returns the existing root. Node-free (no `node new --root` needed). Mutually exclusive with `--name`/`--project`/`--default-kind`/`--meta`."},{kind:"flag",name:"project",type:"path",required:!1,constraint:"One initial project directory. Must already exist and be a directory; resolved to its absolute real path before storing. Creates a purview relationship. Singular \u2014 add more with `crtr profile project add` after creation."},{kind:"flag",name:"default-kind",type:"string",required:!1,constraint:"Installed persona kind for node creates under this profile that omit --kind. Defaults to general when omitted; the daemon validates it against its installed kind registry."},{kind:"flag",name:"meta",type:"string",required:!1,repeatable:!0,constraint:"KEY=VALUE metadata entry (repeat per entry). KEY starts with a letter or digit, followed by letters, digits, `_`, or `-`. Each entry reaches every broker launched under the profile as `CRTR_PROFILE_META_<KEY>` env; readable back via `profile show` \u2014 not for secrets (those go to `profile env`)."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id (`<slug>-<shortid>`). Pass this, or the name, to every other `profile` verb and to `--profile` elsewhere."},{name:"profile_path",type:"string",required:!0,constraint:"Absolute path to the profile directory, `<app space>/profiles/<id>`: its settings root. Documents the profile owns are canvas documents, read with `crtr canvas read`, not files here."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` project relationships in manifest order. `path` defines purview. Empty when --project was omitted."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"default_kind",type:"string",required:!0,constraint:"Default persona kind for node creates that omit --kind; `general` when none is stored."},{name:"metadata",type:"object",required:!0,constraint:"Stored metadata map; empty when --meta was omitted."},{name:"created_at",type:"string",required:!0,constraint:"ISO 8601 creation timestamp."}],outputKind:"object",effects:["Creates a profile row owned by the caller's app (the terminal app for a person) and its directory under that app's space. A name the caller's app already holds is refused."]},run:i(async e=>{const t=e.name,r=e.project,a=e.defaultKind,o=c(e.meta,"meta");if(e.root===!0){if(t!==void 0||r!==void 0||a!==void 0||Object.keys(o).length>0)throw n("`--root` provisions the canonical stable root profile and takes no `--name`/`--project`/`--default-kind`/`--meta`.");return d(await s().ensureProfile("root"))}if(t===void 0)throw n("provide `--name <name>` to create a profile, or `--root` to provision the stable root profile.");return d(await s().createProfile({name:t,...r!==void 0?{projects:[{path:f(r)}]}:{},...a!==void 0?{default_kind:a}:{},...Object.keys(o).length>0?{metadata:o}:{}}))},"run")});function d(e){return{profile_id:e.id,profile_path:e.profile_path,projects:e.projects,app:e.app,default_kind:e.default_kind,metadata:e.metadata,created_at:e.created_at}}i(d,"newProfileOutput");export{k as newLeaf};
1
+ var l=Object.defineProperty;var i=(e,t)=>l(e,"name",{value:t,configurable:!0});import{defineLeaf as p}from"../../core/command.js";import{usage as n}from"../../core/errors.js";import{resolve as f}from"node:path";import{cliClient as s}from"../api-client.js";import{resolveMetaPairs as c}from"./meta.js";const b=p({name:"new",description:"create a new profile",whenToUse:"you are establishing a new agent identity \u2014 a stable id, a display name, and its own documents (a profile owns documents like any other owner). Pass at most one initial project directory here (the parser has no repeatable-flag support); use `profile project add` afterward for every additional one. Use `--root` (no name) to provision the canonical stable root profile \u2014 the default identity a home falls back to \u2014 idempotently, without spawning a node.",help:{name:"profile new",summary:"create a profile of the caller's app in the scoped store, with its own app directory",params:[{kind:"flag",name:"name",type:"string",required:!1,constraint:"Display name for the profile. Also slugified into the stable profile id \u2014 the id never changes on later rename. Required unless `--root` is given; mutually exclusive with `--root`."},{kind:"flag",name:"root",type:"bool",required:!1,constraint:"Provision the canonical stable root profile (fixed id `root-00000000`, name `root`) \u2014 the fallback default identity for a home \u2014 instead of a fresh named one. Idempotent: re-running returns the existing root. Node-free (no `node new --root` needed). Mutually exclusive with `--name`/`--project`/`--default-kind`/`--meta`."},{kind:"flag",name:"project",type:"path",required:!1,constraint:"One initial project directory. Must already exist and be a directory; resolved to its absolute real path before storing. Creates a purview relationship: the repos enclosing it join the view of every node of the profile, with no delivery limit (`profile project add` starts them at `preview`). Singular \u2014 add more with `crtr profile project add` after creation."},{kind:"flag",name:"default-kind",type:"string",required:!1,constraint:"Installed persona kind for node creates under this profile that omit --kind. Defaults to general when omitted; the daemon validates it against its installed kind registry."},{kind:"flag",name:"meta",type:"string",required:!1,repeatable:!0,constraint:"KEY=VALUE metadata entry (repeat per entry). KEY starts with a letter or digit, followed by letters, digits, `_`, or `-`. Each entry reaches every broker launched under the profile as `CRTR_PROFILE_META_<KEY>` env; readable back via `profile show` \u2014 not for secrets (those go to `profile env`)."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id (`<slug>-<shortid>`). Pass this, or the name, to every other `profile` verb and to `--profile` elsewhere."},{name:"profile_path",type:"string",required:!0,constraint:"Absolute path to the profile directory, `<app space>/profiles/<id>`: its settings root. Documents the profile owns are canvas documents, read with `crtr canvas read`, not files here."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` project relationships in manifest order. `path` defines purview and puts the repos enclosing it in the view of every node of the profile. Empty when --project was omitted."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"default_kind",type:"string",required:!0,constraint:"Default persona kind for node creates that omit --kind; `general` when none is stored."},{name:"metadata",type:"object",required:!0,constraint:"Stored metadata map; empty when --meta was omitted."},{name:"created_at",type:"string",required:!0,constraint:"ISO 8601 creation timestamp."}],outputKind:"object",effects:["Creates a profile row owned by the caller's app (the terminal app for a person) and its directory under that app's space. A name the caller's app already holds is refused."]},run:i(async e=>{const t=e.name,r=e.project,a=e.defaultKind,o=c(e.meta,"meta");if(e.root===!0){if(t!==void 0||r!==void 0||a!==void 0||Object.keys(o).length>0)throw n("`--root` provisions the canonical stable root profile and takes no `--name`/`--project`/`--default-kind`/`--meta`.");return d(await s().ensureProfile("root"))}if(t===void 0)throw n("provide `--name <name>` to create a profile, or `--root` to provision the stable root profile.");return d(await s().createProfile({name:t,...r!==void 0?{projects:[{path:f(r)}]}:{},...a!==void 0?{default_kind:a}:{},...Object.keys(o).length>0?{metadata:o}:{}}))},"run")});function d(e){return{profile_id:e.id,profile_path:e.profile_path,projects:e.projects,app:e.app,default_kind:e.default_kind,metadata:e.metadata,created_at:e.created_at}}i(d,"newProfileOutput");export{b as newLeaf};
@@ -1 +1 @@
1
- var p=Object.defineProperty;var t=(e,o)=>p(e,"name",{value:o,configurable:!0});import{defineLeaf as i,defineBranch as d}from"../../core/command.js";import{resolve as s}from"node:path";import{cliClient as a}from"../api-client.js";import{resolveProfileViaDaemon as n}from"./daemon-profiles.js";import{PROFILE_PROJECT_MEMORY_VALUES as l}from"../../api/dto/profiles.js";const c=i({name:"add",description:"add a project to a profile's purview",whenToUse:"a profile needs purview over another project directory \u2014 extend it after creation.",help:{name:"profile project add",summary:"add a project relationship to a profile, deduped by real path",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr."},{kind:"flag",name:"dir",type:"path",required:!0,constraint:"Directory to add. Must exist and be a directory; resolved to its absolute real path before storing. A path already on the profile keeps its position and nothing changes."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"The updated `{path}` relationships in profile order. `path` defines purview."}],outputKind:"object",effects:["Appends a new project relationship to the profile row; purview follows `path`. Running brokers discover the updated projects on their next scope read."]},run:t(async e=>{const{id:o}=await n(e.profile),r=await a().updateProfile(o,{add_project:{path:s(e.dir)}});return{profile_id:r.id,projects:r.projects}},"run")}),m=i({name:"remove",description:"remove a project relationship from a profile's purview",whenToUse:"a profile no longer needs purview over one of its listed project directories \u2014 narrow it back. Works even if the directory itself was since deleted.",help:{name:"profile project remove",summary:"remove a project directory from a profile's manifest",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr."},{kind:"flag",name:"dir",type:"path",required:!0,constraint:"Directory to remove, matched against the stored project `path`. Does not require the directory to still exist on disk. The profile's app home does not change."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"The updated `{path}` relationships in profile order."}],outputKind:"object",effects:["Removes a project relationship from the profile row. This narrows purview. Running brokers discover the updated projects on their next scope read."]},run:t(async e=>{const{id:o}=await n(e.profile),r=await a().updateProfile(o,{remove_project:s(e.dir)});return{profile_id:r.id,projects:r.projects}},"run")}),u=i({name:"limit",description:"set how much of one owner's documents automatic delivery may disclose",whenToUse:"a profile's nodes should get less of one owner's documents (a repo, `user`, an app, a plugin) at boot and workspace-open \u2014 or that limit should be lifted.",help:{name:"profile project limit",summary:"set a profile's automatic delivery limit for one owner handle",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr."},{kind:"flag",name:"owner",type:"string",required:!0,constraint:"Owner the limit applies to. A bare handle (`crouter`, `user`) matches an owner of any type holding that handle. `<type>:<handle>` names one owner exactly \u2014 `repo:crouter`, `profile:work`, `app:terminal`, `person:user` \u2014 and wins over a bare key; use it when a repo and another owner share a handle. A profile's own documents are never capped, whichever key matches."},{kind:"flag",name:"level",type:"enum",choices:[...l],required:!0,constraint:"Maximum automatic delivery of this owner's documents at boot and workspace-open: `none`, `name`, `preview`, or `content`. `content` is no limit and removes the entry. Does not change explicit `crtr canvas read` access."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"delivery_limits",type:"object",required:!0,constraint:"The profile\u2019s limits after the change, keyed by owner handle or `<type>:<handle>`; an owner not listed is not limited."}],outputKind:"object",effects:["Sets or removes one entry in the profile's `delivery_limits`. It caps only automatic `boot` and `workspace-open` delivery; a node's own documents and its own profile's documents are never capped. A `<type>:<handle>` key (for example `repo:crouter`) names one owner exactly and wins over a bare handle key."]},run:t(async e=>{const{id:o}=await n(e.profile),r=await a().updateProfile(o,{set_delivery_limit:{owner:e.owner,level:e.level}});return{profile_id:r.id,delivery_limits:r.delivery_limits}},"run")}),g=d({name:"project",description:"manage a profile's project purview and automatic delivery limits",whenToUse:"changing a profile\u2019s project purview after creation, or limiting how much of one owner's documents its nodes get automatically.",help:{name:"profile project",summary:"manage project purview and per-owner automatic delivery limits",model:"Each `{path}` relationship widens purview: the profile resolves config from `path`. `add` appends a new real path; `remove` narrows purview. `limit` sets the profile's `delivery_limits` entry for one owner handle, which caps only automatic boot and workspace-open delivery of that owner's documents; explicit reads are not capped. Purview and delivery limits are both independent of a per-cwd default pin (`crtr profile default`)."},children:[c,m,u]});export{g as projectBranch};
1
+ var p=Object.defineProperty;var t=(e,o)=>p(e,"name",{value:o,configurable:!0});import{defineLeaf as i,defineBranch as d}from"../../core/command.js";import{resolve as s}from"node:path";import{cliClient as a}from"../api-client.js";import{resolveProfileViaDaemon as n}from"./daemon-profiles.js";import{PROFILE_PROJECT_MEMORY_VALUES as l}from"../../api/dto/profiles.js";const c=i({name:"add",description:"add a project to a profile's purview",whenToUse:"a profile needs purview over another project directory \u2014 extend it after creation.",help:{name:"profile project add",summary:"add a project relationship to a profile, deduped by real path",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr."},{kind:"flag",name:"dir",type:"path",required:!0,constraint:"Directory to add. Must exist and be a directory; resolved to its absolute real path before storing. A path already on the profile keeps its position and nothing changes."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"The updated `{path}` relationships in profile order. `path` defines purview."}],outputKind:"object",effects:["Appends a new project relationship to the profile row. Purview follows `path` for config, and every repo enclosing `path` joins the view of every node of the profile: its documents are readable, listed, and delivered at boot and workspace-open. Each such repo starts at a `preview` delivery limit unless the profile already limits that handle (change it with `profile project limit`). Running brokers discover the updated projects on their next scope read."]},run:t(async e=>{const{id:o}=await n(e.profile),r=await a().updateProfile(o,{add_project:{path:s(e.dir)}});return{profile_id:r.id,projects:r.projects}},"run")}),h=i({name:"remove",description:"remove a project relationship from a profile's purview",whenToUse:"a profile no longer needs purview over one of its listed project directories \u2014 narrow it back. Works even if the directory itself was since deleted.",help:{name:"profile project remove",summary:"remove a project directory from a profile's manifest",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr."},{kind:"flag",name:"dir",type:"path",required:!0,constraint:"Directory to remove, matched against the stored project `path`. Does not require the directory to still exist on disk. The profile's app home does not change."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"The updated `{path}` relationships in profile order."}],outputKind:"object",effects:["Removes a project relationship from the profile row. This narrows purview and drops the path's repos from the view of the profile's nodes (unless a node's cwd is inside them); delivery limits are left in place. Running brokers discover the updated projects on their next scope read."]},run:t(async e=>{const{id:o}=await n(e.profile),r=await a().updateProfile(o,{remove_project:s(e.dir)});return{profile_id:r.id,projects:r.projects}},"run")}),m=i({name:"limit",description:"set how much of one owner's documents automatic delivery may disclose",whenToUse:"a profile's nodes should get less of one owner's documents (a repo, `user`, an app, a plugin) at boot and workspace-open \u2014 or that limit should be lifted.",help:{name:"profile project limit",summary:"set a profile's automatic delivery limit for one owner handle",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr."},{kind:"flag",name:"owner",type:"string",required:!0,constraint:"Owner the limit applies to. A bare handle (`crouter`, `user`) matches an owner of any type holding that handle. `<type>:<handle>` names one owner exactly \u2014 `repo:crouter`, `profile:work`, `app:terminal`, `person:user` \u2014 and wins over a bare key; use it when a repo and another owner share a handle. A profile's own documents are never capped, whichever key matches."},{kind:"flag",name:"level",type:"enum",choices:[...l],required:!0,constraint:"Maximum automatic delivery of this owner's documents at boot and workspace-open: `none`, `name`, `preview`, or `content`. `content` is no limit and removes the entry. Does not change explicit `crtr canvas read` access."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"delivery_limits",type:"object",required:!0,constraint:"The profile\u2019s limits after the change, keyed by owner handle or `<type>:<handle>`; an owner not listed is not limited."}],outputKind:"object",effects:["Sets or removes one entry in the profile's `delivery_limits`. It caps only automatic `boot` and `workspace-open` delivery; a node's own documents and its own profile's documents are never capped. A `<type>:<handle>` key (for example `repo:crouter`) names one owner exactly and wins over a bare handle key."]},run:t(async e=>{const{id:o}=await n(e.profile),r=await a().updateProfile(o,{set_delivery_limit:{owner:e.owner,level:e.level}});return{profile_id:r.id,delivery_limits:r.delivery_limits}},"run")}),g=d({name:"project",description:"manage a profile's project purview and automatic delivery limits",whenToUse:"changing a profile\u2019s project purview after creation, or limiting how much of one owner's documents its nodes get automatically.",help:{name:"profile project",summary:"manage project purview and per-owner automatic delivery limits",model:"Each `{path}` relationship widens purview: the profile resolves config from `path`, and every node of the profile sees the repos enclosing `path` in addition to the repos enclosing its own cwd (cwd repos first, then projects in manifest order). Rule `if: {repo}` conditions hold for any of those repos (cwd repos and project repos); `--owner repo` and write access stay with the cwd's repos. `add` appends a new real path and starts its repos at a `preview` limit; `remove` narrows purview. `limit` sets the profile's `delivery_limits` entry for one owner handle, which caps automatic boot and workspace-open delivery of that owner's documents, whether the repo is in view through the cwd or the project list; explicit reads are not capped. Purview and delivery limits are both independent of a per-cwd default pin (`crtr profile default`)."},children:[c,h,m]});export{g as projectBranch};
@@ -1 +1 @@
1
- var a=Object.defineProperty;var i=(r,e)=>a(r,"name",{value:e,configurable:!0});import{defineLeaf as n}from"../../core/command.js";import{resolveProfileViaDaemon as s}from"./daemon-profiles.js";const l=n({name:"show",description:"one profile's manifest in full",whenToUse:"you need a profile's full project list, owning app, id, and timestamps \u2014 after `profile list` narrows to a name, to find where its nodes will run, or to inspect what a node's bearings named as its profile.",help:{name:"profile show",summary:"print one profile's settings and directory path",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr. A name shared by several profiles fails listing every matching id."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"name",type:"string",required:!0,constraint:"Manifest name."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` relationships in manifest order. `path` defines profile purview."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"paused_at",type:"string | null",required:!0,constraint:"ISO 8601 pause timestamp, or null when the profile is active."},{name:"default_kind",type:"string",required:!0,constraint:"Default node kind for creates that omit --kind; `general` when none is stored."},{name:"metadata",type:"object",required:!0,constraint:"Stored metadata map \u2014 each entry reaches brokers launched under the profile as `CRTR_PROFILE_META_<KEY>` env. Empty when none."},{name:"created_at",type:"string",required:!0,constraint:"ISO 8601 creation timestamp."},{name:"last_used_at",type:"string | null",required:!0,constraint:"ISO 8601 timestamp of the most recent selection, or null if never selected."},{name:"profile_path",type:"string",required:!0,constraint:"Absolute path to the profile directory, `<app space>/profiles/<id>`: its settings root. Documents the profile owns are canvas documents, read with `crtr canvas read`, not files here."},{name:"follow_up",type:"string",required:!0,constraint:"Concrete next commands, including the SQL-backed raw settings surface."}],outputKind:"object",effects:["None. Read-only."]},run:i(async r=>{const e=await s(r.profile),t=e.id;return{profile_id:t,name:e.name,projects:e.projects,app:e.app,paused_at:e.paused_at,default_kind:e.default_kind,metadata:e.metadata,created_at:e.created_at,last_used_at:e.last_used_at,profile_path:e.profile_path,follow_up:e.paused_at===null?`Inspect its SQL-backed settings with \`crtr sys config get-raw --scope profile --profile ${t}\`; manage its purview with \`crtr profile project add ${t} --dir <path>\`, pause it with \`crtr profile pause ${t}\`, or set its default kind with \`crtr profile kind ${t} <kind>\`.`:`Inspect its SQL-backed settings with \`crtr sys config get-raw --scope profile --profile ${t}\`; manage its purview with \`crtr profile project add ${t} --dir <path>\`, or resume it with \`crtr profile resume ${t}\`.`}},"run")});export{l as showLeaf};
1
+ var a=Object.defineProperty;var i=(r,e)=>a(r,"name",{value:e,configurable:!0});import{defineLeaf as n}from"../../core/command.js";import{resolveProfileViaDaemon as o}from"./daemon-profiles.js";const l=n({name:"show",description:"one profile's manifest in full",whenToUse:"you need a profile's full project list, owning app, id, and timestamps \u2014 after `profile list` narrows to a name, to find where its nodes will run, or to inspect what a node's bearings named as its profile.",help:{name:"profile show",summary:"print one profile's settings and directory path",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr. A name shared by several profiles fails listing every matching id."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"name",type:"string",required:!0,constraint:"Manifest name."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` relationships in manifest order. `path` defines profile purview; the repos enclosing each path are in the view of every node of the profile."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"paused_at",type:"string | null",required:!0,constraint:"ISO 8601 pause timestamp, or null when the profile is active."},{name:"default_kind",type:"string",required:!0,constraint:"Default node kind for creates that omit --kind; `general` when none is stored."},{name:"metadata",type:"object",required:!0,constraint:"Stored metadata map \u2014 each entry reaches brokers launched under the profile as `CRTR_PROFILE_META_<KEY>` env. Empty when none."},{name:"created_at",type:"string",required:!0,constraint:"ISO 8601 creation timestamp."},{name:"last_used_at",type:"string | null",required:!0,constraint:"ISO 8601 timestamp of the most recent selection, or null if never selected."},{name:"profile_path",type:"string",required:!0,constraint:"Absolute path to the profile directory, `<app space>/profiles/<id>`: its settings root. Documents the profile owns are canvas documents, read with `crtr canvas read`, not files here."},{name:"follow_up",type:"string",required:!0,constraint:"Concrete next commands, including the SQL-backed raw settings surface."}],outputKind:"object",effects:["None. Read-only."]},run:i(async r=>{const e=await o(r.profile),t=e.id;return{profile_id:t,name:e.name,projects:e.projects,app:e.app,paused_at:e.paused_at,default_kind:e.default_kind,metadata:e.metadata,created_at:e.created_at,last_used_at:e.last_used_at,profile_path:e.profile_path,follow_up:e.paused_at===null?`Inspect its SQL-backed settings with \`crtr sys config get-raw --scope profile --profile ${t}\`; manage its purview with \`crtr profile project add ${t} --dir <path>\`, pause it with \`crtr profile pause ${t}\`, or set its default kind with \`crtr profile kind ${t} <kind>\`.`:`Inspect its SQL-backed settings with \`crtr sys config get-raw --scope profile --profile ${t}\`; manage its purview with \`crtr profile project add ${t} --dir <path>\`, or resume it with \`crtr profile resume ${t}\`.`}},"run")});export{l as showLeaf};
@@ -1,4 +1,4 @@
1
- var N=Object.defineProperty;var i=(e,t)=>N(e,"name",{value:t,configurable:!0});import{prepared as p}from"../sqlite-statements.js";import{cancelCronsOnWake as $,getNode as h}from"../canvas/index.js";import{runStructuredRequestEffects as R,settleStructuredRequestIn as k}from"../canvas/structured-requests.js";import{openDb as y,withCanvasWrite as U}from"../canvas/db.js";import{randomUUID as b}from"node:crypto";import{composeNodeOutcomeDocument as q}from"../runtime/outcome-document.js";import{closeNode as A}from"../runtime/close.js";import{put as F}from"../graph/bodies.js";import{writeDocument as L}from"../graph/documents.js";import{publish as C}from"../graph/events.js";import{nodeObject as v}from"../graph/objects.js";import{readerForNode as z}from"../graph/reader.js";import{endMessageSubscriptions as D}from"../graph/watches.js";import{operationIdContext as M}from"../events/operation-id.js";class W extends Error{static{i(this,"StructuredRequestFinalizationError")}state;constructor(t){super(t==="expired"?"structured_request_expired":"structured_request_settled"),this.state=t}}class g extends Error{static{i(this,"FinalizationError")}code;report;constructor(t,r){super(t),this.code=t,this.report=r}}function B(e){const t=i((r,n=2)=>String(r).padStart(n,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}${t(e.getUTCMilliseconds(),3)}`}i(B,"compactTs");function O(e,t=80){const r=e.split(`
2
- `)[0]??"";return r.length>t?`${r.slice(0,t-1)}\u2026`:r}i(O,"firstLine");function H(e){return e.kind==="final"?{event:"report.final"}:e.result!==void 0?{event:"report.result"}:e.kind==="urgent"?{event:"report.update",level:7}:{event:"report.update",level:e.deliveryTier==="deferred"?3:e.deliveryTier==="urgent"?7:5}}i(H,"eventFor");function S(e,t,r){const n=r.from??t,o=`reports/${B(new Date)}-${b().slice(-8)}-${r.kind}`,u=v(e,t),a=v(e,n),{object:s}=L(e,z(e,n,()=>[]),{owner:u,kind:"report",name:o,preview:O(r.body)||r.kind,privacy:"public"},r.body),c=`${t}/${o}`,{event:_,level:m}=H(r);C(e,{event:_,object:u,actor:a,...m===void 0?{}:{level:m},attrs:{},pointer:s.id,messageBody:{kind:r.kind,label:O(r.body),bodyRef:c,...r.disposition===void 0?{}:{disposition:r.disposition}}});const l=p(e,`SELECT d.destination_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
1
+ var N=Object.defineProperty;var i=(e,t)=>N(e,"name",{value:t,configurable:!0});import{prepared as p}from"../sqlite-statements.js";import{cancelCronsOnWake as $,getNode as h}from"../canvas/index.js";import{runStructuredRequestEffects as R,settleStructuredRequestIn as k}from"../canvas/structured-requests.js";import{openDb as y,withCanvasWrite as U}from"../canvas/db.js";import{randomUUID as b}from"node:crypto";import{composeNodeOutcomeDocument as q}from"../runtime/outcome-document.js";import{closeNode as A}from"../runtime/close.js";import{put as F}from"../graph/bodies.js";import{writeDocument as L}from"../graph/documents.js";import{asRuntimeWriter as W}from"../graph/spaces/own-memory.js";import{publish as C}from"../graph/events.js";import{nodeObject as v}from"../graph/objects.js";import{readerForNode as z}from"../graph/reader.js";import{endMessageSubscriptions as D}from"../graph/watches.js";import{operationIdContext as M}from"../events/operation-id.js";class B extends Error{static{i(this,"StructuredRequestFinalizationError")}state;constructor(t){super(t==="expired"?"structured_request_expired":"structured_request_settled"),this.state=t}}class g extends Error{static{i(this,"FinalizationError")}code;report;constructor(t,r){super(t),this.code=t,this.report=r}}function H(e){const t=i((r,n=2)=>String(r).padStart(n,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}${t(e.getUTCMilliseconds(),3)}`}i(H,"compactTs");function O(e,t=80){const r=e.split(`
2
+ `)[0]??"";return r.length>t?`${r.slice(0,t-1)}\u2026`:r}i(O,"firstLine");function j(e){return e.kind==="final"?{event:"report.final"}:e.result!==void 0?{event:"report.result"}:e.kind==="urgent"?{event:"report.update",level:7}:{event:"report.update",level:e.deliveryTier==="deferred"?3:e.deliveryTier==="urgent"?7:5}}i(j,"eventFor");function S(e,t,r){const n=r.from??t,o=`reports/${H(new Date)}-${b().slice(-8)}-${r.kind}`,u=v(e,t),a=v(e,n),{object:s}=L(e,W(z(e,n,()=>[])),{owner:u,kind:"report",name:o,preview:O(r.body)||r.kind,privacy:"public"},r.body),c=`${t}/${o}`,{event:_,level:m}=j(r);C(e,{event:_,object:u,actor:a,...m===void 0?{}:{level:m},attrs:{},pointer:s.id,messageBody:{kind:r.kind,label:O(r.body),bodyRef:c,...r.disposition===void 0?{}:{disposition:r.disposition}}});const l=p(e,`SELECT d.destination_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
3
3
  JOIN objects receiver ON receiver.id = d.destination_id
4
- WHERE m.body_ref = ? AND receiver.type = 'node' ORDER BY d.destination_id`).all(c).map(w=>w.destination_id);return r.kind==="final"?D(e,u,"final"):r.result?.requestMessageId&&D(e,u,{result:r.result.requestMessageId}),{name:o,ref:c,doc:s,createdAt:s.created_at,deliveredTo:l}}i(S,"writeReport");function j(e,t){const r=y();r.exec("BEGIN IMMEDIATE");try{const n=p(r,"SELECT status, final_report FROM nodes WHERE node_id = ?").get(e);if(n===void 0)throw new g("unknown_node");const o=t.outcome;let u;if(o?.structured!==void 0){const d=k(r,o.structured.requestId,o.structured.declined===void 0?{intended:"completed",result:o.structured.value}:{intended:"declined",declined:o.structured.declined});if(!d.changed||d.row===null||d.row.state!=="completed"&&d.row.state!=="declined")return r.exec("COMMIT"),{written:void 0,...d.row===null?{}:{structured:d.row},structuredRefused:!0};u=d.row}else{const d=r.prepare("SELECT request_id FROM structured_requests WHERE node_id = ? AND state = 'pending'").get(e);if(d!==void 0){const f=k(r,d.request_id,{intended:"failed",failureReason:"finalized"});if(!f.changed||f.row===null||f.row.state!=="failed")return r.exec("COMMIT"),{written:void 0,...f.row===null?{}:{structured:f.row},structuredRefused:!0};u=f.row}}if(n.final_report!==null)throw new g("already_finalized",`${e}/${n.final_report}`);if(n.status!=="active"&&n.status!=="idle"&&n.status!=="done")throw new g("finalization_forbidden");const a=S(r,e,t),s=h(e),c=s?.kind!=="human",_=(s?.outcome_revision??0)+1,m=t.outcome?.kind??"result",l=t.outcome?.reason??"finalized",w=t.outcome?.detailJson??null,E=a.createdAt;if(c){const d=s===null?null:q({node:{...s,status:"done",intent:"done",terminal_reason:l},outcome:{kind:m,reason:l,at:E,revision:_,detailJson:w},finalReport:a.name,...u?.state==="completed"?{structuredResult:u.result}:{}});p(r,"UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = ?, final_report = ?, finalized_at = ?, outcome_kind = ?, outcome_reason = ?, outcome_at = ?, outcome_revision = outcome_revision + 1, outcome_detail_json = ? WHERE node_id = ? AND final_report IS NULL").run(l,a.name,E,m,l,E,w,e),d!==null&&C(r,{event:"outcome",object:v(r,e),actor:null,attrs:{revision:_},messageBody:d})}else p(r,"UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = 'finalized', final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL").run(a.name,E,e);return r.exec("COMMIT"),{written:a,...u===void 0?{}:{structured:u}}}catch(n){throw r.exec("ROLLBACK"),n}}i(j,"commitCanonicalFinal");async function x(e,t){if(!e)throw new Error(`push: empty nodeId (kind=${t.kind}) \u2014 a report must belong to a node`);if(t.kind==="final"&&t.deliveryTier!==void 0)throw new Error("push final does not accept a delivery tier");if(t.kind!=="final"&&t.outcome!==void 0)throw new Error("push outcome is final-only");if(t.kind!=="update"&&t.result!==void 0)throw new Error("a one-off result is an update report");if(F(t.body),t.kind==="final"){const n=j(e,t);if(n.structuredRefused===!0)throw n.structured!==void 0&&R(n.structured),new W(n.structured?.state??"failed");n.structured!==void 0&&t.outcome?.structured?.skipEffects!==!0&&R(n.structured);const{written:o}=n;if($(e),h(e)?.run_id===e){const a=p(y(),"SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ? ORDER BY created DESC").all(e,e);for(const s of a){const c=h(s.node_id);c&&c.status!=="done"&&c.status!=="canceled"&&c.status!=="dead"&&A(s.node_id,{preserveEmpty:!0})}}return{report:o.ref,reportId:o.doc.id,deliveredTo:o.deliveredTo}}const r=U(n=>S(n,e,t));return{report:r.ref,reportId:r.doc.id,deliveredTo:r.deliveredTo}}i(x,"pushWithContext");function T(e,t){return M.current()===void 0?M.fresh(()=>x(e,t)):x(e,t)}i(T,"push");async function oe(e,t,r){return T(e,{kind:"update",body:t,...r})}i(oe,"pushUpdate");async function de(e,t,r){return T(e,{kind:"urgent",body:t,...r})}i(de,"pushUrgent");async function ue(e,t,r){return T(e,{kind:"final",body:t,...r})}i(ue,"pushFinal");export{g as FinalizationError,W as StructuredRequestFinalizationError,T as push,ue as pushFinal,oe as pushUpdate,de as pushUrgent};
4
+ WHERE m.body_ref = ? AND receiver.type = 'node' ORDER BY d.destination_id`).all(c).map(w=>w.destination_id);return r.kind==="final"?D(e,u,"final"):r.result?.requestMessageId&&D(e,u,{result:r.result.requestMessageId}),{name:o,ref:c,doc:s,createdAt:s.created_at,deliveredTo:l}}i(S,"writeReport");function J(e,t){const r=y();r.exec("BEGIN IMMEDIATE");try{const n=p(r,"SELECT status, final_report FROM nodes WHERE node_id = ?").get(e);if(n===void 0)throw new g("unknown_node");const o=t.outcome;let u;if(o?.structured!==void 0){const d=k(r,o.structured.requestId,o.structured.declined===void 0?{intended:"completed",result:o.structured.value}:{intended:"declined",declined:o.structured.declined});if(!d.changed||d.row===null||d.row.state!=="completed"&&d.row.state!=="declined")return r.exec("COMMIT"),{written:void 0,...d.row===null?{}:{structured:d.row},structuredRefused:!0};u=d.row}else{const d=r.prepare("SELECT request_id FROM structured_requests WHERE node_id = ? AND state = 'pending'").get(e);if(d!==void 0){const f=k(r,d.request_id,{intended:"failed",failureReason:"finalized"});if(!f.changed||f.row===null||f.row.state!=="failed")return r.exec("COMMIT"),{written:void 0,...f.row===null?{}:{structured:f.row},structuredRefused:!0};u=f.row}}if(n.final_report!==null)throw new g("already_finalized",`${e}/${n.final_report}`);if(n.status!=="active"&&n.status!=="idle"&&n.status!=="done")throw new g("finalization_forbidden");const a=S(r,e,t),s=h(e),c=s?.kind!=="human",_=(s?.outcome_revision??0)+1,m=t.outcome?.kind??"result",l=t.outcome?.reason??"finalized",w=t.outcome?.detailJson??null,E=a.createdAt;if(c){const d=s===null?null:q({node:{...s,status:"done",intent:"done",terminal_reason:l},outcome:{kind:m,reason:l,at:E,revision:_,detailJson:w},finalReport:a.name,...u?.state==="completed"?{structuredResult:u.result}:{}});p(r,"UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = ?, final_report = ?, finalized_at = ?, outcome_kind = ?, outcome_reason = ?, outcome_at = ?, outcome_revision = outcome_revision + 1, outcome_detail_json = ? WHERE node_id = ? AND final_report IS NULL").run(l,a.name,E,m,l,E,w,e),d!==null&&C(r,{event:"outcome",object:v(r,e),actor:null,attrs:{revision:_},messageBody:d})}else p(r,"UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = 'finalized', final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL").run(a.name,E,e);return r.exec("COMMIT"),{written:a,...u===void 0?{}:{structured:u}}}catch(n){throw r.exec("ROLLBACK"),n}}i(J,"commitCanonicalFinal");async function x(e,t){if(!e)throw new Error(`push: empty nodeId (kind=${t.kind}) \u2014 a report must belong to a node`);if(t.kind==="final"&&t.deliveryTier!==void 0)throw new Error("push final does not accept a delivery tier");if(t.kind!=="final"&&t.outcome!==void 0)throw new Error("push outcome is final-only");if(t.kind!=="update"&&t.result!==void 0)throw new Error("a one-off result is an update report");if(F(t.body),t.kind==="final"){const n=J(e,t);if(n.structuredRefused===!0)throw n.structured!==void 0&&R(n.structured),new B(n.structured?.state??"failed");n.structured!==void 0&&t.outcome?.structured?.skipEffects!==!0&&R(n.structured);const{written:o}=n;if($(e),h(e)?.run_id===e){const a=p(y(),"SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ? ORDER BY created DESC").all(e,e);for(const s of a){const c=h(s.node_id);c&&c.status!=="done"&&c.status!=="canceled"&&c.status!=="dead"&&A(s.node_id,{preserveEmpty:!0})}}return{report:o.ref,reportId:o.doc.id,deliveredTo:o.deliveredTo}}const r=U(n=>S(n,e,t));return{report:r.ref,reportId:r.doc.id,deliveredTo:r.deliveredTo}}i(x,"pushWithContext");function T(e,t){return M.current()===void 0?M.fresh(()=>x(e,t)):x(e,t)}i(T,"push");async function ue(e,t,r){return T(e,{kind:"update",body:t,...r})}i(ue,"pushUpdate");async function se(e,t,r){return T(e,{kind:"urgent",body:t,...r})}i(se,"pushUrgent");async function ce(e,t,r){return T(e,{kind:"final",body:t,...r})}i(ce,"pushFinal");export{g as FinalizationError,B as StructuredRequestFinalizationError,T as push,ce as pushFinal,ue as pushUpdate,se as pushUrgent};
@@ -26,3 +26,7 @@ export declare function readCheck(db: DatabaseSync, object: ObjectRow): (reader:
26
26
  * document, whoever asks. A shipped document is changed by nobody, its own app
27
27
  * included — only the package reconciler writes it, and it asks no reader. */
28
28
  export declare function canChange(db: DatabaseSync, reader: Reader, object: ObjectRow): boolean;
29
+ /** The own-app memory scope a narrowed run lacks for `access` on a document, or null (see `missingOwnMemoryScope`). */
30
+ export declare function missingMemoryScope(db: DatabaseSync, reader: Reader, object: ObjectRow, access: 'read' | 'rw'): string | null;
31
+ /** The same for a document about to be created under `owner`. */
32
+ export declare function missingMemoryScopeUnder(db: DatabaseSync, reader: Reader, owner: ObjectRow, access: 'read' | 'rw'): string | null;
@@ -1,8 +1,8 @@
1
- var l=Object.defineProperty;var n=(o,t)=>l(o,"name",{value:t,configurable:!0});import{prepared as a}from"../sqlite-statements.js";import{SPACES as d}from"./spaces/index.js";import{insideRepo as E}from"./spaces/repo.js";function f(o,t){return a(o,`WITH RECURSIVE chain(rowid, depth) AS (
1
+ var f=Object.defineProperty;var r=(n,t)=>f(n,"name",{value:t,configurable:!0});import{prepared as s}from"../sqlite-statements.js";import{missingOwnMemoryScope as a}from"./spaces/own-memory.js";import{SPACES as h}from"./spaces/index.js";import{insideRepo as d}from"./spaces/repo.js";function E(n,t){return s(n,`WITH RECURSIVE chain(rowid, depth) AS (
2
2
  SELECT owner, 1 FROM objects WHERE rowid = ? AND owner IS NOT NULL
3
3
  UNION ALL
4
4
  SELECT o.owner, c.depth + 1 FROM objects o JOIN chain c ON o.rowid = c.rowid WHERE o.owner IS NOT NULL
5
- ) SELECT o.* FROM chain c CROSS JOIN objects o WHERE o.rowid = c.rowid ORDER BY c.depth`).all(t.rowid)}n(f,"ownerChain");function O(o,t){return c(o,t).space}n(O,"spaceOf");function c(o,t){const e=f(o,t),r=e.length?e[e.length-1]:t,p=d[r.type];if(!p)throw new Error(`${t.type} ${t.id} has no space: its owner chain ends at ${r.type} ${r.id}`);const i=e.length?[...e.slice(0,-1).reverse(),t]:[];return{space:r,path:i,impl:p}}n(c,"locate");function s(o,t){return t.type!=="document"?!1:a(o,"SELECT source_path FROM documents WHERE object = ?").get(t.rowid)?.source_path!=null}n(s,"isShipped");function m(o,t){return a(o,`SELECT a.* FROM objects a WHERE a.type = 'app' AND a.deleted_at IS NULL AND EXISTS (
5
+ ) SELECT o.* FROM chain c CROSS JOIN objects o WHERE o.rowid = c.rowid ORDER BY c.depth`).all(t.rowid)}r(E,"ownerChain");function y(n,t){return c(n,t).space}r(y,"spaceOf");function c(n,t){const o=E(n,t),p=o.length?o[o.length-1]:t,e=h[p.type];if(!e)throw new Error(`${t.type} ${t.id} has no space: its owner chain ends at ${p.type} ${p.id}`);const i=o.length?[...o.slice(0,-1).reverse(),t]:[];return{space:p,path:i,impl:e}}r(c,"locate");function u(n,t){return t.type!=="document"?!1:s(n,"SELECT source_path FROM documents WHERE object = ?").get(t.rowid)?.source_path!=null}r(u,"isShipped");function L(n,t){return s(n,`SELECT a.* FROM objects a WHERE a.type = 'app' AND a.deleted_at IS NULL AND EXISTS (
6
6
  SELECT 1 FROM objects o JOIN documents d ON d.object = o.rowid
7
7
  WHERE o.owner = a.rowid AND o.type = 'document' AND o.deleted_at IS NULL AND d.source_path IS NOT NULL)
8
- ORDER BY a.name = 'crtr', a.name`).all().filter(r=>u(o,t,r))}n(m,"toolApps");function u(o,t,e){return e.type==="app"}n(u,"mayUseTools");function L(o,t,e){return t.class==="person"?!0:t.class==="object"?t.owner.rowid===e.rowid:t.class==="app"?t.app.rowid===e.rowid||e.type==="profile"&&e.owner===t.app.rowid:t.app.rowid===e.rowid||e.type==="profile"&&t.profile?.rowid===e.rowid||e.type==="repo"&&E(o,t,e,"rw")}n(L,"mayActAs");function y(o,t,e){return h(o,e)(t)}n(y,"canRead");function h(o,t){let e=null,r=null;return p=>p.class==="person"||(e??=c(o,t),e.space.type==="app"&&e.path.length>0&&(r??=s(o,t),r&&u(o,p,e.space)))?!0:e.impl.canRead(o,p,e.space,e.path,t)}n(h,"readCheck");function C(o,t,e){if(s(o,e))return!1;const{space:r,path:p,impl:i}=c(o,e);return i.canChange(o,t,r,p,e)}n(C,"canChange");export{C as canChange,y as canRead,s as isShipped,L as mayActAs,u as mayUseTools,f as ownerChain,h as readCheck,O as spaceOf,m as toolApps};
8
+ ORDER BY a.name = 'crtr', a.name`).all().filter(p=>l(n,t,p))}r(L,"toolApps");function l(n,t,o){return o.type==="app"}r(l,"mayUseTools");function C(n,t,o){return t.class==="person"?!0:t.class==="object"?t.owner.rowid===o.rowid:t.class==="app"?t.app.rowid===o.rowid||o.type==="profile"&&o.owner===t.app.rowid:t.app.rowid===o.rowid||o.type==="profile"&&t.profile?.rowid===o.rowid||o.type==="repo"&&d(n,t,o,"rw")}r(C,"mayActAs");function A(n,t,o){return w(n,o)(t)}r(A,"canRead");function w(n,t){let o=null,p=null;return e=>e.class==="person"||(o??=c(n,t),o.space.type==="app"&&o.path.length>0&&(p??=u(n,t),p&&l(n,e,o.space)))?!0:o.impl.canRead(n,e,o.space,o.path,t)}r(w,"readCheck");function I(n,t,o){if(u(n,o))return!1;const{space:p,path:e,impl:i}=c(n,o);return i.canChange(n,t,p,e,o)}r(I,"canChange");function T(n,t,o,p){if(t.class==="person")return null;const{space:e,path:i}=c(n,o);return e.type==="app"&&t.app.rowid===e.rowid?a(n,t,e,i,o,p):null}r(T,"missingMemoryScope");function g(n,t,o,p){if(t.class==="person")return null;const e=c(n,o),{space:i}=e;return i.type!=="app"||t.app.rowid!==i.rowid?null:a(n,t,i,e.path.length===0?[o]:[...e.path],null,p)}r(g,"missingMemoryScopeUnder");export{I as canChange,A as canRead,u as isShipped,C as mayActAs,l as mayUseTools,T as missingMemoryScope,g as missingMemoryScopeUnder,E as ownerChain,w as readCheck,y as spaceOf,L as toolApps};
@@ -124,8 +124,11 @@ export declare function moveDocument(db: DatabaseSync, reader: Reader | null, do
124
124
  }): ObjectRow;
125
125
  /** Mark the document deleted; the row stays with its last preview. Publishes `deleted`
126
126
  * (level 8), then ends the watches on it and drops its rules. A null actor is repo sync or
127
- * node deletion. What it linked to is re-checked by {@link collectUnlinked}. */
128
- export declare function deleteDocument(db: DatabaseSync, reader: Reader | null, doc: ObjectRow, reposFn?: ReposFn): ObjectRow;
127
+ * node deletion; `deletedAt` is the tombstone's own time when sync imports one. What it linked to is re-checked by {@link collectUnlinked}. */
128
+ export declare function deleteDocument(db: DatabaseSync, reader: Reader | null, doc: ObjectRow, reposFn?: ReposFn, deletedAt?: string): ObjectRow;
129
+ /** Repo sync: bring a deleted document back under `name` (checked free first). Revisions, record
130
+ * and links are untouched; the watches and rules deletion dropped are not recreated here. */
131
+ export declare function restoreDocument(db: DatabaseSync, doc: ObjectRow, name: string): ObjectRow;
129
132
  /** Delete each of `targets` that is a kept document nothing links to any more: a live document
130
133
  * whose owner is deleted (node deletion kept it because a live document linked to it) and that
131
134
  * no other live document links to. Each one deleted re-checks what it linked to, so chains of
@@ -1,10 +1,10 @@
1
- var te=Object.defineProperty;var a=(t,n)=>te(t,"name",{value:n,configurable:!0});import{prepared as c}from"../sqlite-statements.js";import{randomUUID as ne}from"node:crypto";import{CrtrError as k,notFound as R,usage as l}from"../errors.js";import{ExitCode as A}from"../../types.js";import{canChange as L,canRead as F,ownerChain as re}from"./access.js";import{bodyOf as oe,get as ie,put as J}from"./bodies.js";import{recordActedOn as N,setLinks as se}from"./edges.js";import{publish as O}from"./events.js";import{resolveName as ae}from"./names.js";import{createObject as ue,getById as ce,getByRowid as f}from"./objects.js";import{replaceRules as B,setRulesOwner as le}from"./rules.js";import{recordWatch as $}from"./watches.js";function _(t){return t.class==="node"?t.node:t.class==="app"?t.app:t.class==="object"?t.object:t.person}a(_,"actorOf");let me=0;function E(t,n){const e=`graph_doc_${++me}`;t.exec(`SAVEPOINT ${e}`);try{const o=n();return t.exec(`RELEASE ${e}`),o}catch(o){throw t.exec(`ROLLBACK TO ${e}`),t.exec(`RELEASE ${e}`),o}}a(E,"atomic");function de(t,n){return new k("conflict",t,A.GENERAL,n)}a(de,"conflict");function y(t){if(t.type!=="document")throw l(`${t.type} ${t.id} is not a document`)}a(y,"assertDocument");function P(t,n){const e=re(t,n);return(e.length?e[e.length-1]:n).type}a(P,"spaceType");function I(t,n,e){if(e!=="public"&&P(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>}`)."})}a(I,"assertPublicInRepo");function K(t,n,e,o){if(e.trim()===""||e!==e.trim())throw l(`invalid document name ${JSON.stringify(e)}`);const r=c(t,"SELECT id FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL").get(n.rowid,e);if(r&&r.id!==o?.id)throw de(`${n.name} already has a document named ${e}`,{name:e,holder:r.id})}a(K,"assertNameFree");function H(t,n,e){if(!F(t,n,e))throw R(`no document ${e.id}`)}a(H,"readable");function D(t,n,e){if(H(t,n,e),!L(t,n,e))throw new k("forbidden",`you cannot change ${e.name}`,A.GENERAL,{id:e.id})}a(D,"changeable");function G(t,n){const e=c(t,`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rowid = rp.parent
2
- WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rowid);return{rowid:n.rowid,rev_id:n.rev_id,number:n.number,body_hash:n.body_hash,author:n.author===null?null:f(t,n.author)?.id??null,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,parents:e.map(o=>o.rev_id)}}a(G,"toRevision");function C(t,n){return c(t,`SELECT * FROM revisions r WHERE r.document = ?
3
- AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid) ORDER BY r.number DESC, r.rev_id`).all(n.rowid).map(o=>G(t,o))}a(C,"heads");function pe(t){return t.map(n=>n.rev_id).sort().join(",")}a(pe,"headsKeyOf");function x(t,n){const e=c(t,"SELECT * FROM documents WHERE object = ?").get(n.rowid);if(!e)throw R(`no document record for ${n.id}`);return{summary:e.summary,heads_key:e.heads_key,revision:e.revision,why_it_exists:e.why_it_exists,unlisted:e.unlisted===1,lint_ignore:e.lint_ignore_json===null?null:JSON.parse(e.lint_ignore_json),extensions:e.extensions_json===null?null:JSON.parse(e.extensions_json),source_path:e.source_path}}a(x,"documentRecord");function T(t,n,e){const o=c(t,`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at)
4
- VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.rowid,e.rev_id??ne(),e.number,e.body_hash,e.author?.rowid??null,e.rationale,e.verbatim?1:0,e.created_at??new Date().toISOString()),r=c(t,"INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const i of e.parents)r.run(o.rowid,i);return o}a(T,"insertRevision");function we(t){const n=new Set;for(const e of t.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(e[1].trim());return[...n]}a(we,"linksIn");function V(t,n,e,o){const r=[],i=[];for(const u of we(o)){const s=ae(t,n,u);"object"in s?r.push({written_as:u,dst:s.object}):"deleted"in s?r.push({written_as:u,dst:s.deleted}):"remote"in s||i.push(u)}return Z(t,se(t,e,r)),i}a(V,"setRevisionLinks");function fe(t,n,e){return n.type==="node"?n.id:c(t,`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.rowid
1
+ var ne=Object.defineProperty;var s=(t,n)=>ne(t,"name",{value:n,configurable:!0});import{prepared as c}from"../sqlite-statements.js";import{randomUUID as re}from"node:crypto";import{CrtrError as O,notFound as R,usage as m}from"../errors.js";import{ExitCode as D}from"../../types.js";import{canChange as k,canRead as J,missingMemoryScope as oe,missingMemoryScopeUnder as ie,ownerChain as se}from"./access.js";import{bodyOf as ae,get as ue,put as P}from"./bodies.js";import{recordActedOn as N,setLinks as ce}from"./edges.js";import{publish as x}from"./events.js";import{resolveName as le}from"./names.js";import{createObject as me,getById as we,getByRowid as d}from"./objects.js";import{replaceRules as B,setRulesOwner as pe}from"./rules.js";import{recordWatch as $}from"./watches.js";function y(t){return t.class==="node"?t.node:t.class==="app"?t.app:t.class==="object"?t.object:t.person}s(y,"actorOf");let de=0;function E(t,n){const e=`graph_doc_${++de}`;t.exec(`SAVEPOINT ${e}`);try{const r=n();return t.exec(`RELEASE ${e}`),r}catch(r){throw t.exec(`ROLLBACK TO ${e}`),t.exec(`RELEASE ${e}`),r}}s(E,"atomic");function fe(t,n){return new O("conflict",t,D.GENERAL,n)}s(fe,"conflict");function _(t){if(t.type!=="document")throw m(`${t.type} ${t.id} is not a document`)}s(_,"assertDocument");function K(t,n){const e=se(t,n);return(e.length?e[e.length-1]:n).type}s(K,"spaceType");function I(t,n,e){if(e!=="public"&&K(t,n)==="repo")throw m(`a repo owns only public documents; ${n.name} cannot own a ${e} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(I,"assertPublicInRepo");function H(t,n,e,r){if(e.trim()===""||e!==e.trim())throw m(`invalid document name ${JSON.stringify(e)}`);const o=c(t,"SELECT id FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL").get(n.rowid,e);if(o&&o.id!==r?.id)throw fe(`${n.name} already has a document named ${e}`,{name:e,holder:o.id})}s(H,"assertNameFree");function C(t,n,e){if(!J(t,n,e))throw R(`no document ${e.id}`)}s(C,"readable");function G(t){return new O("scope_missing",`missing scope ${t}`,D.GENERAL,{scope:t})}s(G,"scopeMissing");function T(t,n,e){C(t,n,e);const r=oe(t,n,e,"rw");if(r!==null)throw G(r);if(!k(t,n,e))throw new O("forbidden",`you cannot change ${e.name}`,D.GENERAL,{id:e.id})}s(T,"changeable");function V(t,n){const e=c(t,`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rowid = rp.parent
2
+ WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rowid);return{rowid:n.rowid,rev_id:n.rev_id,number:n.number,body_hash:n.body_hash,author:n.author===null?null:d(t,n.author)?.id??null,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,parents:e.map(r=>r.rev_id)}}s(V,"toRevision");function W(t,n){return c(t,`SELECT * FROM revisions r WHERE r.document = ?
3
+ AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid) ORDER BY r.number DESC, r.rev_id`).all(n.rowid).map(r=>V(t,r))}s(W,"heads");function he(t){return t.map(n=>n.rev_id).sort().join(",")}s(he,"headsKeyOf");function A(t,n){const e=c(t,"SELECT * FROM documents WHERE object = ?").get(n.rowid);if(!e)throw R(`no document record for ${n.id}`);return{summary:e.summary,heads_key:e.heads_key,revision:e.revision,why_it_exists:e.why_it_exists,unlisted:e.unlisted===1,lint_ignore:e.lint_ignore_json===null?null:JSON.parse(e.lint_ignore_json),extensions:e.extensions_json===null?null:JSON.parse(e.extensions_json),source_path:e.source_path}}s(A,"documentRecord");function j(t,n,e){const r=c(t,`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at)
4
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.rowid,e.rev_id??re(),e.number,e.body_hash,e.author?.rowid??null,e.rationale,e.verbatim?1:0,e.created_at??new Date().toISOString()),o=c(t,"INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const i of e.parents)o.run(r.rowid,i);return r}s(j,"insertRevision");function Ee(t){const n=new Set;for(const e of t.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(e[1].trim());return[...n]}s(Ee,"linksIn");function Y(t,n,e,r){const o=[],i=[];for(const a of Ee(r)){const u=le(t,n,a);"object"in u?o.push({written_as:a,dst:u.object}):"deleted"in u?o.push({written_as:a,dst:u.deleted}):"remote"in u||i.push(a)}return b(t,ce(t,e,o)),i}s(Y,"setRevisionLinks");function _e(t,n,e){return n.type==="node"?n.id:c(t,`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.rowid
5
5
  WHERE o.name = ? AND o.type <> ? AND o.type IN ('person','app','repo','profile')
6
- 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}a(fe,"ownerPrefix");function Y(t,n,e){const o=c(t,`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.rowid = e.dst
7
- WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.rowid);if(o.length===0)return e;const r=new Map;for(const i of o){if(i.deleted_at||i.written_as===i.id)continue;const u=!i.written_as.includes("/");if(u&&i.name===i.written_as)continue;const s=i.owner===null?null:f(t,i.owner),m=s?`${fe(t,s,i.name)}/${i.name}`:i.name,d=u&&i.owner===n.owner?i.name:m;d!==i.written_as&&r.set(i.written_as,d)}return r.size===0?e:e.replace(/\[\[([^[\]\n]+?)\]\]/g,(i,u)=>{const s=r.get(u.trim());return s===void 0?i:`[[${s}]]`})}a(Y,"withCurrentNames");function W(t,n,e){if(e.preview!==void 0){if(e.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});c(t,"UPDATE objects SET preview = ? WHERE rowid = ?").run(e.preview,n.rowid)}e.privacy!==void 0&&(I(t,f(t,n.owner),e.privacy),c(t,"UPDATE objects SET privacy = ? WHERE rowid = ?").run(e.privacy,n.rowid));const o=a((i,u)=>t.prepare(`UPDATE documents SET ${i} = ? WHERE object = ?`).run(u,n.rowid),"set");e.summary!==void 0&&o("summary",e.summary),e.whyItExists!==void 0&&o("why_it_exists",e.whyItExists),e.unlisted!==void 0&&o("unlisted",e.unlisted?1:0),e.lintIgnore!==void 0&&o("lint_ignore_json",e.lintIgnore===null?null:JSON.stringify(e.lintIgnore)),e.extensions!==void 0&&o("extensions_json",e.extensions===null?null:JSON.stringify(e.extensions));const r=f(t,n.rowid);return e.rules!==void 0&&B(t,r,e.rules),r}a(W,"applyDocumentFields");function X(t,n){if(n.owner.deleted_at)throw l(`${n.owner.name} is deleted`);if(n.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});I(t,n.owner,n.privacy),K(t,n.owner,n.name);const e=ue(t,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.rowid,privacy:n.privacy,name:n.name,preview:n.preview});return c(t,"INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(e.rowid),W(t,e,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]})}a(X,"createDocument");function M(t,n){const e=C(t,n),o=pe(e),r=Math.max(0,...e.map(i=>i.number));return c(t,"UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(o,r,n.rowid),{heads_key:o,revision:r,headCount:e.length}}a(M,"setHeads");function Ae(t,n,e,o,r){const i=J(o);return E(t,()=>{const u=_(n),s=X(t,e),m=T(t,s,{number:1,body_hash:i,author:u,rationale:null,parents:[]}),{heads_key:d}=M(t,s);if(!L(t,n,s))throw R(`no owner ${e.owner.name} you can write to`);const w=V(t,n,s,o);return N(t,u,s),$(t,u,s,"write",{readHeadsKey:d}),O(t,{event:"created",object:s,actor:u,attrs:{revision:1},pointer:m.rev_id},r),{object:s,revision:1,unresolved:w}})}a(Ae,"writeDocument");function Le(t,n,e){return c(t,"SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(_(n).rowid,e.rowid)?.read_heads_key??null}a(Le,"readBase");function he(t,n){let e=t;for(const[o,{old:r,new:i}]of n.entries()){if(r==="")throw l(`block ${o+1}: a replacement needs non-empty old text`,{field:"replace"});const u=e.indexOf(r);if(u<0)throw l(`block ${o+1}: old text not found: ${JSON.stringify(r.slice(0,120))}`,{field:"replace",old:r});if(e.indexOf(r,u+1)>=0)throw l(`block ${o+1}: old text occurs more than once: ${JSON.stringify(r.slice(0,120))}`,{field:"replace",old:r});e=e.slice(0,u)+i+e.slice(u+r.length)}return e}a(he,"applyReplacements");function $e(t,n,e,o,r,i,u={},s){if(y(e),D(t,n,e),e.deleted_at)throw l(`${e.name} is deleted`);if(i===null)throw new k("read_first",`read ${e.name} before editing it`,A.GENERAL,{id:e.id});if(r.trim()==="")throw l("an edit needs a rationale",{field:"rationale"});const m=x(t,e);if(m.heads_key!==i)return{stale:{heads_key:m.heads_key,revision:m.revision}};const d=C(t,e);let w;if("body"in o)w=o.body;else{if(d.length!==1)throw l(`${e.name} has ${d.length} heads and needs a merge; pass the whole merged body`,{heads:d.length});w=he(Y(t,e,ie(d[0].body_hash)),o.replace)}const p=J(w);try{return E(t,()=>{const h=_(n),g=1+Math.max(0,...d.map(v=>v.number)),U=T(t,e,{number:g,body_hash:p,author:h,rationale:r,verbatim:o.verbatim===!0,parents:d.map(v=>v.rowid)}),j=U.rev_id,b=c(t,"UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(j,g,e.rowid,i);if(Number(b.changes)===0){const v=x(t,e);throw new q(v.heads_key,v.revision)}const S=W(t,e,u),ee=V(t,n,S,w);return N(t,h,S),$(t,h,S,"write",{readHeadsKey:j}),O(t,{event:"edited",object:S,actor:h,pointer:U.rev_id,attrs:{revision:g,from_revision:m.revision,rationale:r}},s),{revision:g,heads_key:j,unresolved:ee}})}catch(h){if(h instanceof q)return{stale:{heads_key:h.heads_key,revision:h.revision}};throw h}}a($e,"editDocument");class q extends Error{static{a(this,"StaleEdit")}heads_key;revision;constructor(n,e){super("stale"),this.heads_key=n,this.revision=e}}function Ie(t,n,e,o){return y(e),n&&D(t,n,e),E(t,()=>{const r=W(t,e,o);return n&&N(t,_(n),r),r})}a(Ie,"setDocumentRecord");function Ee(t,n,e,o){const r=_(n),{heads_key:i}=x(t,e);$(t,r,e,"read",{readHeadsKey:i}),O(t,{event:"read",object:e,actor:r,attrs:{}},o)}a(Ee,"recordRead");function He(t,n,e,o){return y(e),H(t,n,e),E(t,()=>{const r=x(t,e),i=C(t,e).map(s=>({...s,body:Y(t,e,oe(s.body_hash,r.source_path))})),u=c(t,"SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ? ORDER BY written_as").all(e.rowid).map(s=>({written_as:s.written_as??"",object:f(t,s.dst)})).filter(s=>s.object&&F(t,n,s.object)).map(s=>({...s,deleted:s.object.deleted_at!==null}));return e.deleted_at||Ee(t,n,e,o),{object:e,record:r,heads:i,needsMerge:i.length>1,links:u}})}a(He,"readDocument");function Ce(t,n,e){return y(e),H(t,n,e),c(t,"SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, rowid DESC").all(e.rowid).map(r=>G(t,r))}a(Ce,"history");function We(t,n,e,o){if(y(e),e.deleted_at)throw l(`${e.name} is deleted`);return n&&D(t,n,e),E(t,()=>{const r=o.owner??f(t,e.owner),i=o.name??e.name;K(t,r,i,e),I(t,r,e.privacy),c(t,"UPDATE objects SET name = ?, owner = ? WHERE rowid = ?").run(i,r.rowid,e.rowid);const u=f(t,e.rowid);if(r.rowid!==e.owner&&(le(t,u),n&&!L(t,n,u)))throw R(`no owner ${r.name} you can move documents to`);return n&&N(t,_(n),u),u})}a(We,"moveDocument");function Me(t,n,e,o){return y(e),e.deleted_at?e:(n&&D(t,n,e),E(t,()=>{const r=z(t,n,e,o);return Z(t,Q(t,e.rowid),o),r}))}a(Me,"deleteDocument");function z(t,n,e,o){c(t,"UPDATE objects SET deleted_at = ? WHERE rowid = ?").run(new Date().toISOString(),e.rowid);const r=f(t,e.rowid),i=n?_(n):null;return i&&N(t,i,r),O(t,{event:"deleted",object:r,actor:i,attrs:{}},o),c(t,"DELETE FROM watches WHERE object = ?").run(e.rowid),B(t,r,[]),r}a(z,"markDeleted");function Q(t,n){return c(t,"SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n).map(({dst:e})=>e)}a(Q,"linkTargets");function Z(t,n,e){const o=c(t,`SELECT 1 FROM objects d JOIN objects w ON w.rowid = d.owner
6
+ AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,e)?`${n.type}:${n.name}`:n.name}s(_e,"ownerPrefix");function X(t,n,e){const r=c(t,`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.rowid = e.dst
7
+ WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.rowid);if(r.length===0)return e;const o=new Map;for(const i of r){if(i.deleted_at||i.written_as===i.id)continue;const a=!i.written_as.includes("/");if(a&&i.name===i.written_as)continue;const u=i.owner===null?null:d(t,i.owner),l=u?`${_e(t,u,i.name)}/${i.name}`:i.name,w=a&&i.owner===n.owner?i.name:l;w!==i.written_as&&o.set(i.written_as,w)}return o.size===0?e:e.replace(/\[\[([^[\]\n]+?)\]\]/g,(i,a)=>{const u=o.get(a.trim());return u===void 0?i:`[[${u}]]`})}s(X,"withCurrentNames");function M(t,n,e){if(e.preview!==void 0){if(e.preview.trim()==="")throw m("a document needs a preview",{field:"preview"});c(t,"UPDATE objects SET preview = ? WHERE rowid = ?").run(e.preview,n.rowid)}e.privacy!==void 0&&(I(t,d(t,n.owner),e.privacy),c(t,"UPDATE objects SET privacy = ? WHERE rowid = ?").run(e.privacy,n.rowid));const r=s((i,a)=>t.prepare(`UPDATE documents SET ${i} = ? WHERE object = ?`).run(a,n.rowid),"set");e.summary!==void 0&&r("summary",e.summary),e.whyItExists!==void 0&&r("why_it_exists",e.whyItExists),e.unlisted!==void 0&&r("unlisted",e.unlisted?1:0),e.lintIgnore!==void 0&&r("lint_ignore_json",e.lintIgnore===null?null:JSON.stringify(e.lintIgnore)),e.extensions!==void 0&&r("extensions_json",e.extensions===null?null:JSON.stringify(e.extensions));const o=d(t,n.rowid);return e.rules!==void 0&&B(t,o,e.rules),o}s(M,"applyDocumentFields");function q(t,n){if(n.owner.deleted_at)throw m(`${n.owner.name} is deleted`);if(n.preview.trim()==="")throw m("a document needs a preview",{field:"preview"});I(t,n.owner,n.privacy),H(t,n.owner,n.name);const e=me(t,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.rowid,privacy:n.privacy,name:n.name,preview:n.preview});return c(t,"INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(e.rowid),M(t,e,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]})}s(q,"createDocument");function U(t,n){const e=W(t,n),r=he(e),o=Math.max(0,...e.map(i=>i.number));return c(t,"UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(r,o,n.rowid),{heads_key:r,revision:o,headCount:e.length}}s(U,"setHeads");function Ie(t,n,e,r,o){const i=P(r);return E(t,()=>{const a=y(n),u=ie(t,n,e.owner,"rw");if(u!==null)throw G(u);const l=q(t,e),w=j(t,l,{number:1,body_hash:i,author:a,rationale:null,parents:[]}),{heads_key:f}=U(t,l);if(!k(t,n,l))throw R(`no owner ${e.owner.name} you can write to`);const p=Y(t,n,l,r);return N(t,a,l),$(t,a,l,"write",{readHeadsKey:f}),x(t,{event:"created",object:l,actor:a,attrs:{revision:1},pointer:w.rev_id},o),{object:l,revision:1,unresolved:p}})}s(Ie,"writeDocument");function He(t,n,e){return c(t,"SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(y(n).rowid,e.rowid)?.read_heads_key??null}s(He,"readBase");function ye(t,n){let e=t;for(const[r,{old:o,new:i}]of n.entries()){if(o==="")throw m(`block ${r+1}: a replacement needs non-empty old text`,{field:"replace"});const a=e.indexOf(o);if(a<0)throw m(`block ${r+1}: old text not found: ${JSON.stringify(o.slice(0,120))}`,{field:"replace",old:o});if(e.indexOf(o,a+1)>=0)throw m(`block ${r+1}: old text occurs more than once: ${JSON.stringify(o.slice(0,120))}`,{field:"replace",old:o});e=e.slice(0,a)+i+e.slice(a+o.length)}return e}s(ye,"applyReplacements");function Ce(t,n,e,r,o,i,a={},u){if(_(e),T(t,n,e),e.deleted_at)throw m(`${e.name} is deleted`);if(i===null)throw new O("read_first",`read ${e.name} before editing it`,D.GENERAL,{id:e.id});if(o.trim()==="")throw m("an edit needs a rationale",{field:"rationale"});const l=A(t,e);if(l.heads_key!==i)return{stale:{heads_key:l.heads_key,revision:l.revision}};const w=W(t,e);let f;if("body"in r)f=r.body;else{if(w.length!==1)throw m(`${e.name} has ${w.length} heads and needs a merge; pass the whole merged body`,{heads:w.length});f=ye(X(t,e,ue(w[0].body_hash)),r.replace)}const p=P(f);try{return E(t,()=>{const h=y(n),g=1+Math.max(0,...w.map(v=>v.number)),F=j(t,e,{number:g,body_hash:p,author:h,rationale:o,verbatim:r.verbatim===!0,parents:w.map(v=>v.rowid)}),L=F.rev_id,ee=c(t,"UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(L,g,e.rowid,i);if(Number(ee.changes)===0){const v=A(t,e);throw new z(v.heads_key,v.revision)}const S=M(t,e,a),te=Y(t,n,S,f);return N(t,h,S),$(t,h,S,"write",{readHeadsKey:L}),x(t,{event:"edited",object:S,actor:h,pointer:F.rev_id,attrs:{revision:g,from_revision:l.revision,rationale:o}},u),{revision:g,heads_key:L,unresolved:te}})}catch(h){if(h instanceof z)return{stale:{heads_key:h.heads_key,revision:h.revision}};throw h}}s(Ce,"editDocument");class z extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,e){super("stale"),this.heads_key=n,this.revision=e}}function We(t,n,e,r){return _(e),n&&T(t,n,e),E(t,()=>{const o=M(t,e,r);return n&&N(t,y(n),o),o})}s(We,"setDocumentRecord");function ve(t,n,e,r){const o=y(n),{heads_key:i}=A(t,e);$(t,o,e,"read",{readHeadsKey:i}),x(t,{event:"read",object:e,actor:o,attrs:{}},r)}s(ve,"recordRead");function Me(t,n,e,r){return _(e),C(t,n,e),E(t,()=>{const o=A(t,e),i=W(t,e).map(u=>({...u,body:X(t,e,ae(u.body_hash,o.source_path))})),a=c(t,"SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ? ORDER BY written_as").all(e.rowid).map(u=>({written_as:u.written_as??"",object:d(t,u.dst)})).filter(u=>u.object&&J(t,n,u.object)).map(u=>({...u,deleted:u.object.deleted_at!==null}));return e.deleted_at||ve(t,n,e,r),{object:e,record:o,heads:i,needsMerge:i.length>1,links:a}})}s(Me,"readDocument");function Ue(t,n,e){return _(e),C(t,n,e),c(t,"SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, rowid DESC").all(e.rowid).map(o=>V(t,o))}s(Ue,"history");function Fe(t,n,e,r){if(_(e),e.deleted_at)throw m(`${e.name} is deleted`);return n&&T(t,n,e),E(t,()=>{const o=r.owner??d(t,e.owner),i=r.name??e.name;H(t,o,i,e),I(t,o,e.privacy),c(t,"UPDATE objects SET name = ?, owner = ? WHERE rowid = ?").run(i,o.rowid,e.rowid);const a=d(t,e.rowid);if(o.rowid!==e.owner&&(pe(t,a),n&&!k(t,n,a)))throw R(`no owner ${o.name} you can move documents to`);return n&&N(t,y(n),a),a})}s(Fe,"moveDocument");function Je(t,n,e,r,o){return _(e),e.deleted_at?e:(n&&T(t,n,e),E(t,()=>{const i=Q(t,n,e,r,o);return b(t,Z(t,e.rowid),r),i}))}s(Je,"deleteDocument");function Pe(t,n,e){return _(n),n.deleted_at?E(t,()=>(H(t,d(t,n.owner),e,n),c(t,"UPDATE objects SET deleted_at = NULL, name = ? WHERE rowid = ?").run(e,n.rowid),d(t,n.rowid))):n}s(Pe,"restoreDocument");function Q(t,n,e,r,o){c(t,"UPDATE objects SET deleted_at = ? WHERE rowid = ?").run(o??new Date().toISOString(),e.rowid);const i=d(t,e.rowid),a=n?y(n):null;return a&&N(t,a,i),x(t,{event:"deleted",object:i,actor:a,attrs:{}},r),c(t,"DELETE FROM watches WHERE object = ?").run(e.rowid),B(t,i,[]),i}s(Q,"markDeleted");function Z(t,n){return c(t,"SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n).map(({dst:e})=>e)}s(Z,"linkTargets");function b(t,n,e){const r=c(t,`SELECT 1 FROM objects d JOIN objects w ON w.rowid = d.owner
8
8
  WHERE d.rowid = ? AND d.type = 'document' AND d.deleted_at IS NULL AND w.deleted_at IS NOT NULL
9
9
  AND NOT EXISTS (SELECT 1 FROM edges e JOIN objects s ON s.rowid = e.src
10
- WHERE e.type = 'links-to' AND e.dst = d.rowid AND e.src != d.rowid AND s.deleted_at IS NULL)`),r=[...n];for(;r.length>0;){const i=r.pop();o.get(i)!==void 0&&(z(t,null,f(t,i),e),r.push(...Q(t,i)))}}a(Z,"collectUnlinked");function Ue(t,n){if(n.revisions.length===0)throw l(`import of ${n.name}: no revisions`);return E(t,()=>{const e=X(t,n);c(t,"UPDATE objects SET created_at = ? WHERE rowid = ?").run(n.revisions[0].created_at,e.rowid);let o=null;return n.revisions.forEach((r,i)=>{o=T(t,e,{...r,number:i+1,parents:o===null?[]:[o]}).rowid}),M(t,e),f(t,e.rowid)})}a(Ue,"importDocument");function Fe(t,n,e){if(y(n),n.privacy!=="public"&&P(t,n)==="repo")throw l(`a repo owns only public documents; refusing revisions of ${n.name}`);return E(t,()=>{const o=c(t,"SELECT rowid, number, document FROM revisions WHERE rev_id = ?");let r=e.filter(s=>!o.get(s.rev_id));for(;r.length>0;){const s=[];for(const m of r){const d=m.parents.map(p=>o.get(p));if(d.some(p=>p===void 0)){s.push(m);continue}const w=d;if(w.some(p=>p.document!==n.rowid))throw l(`revision ${m.rev_id} names a parent of another document`);T(t,n,{...m,author:null,number:1+Math.max(0,...w.map(p=>p.number)),parents:w.map(p=>p.rowid)})}if(s.length===r.length)throw l(`revisions of ${n.name} name unknown parents: ${s.map(m=>m.rev_id).join(", ")}`);r=s}const{heads_key:i,headCount:u}=M(t,n);return{heads_key:i,headCount:u}})}a(Fe,"insertImportedRevisions");function Je(t,n){const e=ce(t,n);if(!e||e.type!=="document")throw R(`no document ${n}`);return e}a(Je,"documentById");export{_ as actorOf,W as applyDocumentFields,he as applyReplacements,Z as collectUnlinked,Me as deleteDocument,Je as documentById,x as documentRecord,$e as editDocument,C as heads,Ce as history,Ue as importDocument,Fe as insertImportedRevisions,we as linksIn,We as moveDocument,Le as readBase,He as readDocument,Ee as recordRead,Ie as setDocumentRecord,V as setRevisionLinks,Y as withCurrentNames,Ae as writeDocument};
10
+ WHERE e.type = 'links-to' AND e.dst = d.rowid AND e.src != d.rowid AND s.deleted_at IS NULL)`),o=[...n];for(;o.length>0;){const i=o.pop();r.get(i)!==void 0&&(Q(t,null,d(t,i),e),o.push(...Z(t,i)))}}s(b,"collectUnlinked");function Be(t,n){if(n.revisions.length===0)throw m(`import of ${n.name}: no revisions`);return E(t,()=>{const e=q(t,n);c(t,"UPDATE objects SET created_at = ? WHERE rowid = ?").run(n.revisions[0].created_at,e.rowid);let r=null;return n.revisions.forEach((o,i)=>{r=j(t,e,{...o,number:i+1,parents:r===null?[]:[r]}).rowid}),U(t,e),d(t,e.rowid)})}s(Be,"importDocument");function Ke(t,n,e){if(_(n),n.privacy!=="public"&&K(t,n)==="repo")throw m(`a repo owns only public documents; refusing revisions of ${n.name}`);return E(t,()=>{const r=c(t,"SELECT rowid, number, document FROM revisions WHERE rev_id = ?");let o=e.filter(u=>!r.get(u.rev_id));for(;o.length>0;){const u=[];for(const l of o){const w=l.parents.map(p=>r.get(p));if(w.some(p=>p===void 0)){u.push(l);continue}const f=w;if(f.some(p=>p.document!==n.rowid))throw m(`revision ${l.rev_id} names a parent of another document`);j(t,n,{...l,author:null,number:1+Math.max(0,...f.map(p=>p.number)),parents:f.map(p=>p.rowid)})}if(u.length===o.length)throw m(`revisions of ${n.name} name unknown parents: ${u.map(l=>l.rev_id).join(", ")}`);o=u}const{heads_key:i,headCount:a}=U(t,n);return{heads_key:i,headCount:a}})}s(Ke,"insertImportedRevisions");function Ge(t,n){const e=we(t,n);if(!e||e.type!=="document")throw R(`no document ${n}`);return e}s(Ge,"documentById");export{y as actorOf,M as applyDocumentFields,ye as applyReplacements,b as collectUnlinked,Je as deleteDocument,Ge as documentById,A as documentRecord,Ce as editDocument,W as heads,Ue as history,Be as importDocument,Ke as insertImportedRevisions,Ee as linksIn,Fe as moveDocument,He as readBase,Me as readDocument,ve as recordRead,Pe as restoreDocument,We as setDocumentRecord,Y as setRevisionLinks,X as withCurrentNames,Ie as writeDocument};
@@ -12,7 +12,7 @@ export type Resolution = {
12
12
  missing: true;
13
13
  };
14
14
  /** The owners a bare name is looked up under, in order: own node, repos nearest
15
- * first, profile, app, you, then the apps whose tools the run may use (installed
15
+ * first, the profile's project repos, profile, app, you, then the apps whose tools the run may use (installed
16
16
  * plugins) with `crtr` last. */
17
17
  export declare function viewOf(db: DatabaseSync, reader: Reader): ObjectRow[];
18
18
  /** The owner types that carry a handle; `<type>:<handle>/<name>` names one exactly. */
@@ -1,3 +1,3 @@
1
- var A=Object.defineProperty;var u=(t,e)=>A(t,"name",{value:e,configurable:!0});import{prepared as w}from"../sqlite-statements.js";import{isSafeNodeId as L}from"../canvas/paths.js";import{canRead as I,toolApps as T}from"./access.js";import{getById as x}from"./objects.js";function H(t,e){const s=w(t,"SELECT * FROM objects WHERE type = 'person' AND name = 'user'").get(),l=T(t,e),o=e.class==="node"?[e.node,...e.repos,e.profile,e.app]:e.class==="app"?[e.app]:e.class==="object"?[e.owner,...e.repos,e.app]:[],a=new Set;return[...o,s,...l].filter(r=>!!r&&!a.has(r.rowid)&&!!a.add(r.rowid))}u(H,"viewOf");function E(t,e,s,l){return t.prepare(`SELECT * FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ?
2
- AND deleted_at IS ${l?"NOT NULL ORDER BY deleted_at DESC":"NULL"}`).all(e.rowid,s)}u(E,"namedObjects");const b=["person","app","repo","profile"],y=/^(person|app|repo|profile|node):([^/]+)\/(.+)$/;function M(t,e,s){return s==="node"?w(t,"SELECT * FROM objects WHERE type = 'node' AND id = ?").all(e):s?w(t,"SELECT * FROM objects WHERE type = ? AND (name = ? OR id = ?)").all(s,e,e):w(t,`SELECT * FROM objects WHERE (type = 'node' AND id = ?)
3
- OR (type IN ('person','app','repo','profile') AND (name = ? OR id = ?))`).all(e,e,e)}u(M,"ownersByHandle");function v(t,e,s,l){let o=s.trim().replace(/^\[\[(.*)\]\]$/,"$1").trim();const a=/^([^:/]+):reports\/(.+?)(?:\.md)?$/.exec(o);a&&!y.test(o)&&(o=`${a[1]}/reports/${a[2]}`);const r=l?.readable??(p=>I(t,e,p)),d=x(t,o);if(d&&r(d))return d.deleted_at?{deleted:d}:{object:d};const N=l?.view??H(t,e);for(const p of N){const i=E(t,p,o,!1).find(r);if(i)return{object:i}}const c=y.exec(o),O=o.indexOf("/");if(c||O>0){const p=c?c[2]:o.slice(0,O),i=c?c[3]:o.slice(O+1),m=M(t,p,c?.[1]),D=u(n=>{const f=N.findIndex(g=>g.rowid===n.rowid);return f<0?1/0:f},"rank"),S=m.filter(n=>D(n)<1/0).sort((n,f)=>D(n)-D(f));for(const n of S){const f=E(t,n,i,!1).find(r);if(f)return{object:f}}const R=m.flatMap(n=>E(t,n,i,!1)).filter(r);if(R.length===1)return{object:R[0]};if(R.length>1)return{ambiguous:R};const j=m.flatMap(n=>E(t,n,i,!0)).find(r);return j?{deleted:j}:!c&&m.length===0&&L(p)?{remote:o}:{missing:!0}}for(const p of N){const i=E(t,p,o,!0).find(r);if(i)return{deleted:i}}return{missing:!0}}u(v,"resolveName");export{b as HANDLE_TYPES,M as ownersByHandle,v as resolveName,H as viewOf};
1
+ var A=Object.defineProperty;var u=(t,e)=>A(t,"name",{value:e,configurable:!0});import{prepared as w}from"../sqlite-statements.js";import{isSafeNodeId as L}from"../canvas/paths.js";import{canRead as I,toolApps as T}from"./access.js";import{getById as x}from"./objects.js";function H(t,e){const s=w(t,"SELECT * FROM objects WHERE type = 'person' AND name = 'user'").get(),l=T(t,e),o=e.class==="node"?[e.node,...e.repos,...e.projectRepos,e.profile,e.app]:e.class==="app"?[e.app]:e.class==="object"?[e.owner,...e.repos,e.app]:[],a=new Set;return[...o,s,...l].filter(r=>!!r&&!a.has(r.rowid)&&!!a.add(r.rowid))}u(H,"viewOf");function d(t,e,s,l){return t.prepare(`SELECT * FROM objects WHERE (type = 'document' OR instr(type, '.') > 0) AND owner = ? AND name = ?
2
+ AND deleted_at IS ${l?"NOT NULL ORDER BY deleted_at DESC":"NULL"}`).all(e.rowid,s)}u(d,"namedObjects");const b=["person","app","repo","profile"],y=/^(person|app|repo|profile|node):([^/]+)\/(.+)$/;function M(t,e,s){return s==="node"?w(t,"SELECT * FROM objects WHERE type = 'node' AND id = ?").all(e):s?w(t,"SELECT * FROM objects WHERE type = ? AND (name = ? OR id = ?)").all(s,e,e):w(t,`SELECT * FROM objects WHERE (type = 'node' AND id = ?)
3
+ OR (type IN ('person','app','repo','profile') AND (name = ? OR id = ?))`).all(e,e,e)}u(M,"ownersByHandle");function v(t,e,s,l){let o=s.trim().replace(/^\[\[(.*)\]\]$/,"$1").trim();const a=/^([^:/]+):reports\/(.+?)(?:\.md)?$/.exec(o);a&&!y.test(o)&&(o=`${a[1]}/reports/${a[2]}`);const r=l?.readable??(p=>I(t,e,p)),E=x(t,o);if(E&&r(E))return E.deleted_at?{deleted:E}:{object:E};const N=l?.view??H(t,e);for(const p of N){const i=d(t,p,o,!1).find(r);if(i)return{object:i}}const c=y.exec(o),O=o.indexOf("/");if(c||O>0){const p=c?c[2]:o.slice(0,O),i=c?c[3]:o.slice(O+1),m=M(t,p,c?.[1]),j=u(n=>{const f=N.findIndex(g=>g.rowid===n.rowid);return f<0?1/0:f},"rank"),S=m.filter(n=>j(n)<1/0).sort((n,f)=>j(n)-j(f));for(const n of S){const f=d(t,n,i,!1).find(r);if(f)return{object:f}}const R=m.flatMap(n=>d(t,n,i,!1)).filter(r);if(R.length===1)return{object:R[0]};if(R.length>1)return{ambiguous:R};const D=m.flatMap(n=>d(t,n,i,!0)).find(r);return D?{deleted:D}:!c&&m.length===0&&L(p)?{remote:o}:{missing:!0}}for(const p of N){const i=d(t,p,o,!0).find(r);if(i)return{deleted:i}}return{missing:!0}}u(v,"resolveName");export{b as HANDLE_TYPES,M as ownersByHandle,v as resolveName,H as viewOf};
@@ -4,6 +4,12 @@ import { type ActiveGrant } from '../grants/mirror.js';
4
4
  import type { Checkout, ObjectRow, Reader } from './types.js';
5
5
  /** The repos enclosing a directory, with their checkout roots, nearest first. */
6
6
  export type ReposFn = (cwd: string) => Checkout[];
7
+ /** A node's view of repos: the ones enclosing its cwd, then those of each project path of its profile
8
+ * (manifest order), each repo once. A project path that no longer exists, is not in a repo, or has no
9
+ * work tree (a bare repository) contributes nothing. */
10
+ export declare function viewRepos(cwd: string, profileId: string | null, reposFn: ReposFn, lookups: Pick<ReaderLookups, 'projectPaths'>): Pick<Extract<Reader, {
11
+ class: 'node';
12
+ }>, 'repos' | 'checkouts' | 'projectRepos' | 'projectCheckouts'>;
7
13
  export declare function readerFor(db: DatabaseSync, principal: RequestPrincipal, reposFn: ReposFn): Exclude<Reader, {
8
14
  class: 'object';
9
15
  }>;
@@ -24,6 +30,8 @@ export interface ReaderLookups {
24
30
  terminalHasIssuer(): boolean;
25
31
  object(rowid: number): ObjectRow | null;
26
32
  profile(profileId: string, app: ObjectRow): ObjectRow;
33
+ /** The profile's project paths in manifest order; none in a sandboxed node, whose profile store is unreadable. */
34
+ projectPaths(profileId: string): readonly string[];
27
35
  }
28
36
  export declare function liveLookups(db: DatabaseSync): ReaderLookups;
29
37
  export declare function publishLookups(db: DatabaseSync): ReaderLookups;
@@ -1,3 +1,3 @@
1
- var N=Object.defineProperty;var l=(o,e)=>N(o,"name",{value:e,configurable:!0});import{prepared as f}from"../sqlite-statements.js";import{coveredByAny as E}from"../../shared/env.js";import{parseScope as C}from"@north-light/identity";import{activeGrant as h,grantFor as v}from"../grants/mirror.js";import{prepared as F}from"../canvas/db.js";import{ApiError as L}from"../../api/errors.js";import{readConfig as $}from"../config.js";import{SCOPE_VOCABULARY as _}from"../scopes.js";import{loadExactProfileManifest as x}from"../profiles/manifest.js";import{ensureOwner as O,getByRowid as A,nodeObject as M}from"./objects.js";function S(o){return{repos:o.map(e=>e.repo),checkouts:o}}l(S,"inRepos");function K(o,e,s){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:m(o)};if(e.class==="app")return{class:"app",app:g(o,e.app),scopes:e.scopes};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:m(o)};const a=v(e.actor.app,e.sub);if(!a)throw new L(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const c=e.actor.node_scopes;return{class:"app",app:g(o,e.actor.app),place:e.actor.place,scopes:c===null?a.resolved:c.filter(u=>E(a.resolved,u))}}if(e.class==="cron"){const a=e.grant?h(e.grant)?.resolved??[]:_,c=e.scopes;return{class:"app",app:g(o,e.app),scopes:c===null?a:c.filter(u=>E(a,u)),place:{run:e.origin_run??"",isolated:e.isolated}}}const t=w(o),r=M(o,e.node),n=r.owner===null?null:t.object(r.owner);if(!n)throw new Error(`node ${e.node} has no owner`);const p=f(o,"SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e.node);return{class:"node",node:r,app:n,scopes:H(e.app,e.sub,e.grant,e.scopes,t),cwd:p.cwd,...S(s(p.cwd)),profile:p.profile_id===null?null:t.profile(p.profile_id,n)}}l(K,"readerFor");function T(o,e,s,t=w(o),r=!1){const n=F(o,"SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),p=F(o,`SELECT (SELECT external_id FROM identities WHERE object = ?) AS grantee,
2
- sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(n?.owner??null,e);if(!n||!p)throw new Error(`no node ${e}`);const{grantee:a,sub:c,grant_id:u,scopes_json:d,cwd:i,profile_id:R}=p,j=n.owner===null?null:t.object(n.owner);if(!j)throw new Error(`node ${e} has no owner`);const b=d===null?null:JSON.parse(d);return{class:"node",node:n,app:j,scopes:H(a,c??"",u,b,t,r),cwd:i,...S(s(i)),profile:R===null?null:t.profile(R,j)}}l(T,"readerForNode");function Q(o,e,s=()=>[],t=w(o)){const r=f(o,"SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.rowid);if(r!==void 0)return W(o,e,r.envelope_json===null?null:JSON.parse(r.envelope_json),t);if(e.type==="node")return T(o,e.id,s,t,!0);const n=e.owner===null?null:t.object(e.owner);if(!n)throw new Error(`watcher ${e.id} has no owner`);const a=(n.type==="app"?n:n.type==="profile"&&n.owner!==null?t.object(n.owner):null)??f(o,"SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??f(o,"SELECT * FROM objects WHERE type = 'person' AND name = 'user'").get();if(!a)throw new Error("no terminal app or person object");const c=f(o,"SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(a.rowid)?.external_id??"app:terminal",u=f(o,"SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(c),d=n.type==="person"?null:u.length===0?c==="app:terminal"&&!t.terminalHasIssuer()?_:null:u.length===1?JSON.parse(u[0].resolved_scopes_json).filter(i=>i.mode==="always").map(i=>C(i.scope)):null;return{class:"object",object:e,node:e,owner:n,app:a,scopes:d,repos:n.type==="repo"?[n]:[]}}l(Q,"readerForObject");function W(o,e,s,t){const r=m(o);if(s!==null&&s.app===null)return{class:"object",object:e,node:e,owner:r,app:r,scopes:null,repos:[]};const n=s?.app==null?null:g(o,s.app),p=s?.app==null?null:t.grantFor(s.app,s.sub),a=s===null||p===null?null:s.node_scopes===null?p.resolved:s.node_scopes.filter(u=>E(p.resolved,u)),c=n??(e.owner===null?r:t.object(e.owner)??r);return{class:"object",object:e,node:e,owner:c,app:c,scopes:a,repos:[],...s?.place==null?{}:{place:s.place}}}l(W,"readerForStub");function w(o){return{grantById:h,grantFor:v,terminalHasIssuer:l(()=>!!$("user").runtime?.issuer,"terminalHasIssuer"),object:l(e=>A(o,e),"object"),profile:l((e,s)=>B(o,e,s),"profile")}}l(w,"liveLookups");function X(o){const e=w(o),s=new Map,t=l((r,n)=>(s.has(r)||s.set(r,n()),s.get(r)),"memo");return{grantById:l(r=>t(`g\0${r}`,()=>e.grantById(r)),"grantById"),grantFor:l((r,n)=>t(`a\0${r}\0${n}`,()=>e.grantFor(r,n)),"grantFor"),terminalHasIssuer:l(()=>t("issuer",()=>e.terminalHasIssuer()),"terminalHasIssuer"),object:l(r=>t(`o\0${r}`,()=>e.object(r)),"object"),profile:l((r,n)=>t(`p\0${r}\0${n.rowid}`,()=>e.profile(r,n)),"profile")}}l(X,"publishLookups");function H(o,e,s,t,r,n=!1){const p=s?r.grantById(s):r.grantFor(o,e),a=p&&p.grantee===o?p.resolved:o==="app:terminal"&&!r.terminalHasIssuer()?_:null;if(a===null){if(n)return null;throw new Error(`the grant of ${o} is not active`)}return t===null?a:t.filter(c=>E(a,c))}l(H,"nodeScopes");function m(o){return O(o,"person","local-user","user:local","user")}l(m,"personObject");function g(o,e){return e==="user:local"?m(o):O(o,"app","app",e,e.replace(/^app:/,""))}l(g,"appObject");function B(o,e,s){return f(o,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
3
- WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??O(o,"profile","profile",e,x(e).manifest.name,s)}l(B,"profileObject");export{w as liveLookups,B as profileObject,X as publishLookups,K as readerFor,T as readerForNode,Q as readerForObject};
1
+ var C=Object.defineProperty;var c=(o,e)=>C(o,"name",{value:e,configurable:!0});import{prepared as i}from"../sqlite-statements.js";import{coveredByAny as m,isSandboxedNode as H}from"../../shared/env.js";import{existsSync as N}from"node:fs";import{CrtrError as $}from"../errors.js";import{getStoredProfile as L}from"../scoped-state/profiles.js";import{userScopeRoot as A}from"../scope.js";import{parseScope as M}from"@north-light/identity";import{activeGrant as S,grantFor as v}from"../grants/mirror.js";import{prepared as F}from"../canvas/db.js";import{ApiError as T}from"../../api/errors.js";import{readConfig as W}from"../config.js";import{SCOPE_VOCABULARY as _}from"../scopes.js";import{loadExactProfileManifest as B}from"../profiles/manifest.js";import{ensureOwner as O,getByRowid as P,nodeObject as D}from"./objects.js";function b(o,e,t,s){const r=e===null?[]:s.projectPaths(e),n=t(o),p=new Set(n.map(a=>a.repo.rowid)),l=[];for(const a of r){if(!N(a))continue;let u;try{u=t(a)}catch(f){if(f instanceof $&&f.code==="git-lookup-failed")continue;throw f}for(const f of u)p.has(f.repo.rowid)||(p.add(f.repo.rowid),l.push(f))}return{repos:n.map(a=>a.repo),checkouts:n,projectRepos:l.map(a=>a.repo),projectCheckouts:l}}c(b,"viewRepos");function te(o,e,t){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:E(o)};if(e.class==="app")return{class:"app",app:j(o,e.app),scopes:e.scopes};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:E(o)};const l=v(e.actor.app,e.sub);if(!l)throw new T(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const a=e.actor.node_scopes;return{class:"app",app:j(o,e.actor.app),place:e.actor.place,launched:a,scopes:a===null?l.resolved:a.filter(u=>m(l.resolved,u))}}if(e.class==="cron"){const l=e.grant?S(e.grant)?.resolved??[]:_,a=e.scopes;return{class:"app",app:j(o,e.app),scopes:a===null?l:a.filter(u=>m(l,u)),launched:a,place:{run:e.origin_run??"",isolated:e.isolated}}}const s=w(o),r=D(o,e.node),n=r.owner===null?null:s.object(r.owner);if(!n)throw new Error(`node ${e.node} has no owner`);const p=i(o,"SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e.node);return{class:"node",node:r,app:n,scopes:x(e.app,e.sub,e.grant,e.scopes,s),launched:e.scopes,cwd:p.cwd,...b(p.cwd,p.profile_id,t,s),profile:p.profile_id===null?null:s.profile(p.profile_id,n)}}c(te,"readerFor");function J(o,e,t,s=w(o),r=!1){const n=F(o,"SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),p=F(o,`SELECT (SELECT external_id FROM identities WHERE object = ?) AS grantee,
2
+ sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(n?.owner??null,e);if(!n||!p)throw new Error(`no node ${e}`);const{grantee:l,sub:a,grant_id:u,scopes_json:f,cwd:d,profile_id:g}=p,h=n.owner===null?null:s.object(n.owner);if(!h)throw new Error(`node ${e} has no owner`);const R=f===null?null:JSON.parse(f);return{class:"node",node:n,app:h,scopes:x(l,a??"",u,R,s,r),launched:R,cwd:d,...b(d,g,t,s),profile:g===null?null:s.profile(g,h)}}c(J,"readerForNode");function se(o,e,t=()=>[],s=w(o)){const r=i(o,"SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.rowid);if(r!==void 0)return y(o,e,r.envelope_json===null?null:JSON.parse(r.envelope_json),s);if(e.type==="node")return J(o,e.id,t,s,!0);const n=e.owner===null?null:s.object(e.owner);if(!n)throw new Error(`watcher ${e.id} has no owner`);const l=(n.type==="app"?n:n.type==="profile"&&n.owner!==null?s.object(n.owner):null)??i(o,"SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??i(o,"SELECT * FROM objects WHERE type = 'person' AND name = 'user'").get();if(!l)throw new Error("no terminal app or person object");const a=i(o,"SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(l.rowid)?.external_id??"app:terminal",u=i(o,"SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(a),f=n.type==="person"?null:u.length===0?a==="app:terminal"&&!s.terminalHasIssuer()?_:null:u.length===1?JSON.parse(u[0].resolved_scopes_json).filter(d=>d.mode==="always").map(d=>M(d.scope)):null;return{class:"object",object:e,node:e,owner:n,app:l,scopes:f,repos:n.type==="repo"?[n]:[]}}c(se,"readerForObject");function y(o,e,t,s){const r=E(o);if(t!==null&&t.app===null)return{class:"object",object:e,node:e,owner:r,app:r,scopes:null,repos:[]};const n=t?.app==null?null:j(o,t.app),p=t?.app==null?null:s.grantFor(t.app,t.sub),l=t===null||p===null?null:t.node_scopes===null?p.resolved:t.node_scopes.filter(u=>m(p.resolved,u)),a=n??(e.owner===null?r:s.object(e.owner)??r);return{class:"object",object:e,node:e,owner:a,app:a,scopes:l,launched:t?.node_scopes??null,repos:[],...t?.place==null?{}:{place:t.place}}}c(y,"readerForStub");function w(o){return{grantById:S,grantFor:v,terminalHasIssuer:c(()=>!!W("user").runtime?.issuer,"terminalHasIssuer"),object:c(e=>P(o,e),"object"),profile:c((e,t)=>I(o,e,t),"profile"),projectPaths:c(e=>H()?[]:L(A(),e)?.manifest.projects.map(t=>t.path)??[],"projectPaths")}}c(w,"liveLookups");function ae(o){const e=w(o),t=new Map,s=c((r,n)=>(t.has(r)||t.set(r,n()),t.get(r)),"memo");return{grantById:c(r=>s(`g\0${r}`,()=>e.grantById(r)),"grantById"),grantFor:c((r,n)=>s(`a\0${r}\0${n}`,()=>e.grantFor(r,n)),"grantFor"),terminalHasIssuer:c(()=>s("issuer",()=>e.terminalHasIssuer()),"terminalHasIssuer"),object:c(r=>s(`o\0${r}`,()=>e.object(r)),"object"),profile:c((r,n)=>s(`p\0${r}\0${n.rowid}`,()=>e.profile(r,n)),"profile"),projectPaths:c(r=>s(`j\0${r}`,()=>e.projectPaths(r)),"projectPaths")}}c(ae,"publishLookups");function x(o,e,t,s,r,n=!1){const p=t?r.grantById(t):r.grantFor(o,e),l=p&&p.grantee===o?p.resolved:o==="app:terminal"&&!r.terminalHasIssuer()?_:null;if(l===null){if(n)return null;throw new Error(`the grant of ${o} is not active`)}return s===null?l:s.filter(a=>m(l,a))}c(x,"nodeScopes");function E(o){return O(o,"person","local-user","user:local","user")}c(E,"personObject");function j(o,e){return e==="user:local"?E(o):O(o,"app","app",e,e.replace(/^app:/,""))}c(j,"appObject");function I(o,e,t){return i(o,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
3
+ WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??O(o,"profile","profile",e,B(e).manifest.name,t)}c(I,"profileObject");export{w as liveLookups,I as profileObject,ae as publishLookups,te as readerFor,J as readerForNode,se as readerForObject,b as viewRepos};
@@ -9,9 +9,13 @@ import type { ObjectRow } from '../types.js';
9
9
  * The tree (path → file text) of every public document the repo space owns, deleted ones
10
10
  * included. A revision already in `previous` is copied verbatim, so a remote author and the
11
11
  * links it was written with survive; a revision made here carries its author's object id, and
12
- * a head carries the document's current `links-to` edges.
12
+ * a head carries the document's current `links-to` edges. `carry` names paths of `previous` that
13
+ * are copied through untouched (files whose import was refused and is still pending).
13
14
  */
14
- export declare function exportRepo(db: DatabaseSync, repo: ObjectRow, previous: Map<string, string> | null): Map<string, string>;
15
+ export declare function exportRepo(db: DatabaseSync, repo: ObjectRow, previous: Map<string, string> | null, imported?: Map<string, string> | null): Map<string, string>;
16
+ /** Paths of `tree` whose text differs from the baseline last imported: files that arrived but
17
+ * have not landed in canvas.db. */
18
+ export declare function pendingPaths(tree: Map<string, string> | null, imported: Map<string, string> | null): string[];
15
19
  export interface ImportResult {
16
20
  created: number;
17
21
  revisions: number;
@@ -32,3 +36,5 @@ export interface ImportResult {
32
36
  * document that fails is rolled back alone and reported in `refused`.
33
37
  */
34
38
  export declare function importTree(db: DatabaseSync, repo: ObjectRow, before: Map<string, string>, after: Map<string, string>, reposFn?: ReposFn): ImportResult;
39
+ /** Whether any link target in a serialized ref file is one of `arrived`. */
40
+ export declare function namesArrived(text: string, arrived: ReadonlySet<string>): boolean;
@@ -1,2 +1,2 @@
1
- var B=Object.defineProperty;var f=(e,t)=>B(e,"name",{value:t,configurable:!0});import{prepared as y}from"../../sqlite-statements.js";import{get as T,put as j}from"../bodies.js";import{setLinks as H}from"../edges.js";import{collectUnlinked as W,deleteDocument as D,documentRecord as M,heads as $,importDocument as F,insertImportedRevisions as J,moveDocument as P,setDocumentRecord as Y}from"../documents.js";import{publish as C}from"../events.js";import{getById as I,getByRowid as K,setObjectKind as z}from"../objects.js";import{rulesOf as U}from"../rules.js";import{SPACES as V}from"../spaces/index.js";import{docPath as q,parseDoc as g,serializeDoc as G}from"./ref-format.js";function N(e){const t=V[e.type];if(e.owner!==null||!t||t.sync!=="repo-ref")throw new Error(`${e.type} ${e.id} is not a space that syncs through a repo ref`)}f(N,"assertRepoRefSpace");function Q(e){const t=new Map;for(const[r,i]of e??[]){const l=g(r,i);l.ok&&t.set(l.doc.id,l.doc)}return t}f(Q,"parseMap");function ue(e,t,r){N(t);const i=Q(r),l=y(e,"SELECT * FROM objects WHERE type = 'document' AND owner = ? AND privacy = 'public' ORDER BY rowid").all(t.rowid),u=y(e,"SELECT rowid, rev_id, number, body_hash, author, rationale, created_at FROM revisions WHERE document = ? ORDER BY number, rev_id"),m=y(e,"SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rowid = rp.parent WHERE rp.child = ?"),o=y(e,`SELECT e.written_as, o.id FROM edges e JOIN objects o ON o.rowid = e.dst
2
- WHERE e.type = 'links-to' AND e.src = ? ORDER BY e.written_as, o.id`),d=new Map;for(const n of l){const p=M(e,n),w=new Map((i.get(n.id)?.revisions??[]).map(s=>[s.rev_id,s])),h=new Set($(e,n).map(s=>s.rev_id)),R=o.all(n.rowid).map(s=>({written_as:s.written_as??"",dst:s.id})),v=u.all(n.rowid).map(s=>w.get(s.rev_id)??{rev_id:s.rev_id,number:s.number,parents:m.all(s.rowid).map(k=>k.rev_id),body:T(s.body_hash),body_hash:s.body_hash,author:s.author===null?null:K(e,s.author)?.id??null,rationale:s.rationale,created_at:s.created_at,links:h.has(s.rev_id)?R:[]});if(v.length===0)continue;const O=U(e,n).map(s=>({position:s.position,on:s.on,match:s.match??null,match_frontmatter:s.matchFrontmatter??null,if:s.if??null,deliver:s.deliver})),_={id:n.id,name:n.name,kind:n.kind??"document",privacy:"public",preview:n.preview,summary:p.summary,why_it_exists:p.why_it_exists,unlisted:p.unlisted,lint_ignore:p.lint_ignore,extensions:p.extensions,created_at:n.created_at,deleted_at:n.deleted_at,rules:O,revisions:v};d.set(q(n.id),G(_))}return d}f(ue,"exportRepo");function X(e){return[...e].sort((t,r)=>t.position-r.position).map(t=>({on:t.on,deliver:t.deliver,...t.match!==null&&t.match!==void 0?{match:t.match}:{},...t.match_frontmatter!==null&&t.match_frontmatter!==void 0?{matchFrontmatter:t.match_frontmatter}:{},...t.if!==null&&t.if!==void 0?{if:t.if}:{}}))}f(X,"toRuleSpecs");const Z=f((e,t)=>JSON.stringify(e??null)===JSON.stringify(t??null),"same");function L(e,t){const r={},i=f(l=>!e||!Z(e[l],t[l]),"differs");return i("preview")&&(r.preview=t.preview),i("summary")&&(r.summary=t.summary),i("why_it_exists")&&(r.whyItExists=t.why_it_exists),i("unlisted")&&(r.unlisted=t.unlisted),i("lint_ignore")&&(r.lintIgnore=t.lint_ignore),i("extensions")&&(r.extensions=t.extensions),i("rules")&&(r.rules=X(t.rules)),r}f(L,"changedFields");let b=0;function A(e,t){const r=`repo_sync_import_${++b}`;e.exec(`SAVEPOINT ${r}`);try{const i=t();return e.exec(`RELEASE ${r}`),i}catch(i){throw e.exec(`ROLLBACK TO ${r}`),e.exec(`RELEASE ${r}`),i}}f(A,"atomic");function me(e,t,r,i,l){N(t);const u={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]},m=[];for(const[o,d]of i){if(r.get(o)===d)continue;const n=g(o,d);if(!n.ok){u.refused.push({path:o,reason:n.reason});continue}const p=r.has(o)?g(o,r.get(o)):null;m.push({path:o,prior:p?.ok?p.doc:null,doc:n.doc})}for(const[o,d]of r){if(i.has(o))continue;const n=g(o,d);n.ok&&m.push({path:o,prior:n.doc,doc:null,priorId:n.doc.id})}for(const o of m)for(const d of o.doc?.revisions??[])j(d.body);return A(e,()=>{for(const o of m)try{A(e,()=>ee(e,t,o.prior,o.doc,o.priorId??o.doc.id,u,l))}catch(d){u.refused.push({path:o.path,reason:d.message})}}),u}f(me,"importTree");function ee(e,t,r,i,l,u,m){const o=I(e,l);if(o&&(o.type!=="document"||o.owner!==t.rowid))throw new Error(`id ${l} is held by ${o.type} ${o.name} outside ${t.name}`);if(!i){o&&!o.deleted_at&&(D(e,null,o,m),u.deleted++);return}const d=y(e,"SELECT count(*) AS n FROM revisions WHERE document = ?");let n,p=!1,w=0,h=0;if(o){n=o,w=M(e,n).revision;const a=i.kind;(r?r.kind!==a:n.kind!==a)&&z(e,n,a);const c=L(r,i);Object.keys(c).length>0&&(n=Y(e,null,n,c)),i.name!==n.name&&!n.deleted_at&&(n=P(e,null,n,{name:i.name}))}else{const c=i.revisions.filter(E=>E.parents.length===0).sort((E,S)=>E.number-S.number||E.rev_id.localeCompare(S.rev_id))[0];if(!c)throw new Error(`${i.name}: no root revision`);n=F(e,{id:i.id,owner:t,name:i.name,kind:i.kind,preview:i.preview,privacy:"public",...L(null,i),revisions:[{rev_id:c.rev_id,body_hash:c.body_hash,author:null,rationale:c.rationale,created_at:c.created_at}]}),p=!0,h=1}const R=d.get(n.rowid).n,{headCount:v}=J(e,n,i.revisions.map(a=>({rev_id:a.rev_id,parents:a.parents,body_hash:a.body_hash,rationale:a.rationale,created_at:a.created_at})));h+=d.get(n.rowid).n-R,u.revisions+=h;const _=$(e,n)[0],s=new Map(i.revisions.map(a=>[a.rev_id,a]));if(h>0){const a=(s.get(_.rev_id)?.links??[]).map(c=>({written_as:c.written_as,dst:I(e,c.dst)})).filter(c=>c.dst!==null);W(e,H(e,n,a),m)}if(v>1&&u.needsMerge.push(n.id),i.deleted_at&&!n.deleted_at){D(e,null,n,m),u.deleted++;return}if(p){u.created++,C(e,{event:"created",object:n,actor:null,attrs:{revision:_.number},pointer:_.rev_id},m);return}if(h===0)return;const x=i.revisions.filter(a=>!r?.revisions.some(c=>c.rev_id===a.rev_id)).sort((a,c)=>c.number-a.number||c.created_at.localeCompare(a.created_at))[0]??s.get(_.rev_id);C(e,{event:"edited",object:n,actor:null,pointer:_.rev_id,attrs:{revision:_.number,from_revision:w,rationale:x?.rationale??null,author:x?.author??null,heads:v,needs_merge:v>1}},m)}f(ee,"importOne");export{ue as exportRepo,me as importTree};
1
+ var H=Object.defineProperty;var p=(t,i)=>H(t,"name",{value:i,configurable:!0});import{prepared as g}from"../../sqlite-statements.js";import{get as W,put as P}from"../bodies.js";import{setLinks as J}from"../edges.js";import{collectUnlinked as U,deleteDocument as M,documentRecord as C,heads as D,importDocument as F,insertImportedRevisions as Y,moveDocument as z,restoreDocument as K,setDocumentRecord as V}from"../documents.js";import{CrtrError as G}from"../../errors.js";import{publish as T}from"../events.js";import{getById as x,getByRowid as N,setObjectKind as q}from"../objects.js";import{rulesOf as Q}from"../rules.js";import{SPACES as X}from"../spaces/index.js";import{docPath as Z,mergeDoc as b,parseDoc as y,serializeDoc as $}from"./ref-format.js";function L(t){const i=X[t.type];if(t.owner!==null||!i||i.sync!=="repo-ref")throw new Error(`${t.type} ${t.id} is not a space that syncs through a repo ref`)}p(L,"assertRepoRefSpace");function ee(t){const i=new Map;for(const[o,n]of t??[]){const _=y(o,n);_.ok&&i.set(_.doc.id,_.doc)}return i}p(ee,"parseMap");function ke(t,i,o,n=null){L(i);const _=ee(o),f=g(t,"SELECT * FROM objects WHERE type = 'document' AND owner = ? AND privacy = 'public' ORDER BY rowid").all(i.rowid),m=g(t,"SELECT rowid, rev_id, number, body_hash, author, rationale, created_at FROM revisions WHERE document = ? ORDER BY number, rev_id"),v=g(t,"SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rowid = rp.parent WHERE rp.child = ?"),r=g(t,`SELECT e.written_as, o.id FROM edges e JOIN objects o ON o.rowid = e.dst
2
+ WHERE e.type = 'links-to' AND e.src = ? ORDER BY e.written_as, o.id`),a=new Map;for(const e of f){const d=C(t,e),c=new Map((_.get(e.id)?.revisions??[]).map(s=>[s.rev_id,s])),h=new Set(D(t,e).map(s=>s.rev_id)),w=r.all(e.rowid).map(s=>({written_as:s.written_as??"",dst:s.id})),R=m.all(e.rowid).map(s=>c.get(s.rev_id)??{rev_id:s.rev_id,number:s.number,parents:v.all(s.rowid).map(O=>O.rev_id),body:W(s.body_hash),body_hash:s.body_hash,author:s.author===null?null:N(t,s.author)?.id??null,rationale:s.rationale,created_at:s.created_at,links:h.has(s.rev_id)?w:[]});if(R.length===0)continue;const E=Q(t,e).map(s=>({position:s.position,on:s.on,match:s.match??null,match_frontmatter:s.matchFrontmatter??null,if:s.if??null,deliver:s.deliver})),A={id:e.id,name:e.name,kind:e.kind??"document",privacy:"public",preview:e.preview,summary:d.summary,why_it_exists:d.why_it_exists,unlisted:d.unlisted,lint_ignore:d.lint_ignore,extensions:d.extensions,created_at:e.created_at,deleted_at:e.deleted_at,rules:E,revisions:R};a.set(Z(e.id),$(A))}for(const e of te(o,n)){const d=o.get(e),c=a.get(e);if(c===void 0){a.set(e,d);continue}const h=y(e,d),w=y(e,c);if(!h.ok){a.set(e,d);continue}if(!w.ok)continue;const R=n?.has(e)?y(e,n.get(e)):null,E=b(R?.ok?R.doc:null,w.doc,h.doc);E&&a.set(e,$(E))}return a}p(ke,"exportRepo");function te(t,i){return[...t??[]].filter(([o,n])=>i?.get(o)!==n).map(([o])=>o)}p(te,"pendingPaths");function ne(t){return[...t].sort((i,o)=>i.position-o.position).map(i=>({on:i.on,deliver:i.deliver,...i.match!==null&&i.match!==void 0?{match:i.match}:{},...i.match_frontmatter!==null&&i.match_frontmatter!==void 0?{matchFrontmatter:i.match_frontmatter}:{},...i.if!==null&&i.if!==void 0?{if:i.if}:{}}))}p(ne,"toRuleSpecs");const ie=p((t,i)=>JSON.stringify(t??null)===JSON.stringify(i??null),"same");function j(t,i){const o={},n=p(_=>!t||!ie(t[_],i[_]),"differs");return n("preview")&&(o.preview=i.preview),n("summary")&&(o.summary=i.summary),n("why_it_exists")&&(o.whyItExists=i.why_it_exists),n("unlisted")&&(o.unlisted=i.unlisted),n("lint_ignore")&&(o.lintIgnore=i.lint_ignore),n("extensions")&&(o.extensions=i.extensions),n("rules")&&(o.rules=ne(i.rules)),o}p(j,"changedFields");let oe=0;function B(t,i){const o=`repo_sync_import_${++oe}`;t.exec(`SAVEPOINT ${o}`);try{const n=i();return t.exec(`RELEASE ${o}`),n}catch(n){throw t.exec(`ROLLBACK TO ${o}`),t.exec(`RELEASE ${o}`),n}}p(B,"atomic");function Oe(t,i,o,n,_){L(i);const f={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]},m=[];for(const[r,a]of n){if(o.get(r)===a)continue;const e=y(r,a);if(!e.ok){f.refused.push({path:r,reason:e.reason});continue}const d=o.has(r)?y(r,o.get(r)):null;m.push({path:r,prior:d?.ok?d.doc:null,doc:e.doc})}for(const[r,a]of o){if(n.has(r))continue;const e=y(r,a);e.ok&&m.push({path:r,prior:e.doc,doc:null,priorId:e.doc.id})}for(const r of m)for(const a of r.doc?.revisions??[])P(a.body);const v=[];return B(t,()=>{let r=m;for(;;){const a=[];for(const e of r){const d={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]};try{const c=[];B(t,()=>le(t,i,e.prior,e.doc,e.priorId??e.doc.id,d,_,c)),v.push(...c),f.created+=d.created,f.revisions+=d.revisions,f.deleted+=d.deleted,f.needsMerge.push(...d.needsMerge)}catch(c){ce(c)?a.push({w:e,error:c}):f.refused.push({path:e.path,reason:c.message})}}if(a.length===0)break;if(a.length===r.length){for(const e of a)f.refused.push({path:e.w.path,reason:e.error.message});break}r=a.map(e=>e.w)}re(t,n,new Set(v),new Set(m.map(a=>a.path)))}),f}p(Oe,"importTree");function re(t,i,o,n){if(o.size===0)return;const _=g(t,"INSERT OR IGNORE INTO edges (type, src, dst, written_as, created_at) VALUES ('links-to', ?, ?, ?, ?)"),f=new Date().toISOString();for(const[m,v]of i){if(!n.has(m)&&!ae(v,o))continue;const r=y(m,v);if(!r.ok||r.doc.deleted_at)continue;const a=x(t,r.doc.id);if(!a||a.type!=="document"||a.deleted_at)continue;const e=D(t,a)[0],d=r.doc.revisions.find(c=>c.rev_id===e?.rev_id)?.links??[];for(const c of d){const h=x(t,c.dst);h&&h.rowid!==a.rowid&&_.run(a.rowid,h.rowid,c.written_as,f)}}}p(re,"completeLinks");const se=/"dst":\s*"((?:[^"\\]|\\.)*)"/g;function ae(t,i){for(const o of t.matchAll(se))if(i.has(de(o[1])))return!0;return!1}p(ae,"namesArrived");function de(t){if(!t.includes("\\"))return t;try{return JSON.parse(`"${t}"`)}catch{return t}}p(de,"decodeJsonString");function ce(t){return t instanceof G&&t.code==="conflict"&&t.details?.holder!==void 0}p(ce,"isNameConflict");function le(t,i,o,n,_,f,m,v){const r=x(t,_);if(r&&(r.type!=="document"||r.owner!==i.rowid))throw new Error(`id ${_} is held by ${r.type} ${r.name} outside ${i.name}`);if(!n){r&&!r.deleted_at&&(M(t,null,r,m),f.deleted++);return}const a=g(t,"SELECT count(*) AS n FROM revisions WHERE document = ?");let e,d=!1,c=!1,h=0,w=0;if(r){e=r,h=C(t,e).revision,e.created_at!==n.created_at&&(g(t,"UPDATE objects SET created_at = ? WHERE rowid = ?").run(n.created_at,e.rowid),e=N(t,e.rowid));const l=n.kind;(o?o.kind!==l:e.kind!==l)&&q(t,e,l),c=!!e.deleted_at&&n.deleted_at===null&&(o===null||o.deleted_at!=null);let u=j(c?null:o,n);if(n.deleted_at&&e.deleted_at){const{rules:k,...S}=u;u=S}c&&(e=K(t,e,n.name),v?.push(e.id)),Object.keys(u).length>0&&(e=V(t,null,e,u)),n.name!==e.name&&!e.deleted_at&&(e=z(t,null,e,{name:n.name}))}else{const u=n.revisions.filter(k=>k.parents.length===0).sort((k,S)=>k.number-S.number||k.rev_id.localeCompare(S.rev_id))[0];if(!u)throw new Error(`${n.name}: no root revision`);e=F(t,{id:n.id,owner:i,name:n.name,kind:n.kind,preview:n.preview,privacy:"public",...j(null,n),revisions:[{rev_id:u.rev_id,body_hash:u.body_hash,author:null,rationale:u.rationale,created_at:u.created_at}]}),e.created_at!==n.created_at&&(g(t,"UPDATE objects SET created_at = ? WHERE rowid = ?").run(n.created_at,e.rowid),e=N(t,e.rowid)),d=!0,v?.push(e.id),w=1}const R=a.get(e.rowid).n,{headCount:E}=Y(t,e,n.revisions.map(l=>({rev_id:l.rev_id,parents:l.parents,body_hash:l.body_hash,rationale:l.rationale,created_at:l.created_at})));w+=a.get(e.rowid).n-R,f.revisions+=w;const s=D(t,e)[0],O=new Map(n.revisions.map(l=>[l.rev_id,l]));if(w>0||c){const l=(O.get(s.rev_id)?.links??[]).map(u=>({written_as:u.written_as,dst:x(t,u.dst)})).filter(u=>u.dst!==null);U(t,J(t,e,l),m)}if(E>1&&f.needsMerge.push(e.id),n.deleted_at&&!e.deleted_at){M(t,null,e,m,n.deleted_at),f.deleted++;return}if(n.deleted_at&&e.deleted_at&&e.deleted_at!==n.deleted_at&&g(t,"UPDATE objects SET deleted_at = ? WHERE rowid = ?").run(n.deleted_at,e.rowid),d){f.created++,T(t,{event:"created",object:e,actor:null,attrs:{revision:s.number},pointer:s.rev_id},m);return}if(c){f.created++,T(t,{event:"created",object:e,actor:null,attrs:{revision:s.number},pointer:s.rev_id},m);return}if(w===0)return;const I=n.revisions.filter(l=>!o?.revisions.some(u=>u.rev_id===l.rev_id)).sort((l,u)=>u.number-l.number||u.created_at.localeCompare(l.created_at))[0]??O.get(s.rev_id);T(t,{event:"edited",object:e,actor:null,pointer:s.rev_id,attrs:{revision:s.number,from_revision:h,rationale:I?.rationale??null,author:I?.author??null,heads:E,needs_merge:E>1}},m)}p(le,"importOne");export{ke as exportRepo,Oe as importTree,ae as namesArrived,te as pendingPaths};
@@ -53,14 +53,14 @@ export declare function parseDoc(path: string, text: string): {
53
53
  };
54
54
  /**
55
55
  * Merge one document. Revisions: union by rev_id (first copy kept verbatim). Fields: three-way
56
- * against base, remote wins when both changed. A side missing the file while base had it deleted
56
+ * against base, remote wins when both changed — except that clearing `deleted_at` (a restoration) beats a tombstone. A side missing the file while base had it deleted
57
57
  * it: the result is marked deleted (at `now`) and keeps every revision.
58
58
  */
59
59
  export declare function mergeDoc(base: RefDocument | null, local: RefDocument | null, remote: RefDocument | null, now?: string): RefDocument | null;
60
60
  /**
61
61
  * Merge three trees (path → file text). Per path mergeDoc; then two live documents with one name:
62
62
  * the one already on the remote under that name keeps it, the other becomes `<name>-<id first 8>`.
63
- * A file that fails to parse is refused; it stays in the tree only as the local side's text.
63
+ * A file that fails to parse is refused; it stays in the tree verbatim (the local side's text, else the remote's).
64
64
  */
65
65
  export declare function mergeTrees(base: Map<string, string>, local: Map<string, string>, remote: Map<string, string>, now?: string): {
66
66
  tree: Map<string, string>;