@north-light/cli 0.3.461 → 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 (54) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/profiles.d.ts +0 -2
  4. package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +2 -2
  5. package/dist/builtin-memory/crouter-plugin/bundles-and-memory.md +22 -1
  6. package/dist/builtin-memory/crouter-sdk/release-notes.md +6 -0
  7. package/dist/builtin-memory/internal/agent-shaping.md +2 -2
  8. package/dist/builtin-memory/internal/memory-loading.md +5 -5
  9. package/dist/clients/attach/viewer.js +548 -548
  10. package/dist/commands/doc/shared.js +2 -2
  11. package/dist/commands/profile/list.js +1 -1
  12. package/dist/commands/profile/new.js +1 -1
  13. package/dist/commands/profile/project.js +1 -1
  14. package/dist/commands/profile/show.js +1 -1
  15. package/dist/core/feed/feed.js +3 -3
  16. package/dist/core/graph/access.d.ts +4 -0
  17. package/dist/core/graph/access.js +3 -3
  18. package/dist/core/graph/documents.d.ts +5 -2
  19. package/dist/core/graph/documents.js +7 -7
  20. package/dist/core/graph/names.d.ts +1 -1
  21. package/dist/core/graph/names.js +3 -3
  22. package/dist/core/graph/reader.d.ts +8 -0
  23. package/dist/core/graph/reader.js +3 -3
  24. package/dist/core/graph/repo-sync/exchange.d.ts +8 -2
  25. package/dist/core/graph/repo-sync/exchange.js +2 -2
  26. package/dist/core/graph/repo-sync/ref-format.d.ts +2 -2
  27. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  28. package/dist/core/graph/repo-sync/sync.js +2 -2
  29. package/dist/core/graph/spaces/app.js +1 -1
  30. package/dist/core/graph/spaces/own-memory.d.ts +18 -0
  31. package/dist/core/graph/spaces/own-memory.js +1 -0
  32. package/dist/core/graph/spaces/repo.d.ts +2 -1
  33. package/dist/core/graph/spaces/repo.js +1 -1
  34. package/dist/core/graph/types.d.ts +10 -1
  35. package/dist/core/runtime/nodes.js +1 -1
  36. package/dist/core/runtime/roadmap.js +3 -3
  37. package/dist/core/scopes.d.ts +3 -0
  38. package/dist/core/scopes.js +1 -1
  39. package/dist/core/substrate/delivery/corpus.d.ts +1 -1
  40. package/dist/core/substrate/delivery/corpus.js +1 -1
  41. package/dist/core/substrate/delivery/deliver.js +3 -3
  42. package/dist/core/substrate/subject-fields.d.ts +4 -2
  43. package/dist/core/substrate/subject.js +1 -1
  44. package/dist/daemon/api/handlers/docs.js +1 -1
  45. package/dist/daemon/api/handlers/profiles.d.ts +3 -0
  46. package/dist/daemon/api/handlers/profiles.js +1 -1
  47. package/dist/daemon/api/map.js +2 -2
  48. package/dist/daemon/cron/authority.js +1 -1
  49. package/docs/concepts/scopes-and-trust.md +2 -2
  50. package/docs/plugin/bundles-and-memory.md +23 -1
  51. package/docs/sdk/release-notes.md +6 -0
  52. package/package.json +2 -2
  53. package/packages/crouter-identity/package.json +1 -1
  54. package/runtime.lock.json +12 -12
@@ -1,5 +1,5 @@
1
1
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
- export declare const CONTRACT_LEVEL = "1.5";
2
+ export declare const CONTRACT_LEVEL = "1.6";
3
3
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
4
  export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
5
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -1 +1 @@
1
- var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.5",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
1
+ var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.6",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
@@ -114,6 +114,4 @@ export interface ProfileDTO {
114
114
  last_used_at: string | null;
115
115
  /** Absolute path of the profile's directory, `<app space>/profiles/<id>`: its settings root. The profile's documents live in the canvas, not here. */
116
116
  profile_path: string;
117
- /** @deprecated The same path as `profile_path`; kept for clients written before it existed. Removed in the next breaking release. */
118
- memory_path: string;
119
117
  }
@@ -17,14 +17,14 @@ Scope strings are prefixed with `crtr:`. These are the ones the daemon and CLI c
17
17
  | `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) |
18
18
  | `crtr:schedule` | Creating, pausing, resuming, running and deleting crons (the `crtr cron` commands are hidden without it) |
19
19
  | `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 |
20
- | `crtr:memory:read:app:<id>`, `crtr:memory:rw:app:<id>` | Another app's documents; `…:profile:<profile>` narrows to one of its profiles |
20
+ | `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 |
21
21
  | `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 |
22
22
  | `crtr:conversations:read:app:<id>`, `crtr:objects:read:app:<id>` | Reading another app's conversations or node objects |
23
23
  | `<provider>:<group>` | One tool group of a provider |
24
24
 
25
25
  The `memory` in `crtr:memory:*` is the scope family's name for access to **documents** (see [[crouter-concepts/documents]]); 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.
26
26
 
27
- 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 [[crouter-sdk/nodes]] `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, and a grant holding `crtr:memory:manage:user` also sees `friends` and `private` ones. Who may read any document is the owner-and-privacy model, not a per-call scope.
27
+ 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 [[crouter-sdk/nodes]] `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, and a grant holding `crtr:memory:manage:user` also 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.
28
28
 
29
29
  ```mermaid
30
30
  flowchart LR
@@ -25,6 +25,27 @@ The tar has exactly `bundle.json`, `commands.json`, and `memory/<name>.md` membe
25
25
 
26
26
  ## Documents in the `memory` array
27
27
 
28
- Add a `memory` array to `definePlugin` when the app needs to give agents reusable operating knowledge. Each entry has `name`, `kind`, `whenAndWhyToRead`, `body`, and optional `unlisted`. `name` is a safe relative path without the `.md` suffix. The package writes the frontmatter required by the archive installer, so the author supplies no YAML.
28
+ Add a `memory` array to `definePlugin` when the app needs to give agents reusable operating knowledge. Each entry has `name`, `kind`, `whenAndWhyToRead`, `body`, and optional `unlisted` and `rules`. `name` is a safe relative path without the `.md` suffix. The package writes the frontmatter required by the archive installer, so the author supplies no YAML.
29
29
 
30
30
  `whenAndWhyToRead` is one routing sentence in the form `When <circumstance>, this <kind> should be read because <payoff>.` Keep the body to knowledge the agent needs to use the application. When the plugin is installed, crouter imports each entry as a public, read-only document owned by an app named after the plugin (an installed plugin is an app on the canvas), together with the generated commands. An agent reads it with `crtr canvas read <plugin>/<name>`, and `whenAndWhyToRead` becomes the document's preview. The `memory` array and `whenAndWhyToRead` are the plugin API's names; the result is an ordinary canvas document.
31
+
32
+ ## Loading a document on its own
33
+
34
+ A document without `rules` loads nowhere on its own: an agent reaches it only through a listing, `crtr canvas search`, or a read it was pointed at. Add `rules` when the document must reach an agent because of what that agent is doing. Each rule has the shape of a `crtr doc write --rule` rule:
35
+
36
+ - `on` — `boot` (every session start), `file-read`, `document-read`, `command` or `pre-command`.
37
+ - `deliver` — `name`, `preview` (the `whenAndWhyToRead` line) or `content` (the whole body).
38
+ - `match` — globs the activity must match. It is required on every event except `boot`, which takes none. A `file-read` rule may give `matchFrontmatter` instead.
39
+ - `if` — an optional condition on the receiving node over `kind`, `mode`, `lifecycle`, `cwd`, `repo`, `profile` and `depth`, in the matcher language `crtr doc write --rule -h` describes.
40
+
41
+ ```ts
42
+ memory: [{
43
+ name: 'texting',
44
+ kind: 'knowledge',
45
+ whenAndWhyToRead: 'When replying to a text conversation, this knowledge should be read because a reply that leaks a message label confuses the person reading it.',
46
+ body: '# Texting\n\nNever put a message label such as `[m3]` in a text.\n',
47
+ rules: [{ on: 'boot', deliver: 'content', if: { cwd: { matches: '^/apps/acme/' } } }],
48
+ }],
49
+ ```
50
+
51
+ `definePlugin` rejects a rule the installer would drop. Every rule costs context in every node it reaches, so prefer the narrowest trigger, and keep a `boot` rule at `content` for short, always-relevant guidance. An installation's profile can lower what a plugin's documents deliver at `boot`.
@@ -9,6 +9,12 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
9
9
 
10
10
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
11
11
 
12
+ ## 0.3.462
13
+
14
+ ### `ProfileDTO.memory_path` is removed (profile objects carry only `profile_path`)
15
+
16
+ `memory_path`, the deprecated alias of `profile_path` that 0.3.459 kept, is no longer sent. Read `profile_path`, which holds the same value (`<app space>/profiles/<id>`). A runtime older than 0.3.459 sends only `memory_path`.
17
+
12
18
  ## 0.3.460
13
19
 
14
20
  ### `crouter-sdk keygen` releases its key file when the add fails (no app change)
@@ -63,7 +63,7 @@ Every kind has both a `base` and an `orchestrator` persona; mode picks which one
63
63
 
64
64
  A profile is a stable **agent identity** — itself an owner object: a fixed id, a display name, the documents it owns, and a **purview** of project directories it resolves documents and config from. Select it at spawn with `crtr node new --profile <id-or-name>`; omit and a child inherits the caller's profile. Pin a directory's default profile with `crtr profile default` so the startup chooser stops asking. Manage the identity and purview with `crtr profile new/show/project/rename/delete` (see `crtr profile -h`). `profile rename` changes the name and the profile's canvas handle together (the id stays), so `[[name/x]]` links and `owner=` listings follow it; it refuses when another live profile already holds the new name.
65
65
 
66
- Each project in the purview carries the profile's delivery limit per owner (today set per project), capping how much of that repo's documents reach a node's automatic boot and workspace-open context from any directory that node works in. Give `content` to the repos whose front doors the profile's nodes should operate inside, `preview` or `name` to a project the profile must know exists but rarely enters, and `none` to purview held for config, plugins, and deliberate reads alone. Key a limit by repo handle, or `repo:<handle>` when another owner (such as a profile) shares that handle; the profile's own documents are never capped. It is a delivery dial, not an access one: `crtr canvas read` and `file-read`/`command` rules still reach those documents.
66
+ Every project path in the purview puts the repos enclosing it into the view of every node under the profile, whatever directory that node works in: their documents are listed and readable, and are delivered at boot and workspace-open when a rule of theirs matches, after the repos enclosing the node's own cwd. A rule's `if: {repo}` holds for any of those repos, so a repo-conditioned rule fires for every node of the profile; use `cwd` to condition on where the node works. `crtr profile project add` starts the added repos at a `preview` limit. The profile's delivery limit per owner caps how much of a repo's documents reach automatic boot and workspace-open context, whether the repo is in view through the cwd or the project list: give `content` to the repos whose front doors the profile's nodes should operate inside, `preview` or `name` to a project the profile must know exists but rarely enters, and `none` to purview held for config, plugins, and deliberate reads alone. Key a limit by repo handle, or `repo:<handle>` when another owner (such as a profile) shares that handle; the profile's own documents are never capped. It is a delivery dial, not an access one: `crtr canvas read` and `file-read`/`command` rules still reach those documents.
67
67
 
68
68
  Reach for a **new** profile when a distinct body of work has its own set of directories and its own conventions worth dedicated documents — not for every repo. **The profile as owner** is exactly where cross-repo conventions and your stance toward that body of work belong (see the owners below); a profile spanning several related dirs lets one document reach every node working anywhere in that bundle.
69
69
 
@@ -73,7 +73,7 @@ An owner is itself an object (a node, the person, a profile, an app, a repo), an
73
73
 
74
74
  - **node** — the default owner of what a node writes. Only this node's view includes it; it is deleted with the node unless a kept document links to it. One goal's cross-refresh state.
75
75
  - **profile** (`--owner profile`) — every node running under that profile, across all the dirs in its purview. Cross-repo conventions and the user's stance toward that bundle of work.
76
- - **repo** (`--owner repo`) — any agent operating in that one repo. Facts and procedures tied to that codebase. Every clone and worktree shares one set, synced over the repo's git ref `refs/crtr/docs`; a repo owns only public documents.
76
+ - **repo** (`--owner repo`) — any agent operating in that one repo, and any node whose profile lists the repo as a project. Facts and procedures tied to that codebase. Every clone and worktree shares one set, synced over the repo's git ref `refs/crtr/docs`; a repo owns only public documents.
77
77
  - **user** (`--owner user`) — person-wide facts and preferences that follow the user everywhere, regardless of repo or profile.
78
78
  - **crtr's own documents** (owned by the app `crtr`; markdown files in `src/builtin-memory/` of the crouter repo, imported as read-only documents) — ships inside crtr, so *every crtr user on every host* carries it. This owner holds the runtime's own self-documentation (this document lives here); a change here is a change to the product. Write here only for guidance every crouter user needs, never for anything person- or repo-specific.
79
79
 
@@ -18,7 +18,7 @@ A document is an object on the canvas ([[internal]] has the model): it has an ow
18
18
  A document with no rules does exactly one thing: appears in the listing of the names above it. Everything beyond that is an explicit rule — in `--rule` JSON, `{"on": <event>, "match"?, "match_frontmatter"?, "if"?, "deliver": <level>}` (package frontmatter spells the key `match-frontmatter`). A rule's event constraints and optional `if` over the receiving node must both match; matching rules OR across and fold to their highest delivery (`content` > `preview` > `name`), with no cross-rule deny precedence. Multiple rules per event are legal. The events:
19
19
 
20
20
  - **boot** — the frozen preference system snapshot and first-message knowledge catalog assembled when a context begins. No match; the rule's presence is the match.
21
- - **workspace-open** — first-message context when the node works in the repo that owns the document. Repo-owned documents only.
21
+ - **workspace-open** — first-message context for a repo the node is eligible for (it works in the repo, or the repo is one of its profile's projects), capped by the profile's delivery limit for that repo. Repo-owned documents only.
22
22
  - **file-read** — a `read` tool call returned a matching file: path globs vs the file's absolute path and basename, `./`-anchored globs vs its path relative to the owning repo's checkout, `match_frontmatter` predicates over the read file's own YAML frontmatter.
23
23
  - **document-read** — another document's full body entered context, including `crtr canvas read`: name globs vs its name, `./` anchored to this document's own name.
24
24
  - **command** — a matching shell command ran: globs vs the whole command string, `*` crossing `/`. Delivery is post-execution — right for "you are now in this territory"; when the point is "don't run this at all," use `pre-command` instead.
@@ -39,7 +39,7 @@ Silence is the absence of a rule; there is no `none` level. `summary` is **not**
39
39
 
40
40
  ## Conditions
41
41
 
42
- A rule's `if` is the eligibility predicate over the receiving node — kind, mode, lifecycle, orchestration depth, cwd, the repo it works in, profile — using the standard matcher vocabulary (`crtr doc write --rule -h`). When it fails, that rule delivers nothing; there is no document-level condition, so one document can choose a different level by node kind with two rules. No `if` means always eligible. Persona prose is just boot-content documents with an `if` (`if: {kind: developer, mode: base}`); guidance that should scale with effort is one predicate (`orchestration.depth: {gte: 2}`), not a mechanism.
42
+ A rule's `if` is the eligibility predicate over the receiving node — kind, mode, lifecycle, orchestration depth, cwd, the repos it is eligible for (`repo`: those enclosing its cwd and those of its profile's project paths; use `cwd` for location), profile — using the standard matcher vocabulary (`crtr doc write --rule -h`). When it fails, that rule delivers nothing; there is no document-level condition, so one document can choose a different level by node kind with two rules. No `if` means always eligible. Persona prose is just boot-content documents with an `if` (`if: {kind: developer, mode: base}`); guidance that should scale with effort is one predicate (`orchestration.depth: {gte: 2}`), not a mechanism.
43
43
 
44
44
  ## Listings and dedup
45
45
 
@@ -49,15 +49,15 @@ Every delivery registers the document or listing and its level in the loaded con
49
49
 
50
50
  ## The reader's view
51
51
 
52
- A rule applies to every node whose view includes the document's owner. A node's view is: its own node, the repos it works in (nearest first), its profile, its app, the user, and installed plugins (each an app that owns its shipped documents), with crtr's own documents (owned by the app `crtr`) last. A bare name resolves in that same order, which is what lets a repo's document shadow one of crtr's own. A node's own documents are in no other node's view, so their rules apply only to that node.
52
+ A rule applies to every node whose view includes the document's owner. A node's view is: its own node, the repos it works in (those enclosing its cwd, nearest first), the repos enclosing each project path of its profile (in manifest order; a path that is gone or is not in a repo adds nothing, and a Linux node sandbox sees none), its profile, its app, the user, and installed plugins (each an app that owns its shipped documents), with crtr's own documents (owned by the app `crtr`) last. A bare name resolves in that same order, which is what lets a repo's document shadow one of crtr's own. A rule's `if: {repo}` condition holds when that repo is among the node's eligible repos (cwd repos plus project repos), so a rule on any document, including a user- or profile-owned one, can target a repo the profile covers; match `cwd` to condition on where the node works. `--owner repo` and write access follow only the repos enclosing the cwd, and a repo-relative `./` `file-read` glob stays anchored to the checkout of the repo that owns the rule. A node's own documents are in no other node's view, so their rules apply only to that node.
53
53
 
54
- Each owner the selected profile manages carries a delivery limit — `none`, `name`, `preview`, or `content` — and that value is the maximum level anything that owner holds delivers at `boot` and `workspace-open`, whatever directory the node is working in. It only lowers: a rule authored below the maximum delivers at its authored level. `none` contributes nothing to either automatic event, so a `none` owner cannot shadow a same-named document from a wider owner — that wider document becomes the winner. An owner with no limit set delivers exactly what it authored.
54
+ Each owner the selected profile manages carries a delivery limit — `none`, `name`, `preview`, or `content` — and that value is the maximum level anything that owner holds delivers at `boot` and `workspace-open`, whatever directory the node is working in and whether the repo is in view through its cwd or the profile's project list. `crtr profile project add` starts the added repos at `preview`. It only lowers: a rule authored below the maximum delivers at its authored level. `none` contributes nothing to either automatic event, so a `none` owner cannot shadow a same-named document from a wider owner — that wider document becomes the winner. An owner with no limit set delivers exactly what it authored.
55
55
 
56
56
  A limit is keyed by owner handle. A bare key (`crouter`) matches an owner of any type holding that handle, and handles are unique per type, not across types — so a repo and a profile can both be `crouter`. `<type>:<handle>` (`repo:crouter`, `profile:work`, `app:terminal`, `person:user`; the typed form `resolveName` accepts) names one owner exactly and wins over a bare key. A node's own documents and its own profile's documents are never capped, whichever key matches.
57
57
 
58
58
  The limit reaches those two events and nothing else. `file-read`, `document-read`, `command`, and `pre-command` rules fire at their authored levels; listings, `crtr canvas read`, `crtr canvas search`, config resolution, and plugin discovery all see every document the reader may read. An explicit `crtr canvas read` is itself a content delivery, so reading a capped document returns its whole body and records content — the upgrade path for a document the automatic events disclosed only by name or preview. `if` conditions apply to companion documents delivered by the `document-read` event, not to the deliberate read or its listings.
59
59
 
60
- A workspace's front door is an ordinary repo-owned document carrying the rule pair `{on: workspace-open, deliver: content}` + `{on: file-read, match: "./**", deliver: content}` — the operating guide loads when a node works in that repo or reads its files, not in every boot catalog. `crtr doc lint` requires exactly one `workspace-open` content document per repo the profile manages. Several repos render broad-to-specific.
60
+ A workspace's front door is an ordinary repo-owned document carrying the rule pair `{on: workspace-open, deliver: content}` + `{on: file-read, match: "./**", deliver: content}` — the operating guide loads when a node works in that repo or reads its files, and for a node whose profile lists the repo as a project it loads under that repo's delivery limit; it is not in every boot catalog. `crtr doc lint` requires exactly one `workspace-open` content document per repo the profile manages. Several repos render broad-to-specific.
61
61
 
62
62
  A repo's documents about one package or subsystem are named under its folder (for example `packages/api/…`) and keep their `file-read` rules: reading any file beneath that folder delivers them, filtered against what the loaded context already contains.
63
63