textus 0.54.1 → 0.55.1

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 (175) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -1
  3. data/README.md +61 -27
  4. data/SPEC.md +167 -296
  5. data/docs/architecture/README.md +20 -8
  6. data/docs/reference/conventions.md +40 -56
  7. data/exe/textus +1 -1
  8. data/lib/textus/action/accept.rb +23 -21
  9. data/lib/textus/action/audit.rb +24 -61
  10. data/lib/textus/action/base.rb +9 -9
  11. data/lib/textus/action/blame.rb +18 -36
  12. data/lib/textus/action/boot.rb +2 -4
  13. data/lib/textus/action/data_mv.rb +20 -31
  14. data/lib/textus/action/deps.rb +3 -18
  15. data/lib/textus/action/doctor.rb +2 -9
  16. data/lib/textus/action/drain.rb +11 -19
  17. data/lib/textus/action/enqueue.rb +14 -30
  18. data/lib/textus/action/get.rb +12 -56
  19. data/lib/textus/action/ingest.rb +141 -77
  20. data/lib/textus/action/jobs.rb +6 -15
  21. data/lib/textus/action/key_delete.rb +6 -16
  22. data/lib/textus/action/key_delete_prefix.rb +8 -17
  23. data/lib/textus/action/key_mv.rb +54 -61
  24. data/lib/textus/action/key_mv_prefix.rb +13 -22
  25. data/lib/textus/action/list.rb +7 -21
  26. data/lib/textus/action/propose.rb +16 -26
  27. data/lib/textus/action/published.rb +3 -5
  28. data/lib/textus/action/pulse.rb +20 -27
  29. data/lib/textus/action/put.rb +15 -29
  30. data/lib/textus/action/rdeps.rb +3 -18
  31. data/lib/textus/action/reject.rb +12 -21
  32. data/lib/textus/action/rule_explain.rb +12 -22
  33. data/lib/textus/action/rule_lint.rb +10 -16
  34. data/lib/textus/action/rule_list.rb +5 -9
  35. data/lib/textus/action/schema_envelope.rb +3 -10
  36. data/lib/textus/action/uid.rb +3 -17
  37. data/lib/textus/action/where.rb +3 -18
  38. data/lib/textus/boot.rb +8 -16
  39. data/lib/textus/contract/arg.rb +10 -0
  40. data/lib/textus/contract/dsl.rb +88 -0
  41. data/lib/textus/contract/spec.rb +25 -0
  42. data/lib/textus/contract.rb +0 -162
  43. data/lib/textus/doctor/check/audit_log.rb +2 -2
  44. data/lib/textus/doctor/check/generator_drift.rb +2 -2
  45. data/lib/textus/doctor/check/orphaned_publish_targets.rb +3 -3
  46. data/lib/textus/doctor/check/protocol_version.rb +2 -2
  47. data/lib/textus/doctor/check/raw_asset_paths.rb +1 -1
  48. data/lib/textus/doctor/check/schema_parse_error.rb +1 -1
  49. data/lib/textus/doctor/check/schema_violations.rb +2 -2
  50. data/lib/textus/doctor/check/schemas.rb +1 -1
  51. data/lib/textus/doctor/check/sentinels.rb +4 -4
  52. data/lib/textus/doctor/check/templates.rb +1 -1
  53. data/lib/textus/doctor/check/unowned_schema_fields.rb +1 -1
  54. data/lib/textus/doctor/check.rb +4 -7
  55. data/lib/textus/doctor.rb +1 -1
  56. data/lib/textus/errors.rb +6 -0
  57. data/lib/textus/format/base.rb +0 -4
  58. data/lib/textus/format/json.rb +5 -6
  59. data/lib/textus/format/markdown.rb +5 -6
  60. data/lib/textus/format/shared.rb +17 -0
  61. data/lib/textus/format/text.rb +5 -4
  62. data/lib/textus/format/yaml.rb +30 -6
  63. data/lib/textus/format.rb +6 -0
  64. data/lib/textus/gate/auth.rb +2 -17
  65. data/lib/textus/gate/binder.rb +50 -0
  66. data/lib/textus/gate.rb +64 -88
  67. data/lib/textus/init.rb +2 -4
  68. data/lib/textus/jobs.rb +3 -9
  69. data/lib/textus/manifest/capabilities.rb +3 -3
  70. data/lib/textus/manifest/entry/base.rb +1 -1
  71. data/lib/textus/manifest/entry/publish/subtree_mirror.rb +2 -2
  72. data/lib/textus/manifest/entry/publish/to_paths.rb +2 -5
  73. data/lib/textus/manifest/schema/semantics/cross_field.rb +53 -0
  74. data/lib/textus/manifest/schema/semantics/invariants.rb +125 -0
  75. data/lib/textus/manifest/schema/semantics/migration.rb +83 -0
  76. data/lib/textus/manifest/schema/semantics.rb +11 -216
  77. data/lib/textus/meta.rb +54 -0
  78. data/lib/textus/{ports → port}/audit_log.rb +44 -4
  79. data/lib/textus/{ports → port}/build_lock.rb +2 -2
  80. data/lib/textus/{ports → port}/clock.rb +1 -1
  81. data/lib/textus/{ports → port}/publisher.rb +5 -5
  82. data/lib/textus/{ports → port}/sentinel_store.rb +3 -3
  83. data/lib/textus/{ports → port}/storage/file_stat.rb +1 -1
  84. data/lib/textus/{ports → port}/storage/file_store.rb +2 -2
  85. data/lib/textus/port/store.rb +93 -0
  86. data/lib/textus/{ports → port}/watcher_lock.rb +3 -3
  87. data/lib/textus/produce/engine.rb +1 -1
  88. data/lib/textus/schema/tools.rb +11 -7
  89. data/lib/textus/store/compositor.rb +34 -0
  90. data/lib/textus/store/container.rb +43 -0
  91. data/lib/textus/store/cursor.rb +26 -0
  92. data/lib/textus/store/envelope/reader.rb +43 -0
  93. data/lib/textus/store/envelope/writer.rb +195 -0
  94. data/lib/textus/store/geometry.rb +81 -0
  95. data/lib/textus/store/index/builder.rb +74 -0
  96. data/lib/textus/store/index/lookup.rb +60 -0
  97. data/lib/textus/store/jobs/base.rb +13 -0
  98. data/lib/textus/store/jobs/index.rb +15 -0
  99. data/lib/textus/store/jobs/materialize.rb +15 -0
  100. data/lib/textus/store/jobs/plan.rb +11 -0
  101. data/lib/textus/store/jobs/planner.rb +104 -0
  102. data/lib/textus/store/jobs/queue.rb +154 -0
  103. data/lib/textus/store/jobs/registry.rb +19 -0
  104. data/lib/textus/store/jobs/retention.rb +50 -0
  105. data/lib/textus/store/jobs/sweep.rb +21 -0
  106. data/lib/textus/store/jobs/worker.rb +64 -0
  107. data/lib/textus/store/session.rb +37 -0
  108. data/lib/textus/store.rb +21 -13
  109. data/lib/textus/{surfaces → surface}/cli/group/data.rb +1 -1
  110. data/lib/textus/{surfaces → surface}/cli/group/key.rb +1 -1
  111. data/lib/textus/{surfaces → surface}/cli/group/mcp.rb +1 -1
  112. data/lib/textus/{surfaces → surface}/cli/group/rule.rb +1 -1
  113. data/lib/textus/{surfaces → surface}/cli/group/schema.rb +1 -1
  114. data/lib/textus/{surfaces → surface}/cli/group.rb +2 -2
  115. data/lib/textus/{surfaces → surface}/cli/runner.rb +10 -63
  116. data/lib/textus/surface/cli/sources.rb +41 -0
  117. data/lib/textus/{surfaces → surface}/cli/verb/doctor.rb +4 -6
  118. data/lib/textus/{surfaces → surface}/cli/verb/get.rb +4 -4
  119. data/lib/textus/{surfaces → surface}/cli/verb/init.rb +1 -1
  120. data/lib/textus/{surfaces → surface}/cli/verb/mcp_serve.rb +3 -3
  121. data/lib/textus/{surfaces → surface}/cli/verb/put.rb +6 -11
  122. data/lib/textus/{surfaces → surface}/cli/verb/schema_diff.rb +1 -1
  123. data/lib/textus/{surfaces → surface}/cli/verb/schema_init.rb +1 -1
  124. data/lib/textus/{surfaces → surface}/cli/verb/schema_migrate.rb +1 -1
  125. data/lib/textus/{surfaces → surface}/cli/verb/watch.rb +2 -2
  126. data/lib/textus/{surfaces → surface}/cli/verb.rb +3 -8
  127. data/lib/textus/{surfaces → surface}/cli.rb +1 -1
  128. data/lib/textus/{surfaces → surface}/mcp/catalog.rb +9 -26
  129. data/lib/textus/{surfaces → surface}/mcp/errors.rb +1 -1
  130. data/lib/textus/{surfaces → surface}/mcp/server.rb +5 -5
  131. data/lib/textus/{surfaces → surface}/mcp.rb +2 -2
  132. data/lib/textus/surface/projector.rb +27 -0
  133. data/lib/textus/{surfaces → surface}/role_scope.rb +1 -1
  134. data/lib/textus/{surfaces → surface}/watcher.rb +8 -8
  135. data/lib/textus/value/call.rb +30 -0
  136. data/lib/textus/value/command.rb +16 -0
  137. data/lib/textus/value/envelope.rb +89 -0
  138. data/lib/textus/value/etag.rb +39 -0
  139. data/lib/textus/value/result.rb +26 -0
  140. data/lib/textus/value/role.rb +38 -0
  141. data/lib/textus/value/types.rb +13 -0
  142. data/lib/textus/{uid.rb → value/uid.rb} +9 -7
  143. data/lib/textus/version.rb +1 -1
  144. data/lib/textus/workflow/loader.rb +4 -4
  145. data/lib/textus/workflow/runner.rb +4 -18
  146. data/lib/textus.rb +9 -10
  147. metadata +100 -62
  148. data/lib/textus/action/write_verb.rb +0 -44
  149. data/lib/textus/call.rb +0 -28
  150. data/lib/textus/command.rb +0 -41
  151. data/lib/textus/container.rb +0 -26
  152. data/lib/textus/contract/around.rb +0 -29
  153. data/lib/textus/contract/binder.rb +0 -88
  154. data/lib/textus/contract/resources/build_lock.rb +0 -17
  155. data/lib/textus/contract/resources/cursor.rb +0 -26
  156. data/lib/textus/contract/sources.rb +0 -39
  157. data/lib/textus/contract/view.rb +0 -15
  158. data/lib/textus/cursor_store.rb +0 -24
  159. data/lib/textus/envelope/reader.rb +0 -46
  160. data/lib/textus/envelope/writer.rb +0 -209
  161. data/lib/textus/envelope.rb +0 -79
  162. data/lib/textus/etag.rb +0 -36
  163. data/lib/textus/jobs/base.rb +0 -23
  164. data/lib/textus/jobs/materialize.rb +0 -20
  165. data/lib/textus/jobs/plan.rb +0 -9
  166. data/lib/textus/jobs/planner.rb +0 -101
  167. data/lib/textus/jobs/retention.rb +0 -48
  168. data/lib/textus/jobs/sweep.rb +0 -27
  169. data/lib/textus/jobs/worker.rb +0 -67
  170. data/lib/textus/layout.rb +0 -83
  171. data/lib/textus/ports/job_store/job.rb +0 -65
  172. data/lib/textus/ports/job_store.rb +0 -123
  173. data/lib/textus/role.rb +0 -36
  174. data/lib/textus/session.rb +0 -35
  175. data/lib/textus/types.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08d1e232ff648999588cfcfe0cccafd7954a020805f3e344aabdbbb2798b51af'
4
- data.tar.gz: 4b8545d90d8aa990d63565f1d2caaff7e3e519d7504ae22a04dad68507c90949
3
+ metadata.gz: 27ac7fc2922b98c97c7873939781a13330e0765b7f0521449aa4871c414fc201
4
+ data.tar.gz: 055ed0684590d9ca8cf5e2ad49fc17ee05c70b8cbaa5f8b589b9bb9a3de99f0e
5
5
  SHA512:
6
- metadata.gz: 28b8de7a9e84208008c7082cd597d6905b743fe035b0344953cc82d5581f8a6ce98abf4bae40e5f1040780a1874743e7403695831709a539ea2cd4fd7dbc4c79
7
- data.tar.gz: 9be159077a4f4e17e0118b3efea9085d3e5f6f463d7f558d6fcfdc45912c07cde6fe0824de213171806245cbca3b38f689f6b6922a2f7ddf9fd4dcb0c0bdc2a7
6
+ metadata.gz: 437568a688e1ff1b6f9786d1965633e9dfa69be5b6f7450ffeeaecb3c0be5a6729e94851f2066a26e2d313f8d23335d2ff12b83ad5e73718b27e784887c9ceb4
7
+ data.tar.gz: a8f4167af7c3ce315301996d3d3fedc13139f8eae4804b445ec4ed930da0d284ec96cab3467af1169ce8f6a64288d6f1d805bbfad60c0acb949f62e03ce1322d
data/CHANGELOG.md CHANGED
@@ -9,6 +9,56 @@ The **gem version** (`0.x.y`) is distinct from the **protocol version**
9
9
  bump is a breaking change that requires a store migration; the gem version
10
10
  tracks both additive improvements and breaking protocol bumps independently.
11
11
 
12
+ ## 0.55.1 — 2026-06-22 — CI fix: converge_now purges done jobs
13
+
14
+ ### Fixed
15
+
16
+ - **publish_tree prune spec** — `converge_now` now purges "done" jobs before reseeding. The SQLite-backed job queue (`INSERT OR IGNORE`) was silently skipping re-enqueues for already-done jobs, so the second materialize never ran and the source-removed file was never pruned from the published tree.
17
+
18
+ ## 0.55.0 — 2026-06-22 — Architecture Deepening Phase 2
19
+
20
+ **ADR-0119**: dry-monad actions, container split, geometry authority.
21
+
22
+ ### Added
23
+
24
+ - **dry-monad Success/Failure actions** (PR #234): all 30 actions now return `Success(result)` or `Failure(code:, message:)` via `Dry::Monads`. `Value::Result.unwrap` converts Failure → `ActionError`. `be_success`/`be_failure` RSpec matchers replace `spec/unit/` tests.
25
+ - **`Container::Infrastructure` / `Container::Coordination`** (PR #234): single `LazyContainer` replaced by two `Data.define` records — Infrastructure owns manifest, file_store, geometry; Coordination owns gate, compositor, workflows, audit_log. Wired at boot via `wire_gate!`.
26
+ - **`Geometry` as sole path authority** (PR #234): `root`, `schemas_dir`, `hooks_dir`, `store_db_path` delegated to `Geometry`. Removed `root` from Infrastructure. High-value `File.join` callers converted (data_mv, ingest, base, loader, etag, schema/tools, 6 doctor checks).
27
+ - **`bin/db` helper**: sqlite3 wrapper for `.textus/.state/store.db` — `bin/db "SELECT * FROM jobs"` or just `bin/db` for interactive shell.
28
+ - **Centralized SQLite index** (PR #228, #233): `Store::Index::Builder` manages entries FTS index in `store.db`.
29
+
30
+ ### Changed
31
+
32
+ - **Gate reads `Action::VERBS` directly** (PR #234): `Gate::VERB_ACTIONS` deleted. Gate dispatches via `Action::VERBS[cmd.verb]`.
33
+ - **Composite actions** (PR #232): `Accept`, `Propose`, `Reject` separated as composite verbs with declarative `chain` steps, never instantiating sibling action classes directly.
34
+ - **`knowledge.decisions` → keyless nested mode**: decision files no longer enumerate in `list`; accessible via `get` with full key.
35
+
36
+ ### Fixed
37
+
38
+ - **CLI `--args` JSON parsing** (PR #233): `Runner.coerce` now `JSON.parse(raw)` for `Hash`-typed args — fixes `textus enqueue --args='{"key":"..."}'` which previously passed the raw JSON string to `Queue::Job`.
39
+ - **adr-log workflow convention drift** (PR #233): `Get.new(key:).call(...)` → `Get.call(key: k, ...)` — the old pattern raised silently since actions became class-method-only.
40
+ - **RuboCop `Lint/NoReturnInBeginEndBlocks`**: enqueue action restructured with method-level `rescue`.
41
+
42
+ ### Removed
43
+
44
+ - `Gate::VERB_ACTIONS` constant (replaced by `Action::VERBS`)
45
+ - `LazyContainer` (replaced by `Container::Infrastructure`/`Coordination`)
46
+ - `spec/unit/` directory + `spec/support/spec_layout.rb` (378 examples removed)
47
+ - Orphaned `.textus/.state/jobs.sqlite3` database file
48
+
49
+ ## 0.54.2 — 2026-06-18 — ingest dedup, .run → .state rename
50
+
51
+ ### Added
52
+
53
+ - **Content-hash dedup in `Action::Ingest`**: re-ingesting the same URL or file bytes silently creates a supersede chain — new entry with full content, old stripped to tombstone with a `superseded_by` pointer.
54
+ - **`Ports::RawIndex`**: lightweight YAML-based O(1) index for raw lane content-hash/URL lookups, stored at `.textus/.state/indexes/raw.yaml`.
55
+ - **`content_hash` field**: every new raw entry stores a `sha256:` content hash for fast dedup lookups.
56
+ - **Asset file move on re-ingest**: asset files are moved (not copied) to the new date path on supersede.
57
+
58
+ ### Changed
59
+
60
+ - **`.run` → `.state` rename**: the runtime artifact directory is renamed from `.textus/.run/` to `.textus/.state/`. The `state/` subdirectory becomes `cursors/`. Layout path methods follow automatically. Manual migration step: `mv .textus/.run .textus/.state`.
61
+
12
62
  ## 0.54.1 — 2026-06-17 — MCP SDK, dry ecosystem, ERB templates
13
63
 
14
64
  ### Added
@@ -406,7 +456,7 @@ No `textus/3` wire-format change. Manifest schema gains one optional, backward-c
406
456
  segment-wise globstar — `**` spans zero or more path segments; within a
407
457
  segment `*` is anchored and `{a,b}` alternates (stdlib `File.fnmatch`,
408
458
  no new dependency). Documented in
409
- [`docs/reference/zones.md`](docs/reference/zones.md#nested-entries).
459
+ [`docs/reference/zones.md`](docs/reference/lanes.md#nested-entries).
410
460
 
411
461
  ### Internal
412
462
 
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- <!-- Generated from .textus/data/knowledge/readme.md edit there, then run `textus drain`. Do not hand-edit README.md (it is clobbered on drain and flagged by doctor). ADR 0103. -->
1
+ <!-- Generated from knowledge.readme.* fragments by the artifacts.docs.readme workflow. Edit the fragments under .textus/data/knowledge/readme/ and run `textus drain`. -->
2
+ <!-- This is a fragment of the README. Edit here; the README is composed from fragments by a workflow. -->
2
3
  <p align="center">
3
4
  <picture>
4
5
  <source media="(prefers-color-scheme: dark)" srcset="assets/branding/wordmark-dark.png">
@@ -18,6 +19,8 @@
18
19
 
19
20
  *textus* is Latin for "the fabric a text is woven from" — same root as *context*, from *con-texere*, "to weave together."
20
21
 
22
+
23
+ <!-- This is a fragment of the README. Edit here; the README is composed from fragments by a workflow. -->
21
24
  ## The idea
22
25
 
23
26
  Three actors write to your repo today:
@@ -39,6 +42,9 @@ flowchart LR
39
42
  agent -->|keep| notebook["notebook<br/>(workspace)"]
40
43
  agent -->|propose| proposals["proposals<br/>(queue)"]
41
44
  automation -->|drain| artifacts["artifacts<br/>(machine)"]
45
+ human -->|ingest| raw["raw<br/>(intake)"]
46
+ agent -->|ingest| raw
47
+ automation -->|ingest| raw
42
48
 
43
49
  proposals ==>|human accept| knowledge
44
50
  knowledge -.->|projection source| artifacts
@@ -46,13 +52,15 @@ flowchart LR
46
52
  classDef actor fill:#238636,stroke:#2ea043,color:#fff;
47
53
  classDef gate fill:#9e6a03,stroke:#bb8009,color:#fff;
48
54
  classDef anchor fill:#1f6feb,stroke:#388bfd,color:#fff;
55
+ classDef intake fill:#6e40c9,stroke:#8b5cf6,color:#fff;
49
56
  class human,agent,automation actor;
50
57
  class proposals gate;
51
58
  class knowledge anchor;
59
+ class raw intake;
52
60
  ```
53
61
 
54
62
  *Each actor writes only into its own lane; low-trust input climbs to authoritative lanes only by passing a guarded transition (an agent's proposal needs a human `accept`).*
55
- *Colour legend: **green** = writers · **amber** = the review gate (`proposals`) · **blue** = the trust anchor (`knowledge`).*
63
+ *Colour legend: **green** = writers · **amber** = the review gate (`proposals`) · **blue** = the trust anchor (`knowledge`) · **purple** = write-once intake (`raw`).*
56
64
 
57
65
  The point of those lanes is to **build context you can trust**. Place each lane on two axes — how durable it is, and how much you can rely on it without review — and the value shows up as a climb: the high-trust corner (durable *and* authoritative = `knowledge`) is the one place nothing is *written* directly. It's *earned* by crossing the `accept` gate.
58
66
 
@@ -64,9 +72,10 @@ DURABLE │ notebook │ knowledge ★ the goal
64
72
  (kept) │ agent's working truth │ canon — a human authors │
65
73
  │ durable, but low-trust │ here · the context you ship │
66
74
  ├──────────────────────────┼───────────────────────────────┤
67
- TRANSIENT │ artifacts.* │ proposals (queue) │
68
- (staging) │ computed outputs and │ a candidate, in review │
69
- external inputs │ ▲ climbs via human accept │
75
+ TRANSIENT │ artifacts (outputs) │ proposals (queue) │
76
+ (staging) │ computed, machine-made │ a candidate, in review │
77
+ raw (inputs) │ ▲ climbs via human accept │
78
+ │ ingested, write-once │ │
70
79
  └──────────────────────────┴───────────────────────────────┘
71
80
  raw material ──── propose ────► a human accept lifts it to canon
72
81
  ```
@@ -84,6 +93,8 @@ An agent that tries to write directly into `knowledge/` gets `write_forbidden`.
84
93
 
85
94
  That's the load-bearing claim: **coordination is a protocol invariant, not a library convenience.**
86
95
 
96
+
97
+ <!-- This is a fragment of the README. Edit here; the README is composed from fragments by a workflow. -->
87
98
  ## See it in four commands
88
99
 
89
100
  ```sh
@@ -135,7 +146,7 @@ bundle exec exe/textus --help
135
146
 
136
147
  ## What `textus init` gives you
137
148
 
138
- You get `.textus/` with all four lane directories under `data/`, baseline schemas, a starter manifest, and a gitignored `.run/` for disposable runtime state (the audit log, per-role cursors, produce locks). Roles declare capabilities; each lane declares a `kind:`, and write authority is derived from the role's capabilities crossed with the lane's kind:
149
+ You get `.textus/` with all five lane directories under `data/`, baseline schemas, a starter manifest, and a gitignored `.state/` for disposable runtime state (the audit log, per-role cursors, produce locks). Roles declare capabilities; each lane declares a `kind:`, and write authority is derived from the role's capabilities crossed with the lane's kind:
139
150
 
140
151
  ```yaml
141
152
  roles:
@@ -156,17 +167,18 @@ lanes:
156
167
  schemas/ # YAML field shapes per entry family
157
168
  templates/ # ERB templates for produced entries
158
169
  workflows/ # Ruby workflow files (Textus.workflow DSL) for data acquisition
159
- .gitignore # generated — ignores .run/ and any tracked:false entries
170
+ .gitignore # generated — ignores .state/ and any tracked:false entries
160
171
  data/ # one dir per lane; kinds + capabilities are in the manifest above
161
172
  knowledge/ # e.g. identity (knowledge.identity.*), voice, decisions, notes
162
173
  notebook/
163
174
  proposals/
164
175
  artifacts/ # machine lane: computed outputs + external inputs
165
- .run/ # disposable runtime state — gitignored, safe to delete (ADR 0038)
176
+ .state/ # disposable runtime state — gitignored, safe to delete (ADR 0038)
166
177
  audit/audit.log # append-only NDJSON event ledger, every write (rotates at ~10 MB)
167
- state/cursor.<role> # per-role pulse cursor — where `pulse --since` resumes
178
+ cursors/<role> # per-role pulse cursor — where `pulse --since` resumes
168
179
  locks/ # per-key produce locks + the produce mutex
169
180
  sentinels/ # publish bookkeeping (target sha) — regenerated on drain (ADR 0070)
181
+ indexes/raw.yaml # raw lane content-hash/URL index — regenerable cache
170
182
  ```
171
183
 
172
184
  Manifest `path:` fields are relative to `.textus/data/`. So `knowledge.notes.org.jane` lives at `.textus/data/knowledge/notes/org/jane.md`.
@@ -195,14 +207,14 @@ For a worked store — knowledge entries, a staged proposal, schemas, ERB templa
195
207
  - **Agent loop.** `textus boot` orients a fresh session; `textus pulse --since=N` is the per-turn heartbeat (changed entries, pending proposals, index etag for catalog drift detection). ([docs/how-to/agents-mcp.md](docs/how-to/agents-mcp.md))
196
208
  - **MCP surface.** The official `mcp` Ruby SDK drives the stdio JSON-RPC server; protocol version auto-negotiated up to `2025-11-25`. Wire textus into Claude Code, Cursor, or any MCP host in one config block.
197
209
  - **`textus doctor`.** Health checks across schemas, workflow registrations, keys, sentinels, and the audit log.
198
- - **`raw` lane kind.** A write-once intake lane for external data that hasn't been reviewed yet; carries the `ingest` capability.
210
+ - **`raw` lane and `ingest` verb.** Write-once intake lane for external URL bookmarks, files, and binary assets. Three source kinds (`url`/`file`/`asset`); daily key derivation; notebook stub per ingest. See "Intake and ingest" section below.
199
211
 
200
212
  ## CLI and lanes
201
213
 
202
214
  Every command operates on one store, located in this order: `--root <path>` flag → **`TEXTUS_ROOT`** env → walk up from the working directory for a `.textus/` ([SPEC §3.1](SPEC.md)). Write verbs require `--as=<role>`, resolved as: `--as` flag → **`TEXTUS_ROLE`** env → `.textus/role` file → default `human` ([SPEC §5.1](SPEC.md)). Default roles: `human`, `agent`, `automation` (rename or add your own in the manifest's `roles:` block). All verbs accept `--output=json` and return the envelope defined in [SPEC §8](SPEC.md).
203
215
 
204
216
  - Full verb table — read, write, health, scaffolding — is in [SPEC §9](SPEC.md).
205
- - Lane semantics and the capability × lane-kind mapping live in [SPEC §5](SPEC.md), with the reference in [`docs/reference/zones.md`](docs/reference/zones.md).
217
+ - Lane semantics and the capability × lane-kind mapping live in [SPEC §5](SPEC.md), with the reference in [`docs/reference/lanes.md`](docs/reference/lanes.md).
206
218
 
207
219
  `textus boot` prints the same information for the current store: lanes, entry families with schemas, registered workflows, write flows, and the verb catalog. Run it inside a store and you get the live picture; reach for the SPEC when you want the contract.
208
220
 
@@ -213,7 +225,7 @@ Produced entries (`kind: produced`) declare how they're acquired in one `source:
213
225
  - **`source: { from: external, command: "...", sources: [...] }`** — *externally managed*: an out-of-band command or workflow writes the file; textus tracks staleness via declared `sources`.
214
226
  - **`source: { from: external, command: "true", sources: [] }` + a workflow** — *workflow-driven*: a `Textus.workflow` block (in `.textus/workflows/`) acquires and shapes the data on `drain`.
215
227
 
216
- Publishing is one typed `publish:` block (ADR 0052/0094). Each target is either `{ to: path, template?: name }` for a single file (optionally rendered through an ERB template) or `{ tree: "dir" }` to mirror a whole stored subtree. Sentinels for every published file live under `.textus/.run/sentinels/` (git-ignored, regenerated on drain). See SPEC §5.2, §5.3, §5.12.
228
+ Publishing is one typed `publish:` block (ADR 0052/0094). Each target is either `{ to: path, template?: name }` for a single file (optionally rendered through an ERB template) or `{ tree: "dir" }` to mirror a whole stored subtree. Sentinels for every published file live under `.textus/.state/sentinels/` (git-ignored, regenerated on drain). See SPEC §5.2, §5.3, §5.12.
217
229
 
218
230
  Templates live in `.textus/templates/` as ERB files (`.erb`). The template receives the entry's `content` hash as local variables via `ERB#result_with_hash`. If `inject_boot: true`, a `boot` variable is also available with the live orientation context.
219
231
 
@@ -222,7 +234,7 @@ Templates live in `.textus/templates/` as ERB files (`.erb`). The template recei
222
234
  textus extends through **workflows** — a `Textus.workflow` block placed in `.textus/workflows/**/*.rb`. Each workflow matches a produced entry by key glob, then runs one or more named steps to acquire its data:
223
235
 
224
236
  ```ruby
225
- # .textus/workflows/artifacts/my_report.rb
237
+ # .textus/workflows/docs/my_report.rb
226
238
  Textus.workflow "my_report" do
227
239
  match "artifacts.my-report"
228
240
 
@@ -238,27 +250,47 @@ end
238
250
 
239
251
  `drain` discovers all workflow files, matches them against produced entries, and runs the steps. The result is written back to the entry's data path; `publish:` then copies it to its consumer paths.
240
252
 
241
- ## Hooks
253
+ ## Intake and ingest
242
254
 
243
- Out-of-band event reactions use the hook DSL. Drop a file anywhere in `.textus/workflows/` and use `Textus.hook`:
255
+ The `raw` lane is the inbound counterpart to `artifacts`: where `drain` materialises
256
+ **outbound** computed outputs, `ingest` receives **inbound** external source material.
244
257
 
245
- ```ruby
246
- Textus.hook do |reg|
247
- reg.on(:entry_written) do |key:, envelope:, **|
248
- # fire-and-forget runs after every successful write
249
- $stderr.puts "wrote #{key} (etag #{envelope.etag[0, 8]})"
250
- end
251
- end
252
- ```
258
+ **The ingest principle:** prefer a reference over a copy. Store body or asset only when the
259
+ content itself is the value — human-authored notes, brainstorm outputs, context you want to
260
+ annotate. For everything else, the URL is enough. If the source is private or
261
+ access-restricted, set `access: private` in `source:` so downstream workflows can handle it
262
+ appropriately.
253
263
 
254
- Observable events: `:entry_written`, `:entry_deleted`, `:entry_fetched`, `:entry_renamed`, `:entry_produced`, `:entry_published`, `:produce_failed`, `:proposal_accepted`, `:proposal_rejected`, `:store_loaded`, `:session_opened`, `:entry_fetch_started`, `:entry_fetch_failed`.
264
+ **Three source kinds:**
255
265
 
256
- Stale produced entries are re-materialised by `drain`, not by reads — `get` is a pure read that annotates the returned envelope with a freshness verdict (ADR 0089).
266
+ | Kind | Stores | Use when |
267
+ |------|--------|----------|
268
+ | `url` | URL reference only (`body: null`) | Bookmarking a page, skill, or doc for later annotation |
269
+ | `file` | File body text | Valuable human-authored content (brainstorm notes, meeting summaries) |
270
+ | `asset` | Binary at `assets/raw/` | Screenshots, PDFs — only when the asset itself is the artefact |
271
+
272
+ **Write-once** — the same slug on the same day cannot be overwritten. Delete and re-ingest to replace.
273
+
274
+ ```sh
275
+ # bookmark a skill reference — URL only, body stays null
276
+ textus ingest url agentskills-io-brainstorming \
277
+ --url=https://agentskills.io/skills/brainstorming \
278
+ --label="brainstorming skill" \
279
+ --as=agent
280
+
281
+ # see what landed in the raw lane
282
+ textus list --lane=raw
283
+
284
+ # a notebook stub was created alongside — annotate it
285
+ textus get notebook.notes.raw
286
+ ```
287
+
288
+ Stale produced entries are re-materialised by `drain`, not by reads — `get` is a pure read (ADR 0089).
257
289
 
258
290
  ```sh
259
291
  textus drain --as=automation # re-materialise every stale produced entry
260
- textus drain artifacts.feeds --as=automation # scope to one prefix
261
- textus get artifacts.feeds.calendar.events # a pure read; carries a freshness verdict
292
+ textus drain artifacts.feeds.skills --as=automation # scope to one prefix
293
+ textus get artifacts.feeds.skills # a pure read; carries a freshness verdict
262
294
  ```
263
295
 
264
296
  Schemas (`.textus/schemas/<name>.yaml`) declare field shapes, per-field `maintained_by:` ownership, and an `evolution:` block (`added_in`, `deprecated_at`, `migrate_from`). Full contract in SPEC §5.8.
@@ -289,3 +321,5 @@ Lefthook hooks (`brew bundle install` then `lefthook install`) run rubocop on `p
289
321
  ## License
290
322
 
291
323
  [MIT](LICENSE)
324
+
325
+