@north-light/cli 0.3.549 → 0.3.551

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 (161) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/custom-objects.d.ts +1 -1
  4. package/dist/api/dto/docs.d.ts +12 -0
  5. package/dist/api/dto/objects.d.ts +17 -1
  6. package/dist/api/dto/objects.js +1 -1
  7. package/dist/api/dto/permissions.d.ts +76 -0
  8. package/dist/api/dto/permissions.js +1 -0
  9. package/dist/api/index.d.ts +3 -2
  10. package/dist/api/index.js +1 -1
  11. package/dist/api/routes.d.ts +4 -0
  12. package/dist/api/routes.js +1 -1
  13. package/dist/builtin-memory/crouter-concepts/documents.md +1 -1
  14. package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +2 -2
  15. package/dist/builtin-memory/crouter-sdk/release-notes.md +11 -0
  16. package/dist/builtin-memory/internal/INDEX.md +2 -1
  17. package/dist/builtin-memory/internal/agent-shaping.md +1 -1
  18. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  19. package/dist/builtin-memory/northlight-apps/guides/files-and-documents.md +3 -3
  20. package/dist/builtin-memory/northlight-apps/guides/many-users.md +1 -1
  21. package/dist/builtin-memory/northlight-apps/guides/test-your-app.md +1 -1
  22. package/dist/clients/attach/viewer.js +9 -9
  23. package/dist/commands/canvas/list.js +2 -2
  24. package/dist/commands/canvas/share.d.ts +2 -0
  25. package/dist/commands/canvas/share.js +2 -0
  26. package/dist/commands/canvas.js +1 -1
  27. package/dist/commands/doc/edit.js +1 -1
  28. package/dist/commands/doc/shared.js +1 -1
  29. package/dist/commands/doc/write.js +1 -1
  30. package/dist/commands/sys/sync-skills.js +2 -2
  31. package/dist/core/canvas/canvas.js +20 -20
  32. package/dist/core/canvas/db.js +4 -4
  33. package/dist/core/canvas/export-sqlite.js +2 -2
  34. package/dist/core/canvas/import-sqlite.js +5 -5
  35. package/dist/core/canvas/outcome-actions.js +3 -3
  36. package/dist/core/canvas/person.js +11 -9
  37. package/dist/core/canvas/schema-pg.sql +49 -5
  38. package/dist/core/environments.js +1 -1
  39. package/dist/core/feed/feed.js +1 -1
  40. package/dist/core/grants/mirror.d.ts +9 -1
  41. package/dist/core/grants/mirror.js +2 -2
  42. package/dist/core/grants/sync.js +1 -1
  43. package/dist/core/graph/access.d.ts +8 -49
  44. package/dist/core/graph/access.js +4 -7
  45. package/dist/core/graph/custom.d.ts +5 -2
  46. package/dist/core/graph/custom.js +9 -9
  47. package/dist/core/graph/deletion.js +5 -4
  48. package/dist/core/graph/documents.d.ts +12 -3
  49. package/dist/core/graph/documents.js +13 -7
  50. package/dist/core/graph/edges.js +3 -3
  51. package/dist/core/graph/events.js +12 -8
  52. package/dist/core/graph/kinds/document.js +4 -4
  53. package/dist/core/graph/kinds/kind.js +1 -1
  54. package/dist/core/graph/kinds/node.js +5 -5
  55. package/dist/core/graph/names.d.ts +16 -1
  56. package/dist/core/graph/names.js +11 -3
  57. package/dist/core/graph/objects.d.ts +19 -2
  58. package/dist/core/graph/objects.js +16 -10
  59. package/dist/core/graph/package-docs.js +5 -5
  60. package/dist/core/graph/package-files.js +2 -2
  61. package/dist/core/graph/reader.js +3 -3
  62. package/dist/core/graph/repo-sync/exchange.js +2 -2
  63. package/dist/core/graph/rules.d.ts +7 -5
  64. package/dist/core/graph/rules.js +3 -3
  65. package/dist/core/graph/types.d.ts +12 -1
  66. package/dist/core/graph/types.js +1 -1
  67. package/dist/core/graph/watches.d.ts +1 -1
  68. package/dist/core/graph/watches.js +9 -8
  69. package/dist/core/peer/envelope.d.ts +1 -1
  70. package/dist/core/peer/envelope.js +1 -1
  71. package/dist/core/peer/stubs.d.ts +1 -1
  72. package/dist/core/peer/stubs.js +1 -1
  73. package/dist/core/permissions/boot.d.ts +17 -0
  74. package/dist/core/permissions/boot.js +18 -0
  75. package/dist/core/permissions/builtin.d.ts +8 -0
  76. package/dist/core/permissions/builtin.js +20 -0
  77. package/dist/core/permissions/ceiling.d.ts +57 -0
  78. package/dist/core/permissions/ceiling.js +22 -0
  79. package/dist/core/permissions/compile.d.ts +14 -0
  80. package/dist/core/permissions/compile.js +1 -0
  81. package/dist/core/permissions/errors.d.ts +7 -0
  82. package/dist/core/permissions/errors.js +1 -0
  83. package/dist/core/permissions/evaluator.d.ts +28 -0
  84. package/dist/core/permissions/evaluator.js +6 -0
  85. package/dist/core/permissions/facts.d.ts +56 -0
  86. package/dist/core/permissions/facts.js +2 -0
  87. package/dist/core/permissions/schema-parser.d.ts +29 -0
  88. package/dist/core/permissions/schema-parser.js +2 -0
  89. package/dist/core/permissions/schemas.d.ts +21 -0
  90. package/dist/core/permissions/schemas.js +2 -0
  91. package/dist/core/permissions/sql.d.ts +16 -0
  92. package/dist/core/permissions/sql.js +1 -0
  93. package/dist/core/runtime/broker/frame-client.d.ts +1 -1
  94. package/dist/core/runtime/broker/frame-client.js +1 -1
  95. package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
  96. package/dist/core/runtime/broker/frame-dispatch.js +1 -1
  97. package/dist/core/runtime/broker/frame-read.d.ts +4 -3
  98. package/dist/core/runtime/broker/frame-read.js +1 -1
  99. package/dist/core/runtime/broker-protocol.d.ts +1 -1
  100. package/dist/core/runtime/host.js +3 -3
  101. package/dist/core/runtime/placement.js +1 -1
  102. package/dist/core/runtime/roadmap.js +1 -1
  103. package/dist/core/substrate/delivery/corpus.d.ts +2 -2
  104. package/dist/core/substrate/delivery/corpus.js +3 -3
  105. package/dist/core/substrate/delivery/deliver.js +2 -2
  106. package/dist/core/substrate/delivery/render-event.js +6 -6
  107. package/dist/core/substrate/subject-fields.d.ts +2 -0
  108. package/dist/core/substrate/subject.js +1 -1
  109. package/dist/daemon/api/app-listener.js +12 -12
  110. package/dist/daemon/api/bridge.d.ts +1 -1
  111. package/dist/daemon/api/bridge.js +4 -4
  112. package/dist/daemon/api/handlers/attach.d.ts +4 -1
  113. package/dist/daemon/api/handlers/attach.js +1 -1
  114. package/dist/daemon/api/handlers/canvas.js +5 -5
  115. package/dist/daemon/api/handlers/check.d.ts +2 -0
  116. package/dist/daemon/api/handlers/check.js +1 -0
  117. package/dist/daemon/api/handlers/custom-objects.js +2 -2
  118. package/dist/daemon/api/handlers/docs.js +1 -1
  119. package/dist/daemon/api/handlers/files.js +1 -1
  120. package/dist/daemon/api/handlers/memory.js +7 -6
  121. package/dist/daemon/api/handlers/nodes.js +1 -1
  122. package/dist/daemon/api/handlers/objects.js +3 -3
  123. package/dist/daemon/api/handlers/relations.d.ts +2 -0
  124. package/dist/daemon/api/handlers/relations.js +1 -0
  125. package/dist/daemon/api/handlers/schemas.d.ts +12 -0
  126. package/dist/daemon/api/handlers/schemas.js +1 -0
  127. package/dist/daemon/api/handlers/shares.d.ts +2 -0
  128. package/dist/daemon/api/handlers/shares.js +1 -0
  129. package/dist/daemon/api/operations.js +1 -1
  130. package/dist/daemon/api/router.d.ts +10 -6
  131. package/dist/daemon/api/router.js +1 -1
  132. package/dist/daemon/api/server.js +1 -1
  133. package/dist/daemon/crtrd.js +12 -12
  134. package/dist/daemon/reconcilers/delivery-lane.js +2 -2
  135. package/dist/doc-editor/editor.css +1 -1
  136. package/dist/doc-editor/editor.js +16 -16
  137. package/dist/migrations/004-canvas-documents/fields.d.ts +1 -1
  138. package/dist/migrations/004-canvas-documents/fields.js +2 -2
  139. package/docs/apps/guides/files-and-documents.md +3 -3
  140. package/docs/apps/guides/many-users.md +1 -1
  141. package/docs/apps/guides/test-your-app.md +1 -1
  142. package/docs/cli/documents-and-preferences.md +1 -1
  143. package/docs/concepts/documents.md +1 -1
  144. package/docs/concepts/scopes-and-trust.md +2 -2
  145. package/docs/sdk/release-notes.md +11 -0
  146. package/package.json +2 -2
  147. package/packages/crouter-identity/dist/index.js +14 -1
  148. package/packages/crouter-identity/package.json +1 -1
  149. package/runtime.lock.json +13 -13
  150. package/dist/core/graph/spaces/app.d.ts +0 -3
  151. package/dist/core/graph/spaces/app.js +0 -1
  152. package/dist/core/graph/spaces/index.d.ts +0 -18
  153. package/dist/core/graph/spaces/index.js +0 -1
  154. package/dist/core/graph/spaces/own-memory.d.ts +0 -18
  155. package/dist/core/graph/spaces/own-memory.js +0 -1
  156. package/dist/core/graph/spaces/person.d.ts +0 -5
  157. package/dist/core/graph/spaces/person.js +0 -3
  158. package/dist/core/graph/spaces/repo.d.ts +0 -14
  159. package/dist/core/graph/spaces/repo.js +0 -1
  160. package/dist/core/runs/confinement.d.ts +0 -25
  161. package/dist/core/runs/confinement.js +0 -2
@@ -1,4 +1,4 @@
1
- import type { Privacy, RuleSpec } from '../../core/graph/types.js';
1
+ import { type Privacy, type RuleSpec } from '../../core/graph/types.js';
2
2
  export type DocumentKind = 'knowledge' | 'preference' | 'report';
3
3
  export interface DocumentFields {
4
4
  kind: DocumentKind;
@@ -1,2 +1,2 @@
1
- var c=Object.defineProperty;var r=(e,t)=>c(e,"name",{value:t,configurable:!0});import{rulesFromFrontmatter as u}from"../../core/graph/rules.js";const f=new Set(["kind","privacy","unlisted","lint-ignore","extensions","when-and-why-to-read","short-form","rationale","surfaces","gate","slash","origin","last-updated","name","namespace"]),p=["public","friends","private"],s=r(e=>typeof e=="string"&&e.trim()!==""?e.trim():null,"str");function m(e){for(const t of e.split(`
2
- `)){const i=t.trim().replace(/^#+\s*/,"").replace(/^[-*]\s+/,"").trim();if(i!==""&&i!=="---")return i}return""}r(m,"firstLine");function g(e,t,i){const n=e??{},o=Object.keys(n).filter(a=>!f.has(a));let l=i.kind??"knowledge";i.kind===void 0&&n.kind!==void 0&&(n.kind==="knowledge"||n.kind==="preference"?l=n.kind:o.push("kind"));const d=typeof n.privacy=="string"&&p.includes(n.privacy)?n.privacy:i.missingPrivacy;return{kind:l,privacy:d,preview:s(n["when-and-why-to-read"])?.replace(/\s+/g," ")??m(t),summary:s(n["short-form"]),whyItExists:s(n.rationale),unlisted:n.unlisted===!0,lintIgnore:n["lint-ignore"]??null,extensions:n.extensions??null,rules:u(n),dropped:o}}r(g,"documentFields");function h(e,t){return e.map(i=>({...i,if:i.if===void 0?t:{all:[i.if,t]}}))}r(h,"andIntoRules");export{h as andIntoRules,g as documentFields,m as firstLine};
1
+ var u=Object.defineProperty;var r=(n,t)=>u(n,"name",{value:t,configurable:!0});import{rulesFromFrontmatter as f}from"../../core/graph/rules.js";import{readLegacyPrivacy as p}from"../../core/graph/types.js";const m=new Set(["kind","privacy","unlisted","lint-ignore","extensions","when-and-why-to-read","short-form","rationale","surfaces","gate","slash","origin","last-updated","name","namespace"]),g=["public","private"],o=r(n=>typeof n=="string"&&n.trim()!==""?n.trim():null,"str");function y(n){for(const t of n.split(`
2
+ `)){const i=t.trim().replace(/^#+\s*/,"").replace(/^[-*]\s+/,"").trim();if(i!==""&&i!=="---")return i}return""}r(y,"firstLine");function v(n,t,i){const e=n??{},l=Object.keys(e).filter(c=>!m.has(c));let d=i.kind??"knowledge";i.kind===void 0&&e.kind!==void 0&&(e.kind==="knowledge"||e.kind==="preference"?d=e.kind:l.push("kind"));const s=p(e.privacy),a=typeof s=="string"&&g.includes(s)?s:i.missingPrivacy;return{kind:d,privacy:a,preview:o(e["when-and-why-to-read"])?.replace(/\s+/g," ")??y(t),summary:o(e["short-form"]),whyItExists:o(e.rationale),unlisted:e.unlisted===!0,lintIgnore:e["lint-ignore"]??null,extensions:e.extensions??null,rules:f(e),dropped:l}}r(v,"documentFields");function x(n,t){return n.map(i=>({...i,if:i.if===void 0?t:{all:[i.if,t]}}))}r(x,"andIntoRules");export{x as andIntoRules,v as documentFields,y as firstLine};
@@ -19,7 +19,7 @@ A non-2xx answer is thrown as an `APIError`, never returned as bytes. A bad `ran
19
19
 
20
20
  ## Read the person's public documents
21
21
 
22
- `runtime.memory` is a read-only view of the `knowledge` and `preference` documents the person owns (`memory` and the `/v1/memory` routes are the API's names for this). An app reads the person's `public` documents with `memory.list` and `memory.get`, and needs no scope to do it. Without more, it never sees a `friends` or `private` document and cannot tell one from a document that does not exist: both answer `404 not_found`. The optional scope [`crtr:memory:read_private:user`](#private-memory) adds `private`. The list is what the app's agents can read about the person, so an app can show the person exactly which of their documents it draws on.
22
+ `runtime.memory` is a read-only view of the `knowledge` and `preference` documents the person owns (`memory` and the `/v1/memory` routes are the API's names for this). An app reads the person's `public` documents with `memory.list` and `memory.get`, and needs no scope to do it. Without more, it never sees a `private` document and cannot tell one from a document that does not exist: both answer `404 not_found`. The optional scope [`crtr:memory:read_private:user`](#private-memory) adds `private`. The list is what the app's agents can read about the person, so an app can show the person exactly which of their documents it draws on.
23
23
 
24
24
  ```ts
25
25
  const page = await runtime.memory.list({store: 'user'});
@@ -56,13 +56,13 @@ for await (const item of runtime.memory.listAll({store: 'user', path: 'work'}))
56
56
 
57
57
  - `body` is the current markdown as stored; `[[links]]` are not resolved. A body over 1 MiB is cut to its longest whole-character prefix of at most 1 MiB, with `truncated: true`; `size` is always the full length in UTF-8 bytes.
58
58
  - `grantee` is `app:<id>` of the app whose agent wrote the first revision, or `null` when the person wrote it.
59
- - An absent or deleted name, a `friends` name, and a `private` name without `crtr:memory:read_private:user` reject with `NotFoundError` (`code: 'not_found'`); a malformed field rejects with `BadRequestError`.
59
+ - An absent or deleted name, and a `private` name without `crtr:memory:read_private:user` reject with `NotFoundError` (`code: 'not_found'`); a malformed field rejects with `BadRequestError`.
60
60
 
61
61
  ## Private memory
62
62
 
63
63
  `crtr:memory:read_private:user` is an [optional permission](/docs/guides/grant-changes#optional-permissions) that lets your app read the person's `private` documents. Declare it, then let the person switch it on from your app (sign-in request or `permissionUrl`).
64
64
 
65
- - **Read-only, and never `friends`.** With the scope `memory.list` and `memory.get` return `public` and `private` documents; a `friends` document still reads as absent, and the scope writes nothing. Each private document arrives with `privacy: 'private'`.
65
+ - **Read-only.** With the scope `memory.list` and `memory.get` return `public` and `private` documents, and the scope writes nothing. Each private document arrives with `privacy: 'private'`.
66
66
  - **Both server reads and runs.** Your server's `memory` calls see private documents while the grant holds the scope. A run sees them only if it was started with the scope: `runs.start` `scopes` must cover `crtr:memory:read_private:user` (name it, or name `crtr:memory:manage:user` if the grant holds that) (a run with no `scopes` list gets what the grant holds), and the start is refused `403 scope_missing` while the grant lacks it. Add the scope to the runs that need it only while `resolved_scopes` holds it.
67
67
  - **The person can turn it off at any time.** Reads then stop returning private documents at once, including from a run that is still live. That run keeps what it already read in its context, so restart the runs that used it when the permission turns off (`watchGrants` tells you).
68
68
  - **Never show a private detail to anyone but the owner**, unless the owner asked your app to. A visitor, a guest or another user must not see a fact, quote or summary taken from a private document.
@@ -46,7 +46,7 @@ A visitor chatting with B's clone starts an app run on **B's runtime and B's AI
46
46
 
47
47
  A run your app starts is **your app's agent**, not a continuation of another app's agent. It starts in `/apps/<app>/files`, uses the person's AI under `crtr:llm`, and reaches your app's own documents (owned by `app:<your app>`) without a `crtr:memory` scope. Other people's or apps' data is not shared merely because the app has many users. The grant's resolved permissions govern access to the person's files (`crtr:files:<access>:user:<dir>`), the person's documents (`crtr:memory:<access>:user`), another app's space (only where that app allows it), and another app's conversations (`crtr:conversations:read:app:<B>`).
48
48
 
49
- The exception is the person's **public** documents (owned by the person, read through `memory`; see [Files and documents](/docs/guides/files-and-documents)): an app with an active grant can read these even without a user-memory scope. A third-party app cannot see `friends` documents, and sees `private` ones only with the optional `crtr:memory:read_private:user` the person switched on; `crtr:memory:read:user` reveals neither. They look absent from lists, searches, reads and history. Only a holder of `crtr:memory:manage:user` (a first-party app), or the person, reads all three levels. A document without a privacy field reads as `private`. Public read access is not write access.
49
+ The exception is the person's **public** documents (owned by the person, read through `memory`; see [Files and documents](/docs/guides/files-and-documents)): an app with an active grant can read these even without a user-memory scope. A third-party app sees `private` documents only with the optional `crtr:memory:read_private:user` the person switched on; `crtr:memory:read:user` does not reveal them. They look absent from lists, searches, reads and history. `crtr:memory:read_private:user` and `crtr:memory:manage:user` (a first-party app) widen the app's ceiling to `private`, and the app's schema and relation facts decide visibility within it. The person reads both levels. A document without a privacy field reads as `private`. Public read access is not write access.
50
50
 
51
51
  Every run is sandboxed: its shell runs as the app's OS user and sees only the app's space and the folders its permissions name. Normal runs have network access. Start a run with `isolated: true` when the agent should have **no network** and **no `/apps/<app>/files` mount**: its cwd is its own node folder, and all children inherit the restriction. The shell, stored conversation and model calls still work, and the run's `scopes` can narrow its commands. An isolated run cannot turn isolation off in a child or schedule, or use an outside cwd or worktree.
52
52
 
@@ -117,7 +117,7 @@ In your callback handler, check `authorizationRefusalOf(url)` before reading `co
117
117
 
118
118
  ## Memory and providers
119
119
 
120
- `user.memory.put({name, body, privacy = 'public', kind = 'knowledge', ...})` and `remove(name)`. Without `crtr:memory:manage:user` in the grant, only `public` documents are listed and others read as 404, as on the runtime; `crtr:memory:read_private:user` adds `private` (never `friends`). FakeCloud applies the user's whole grant (`user.grant.set`), not the `scopes` a run was started with; test narrowed runs on the real runtime. List items carry the full `MemoryListItem` fields.
120
+ `user.memory.put({name, body, privacy = 'public', kind = 'knowledge', ...})` and `remove(name)`. Without `crtr:memory:manage:user` in the grant, only `public` documents are listed and others read as 404, as on the runtime; `crtr:memory:read_private:user` adds `private`. FakeCloud applies the user's whole grant (`user.grant.set`), not the `scopes` a run was started with; test narrowed runs on the real runtime. List items carry the full `MemoryListItem` fields.
121
121
 
122
122
  ```ts
123
123
  cloud.providers.define('sms', { tools: { send: { group: 'send', handler: ({ userId, arguments: args }) => ({ sent: true }) } } });
@@ -47,6 +47,6 @@ printf '%s\n' 'Production changes need an explicit approval.' | \
47
47
  crtr doc edit <name> --rationale "Updated the approval rule."
48
48
  ```
49
49
 
50
- `crtr doc edit` replaces the whole body you pipe; it does not append text. For a partial change, use `crtr doc edit <name> --rationale '…' --blocks <<'EOF'` with whole-line `<<<<<<< SEARCH`, `=======`, and `>>>>>>> REPLACE` markers around verbatim old and new text. An empty REPLACE deletes text. Scripts can still use `--replace '[{"old":"…","new":"…"}]'`. Read the document before you edit it; an edit made from an older revision is refused. Use `crtr doc history <name>` to read the revision history. If you want to create a document yourself, use `crtr doc write`; it takes a name, a preview sentence that tells agents when to read it, and the document body on stdin. `--kind knowledge|preference` (default `knowledge`) sets how agents use it, and `--owner node|user|profile|app|repo` chooses who owns it (default: the writing node; `profile:<name>` and `repo:<handle>` name a specific one). New documents are `privacy: public` unless `--privacy friends` or `--privacy private` is supplied; a repository owns only public documents. `crtr doc edit --privacy <level>` changes the level. An edit that changes only fields (privacy, preview, summary, rules, `--why-it-exists`) takes effect at once but makes no new revision: revisions are versions of the body, so the revision number does not move and `crtr doc history` shows nothing for it.
50
+ `crtr doc edit` replaces the whole body you pipe; it does not append text. For a partial change, use `crtr doc edit <name> --rationale '…' --blocks <<'EOF'` with whole-line `<<<<<<< SEARCH`, `=======`, and `>>>>>>> REPLACE` markers around verbatim old and new text. An empty REPLACE deletes text. Scripts can still use `--replace '[{"old":"…","new":"…"}]'`. Read the document before you edit it; an edit made from an older revision is refused. Use `crtr doc history <name>` to read the revision history. If you want to create a document yourself, use `crtr doc write`; it takes a name, a preview sentence that tells agents when to read it, and the document body on stdin. `--kind knowledge|preference` (default `knowledge`) sets how agents use it, and `--owner node|user|profile|app|repo` chooses who owns it (default: the writing node; `profile:<name>` and `repo:<handle>` name a specific one). New documents are `privacy: public` unless `--privacy private` is supplied; a repository owns only public documents. `crtr doc edit --privacy <level>` changes the level. An edit that changes only fields (privacy, preview, summary, rules, `--why-it-exists`) takes effect at once and records a new revision with the body unchanged, attributed to you with the rationale: the revision number advances and `crtr doc history` lists it. Another reader that read the document earlier must read it again before its next body edit.
51
51
 
52
52
  Documents are for reusable current truth and durable instructions, not a transcript of one conversation. When a fact changes, update the existing document rather than saving a second version.
@@ -33,7 +33,7 @@ An installed plugin is an app named after the plugin, and the documentation that
33
33
 
34
34
  ## Who may read
35
35
 
36
- Each document is `public` (the default), `friends` or `private`. The person reads everything, a repo owns only public documents, and a reader that cannot see a document gets the same answer as for one that does not exist. A node's own owner space is open to it; an app's access to the person's documents is by privacy level and the scopes its grant holds (see [Scopes and trust](./scopes-and-trust.md)).
36
+ Each document is `public` (the default) or `private`. The person reads everything, a repo owns only public documents, and a reader that cannot see a document gets the same answer as for one that does not exist. A node's own owner space is open to it; an app's access to the person's documents is by privacy level and the scopes its grant holds (see [Scopes and trust](./scopes-and-trust.md)).
37
37
 
38
38
  ```mermaid
39
39
  flowchart LR
@@ -15,7 +15,7 @@ Scope strings are prefixed with `crtr:`. These are the ones the daemon and CLI c
15
15
  | `crtr:act` | Creating, forking, reviving, closing and messaging nodes; `bash` and `files`; creating a human request or review (the `crtr human` commands are hidden from a run without it) |
16
16
  | `crtr:schedule` | Creating, pausing, resuming, running and deleting crons (the `crtr cron` commands are hidden without it) |
17
17
  | `crtr:memory:read:user[:path]`, `crtr:memory:rw:user[:path]`, `crtr:memory:manage:user` | The person's documents: reading, writing (`crtr doc`), and `manage` for every privacy level and every document |
18
- | `crtr:memory:read_private:user` | Read-only access to the person's `public` and `private` documents, never `friends`; no path suffix; an optional permission the person switches on; `manage:user` covers it, `read:user` and `rw:user` do not |
18
+ | `crtr:memory:read_private:user` | Read-only access to the person's `public` and `private` documents; no path suffix; an optional permission the person switches on; `manage:user` covers it, `read:user` and `rw:user` do not |
19
19
  | `crtr:memory:read:app:<id>`, `crtr:memory:rw:app:<id>` | An app's documents; `…:profile:<profile>` narrows to one of its profiles. A run with no `scopes` list, and an app's own server, reads and writes its own app's space freely; a run started with a `scopes` list needs these for its own app too |
20
20
  | `crtr:files:read\|rw:user:<dir>`, `crtr:files:read\|rw:app:<id>:<dir>`, `crtr:files:share` | Host files the run may mount, and sharing files beyond the app's own space |
21
21
  | `crtr:conversations:read:app:<id>`, `crtr:objects:read:app:<id>` | Reading another app's conversations or node objects |
@@ -23,7 +23,7 @@ Scope strings are prefixed with `crtr:`. These are the ones the daemon and CLI c
23
23
 
24
24
  The `memory` in `crtr:memory:*` is the scope family's name for access to **documents** (see [Documents](./documents.md)); there is no separate memory store. The older short names (`ask`, `act`, `schedule`, `memory:read`, `memory:write`, `llm`) are not accepted by `crtr sys connect --scopes`. A node's own documents, and an app's documents for its own nodes, need no `crtr:memory` scope.
25
25
 
26
- This is an allow-list, not a claim that code will behave. The daemon checks the scope at the route that performs the action and rejects an unavailable one. The SDK’s [`nodes.create`](../sdk/nodes.md) `scopes` field narrows a run: a run's list must stay inside what its creator and grant hold, and `crtr:act` requires `crtr:llm`. `client.memory` is not scope-gated: it lists and reads the person's `knowledge` and `preference` documents, and the answer is filtered by privacy — an app sees `public` documents, a grant holding `crtr:memory:read_private:user` also sees `private` ones, and a grant holding `crtr:memory:manage:user` sees `friends` and `private` ones. Who may read any document is the owner-and-privacy model, not a per-call scope. The exception is a run started with a `scopes` list: it reads its own app's documents (including automatic delivery of them at boot) only with `crtr:memory:read:app:<id>` and writes or edits them only with `crtr:memory:rw:app:<id>`; a refused write answers `scope_missing` naming the scope. Such a run still publishes its own reports with `crtr push` and its promoted node still gets a roadmap, but cannot edit any other document through that path, and a child never regains a scope its parent's list dropped.
26
+ This is an allow-list, not a claim that code will behave. The daemon checks the scope at the route that performs the action and rejects an unavailable one. The SDK’s [`nodes.create`](../sdk/nodes.md) `scopes` field narrows a run: a run's list must stay inside what its creator and grant hold, and `crtr:act` requires `crtr:llm`. `client.memory` is not scope-gated: it lists and reads the person's `knowledge` and `preference` documents, and the answer is filtered by privacy, which is `public` or `private`. An app's ceiling is `public` documents; a grant holding `crtr:memory:read_private:user` or `crtr:memory:manage:user` widens it to `private` ones. Within that ceiling, the app's registered schema and the relation facts it writes decide who may read a document, not a per-call scope. The exception is a run started with a `scopes` list: it reads its own app's documents (including automatic delivery of them at boot) only with `crtr:memory:read:app:<id>` and writes or edits them only with `crtr:memory:rw:app:<id>`; a refused write answers `scope_missing` naming the scope. Such a run still publishes its own reports with `crtr push` and its promoted node still gets a roadmap, but cannot edit any other document through that path, and a child never regains a scope its parent's list dropped.
27
27
 
28
28
  ```mermaid
29
29
  flowchart LR
@@ -7,6 +7,17 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
7
7
 
8
8
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
9
9
 
10
+ ## 0.3.550
11
+
12
+ An app registers a permission schema for its own object types and decides who may read each object with relations. Additive, except that `friends` leaves the privacy levels and nodes move under their profile.
13
+
14
+ - **Schemas, relations, check.** `client.schemas.register({ namespace, source })` (`POST /v1/schemas`) registers the schema for a namespace the app first claims; `client.relations.write | replace | list` (`/v1/relations`) writes and reads facts such as `firstlight/memory:<id> editor user:<id>`; `client.check(subject, permission, resource)` (`POST /v1/check`) answers whether a person may act. A type or relation outside the app's own namespace is refused `403 not_schema_owner`; a namespace another app registered first is refused `namespace_taken`.
15
+ - **Typed documents.** `docs.write` takes `schema_type` (the app type the document is an instance of) and `relations` (facts written in the same transaction), and `owner: 'user'` writes under the person the call token names. `docs.get` returns `extensions` and `schema_type`. `docs.delete(ref)`, `docs.list(params)` and `docs.search(params)` are new; `list` and `search` filter by `schema_type` and by `extensions` inside the permission-filtered query, before the page limit. Document history entries carry `acting_app` and `acting_for`.
16
+ - **`friends` is removed.** Privacy is `public` or `private`. A write naming `friends` is refused `400 invalid_request`. Existing `friends` documents became `private`; a `friends` value that arrives from an older source (a peer on an older release, a plugin package, a legacy canvas) is read as `private`.
17
+ - **Nodes are owned by their profile.** A node started under a profile now has that profile object as its `owner`, where it used to have the app: `owner` on list, search, read and event payloads names the profile, and listing with `owner=<app>&type=node` no longer returns those nodes; list them with `owner=<profile>`. Events carry the node's app as `app`. A node with no profile stays under its app.
18
+ - **A fields-only edit records a revision.** `docs.edit` with only fields (preview, summary, privacy, rules, …) now records a revision with the body unchanged, attributed to the editor with the rationale: the returned `revision` advances and `docs.history` lists it. Before, it changed the fields and left the revision alone.
19
+ - **FakeCloud** serves the new routes in memory: it parses and validates a registered schema source, refuses facts the schema does not declare, and evaluates `check`, `list` and `search` against the schema the way the runtime does. Deleting a typed document removes the facts that name it.
20
+
10
21
  ## 0.3.548
11
22
 
12
23
  `canvas.watch` with a `watcher` that is a node of your own app now works under `crtr:objects:link:node`. It answered `404 not_found` (`watcher not found`) before. The node must be reachable from the caller under run confinement and must itself be able to read the object. A watch on a node of another machine now forwards every event level, and that node's own listening config decides how each is delivered, where an event below level 3 was dropped before it left the object's machine.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.549",
3
+ "version": "0.3.551",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -140,7 +140,7 @@
140
140
  },
141
141
  "license": "UNLICENSED",
142
142
  "dependencies": {
143
- "@north-light/identity": "0.3.549",
143
+ "@north-light/identity": "0.3.551",
144
144
  "@earendil-works/pi-agent-core": "1.1.0",
145
145
  "@earendil-works/pi-ai": "1.1.0",
146
146
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -14,14 +14,27 @@ function validPath(path, directoryOnly = false) {
14
14
  !path.split('/').some((part, index, parts) => part === '.' || (part === '' && index !== parts.length - 1)) &&
15
15
  (!directoryOnly || !path.endsWith('/'));
16
16
  }
17
+ // `covers` parses both sides on every call, and a grant can hold thousands of scopes: parse each string once. A parse is pure; a throw is not cached.
18
+ const selectorCache = new Map();
19
+ const SELECTOR_CACHE_MAX = 20_000;
17
20
  function selector(value) {
21
+ let s = selectorCache.get(value);
22
+ if (s === undefined) {
23
+ s = parseSelector(value);
24
+ if (selectorCache.size >= SELECTOR_CACHE_MAX)
25
+ selectorCache.clear();
26
+ selectorCache.set(value, s);
27
+ }
28
+ return s;
29
+ }
30
+ function parseSelector(value) {
18
31
  if (value === 'crtr:llm' || value === 'crtr:act' || value === 'crtr:schedule')
19
32
  return { kind: value.slice(5), value };
20
33
  if (value === 'crtr:files:share')
21
34
  return { kind: 'share', value };
22
35
  if (value === 'crtr:memory:manage:user')
23
36
  return { kind: 'memory', access: 'manage', holder: 'user' };
24
- // Read-only access to the person's `public` and `private` memory (never `friends`). It names no document path.
37
+ // Read-only access to the person's `public` and `private` memory. It names no document path.
25
38
  if (value === 'crtr:memory:read_private:user')
26
39
  return { kind: 'memory', access: 'read_private', holder: 'user' };
27
40
  let match = /^crtr:memory:(read|rw):user(?::(.+))?$/.exec(value);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.549",
3
+ "version": "0.3.551",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.549",
3
+ "version": "0.3.551",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.549",
9
+ "version": "0.3.551",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -24,7 +24,7 @@
24
24
  "@milkdown/preset-gfm": "^7.22.3",
25
25
  "@milkdown/prose": "^7.22.3",
26
26
  "@milkdown/utils": "^7.22.3",
27
- "@north-light/identity": "0.3.549",
27
+ "@north-light/identity": "0.3.551",
28
28
  "cron-parser": "^5.6.0",
29
29
  "esbuild": "^0.27.7",
30
30
  "jose": "^6.2.12",
@@ -12455,17 +12455,17 @@
12455
12455
  },
12456
12456
  "packages/crouter-api": {
12457
12457
  "name": "@north-light/api",
12458
- "version": "0.3.549",
12458
+ "version": "0.3.551",
12459
12459
  "license": "UNLICENSED"
12460
12460
  },
12461
12461
  "packages/crouter-env-docker": {
12462
12462
  "name": "@north-light/env-docker",
12463
- "version": "0.3.549",
12463
+ "version": "0.3.551",
12464
12464
  "license": "UNLICENSED"
12465
12465
  },
12466
12466
  "packages/crouter-identity": {
12467
12467
  "name": "@north-light/identity",
12468
- "version": "0.3.549",
12468
+ "version": "0.3.551",
12469
12469
  "license": "UNLICENSED",
12470
12470
  "dependencies": {
12471
12471
  "jose": "^6.2.1"
@@ -12473,21 +12473,21 @@
12473
12473
  },
12474
12474
  "packages/crouter-plugin": {
12475
12475
  "name": "@north-light/plugin",
12476
- "version": "0.3.549",
12476
+ "version": "0.3.551",
12477
12477
  "license": "UNLICENSED",
12478
12478
  "dependencies": {
12479
- "@north-light/api": "0.3.549",
12480
- "@north-light/identity": "0.3.549"
12479
+ "@north-light/api": "0.3.551",
12480
+ "@north-light/identity": "0.3.551"
12481
12481
  }
12482
12482
  },
12483
12483
  "packages/crouter-sdk": {
12484
12484
  "name": "@north-light/sdk",
12485
- "version": "0.3.549",
12485
+ "version": "0.3.551",
12486
12486
  "license": "UNLICENSED",
12487
12487
  "dependencies": {
12488
- "@north-light/api": "0.3.549",
12489
- "@north-light/identity": "0.3.549",
12490
- "@north-light/plugin": "0.3.549",
12488
+ "@north-light/api": "0.3.551",
12489
+ "@north-light/identity": "0.3.551",
12490
+ "@north-light/plugin": "0.3.551",
12491
12491
  "jose": "^6.2.1"
12492
12492
  },
12493
12493
  "bin": {
@@ -1,3 +0,0 @@
1
- import type { Space } from './index.js';
2
- /** An app: its nodes (with their documents and jobs), its profiles and its app documents. */
3
- export declare const appSpace: Space;
@@ -1 +0,0 @@
1
- var S=Object.defineProperty;var l=(i,n)=>S(i,"name",{value:n,configurable:!0});import{covers as u}from"@north-light/identity";import{identityOf as a}from"./index.js";import{memoryScope as r,missingOwnMemoryScope as p}from"./own-memory.js";import{objectPlace as x,runPlace as f,runsSeeEachOther as O}from"../../runs/confinement.js";const M={sync:"runtime",async canRead(i,n,e,t,o){if(n.class==="person"||t.length===0)return!0;if(n.app.id===e.id)return await w(i,n,t)&&await p(i,n,e,t,o,"read")===null;const s=(await a(i,e)).external_id,c=l(y=>n.scopes!==null&&n.scopes.some(g=>u(g,y)),"held");return t[0].type==="node"?c(`crtr:objects:read:${s}`)||o.type==="document"&&o.kind==="report"&&c(`crtr:conversations:read:${s}`):c(await r(i,s,"read",t))},async canChange(i,n,e,t,o){if(n.class==="person")return!0;if(n.app.id===e.id)return t.length===0||await w(i,n,t)&&await p(i,n,e,t,o,"rw")===null;if(t.length===0||t[0].type==="node")return!1;const s=await r(i,(await a(i,e)).external_id,"rw",t);return n.scopes!==null&&n.scopes.some(c=>u(c,s))}},m=new WeakMap;async function w(i,n,e){if(n.class==="person"||e.length===0||e[0].type!=="node"||n.class==="app"&&n.place===void 0)return!0;let t=m.get(n);if(t===void 0&&(t=n.class==="app"?n.place:n.class==="node"?await f(n.node.id,i):n.place??await x(n.object.id,i),m.set(n,t)),t===null)return!0;const o=await f(e[0].id,i);return o===null?!t.isolated:O(t,o)}l(w,"runConfined");export{M as appSpace};
@@ -1,18 +0,0 @@
1
- import type { ObjectRow, Reader } from '../types.js';
2
- import type { CanvasDb } from '../../canvas/db.js';
3
- export interface IdentityRow {
4
- object: string;
5
- scheme: string;
6
- external_id: string;
7
- }
8
- export type SpaceType = 'person' | 'app' | 'repo';
9
- /** The top of an owner chain. Only a space decides access and sync for what it owns.
10
- * `path` is the owner chain below the space, top first, ending at the object
11
- * (e.g. `[node, document]`); it is empty when the object is the space itself. */
12
- export interface Space {
13
- sync: 'runtime' | 'repo-ref';
14
- canRead(db: CanvasDb, reader: Reader, space: ObjectRow, path: ObjectRow[], object: ObjectRow): Promise<boolean>;
15
- canChange(db: CanvasDb, reader: Reader, space: ObjectRow, path: ObjectRow[], object: ObjectRow): Promise<boolean>;
16
- }
17
- export declare const SPACES: Record<SpaceType, Space>;
18
- export declare function identityOf(db: CanvasDb, object: ObjectRow): Promise<IdentityRow>;
@@ -1 +0,0 @@
1
- var p=Object.defineProperty;var o=(r,e)=>p(r,"name",{value:e,configurable:!0});import{appSpace as i}from"./app.js";import{personSpace as n}from"./person.js";import{repoSpace as a}from"./repo.js";const f={person:n,app:i,repo:a};async function E(r,e){const t=await r.prepare("SELECT object, scheme, external_id FROM identities WHERE object = ?").get(e.id);if(!t)throw new Error(`${e.type} ${e.id} has no identity`);return t}o(E,"identityOf");export{f as SPACES,E as identityOf};
@@ -1,18 +0,0 @@
1
- import type { ObjectRow, Reader } from '../types.js';
2
- import type { CanvasDb } from '../../canvas/db.js';
3
- type Path = ObjectRow[];
4
- /** The scope a run launched with a `scopes` list lacks to read (`read`) or change (`rw`) one of its own app's documents, or null.
5
- * Own-app memory needs no grant scope, but a launched list narrows it (identity MEM-2): reading needs `crtr:memory:read:app:<A>`,
6
- * writing `crtr:memory:rw:app:<A>` (profile forms for a profile's documents). Only documents are gated — never a node, job or
7
- * lifecycle object — and a run with no list, an app's server and the person keep full access. A report is readable inside its
8
- * own run (a run hears its children through them); writing one is the runtime's own act (see `asRuntimeWriter`). */
9
- export declare function missingOwnMemoryScope(db: CanvasDb, reader: Reader, space: ObjectRow, path: Path, object: {
10
- type: string;
11
- kind: string | null;
12
- } | null, access: 'read' | 'rw'): Promise<string | null>;
13
- /** The same reader with its launched list lifted, for the runtime's own writes inside a narrowed run: a `crtr push` report and the
14
- * scaffolded roadmap of a promoted node. Wrap only the one call that creates the document the runtime names. */
15
- export declare function asRuntimeWriter<R extends Reader>(reader: R): R;
16
- /** `crtr:memory:<access>:app:<B>` for an app document, or its profile form for a profile and what it owns. */
17
- export declare function memoryScope(db: CanvasDb, app: string, access: 'read' | 'rw', path: Path): Promise<string>;
18
- export {};
@@ -1 +0,0 @@
1
- var m=Object.defineProperty;var o=(n,t)=>m(n,"name",{value:t,configurable:!0});import{covers as f}from"@north-light/identity";import{runPlace as s}from"../../runs/confinement.js";const a=o(async(n,t)=>(await n.prepare("SELECT external_id FROM identities WHERE object = ?").get(t.id)).external_id,"externalId");async function $(n,t,l,e,r,u){if(t.class==="person"||e.length===0||r!==null&&r.type!=="document")return null;const i=t.launched;if(i==null||u==="read"&&r?.kind==="report"&&e[0].type==="node"&&await y(n,t,e[0].id))return null;const c=await d(n,await a(n,l),u,e);return i.some(p=>f(p,c))?null:c}o($,"missingOwnMemoryScope");async function y(n,t,l){const e=t.class==="node"?await s(t.node.id,n):t.class==="person"?null:t.place??null,r=await s(l,n);return e!==null&&r!==null&&e.run===r.run}o(y,"inReadersRun");function g(n){return n.class==="person"?n:{...n,launched:null}}o(g,"asRuntimeWriter");async function d(n,t,l,e){const r=e[0];return r.type!=="profile"?`crtr:memory:${l}:${t}`:`crtr:memory:${l}:${t}:profile:${await a(n,r)}`}o(d,"memoryScope");export{g as asRuntimeWriter,d as memoryScope,$ as missingOwnMemoryScope};
@@ -1,5 +0,0 @@
1
- import type { Space } from './index.js';
2
- /** You: your documents, and nodes started with your own authority. Reads and
3
- * changes use the runtime-spaces memory permission strings; a change also
4
- * requires the document's grantee — the app of the node that wrote its first revision. */
5
- export declare const personSpace: Space;
@@ -1,3 +0,0 @@
1
- import{covers as t}from"@north-light/identity";const f={sync:"runtime",async canRead(a,e,n,s,i){return e.class==="person"||e.class==="object"&&e.owner.id===n.id?!0:e.scopes===null?!1:e.app.id===n.id||s.length===0?!0:s[0].type!=="document"?!1:e.scopes.some(o=>t(o,"crtr:memory:manage:user"))||i.privacy==="public"?!0:i.privacy==="private"&&e.scopes.some(o=>t(o,"crtr:memory:read_private:user"))},async canChange(a,e,n,s,i){if(e.class==="person"||e.class==="object"&&e.owner.id===n.id||e.app.id===n.id||e.scopes?.some(r=>t(r,"crtr:memory:manage:user")))return!0;if(s.length===0)return e.scopes?.some(r=>t(r,"crtr:memory:rw:user"))??!1;if(s[0].type!=="document")return!1;const o=`crtr:memory:rw:user:${i.name}`;if(e.scopes===null||!e.scopes.some(r=>t(r,o)))return!1;const u=await a.prepare(`SELECT author.type, author.owner AS app FROM revisions r JOIN objects author ON author.id = r.author
2
- WHERE r.document = ? ORDER BY r.number, r.created_at LIMIT 1`).get(s[0].id);return u?u.type==="person"||u.app===e.app.id?!0:(await a.prepare(`SELECT g.resolved_scopes_json FROM grants g JOIN identities i ON i.external_id = g.grantee
3
- WHERE i.object = ? AND i.scheme = 'app'`).all(u.app)).some(({resolved_scopes_json:r})=>JSON.parse(r).some(({scope:c,mode:p})=>p==="always"&&t(c,"crtr:memory:manage:user"))):!1}};export{f as personSpace};
@@ -1,14 +0,0 @@
1
- import type { Reader } from '../types.js';
2
- import type { Space } from './index.js';
3
- /** Whether a node has a checkout of `repo` that its file permissions reach: read to read the
4
- * repo's documents, rw to change them. A checkout is one enclosing the node's cwd or one of its
5
- * profile's project paths; the cwd alone never lets a run in, since a node may be started in any
6
- * directory. A repo's documents change iff its files do. A repo-owned custom object is inside its
7
- * own repo. A Mac terminal node reaches every path. */
8
- export declare function insideRepo(db: Parameters<Space['canRead']>[0], reader: Reader, repo: {
9
- id: string;
10
- }, access: 'read' | 'rw'): Promise<boolean>;
11
- /** A repo: read and changed by a run with a checkout of it (enclosing its cwd or one of its
12
- * profile's project paths) that its file permissions reach (see `insideRepo`). It owns only public documents, so a
13
- * non-public one is never changed. */
14
- export declare const repoSpace: Space;
@@ -1 +0,0 @@
1
- var p=Object.defineProperty;var i=(e,t)=>p(e,"name",{value:t,configurable:!0});import{fileRoot as l}from"../../spaces/file-root.js";import{identityOf as u}from"./index.js";async function r(e,t,n,s){if(t.class==="object")return t.repos.some(o=>o.id===n.id);if(t.class!=="node")return!1;const c=[...t.checkouts,...t.projectCheckouts].filter(o=>o.repo.id===n.id);if(c.length===0)return!1;const a={app:(await u(e,t.app)).external_id,resolved:t.scopes??[],launched:null};return c.some(o=>"root"in l(a,o.root,s))}i(r,"insideRepo");const y={sync:"repo-ref",async canRead(e,t,n,s){return t.class==="person"||s.length===0?!0:await r(e,t,n,"read")},async canChange(e,t,n,s,c){return c.privacy!=="public"?!1:t.class==="person"?!0:await r(e,t,n,"rw")}};export{r as insideRepo,y as repoSpace};
@@ -1,25 +0,0 @@
1
- import { type CanvasDb } from '../canvas/db.js';
2
- /** A node's run and whether that run is isolated (the node's own flag or its root's). */
3
- export interface RunPlace {
4
- run: string;
5
- isolated: boolean;
6
- }
7
- /** The run a node belongs to: its `run_id`, or the node itself when it has none.
8
- * A deleted node of an isolated run keeps its run through `deleted_isolated_nodes`,
9
- * so what it leaves behind stays confined; any other node without a row is null. */
10
- export declare function runPlace(nodeId: string, db?: CanvasDb): Promise<RunPlace | null>;
11
- /** Record, before a node's row is deleted, the run of a node of an isolated run. */
12
- export declare function rememberDeletedIsolated(nodeId: string, db?: CanvasDb): Promise<void>;
13
- /** The place of the node that registered a custom object, which is where that object
14
- * reads from; null when the person or the app's server registered it. */
15
- export declare function objectPlace(objectId: string, db?: CanvasDb): Promise<RunPlace | null>;
16
- /** The confinement predicate: two places see each other when they are the same
17
- * run, or when neither run is isolated. */
18
- export declare function runsSeeEachOther(a: RunPlace, b: RunPlace): boolean;
19
- /** Whether a node caller of `app` may reach `target` (a node id) under run confinement.
20
- * Confinement applies only within one app; cross-app reach stays with the scope rules. */
21
- export declare function nodeMayReach(callerNode: string, callerApp: string, targetNode: string, targetApp: string | null, db?: CanvasDb): Promise<boolean>;
22
- /** The same question for a caller that has a place but no node (a cron command: its origin run). */
23
- export declare function placeMayReach(caller: RunPlace, callerApp: string, targetNode: string, targetApp: string | null, db?: CanvasDb): Promise<boolean>;
24
- /** The node ids of one run (its root and every node carrying its `run_id`). */
25
- export declare function runNodeIds(run: string, db?: CanvasDb): Promise<string[]>;
@@ -1,2 +0,0 @@
1
- var s=Object.defineProperty;var n=(t,e)=>s(t,"name",{value:e,configurable:!0});import{openDb as o}from"../canvas/db.js";async function u(t,e){e??=await o();const r=await e.prepare(`SELECT n.run_id, n.isolated, r.isolated AS root_isolated FROM nodes n
2
- LEFT JOIN nodes r ON r.node_id = n.run_id WHERE n.node_id = ?`).get(t);if(!r){const a=await e.prepare("SELECT run_id FROM deleted_isolated_nodes WHERE node_id = ?").get(t);return a?{run:a.run_id,isolated:!0}:null}return{run:r.run_id??t,isolated:r.isolated===1||r.root_isolated===1}}n(u,"runPlace");async function E(t,e){e??=await o();const r=await u(t,e);r?.isolated&&await e.prepare("INSERT INTO deleted_isolated_nodes (node_id, run_id) VALUES (?, ?) ON CONFLICT (node_id) DO UPDATE SET run_id = excluded.run_id").run(t,r.run)}n(E,"rememberDeletedIsolated");async function w(t,e){e??=await o();const r=await e.prepare("SELECT registered_by FROM custom_objects WHERE object = ?").get(t);return r?.registered_by?await u(r.registered_by,e)??{run:r.registered_by,isolated:!1}:null}n(w,"objectPlace");function l(t,e){return t.run===e.run||!t.isolated&&!e.isolated}n(l,"runsSeeEachOther");async function f(t,e,r,a,i){i??=await o();const d=await u(t,i);return d===null?!0:await c(d,e,r,a,i)}n(f,"nodeMayReach");async function c(t,e,r,a,i){if(i??=await o(),(a??"app:terminal")!==e)return!0;const d=await u(r,i);return d===null?!t.isolated:l(t,d)}n(c,"placeMayReach");async function y(t,e){return e??=await o(),(await e.prepare("SELECT node_id FROM nodes WHERE run_id = ? OR node_id = ?").all(t,t)).map(r=>r.node_id)}n(y,"runNodeIds");export{f as nodeMayReach,w as objectPlace,c as placeMayReach,E as rememberDeletedIsolated,y as runNodeIds,u as runPlace,l as runsSeeEachOther};