textus 0.52.0 → 0.53.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +62 -54
- data/SPEC.md +62 -187
- data/docs/architecture/README.md +88 -77
- data/exe/textus +1 -1
- data/lib/textus/action/accept.rb +53 -0
- data/lib/textus/action/audit.rb +133 -0
- data/lib/textus/action/base.rb +42 -0
- data/lib/textus/{read → action}/blame.rb +30 -22
- data/lib/textus/action/boot.rb +26 -0
- data/lib/textus/action/data_mv.rb +71 -0
- data/lib/textus/action/deps.rb +48 -0
- data/lib/textus/action/doctor.rb +26 -0
- data/lib/textus/action/drain.rb +41 -0
- data/lib/textus/action/enqueue.rb +55 -0
- data/lib/textus/action/get.rb +80 -0
- data/lib/textus/action/jobs.rb +38 -0
- data/lib/textus/action/key_delete.rb +46 -0
- data/lib/textus/action/key_delete_prefix.rb +46 -0
- data/lib/textus/action/key_mv.rb +143 -0
- data/lib/textus/action/key_mv_prefix.rb +59 -0
- data/lib/textus/action/list.rb +44 -0
- data/lib/textus/action/propose.rb +54 -0
- data/lib/textus/action/published.rb +26 -0
- data/lib/textus/action/pulse/scanner.rb +118 -0
- data/lib/textus/action/pulse.rb +87 -0
- data/lib/textus/action/put.rb +63 -0
- data/lib/textus/action/rdeps.rb +49 -0
- data/lib/textus/action/reject.rb +49 -0
- data/lib/textus/action/rule_explain.rb +95 -0
- data/lib/textus/action/rule_lint.rb +70 -0
- data/lib/textus/action/rule_list.rb +46 -0
- data/lib/textus/action/schema_envelope.rb +31 -0
- data/lib/textus/action/uid.rb +35 -0
- data/lib/textus/action/where.rb +38 -0
- data/lib/textus/action/write_verb.rb +58 -0
- data/lib/textus/background/job/base.rb +27 -0
- data/lib/textus/background/job/materialize.rb +31 -0
- data/lib/textus/background/job/refresh.rb +22 -0
- data/lib/textus/background/job/sweep.rb +31 -0
- data/lib/textus/background/job.rb +19 -0
- data/lib/textus/background/plan.rb +9 -0
- data/lib/textus/background/planner/plan.rb +113 -0
- data/lib/textus/{maintenance → background}/retention/apply.rb +7 -9
- data/lib/textus/background/worker.rb +67 -0
- data/lib/textus/boot.rb +53 -45
- data/lib/textus/command.rb +36 -0
- data/lib/textus/container.rb +1 -1
- data/lib/textus/{domain → core}/duration.rb +1 -1
- data/lib/textus/{domain → core}/freshness/evaluator.rb +11 -11
- data/lib/textus/{domain → core}/freshness/verdict.rb +2 -2
- data/lib/textus/{domain → core}/freshness.rb +2 -2
- data/lib/textus/{domain → core}/retention/sweep.rb +7 -7
- data/lib/textus/{domain → core}/retention.rb +2 -2
- data/lib/textus/{domain → core}/sentinel.rb +1 -1
- data/lib/textus/doctor/check/generator_drift.rb +1 -1
- data/lib/textus/doctor/check/handler_permit.rb +34 -0
- data/lib/textus/doctor/check/hooks.rb +11 -18
- data/lib/textus/doctor/check/illegal_keys.rb +1 -1
- data/lib/textus/doctor/check/intake_registration.rb +5 -5
- data/lib/textus/doctor/check/proposal_targets.rb +3 -3
- data/lib/textus/doctor/check/rule_ambiguity.rb +2 -2
- data/lib/textus/doctor/check/schema_violations.rb +8 -2
- data/lib/textus/doctor/check.rb +12 -9
- data/lib/textus/{read → doctor}/validator.rb +22 -13
- data/lib/textus/doctor.rb +6 -6
- data/lib/textus/envelope/io/writer.rb +65 -36
- data/lib/textus/envelope.rb +5 -3
- data/lib/textus/errors.rb +17 -9
- data/lib/textus/events.rb +21 -0
- data/lib/textus/gate/auth.rb +181 -0
- data/lib/textus/gate.rb +114 -0
- data/lib/textus/init/templates/machine_intake.rb +39 -35
- data/lib/textus/init/templates/orientation_reducer.rb +15 -11
- data/lib/textus/init.rb +90 -73
- data/lib/textus/key/path.rb +9 -2
- data/lib/textus/layout.rb +13 -0
- data/lib/textus/manifest/data.rb +14 -14
- data/lib/textus/manifest/entry/base.rb +15 -11
- data/lib/textus/manifest/entry/parser.rb +6 -6
- data/lib/textus/manifest/entry/produced.rb +3 -2
- data/lib/textus/manifest/entry/publish/mode.rb +1 -1
- data/lib/textus/manifest/entry/publish/to_paths.rb +2 -1
- data/lib/textus/manifest/entry/validators/events.rb +1 -1
- data/lib/textus/{domain/policy/handler_allowlist.rb → manifest/policy/handler_permit.rb} +4 -4
- data/lib/textus/{domain → manifest}/policy/matcher.rb +2 -2
- data/lib/textus/{domain → manifest}/policy/publish_target.rb +2 -2
- data/lib/textus/manifest/policy/react.rb +30 -0
- data/lib/textus/{domain → manifest}/policy/retention.rb +3 -3
- data/lib/textus/{domain → manifest}/policy/source.rb +24 -19
- data/lib/textus/manifest/policy.rb +36 -48
- data/lib/textus/manifest/resolver.rb +3 -2
- data/lib/textus/manifest/rules.rb +4 -4
- data/lib/textus/manifest/schema/keys.rb +17 -11
- data/lib/textus/manifest/schema/validator.rb +24 -22
- data/lib/textus/manifest/schema/vocabulary.rb +1 -1
- data/lib/textus/manifest/schema.rb +2 -2
- data/lib/textus/manifest.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/handler.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/intake.rb +22 -20
- data/lib/textus/{produce → pipeline}/acquire/projection.rb +13 -11
- data/lib/textus/{produce → pipeline}/acquire/serializer/json.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/serializer/text.rb +1 -1
- data/lib/textus/{produce → pipeline}/acquire/serializer/yaml.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/serializer.rb +1 -1
- data/lib/textus/{produce → pipeline}/engine.rb +7 -5
- data/lib/textus/{produce → pipeline}/render.rb +3 -1
- data/lib/textus/ports/audit_log.rb +31 -5
- data/lib/textus/ports/audit_subscriber.rb +4 -4
- data/lib/textus/{domain/jobs → ports/queue}/job.rb +19 -12
- data/lib/textus/ports/queue.rb +1 -1
- data/lib/textus/ports/sentinel_store.rb +2 -2
- data/lib/textus/ports/watcher_lock.rb +48 -0
- data/lib/textus/projection.rb +8 -8
- data/lib/textus/schema/tools.rb +4 -3
- data/lib/textus/session.rb +6 -3
- data/lib/textus/step/base.rb +35 -0
- data/lib/textus/step/builtin/csv_fetch.rb +19 -0
- data/lib/textus/step/builtin/ical_events_fetch.rb +30 -0
- data/lib/textus/step/builtin/json_fetch.rb +18 -0
- data/lib/textus/step/builtin/markdown_links_fetch.rb +20 -0
- data/lib/textus/step/builtin/rss_fetch.rb +26 -0
- data/lib/textus/step/builtin.rb +22 -0
- data/lib/textus/{hooks → step}/catalog.rb +3 -3
- data/lib/textus/{hooks → step}/context.rb +15 -13
- data/lib/textus/step/discovery.rb +24 -0
- data/lib/textus/{hooks → step}/error_log.rb +1 -1
- data/lib/textus/{hooks → step}/event_bus.rb +15 -16
- data/lib/textus/step/fetch.rb +13 -0
- data/lib/textus/{hooks → step}/fire_report.rb +1 -1
- data/lib/textus/step/loader.rb +108 -0
- data/lib/textus/step/observe.rb +31 -0
- data/lib/textus/step/registry_store.rb +66 -0
- data/lib/textus/{hooks → step}/signature.rb +1 -1
- data/lib/textus/step/transform.rb +12 -0
- data/lib/textus/step/validate.rb +11 -0
- data/lib/textus/step.rb +10 -0
- data/lib/textus/store.rb +17 -15
- data/lib/textus/surfaces/cli/group/data.rb +11 -0
- data/lib/textus/surfaces/cli/group/key.rb +11 -0
- data/lib/textus/surfaces/cli/group/mcp.rb +11 -0
- data/lib/textus/surfaces/cli/group/rule.rb +11 -0
- data/lib/textus/surfaces/cli/group/schema.rb +11 -0
- data/lib/textus/surfaces/cli/group.rb +50 -0
- data/lib/textus/surfaces/cli/runner.rb +236 -0
- data/lib/textus/surfaces/cli/verb/doctor.rb +21 -0
- data/lib/textus/surfaces/cli/verb/get.rb +21 -0
- data/lib/textus/surfaces/cli/verb/init.rb +20 -0
- data/lib/textus/surfaces/cli/verb/mcp_serve.rb +24 -0
- data/lib/textus/surfaces/cli/verb/put.rb +30 -0
- data/lib/textus/surfaces/cli/verb/schema_diff.rb +17 -0
- data/lib/textus/surfaces/cli/verb/schema_init.rb +21 -0
- data/lib/textus/surfaces/cli/verb/schema_migrate.rb +21 -0
- data/lib/textus/surfaces/cli/verb/watch.rb +19 -0
- data/lib/textus/surfaces/cli/verb.rb +111 -0
- data/lib/textus/surfaces/cli.rb +148 -0
- data/lib/textus/surfaces/mcp/catalog.rb +99 -0
- data/lib/textus/surfaces/mcp/errors.rb +34 -0
- data/lib/textus/surfaces/mcp/server.rb +145 -0
- data/lib/textus/surfaces/mcp/session.rb +9 -0
- data/lib/textus/surfaces/mcp/tool_schemas.rb +17 -0
- data/lib/textus/surfaces/mcp.rb +8 -0
- data/lib/textus/surfaces/role_scope.rb +38 -0
- data/lib/textus/surfaces/watcher.rb +38 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus.rb +64 -22
- metadata +132 -118
- data/lib/textus/cli/group/hook.rb +0 -9
- data/lib/textus/cli/group/key.rb +0 -9
- data/lib/textus/cli/group/mcp.rb +0 -9
- data/lib/textus/cli/group/rule.rb +0 -9
- data/lib/textus/cli/group/schema.rb +0 -9
- data/lib/textus/cli/group/zone.rb +0 -9
- data/lib/textus/cli/group.rb +0 -48
- data/lib/textus/cli/runner.rb +0 -193
- data/lib/textus/cli/verb/doctor.rb +0 -17
- data/lib/textus/cli/verb/get.rb +0 -18
- data/lib/textus/cli/verb/hook_run.rb +0 -48
- data/lib/textus/cli/verb/hooks.rb +0 -50
- data/lib/textus/cli/verb/init.rb +0 -18
- data/lib/textus/cli/verb/mcp_serve.rb +0 -22
- data/lib/textus/cli/verb/put.rb +0 -30
- data/lib/textus/cli/verb/schema_diff.rb +0 -15
- data/lib/textus/cli/verb/schema_init.rb +0 -19
- data/lib/textus/cli/verb/schema_migrate.rb +0 -19
- data/lib/textus/cli/verb/serve.rb +0 -19
- data/lib/textus/cli/verb.rb +0 -116
- data/lib/textus/cli.rb +0 -138
- data/lib/textus/dispatcher.rb +0 -54
- data/lib/textus/doctor/check/handler_allowlist.rb +0 -34
- data/lib/textus/domain/action.rb +0 -9
- data/lib/textus/domain/jobs/registry.rb +0 -37
- data/lib/textus/domain/permission.rb +0 -7
- data/lib/textus/domain/policy/base_guards.rb +0 -25
- data/lib/textus/domain/policy/evaluation.rb +0 -15
- data/lib/textus/domain/policy/guard.rb +0 -35
- data/lib/textus/domain/policy/guard_factory.rb +0 -40
- data/lib/textus/domain/policy/predicates/author_held.rb +0 -33
- data/lib/textus/domain/policy/predicates/etag_match.rb +0 -32
- data/lib/textus/domain/policy/predicates/fresh_within.rb +0 -59
- data/lib/textus/domain/policy/predicates/registry.rb +0 -39
- data/lib/textus/domain/policy/predicates/schema_valid.rb +0 -61
- data/lib/textus/domain/policy/predicates/target_is_canon.rb +0 -33
- data/lib/textus/domain/policy/predicates/zone_writable_by.rb +0 -39
- data/lib/textus/hooks/builtin.rb +0 -70
- data/lib/textus/hooks/loader.rb +0 -54
- data/lib/textus/hooks/rpc_registry.rb +0 -43
- data/lib/textus/jobs/handlers.rb +0 -62
- data/lib/textus/jobs/scheduler.rb +0 -36
- data/lib/textus/jobs/seeder.rb +0 -57
- data/lib/textus/maintenance/drain.rb +0 -42
- data/lib/textus/maintenance/key_delete_prefix.rb +0 -48
- data/lib/textus/maintenance/key_mv_prefix.rb +0 -68
- data/lib/textus/maintenance/rule_lint.rb +0 -66
- data/lib/textus/maintenance/serve.rb +0 -30
- data/lib/textus/maintenance/worker.rb +0 -74
- data/lib/textus/maintenance/zone_mv.rb +0 -64
- data/lib/textus/maintenance.rb +0 -15
- data/lib/textus/mcp/catalog.rb +0 -70
- data/lib/textus/mcp/errors.rb +0 -32
- data/lib/textus/mcp/server.rb +0 -138
- data/lib/textus/mcp/session.rb +0 -7
- data/lib/textus/mcp/tool_schemas.rb +0 -15
- data/lib/textus/mcp.rb +0 -6
- data/lib/textus/mustache.rb +0 -117
- data/lib/textus/ports/produce_on_write_subscriber.rb +0 -73
- data/lib/textus/produce/events.rb +0 -36
- data/lib/textus/read/audit.rb +0 -130
- data/lib/textus/read/boot.rb +0 -26
- data/lib/textus/read/capabilities.rb +0 -70
- data/lib/textus/read/deps.rb +0 -38
- data/lib/textus/read/doctor.rb +0 -27
- data/lib/textus/read/freshness.rb +0 -152
- data/lib/textus/read/get.rb +0 -73
- data/lib/textus/read/jobs.rb +0 -31
- data/lib/textus/read/list.rb +0 -24
- data/lib/textus/read/published.rb +0 -22
- data/lib/textus/read/pulse.rb +0 -98
- data/lib/textus/read/rdeps.rb +0 -39
- data/lib/textus/read/rule_explain.rb +0 -96
- data/lib/textus/read/rule_list.rb +0 -54
- data/lib/textus/read/schema_envelope.rb +0 -25
- data/lib/textus/read/uid.rb +0 -29
- data/lib/textus/read/validate_all.rb +0 -36
- data/lib/textus/read/where.rb +0 -24
- data/lib/textus/role_scope.rb +0 -78
- data/lib/textus/write/accept.rb +0 -58
- data/lib/textus/write/enqueue.rb +0 -50
- data/lib/textus/write/key_delete.rb +0 -65
- data/lib/textus/write/key_mv.rb +0 -141
- data/lib/textus/write/propose.rb +0 -54
- data/lib/textus/write/put.rb +0 -74
- data/lib/textus/write/reject.rb +0 -68
data/SPEC.md
CHANGED
|
@@ -22,18 +22,17 @@
|
|
|
22
22
|
- [5.1 Role resolution](#51-role-resolution)
|
|
23
23
|
- [5.1.1 Capabilities](#511-capabilities)
|
|
24
24
|
- [5.2 Source layer (produced entries)](#52-source-layer-produced-entries)
|
|
25
|
-
- [5.2.1
|
|
26
|
-
- [5.2.2 External source (`from:
|
|
25
|
+
- [5.2.1 Derived source (`from: derive`)](#521-derived-source-from-derive)
|
|
26
|
+
- [5.2.2 External source (`from: external`)](#522-external-source-from-external)
|
|
27
27
|
- [5.3 Publish layer](#53-publish-layer-publish)
|
|
28
|
-
- [5.4 Intake source (`from:
|
|
28
|
+
- [5.4 Intake source (`from: fetch`)](#54-intake-source-from-fetch)
|
|
29
29
|
- [5.5 Pending / accept workflow](#55-pending--accept-workflow)
|
|
30
30
|
- [5.6 Audit log](#56-audit-log)
|
|
31
31
|
- [5.7 Security bounds](#57-security-bounds)
|
|
32
32
|
- [5.8 Schema evolution](#58-schema-evolution)
|
|
33
|
-
- [5.9
|
|
34
|
-
- [5.10
|
|
35
|
-
- [5.11
|
|
36
|
-
- [5.12 Storage formats](#512-storage-formats)
|
|
33
|
+
- [5.9 Hooks](#59-hooks)
|
|
34
|
+
- [5.10 Rules](#510-rules)
|
|
35
|
+
- [5.11 Storage formats](#511-storage-formats)
|
|
37
36
|
- [6. Schemas](#6-schemas)
|
|
38
37
|
- [7. Entry file format](#7-entry-file-format)
|
|
39
38
|
- [8. Envelope (the wire format)](#8-envelope-the-wire-format)
|
|
@@ -48,10 +47,6 @@
|
|
|
48
47
|
- [13.1 Layered architecture (internal)](#131-layered-architecture-internal)
|
|
49
48
|
- [14. Open questions (v3.x scope)](#14-open-questions-v3x-scope)
|
|
50
49
|
- [15. Implementation checklist](#15-implementation-checklist)
|
|
51
|
-
- [16. Migrating from textus/2](#16-migrating-from-textus2)
|
|
52
|
-
- [16.1 Breaking changes in 0.31.0 (capability-based roles)](#161-breaking-changes-in-0310-capability-based-roles)
|
|
53
|
-
- [16.2 Breaking changes in 0.33.0 (workspace/keep + Setup-1 scaffold)](#162-breaking-changes-in-0330-workspacekeep--setup-1-scaffold)
|
|
54
|
-
- [16.3 Breaking changes in 0.35.0 (proposal target-canon + `author_held`)](#163-breaking-changes-in-0350-proposal-target-canon--author_held)
|
|
55
50
|
|
|
56
51
|
---
|
|
57
52
|
|
|
@@ -97,7 +92,7 @@ textus is organized as five composable layers. Each layer has a single responsib
|
|
|
97
92
|
|
|
98
93
|
| Layer | Name | Responsibility |
|
|
99
94
|
|---|---|---|
|
|
100
|
-
| L1 | **Store** | Plain-file backend: `.textus/
|
|
95
|
+
| L1 | **Store** | Plain-file backend: `.textus/data/<lane>/...` with YAML frontmatter + Markdown body, addressed by dotted keys, schema-validated, etag-versioned. |
|
|
101
96
|
| L2 | **Sources** | Declared external inputs (the `artifacts` machine zone in the default scaffold, under the `artifacts.feeds.*` keys; any `machine` zone, writable by a role with `converge`): URLs, files, feeds with declared parsers and TTLs. textus *describes* sources; external automation fetches and pipes results through `textus put`. |
|
|
102
97
|
| L3 | **Source** | An entry's `source:` *acquires* **data** — a pure in-process projection from store entries (select/pluck/sort/transform), an external fetch via a handler, or an out-of-band command. Acquire-only: rendering is not a source concern. No shell execution. |
|
|
103
98
|
| L4 | **Publish** | Emits a produced entry's data to repo-relative paths, declared via a **list** of `publish:` targets. A target with no `template:` copies the data verbatim (json/yaml re-serialized without `_meta`; other formats byte-copied); a target with a `template:` renders the data through it. A `{ tree: }` target mirrors a subtree (ADR 0047). Published artifacts are clean content — textus's `_meta` provenance stays in the store. A sentinel under `.textus/.run/sentinels/<target-rel-path>.textus-managed.json` (git-ignored runtime state) records the source, sha256, and `mode: "copy"`. |
|
|
@@ -130,22 +125,23 @@ The root is `.textus/` at the project working directory. A typical tree:
|
|
|
130
125
|
|
|
131
126
|
```
|
|
132
127
|
.textus/
|
|
133
|
-
manifest.yaml # internal: key → subtree mapping + role/
|
|
134
|
-
audit.log # internal, append-only NDJSON log of every successful write
|
|
128
|
+
manifest.yaml # internal: key → subtree mapping + role/lane declarations
|
|
135
129
|
schemas/ # internal: YAML schema files
|
|
136
130
|
templates/ # internal: Mustache templates referenced by derived entries
|
|
137
|
-
|
|
138
|
-
.run/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
131
|
+
steps/ # internal: user-injectable step handlers (fetch, derive, external)
|
|
132
|
+
.run/ # runtime (git-ignored): audit log, sentinels, locks, queue, pulse cursors
|
|
133
|
+
audit.log # append-only NDJSON log of every successful write
|
|
134
|
+
sentinels/ # byte-copied publish bookkeeping (see §5.3)
|
|
135
|
+
data/ # ALL user content lives here
|
|
136
|
+
knowledge/ # lane: knowledge (kind: canon — author-holders write)
|
|
137
|
+
notebook/ # lane: notebook (kind: workspace — keep-holders write; agent's own durable lane)
|
|
138
|
+
proposals/ # lane: proposals (kind: queue — propose-holders write)
|
|
139
|
+
artifacts/ # lane: artifacts (kind: machine — converge-holders write)
|
|
144
140
|
```
|
|
145
141
|
|
|
146
|
-
Textus internals (`manifest.yaml`, `schemas/`, `templates/`, `
|
|
142
|
+
Textus internals (`manifest.yaml`, `schemas/`, `templates/`, `steps/`) live directly under `.textus/`; disposable runtime state (audit log, publish `sentinels/`, fetch/build locks, pulse cursors, job queue) lives under `.textus/.run/` (git-ignored, ADR 0038/0070). **All user content lives under `.textus/data/`.** Manifest `path:` fields are relative to `.textus/` — they include the `data/` prefix explicitly (e.g. `path: data/knowledge/foo.md`).
|
|
147
143
|
|
|
148
|
-
|
|
144
|
+
Lane directories under `data/` are conventional; their write semantics are derived from the lane's declared `kind:` (and the capabilities roles hold), not the directory name.
|
|
149
145
|
|
|
150
146
|
`.textus/audit.log` is an append-only NDJSON file written under a file lock by every successful `put`, `key_delete`, `key_mv`, and `accept`. Convergence (`drain`/`serve`) writes through these same verbs — a produced entry logs as `put`, a swept entry as `key_delete` — so there is no distinct `drain` audit verb. `.textus/role` (one line containing a role name) is optional and participates in the role-resolution order (§5).
|
|
151
147
|
|
|
@@ -172,7 +168,7 @@ roles:
|
|
|
172
168
|
- { name: agent, can: [propose] }
|
|
173
169
|
- { name: automation, can: [converge] }
|
|
174
170
|
|
|
175
|
-
|
|
171
|
+
lanes:
|
|
176
172
|
- name: knowledge
|
|
177
173
|
kind: canon
|
|
178
174
|
- name: notebook
|
|
@@ -186,20 +182,20 @@ zones:
|
|
|
186
182
|
|
|
187
183
|
entries:
|
|
188
184
|
- key: knowledge.identity.self
|
|
189
|
-
path: knowledge/identity/self.md
|
|
190
|
-
|
|
185
|
+
path: data/knowledge/identity/self.md
|
|
186
|
+
lane: knowledge
|
|
191
187
|
schema: identity
|
|
192
188
|
|
|
193
189
|
- key: knowledge.network.org
|
|
194
|
-
path: knowledge/network/org
|
|
195
|
-
|
|
190
|
+
path: data/knowledge/network/org
|
|
191
|
+
lane: knowledge
|
|
196
192
|
schema: person
|
|
197
193
|
owner: human:network
|
|
198
194
|
nested: true
|
|
199
195
|
|
|
200
196
|
- key: artifacts.catalogs.people
|
|
201
|
-
path: artifacts/catalogs/people.md
|
|
202
|
-
|
|
197
|
+
path: data/artifacts/catalogs/people.md
|
|
198
|
+
lane: artifacts
|
|
203
199
|
schema: null
|
|
204
200
|
owner: automation:converge
|
|
205
201
|
|
|
@@ -232,9 +228,9 @@ For `nested: true`, the recursive glob matches the format's extension (markdown
|
|
|
232
228
|
**Subtree mirror (a `{ tree: }` target).** A nested manifest entry MAY include a `{ tree: "dir" }` target to mirror its entire stored subtree (`zones/<path>/**`) to a single target directory, preserving relative layout (case and extension preserved). It is **path-driven, not key-driven**: no keys are enumerated, no template variables are interpreted, and the mirrored files are opaque payload (never addressable). The entry's `ignore:` globs (§4, ADR 0042) filter the walk; each mirrored file gets its own sentinel; and on every drain the whole target directory is pruned of textus-managed files the current source no longer produces (unmanaged files are never touched). When a `{ tree: }` target directory overlaps another entry's `{ to: }` target (e.g. a derived `SKILL.md` written into the mirrored dir), the mirroring entry **must** `ignore:` that filename or prune will delete it — `doctor` flags this as `publish.tree_index_overlap`. See ADR 0047.
|
|
233
229
|
|
|
234
230
|
```yaml
|
|
235
|
-
- key:
|
|
236
|
-
path:
|
|
237
|
-
|
|
231
|
+
- key: knowledge.skills
|
|
232
|
+
path: data/knowledge/skills
|
|
233
|
+
lane: knowledge
|
|
238
234
|
schema: skill
|
|
239
235
|
nested: true
|
|
240
236
|
publish:
|
|
@@ -242,11 +238,9 @@ For `nested: true`, the recursive glob matches the format's extension (markdown
|
|
|
242
238
|
ignore: ["*.tmp", ".DS_Store"]
|
|
243
239
|
```
|
|
244
240
|
|
|
245
|
-
|
|
241
|
+
**Lookup rule:** to resolve a key, find the entry with the longest `key:` prefix that matches. If that entry has `nested: true`, the remaining segments map to subdirectories under its `path`. Otherwise the key must equal an entry exactly. The resolved filesystem path is `<.textus root>/<entry.path>[/<remaining>...].md` — manifest `path:` values include the `data/` prefix (e.g. `data/knowledge/network/org`).
|
|
246
242
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
## 5. Zones and capability-based write gates
|
|
243
|
+
## 5. Lanes and capability-based write gates
|
|
250
244
|
|
|
251
245
|
Write authority is **derived**, never declared per-zone. Each zone declares a `kind:`; each zone-kind requires one capability to write to it. A role may write a zone iff its capability set (`role.can`) contains the verb that zone-kind requires. textus gates **writes, not reads**: reads are unrestricted at the protocol layer (the `.textus/` files are on disk). Per-role read-scoping, if needed, is an agent-surface projection, not a manifest field.
|
|
252
246
|
|
|
@@ -366,24 +360,24 @@ when the acting role holds `author`). See §5.11 for composing extra predicates
|
|
|
366
360
|
|
|
367
361
|
### 5.2 Source layer (produced entries)
|
|
368
362
|
|
|
369
|
-
Produced entries live in a `machine`
|
|
363
|
+
Produced entries live in a `machine` lane (writable by a role holding `converge`; `automation` by default) — `artifacts` in the default scaffold. They are not authored by hand; their **data** is acquired from a declared `source:` block with a `from:` discriminator (`derive | fetch | external`). A `source:` is **acquire-only**: it produces the data the store holds; it does **not** render. Rendering is a publish concern (§5.3). Every produced entry is `kind: produced` (ADR 0095); the **produce-method** is read from `source.from` — `from: derive | external` is *derived* (internal projection / out-of-band command), `from: fetch` is *intake* (external fetch, §5.4). `kind:` no longer restates the produce-method (the former `kind: derived` / `kind: intake` are rejected at load with a fold hint).
|
|
370
364
|
|
|
371
|
-
#### 5.2.1
|
|
365
|
+
#### 5.2.1 Derived source (`from: derive`)
|
|
372
366
|
|
|
373
|
-
A derived entry produced by a pure in-process projection declares `source: { from:
|
|
367
|
+
A derived entry produced by a pure in-process projection declares `source: { from: derive, ... }`. The projection fields are **flat** under `source:` (there is no nested `derive:` block). The stored form is **data** — serialized via the `format:` strategy (e.g. `json`, `yaml`, `markdown-table`); no template is consulted at acquire time.
|
|
374
368
|
|
|
375
369
|
```yaml
|
|
376
370
|
- key: artifacts.derived.people
|
|
377
|
-
kind: produced # produce-method (derived) read from source.from:
|
|
378
|
-
|
|
371
|
+
kind: produced # produce-method (derived) read from source.from: derive
|
|
372
|
+
lane: artifacts
|
|
373
|
+
path: data/artifacts/derived/people.json
|
|
379
374
|
source:
|
|
380
|
-
from:
|
|
375
|
+
from: derive
|
|
381
376
|
select: knowledge.network.org # prefix OR [list of prefixes]
|
|
382
377
|
pluck: [name, relationship, org]
|
|
383
378
|
sort_by: name # optional
|
|
384
379
|
limit: 1000 # default 1000, max 1000
|
|
385
380
|
format: json # one of: list, hash, yaml-list-in-md, json, markdown-table
|
|
386
|
-
transform: rank_by_recency # optional — names a :transform_rows hook
|
|
387
381
|
on_write: async # sync | async (default async)
|
|
388
382
|
```
|
|
389
383
|
|
|
@@ -391,24 +385,22 @@ A derived entry produced by a pure in-process projection declares `source: { fro
|
|
|
391
385
|
|
|
392
386
|
`format` controls how the acquired data is serialized for storage. Permitted values: `list`, `hash`, `yaml-list-in-md`, `json`, `markdown-table`.
|
|
393
387
|
|
|
394
|
-
`transform:` (optional) names a registered `:transform_rows` hook (see §5.10). The hook receives the projected rows array and may reorder, filter, or augment before serialization — it shapes the **data**, not its presentation.
|
|
395
|
-
|
|
396
388
|
`on_write:` (`sync` | `async`, default `async`) controls the write-trigger strategy: `sync` rebuilds the entry's data inline before the triggering write returns; `async` defers it to a background pass that completes before process exit.
|
|
397
389
|
|
|
398
|
-
> **No source-level `template:` / `
|
|
390
|
+
> **No source-level `template:` / `provenance:`.** Those are retired from `source:` (and from the entry top level). Rendering moves to a publish target (§5.3); provenance is carried in the data's `_meta` (§5.11). A manifest carrying `source: { from: template }` or an entry-level `template:`/`provenance:` is **rejected at load** with a fold hint (ADR 0094).
|
|
399
391
|
|
|
400
|
-
#### 5.2.2 External source (`from:
|
|
392
|
+
#### 5.2.2 External source (`from: external`)
|
|
401
393
|
|
|
402
|
-
A derived entry that is produced by a build tool *outside* textus — `rake`, `just`, a shell script, anything — declares `source: { from:
|
|
394
|
+
A derived entry that is produced by a build tool *outside* textus — `rake`, `just`, a shell script, anything — declares `source: { from: external, ... }`. textus does **not** execute the command (consistent with §2); the external automation is responsible for writing the file. textus records `sources:` so `doctor`'s `generator_drift` check can compare source mtimes against the derived file's `_meta.generated.at` and report staleness. (Generator/build drift is dependency-based, not age-based; ADR 0085 keeps it out of the internal `freshness` scan — it is a `doctor` health check.)
|
|
403
395
|
|
|
404
396
|
```yaml
|
|
405
397
|
- key: artifacts.derived.skills
|
|
406
|
-
path: artifacts/derived/skills.md
|
|
407
|
-
kind: produced # produce-method (derived) read from source.from:
|
|
408
|
-
|
|
398
|
+
path: data/artifacts/derived/skills.md
|
|
399
|
+
kind: produced # produce-method (derived) read from source.from: external
|
|
400
|
+
lane: artifacts
|
|
409
401
|
owner: automation:catalog-skills
|
|
410
402
|
source:
|
|
411
|
-
from:
|
|
403
|
+
from: external
|
|
412
404
|
command: "rake catalog:skills" # informational; external automation invokes it
|
|
413
405
|
sources: # dotted keys OR repo-relative paths
|
|
414
406
|
- knowledge.projects
|
|
@@ -419,7 +411,7 @@ A derived entry that is produced by a build tool *outside* textus — `rake`, `j
|
|
|
419
411
|
|
|
420
412
|
**`command:`** is recorded in the staleness row's `generator` field but never executed. It exists so `doctor`'s `generator_drift` output can carry a hint about how to regenerate.
|
|
421
413
|
|
|
422
|
-
**Generator-drift contract.** An entry with `source: { from:
|
|
414
|
+
**Generator-drift contract.** An entry with `source: { from: external }` is reported by `doctor`'s `generator_drift` check as drifted when:
|
|
423
415
|
- The derived file does not exist, OR
|
|
424
416
|
- `_meta.generated.at` is missing or unparseable, OR
|
|
425
417
|
- Any `sources:` element has been modified after `_meta.generated.at`.
|
|
@@ -435,13 +427,13 @@ generated:
|
|
|
435
427
|
|
|
436
428
|
`generated.from` SHOULD match `source.sources` — they're the same list, recorded twice so a diff proves what was actually consumed.
|
|
437
429
|
|
|
438
|
-
`from:
|
|
430
|
+
`from: external` and `from: derive` are alternatives — exactly one per derived entry. The external automation produces the bytes directly for `from: external`; the in-process projection produces them for `from: derive`. Either way the stored form is data; rendering is deferred to publish (§5.3).
|
|
439
431
|
|
|
440
432
|
### 5.3 Publish layer (`publish:`)
|
|
441
433
|
|
|
442
434
|
Rendering and emission are a **publish** concern, orthogonal to acquire (§5.2). `publish:` is always a **list** of targets (ADR 0094). Each element is exactly one of two shapes:
|
|
443
435
|
|
|
444
|
-
- a **to-target** — `{ to: <path>, template?: <name
|
|
436
|
+
- a **to-target** — `{ to: <path>, template?: <name> }` — emit the entry's data to one repo-relative path;
|
|
445
437
|
- a **tree-target** — `{ tree: <dir> }` — mirror the entry's stored subtree (ADR 0047).
|
|
446
438
|
|
|
447
439
|
The legacy *map* forms — `publish: { to: [...] }` and `publish: { tree: ... }` — and the older top-level `publish_to:` / `publish_tree:` keys are **rejected at load** with a migration message: `publish:` is a list, and a mirror is a `{ tree: }` element of it.
|
|
@@ -470,7 +462,7 @@ A sentinel is written for each published file at `<store_root>/.run/sentinels/<t
|
|
|
470
462
|
|
|
471
463
|
**Publish presence is a uniform rule across all kinds.** Absent → the entry is terminal data (consumed internally via another entry's `select`, or read via `get`). Present → emit to the listed targets, every kind through one publish path. A `from: command` entry with publish targets emits the bytes the command already wrote into the store; without targets it is a staleness-only signal.
|
|
472
464
|
|
|
473
|
-
### 5.4 Intake source (`from:
|
|
465
|
+
### 5.4 Intake source (`from: fetch`)
|
|
474
466
|
|
|
475
467
|
Intake entries acquire their data via `source: { from: handler, ... }` — an external fetch through a registered handler. The `source:` block fully replaces the former `intake:` block; the entry's `kind:` is `produced` and the *intake* produce-method is read from `source.from: handler` (ADR 0095). Like every `source:`, it is acquire-only — a fetched feed is **data**, and if it needs rendering for a consumer that is a publish target's `template:` (§5.3), never the handler's job. textus itself makes no implicit network calls: the handler runs only when `textus drain`/`serve` or a `hook run` event re-pulls a stale entry past its `source.ttl` — a `get` never runs it (ADR 0089).
|
|
476
468
|
|
|
@@ -516,7 +508,7 @@ Proposal entries are full patches authored into the `proposals` queue zone (writ
|
|
|
516
508
|
proposal:
|
|
517
509
|
target_key: working.network.org.bob
|
|
518
510
|
action: put
|
|
519
|
-
|
|
511
|
+
_meta:
|
|
520
512
|
name: bob
|
|
521
513
|
relationship: peer
|
|
522
514
|
org: acme
|
|
@@ -524,7 +516,7 @@ frontmatter:
|
|
|
524
516
|
Proposed body content.
|
|
525
517
|
```
|
|
526
518
|
|
|
527
|
-
`proposal.target_key` names the entry the patch would create or modify, and `proposal.action` is `put` or `delete`. The
|
|
519
|
+
`proposal.target_key` names the entry the patch would create or modify, and `proposal.action` is `put` or `delete`. The sibling `_meta` block and the body are the proposed new content — a proposal carries the same `{ _meta, body }` envelope shape it intends `accept` to write (ADR 0113). A proposal's `target_key` MUST resolve to a `canon` zone; `accept` refuses any other target (`target_is_canon`, ADR 0035).
|
|
528
520
|
|
|
529
521
|
`textus accept <proposal-key>` is a **transition** (not a capability) that requires the **`author` capability**: the resolved role must hold `author` (the single trust anchor — `human` by default). It copies the patch into the target zone, records provenance (originating proposal key, original role, original timestamp) in the audit log, and removes the proposal entry. The `reject` transition likewise requires `author`. Roles holding only `propose` (e.g. `agent`) can propose but cannot accept or reject.
|
|
530
522
|
|
|
@@ -614,11 +606,7 @@ evolution:
|
|
|
614
606
|
|
|
615
607
|
**Override rule:** a role holding the `author` capability (the trust anchor — `human` by default) is permitted to write any `maintained_by` field, regardless of declared owner. The trust anchor overrides agent-maintained fields by design: schema field ownership (`maintained_by:`) makes the boundary explicit, not implicit. All other role mismatches are reported by `doctor --check=schema_violations` with code `role_authority`, including fields `key`, `field`, `expected`, and `last_writer`.
|
|
616
608
|
|
|
617
|
-
### 5.9
|
|
618
|
-
|
|
619
|
-
Row transforms are RPC hooks on the `:transform_rows` event. See §5.10.
|
|
620
|
-
|
|
621
|
-
### 5.10 Hooks
|
|
609
|
+
### 5.9 Hooks
|
|
622
610
|
|
|
623
611
|
This section is the normative event table. For the hook-author's guide (how to define and test hooks), see [`docs/how-to/writing-hooks.md`](docs/how-to/writing-hooks.md).
|
|
624
612
|
|
|
@@ -682,7 +670,7 @@ The primary entity is always `key:` (for `:proposal_accepted`, `key:` is the pen
|
|
|
682
670
|
|
|
683
671
|
Each handler runs under `Timeout.timeout(2)`.
|
|
684
672
|
|
|
685
|
-
### 5.
|
|
673
|
+
### 5.10 Rules
|
|
686
674
|
|
|
687
675
|
A manifest MAY declare a top-level `rules:` block — a list of rule blocks matched against entry keys by glob. Each block carries one or more slots:
|
|
688
676
|
|
|
@@ -692,7 +680,7 @@ rules:
|
|
|
692
680
|
retention: { ttl: 90d, action: archive }
|
|
693
681
|
|
|
694
682
|
- match: feeds.calendar.**
|
|
695
|
-
|
|
683
|
+
handler_permit: [ical-events]
|
|
696
684
|
|
|
697
685
|
- match: proposals.**
|
|
698
686
|
guard:
|
|
@@ -704,7 +692,7 @@ rules:
|
|
|
704
692
|
| Slot | Type | Meaning |
|
|
705
693
|
|---|---|---|
|
|
706
694
|
| `retention` | `{ ttl, action: drop\|archive }` | Age-based garbage collection (ADR 0093). `action` is `drop` (delete the entry) or `archive` (copy to `<store>/archive/<relative-path>` then delete). Age is measured from `_meta.last_fetched_at` (intake entries) when present, else the leaf file's modification time. **Destructive — applied only on the convergence sweep (the destructive phase of `drain`/`serve`), never on a write or read.** Orthogonal to production: an intake entry may declare both `source: { ..., ttl: 1h }` (re-pull cadence) and a `retention: { ttl: 90d, action: archive }` rule. `retention:` on a `derived` entry is rejected at load. |
|
|
707
|
-
| `
|
|
695
|
+
| `handler_permit` | list of strings | Constrains which `source.handler:` names may be used by intake entries matched by this block. Enforced by `textus doctor`. |
|
|
708
696
|
| `guard` | `{ <transition>: [predicates] }` | Extra predicates composed (AND) onto a write transition's built-in **base** guard (ADR 0031). Keyed by transition (`put`, `key_delete`, `key_mv`, `accept`, `reject`, `converge`). Predicate names are drawn from the closed vocabulary (`zone_writable_by`, `schema_valid`, `author_held`, `target_is_canon`, `etag_match`, `fresh_within`); parameterized predicates use `{ name: param }` form, e.g. `{ fresh_within: "1h" }`. Enforced — the transition refuses (`guard_failed`) if any predicate fails; the topology refusal keeps the `write_forbidden` code. |
|
|
709
697
|
|
|
710
698
|
The `retention:` slot handles age-based GC only. Write-trigger strategy for derived entries (`on_write: sync|async`) is declared on the entry's own `source:` block (§5.2.1), not in `rules:`. Generator/build drift — a derived entry whose sources changed since its `generated.at` — is reported by the `textus doctor` `generator_drift` check rather than any rule slot.
|
|
@@ -715,7 +703,7 @@ The `retention:` slot handles age-based GC only. Write-trigger strategy for deri
|
|
|
715
703
|
|
|
716
704
|
**Read surface.** `textus rule list` dumps every block. `textus rule explain KEY` shows the resolved `RuleSet` for one key — lean effective `{retention, guard}` by default; `--detail` adds every matched block and the effective guard predicate names for every write transition (ADR 0059).
|
|
717
705
|
|
|
718
|
-
### 5.
|
|
706
|
+
### 5.11 Storage formats
|
|
719
707
|
|
|
720
708
|
An entry's `format:` selects a storage strategy. All strategies expose the same `parse(bytes) → {_meta, body, content}` and `serialize(meta:, body:, content:) → bytes` contract. The store, audit, etag, and projection layers operate on the parsed shape; only (de)serialization differs.
|
|
721
709
|
|
|
@@ -811,7 +799,7 @@ Every successful CLI response (`--output=json`) is a single JSON envelope:
|
|
|
811
799
|
**Field rules:**
|
|
812
800
|
- `protocol` MUST be the exact string `textus/3`.
|
|
813
801
|
- `key` MUST be the canonical resolved key.
|
|
814
|
-
- `zone` MUST be one of the
|
|
802
|
+
- `zone` MUST be one of the lanes declared in the manifest (`knowledge`, `notebook`, `feeds`, `proposals`, `artifacts` in the default Setup-1 scaffold).
|
|
815
803
|
- `path` MUST be an absolute filesystem path.
|
|
816
804
|
- `format` MUST be one of `markdown`, `json`, `yaml`, `text` (§5.12). Absent envelopes are treated as `markdown` for back-compat.
|
|
817
805
|
- `body` is the raw on-disk bytes as a UTF-8 string for every format.
|
|
@@ -885,15 +873,15 @@ All verbs accept `--output=json` and emit a canonical envelope (success or error
|
|
|
885
873
|
| `key mv OLD NEW [--as=R] [--dry-run]` | write | per zone (same-zone only) |
|
|
886
874
|
| `key uid K` | read | any |
|
|
887
875
|
|
|
888
|
-
**`textus boot` envelope extras.** In addition to
|
|
876
|
+
**`textus boot` envelope extras.** In addition to lanes, entries, hooks, write flows, and the `cli_verbs` catalog, the boot envelope includes an `agent_quickstart` block synthesized from the manifest's role capabilities:
|
|
889
877
|
|
|
890
878
|
```json
|
|
891
879
|
{
|
|
892
880
|
"agent_quickstart": {
|
|
893
881
|
"read_verbs": ["get", "list", "pulse", "schema_show", "boot", "rule_explain", "where", "deps", "rdeps"],
|
|
894
882
|
"write_verbs": ["accept", "key_delete", "key_mv", "propose", "put", "reject"],
|
|
895
|
-
"
|
|
896
|
-
"
|
|
883
|
+
"writable_lanes": ["proposals"],
|
|
884
|
+
"propose_lane": "proposals",
|
|
897
885
|
"latest_seq": 1842
|
|
898
886
|
}
|
|
899
887
|
}
|
|
@@ -952,7 +940,7 @@ Every `Textus::Error` exposes `code`, `message`, and an optional `hint:`. The hi
|
|
|
952
940
|
|
|
953
941
|
## 10.2 `textus doctor`
|
|
954
942
|
|
|
955
|
-
`textus doctor` returns a health-check envelope: `{ "protocol": "textus/3", "ok": bool, "issues": [...], "summary": {error, warning, info} }`. Each issue carries `code`, `level` (`error|warning|info`), `subject`, `message`, and optionally `fix`. `ok` is true iff no error-level issues are present; warnings and info do not flip the bit. Builtin checks: `protocol_version`, `manifest_files`, `schemas`, `schema_parse_error`, `templates`, `
|
|
943
|
+
`textus doctor` returns a health-check envelope: `{ "protocol": "textus/3", "ok": bool, "issues": [...], "summary": {error, warning, info} }`. Each issue carries `code`, `level` (`error|warning|info`), `subject`, `message`, and optionally `fix`. `ok` is true iff no error-level issues are present; warnings and info do not flip the bit. Builtin checks: `protocol_version`, `manifest_files`, `schemas`, `schema_parse_error`, `templates`, `intake_registration`, `illegal_keys`, `sentinels`, `audit_log`, `unowned_schema_fields`, `schema_violations`, `rule_ambiguity`, `handler_permit`, `fetch_locks`, `proposal_targets`, `publish.tree_index_overlap`, `generator_drift`. Additional registered `:validate` hooks (§5.10) run after the builtin set. Exit code is 0 on `ok`, 1 otherwise.
|
|
956
944
|
|
|
957
945
|
## 11. Versioning
|
|
958
946
|
|
|
@@ -1018,7 +1006,7 @@ Given a proposal entry `proposals.knowledge.self.patch` proposing a change to `k
|
|
|
1018
1006
|
|
|
1019
1007
|
## 13.1 Layered architecture (internal)
|
|
1020
1008
|
|
|
1021
|
-
Textus internals are organized
|
|
1009
|
+
Textus internals are organized as one-way layers — **Surfaces** (`surfaces/cli/`, `surfaces/mcp/`) → **Contract** (`contract/`) → **Dispatch** (`dispatch/`) → **Manifest + Core + Ports + Step** (domain and adapters). Each layer imports only from layers to its right. Plugin authors touch only the Step DSL and the manifest YAML; the layering is internal and may evolve.
|
|
1022
1010
|
|
|
1023
1011
|
See [`docs/architecture/README.md`](docs/architecture/README.md) for an ASCII diagram and the full read-path walkthrough.
|
|
1024
1012
|
|
|
@@ -1050,116 +1038,3 @@ A `textus/3` implementation MAY:
|
|
|
1050
1038
|
- Provide alternate output formats (`--output=yaml`, `--output=table`) for human use.
|
|
1051
1039
|
- Support additional schema field types beyond §6, marked as `vendor:<name>` extensions.
|
|
1052
1040
|
|
|
1053
|
-
## 16. Migrating from textus/2
|
|
1054
|
-
|
|
1055
|
-
textus does not ship a built-in textus/2 → textus/3 migrator. The historical upgrade path (via the one-shot `textus migrate` in the 0.11.x line) is recorded in `CHANGELOG.md` §0.11.0. `textus doctor` refuses a store still declaring `version: textus/2`. The textus/2 → textus/3 rename table is kept below for reference.
|
|
1056
|
-
|
|
1057
|
-
**Vocabulary summary** (textus/2 → textus/3 rename table, for reference):
|
|
1058
|
-
|
|
1059
|
-
| Category | textus/2 | textus/3 (current) |
|
|
1060
|
-
|---|---|---|
|
|
1061
|
-
| Actor | `ai` | `agent` |
|
|
1062
|
-
| Actor | `script` | `automation` |
|
|
1063
|
-
| Actor | `build` | `automation` |
|
|
1064
|
-
| Zone | `inbox` | `intake` |
|
|
1065
|
-
| Manifest | `writable_by:` | (removed — authority is derived from `kind:` × `can:`) |
|
|
1066
|
-
| Manifest | `policies:` | `rules:` |
|
|
1067
|
-
| Manifest | `handler_allowlist:` | `intake_handler_allowlist:` |
|
|
1068
|
-
| Manifest | `promote_requires:` | `guard: { accept: [...] }` |
|
|
1069
|
-
| Manifest | `projection:` | `source: { from: project, select: ..., ... }` (flat fields) |
|
|
1070
|
-
| Manifest | `generator:` | `source: { from: command, ... }` |
|
|
1071
|
-
| Hook event | `:intake` | `:resolve_handler` |
|
|
1072
|
-
| Hook event | `:reduce` | `:transform_rows` |
|
|
1073
|
-
| Hook event | `:check` | `:validate` |
|
|
1074
|
-
| Hook event | `:put` | `:entry_written` |
|
|
1075
|
-
| Hook event | `:deleted` | `:entry_deleted` |
|
|
1076
|
-
| Hook event | `:refreshed` | `:entry_fetched` |
|
|
1077
|
-
| Hook event | `:built` | `:entry_produced` |
|
|
1078
|
-
| Hook event | `:accepted` | `:proposal_accepted` |
|
|
1079
|
-
| Hook event | `:reject` | `:proposal_rejected` |
|
|
1080
|
-
| Hook event | `:published` | `:entry_published` |
|
|
1081
|
-
| Hook event | `:mv` | `:entry_renamed` |
|
|
1082
|
-
| Hook event | `:loaded` | `:store_loaded` |
|
|
1083
|
-
| Hook event | `:refresh_began` | `:entry_fetch_started` |
|
|
1084
|
-
| Hook event | `:refresh_detached` | `:fetch_backgrounded` |
|
|
1085
|
-
| Hook event | `:refresh_failed` | `:entry_fetch_failed` |
|
|
1086
|
-
| Hook DSL | `Textus.hook(ev, name)` / sugar | `Textus.on(ev, name)` |
|
|
1087
|
-
| Source field | `projection.reduce:` | `source.transform:` |
|
|
1088
|
-
| `_meta` key | `reducer` | `transform` |
|
|
1089
|
-
| CLI flag | `--format=json` (envelope) | `--output=json` |
|
|
1090
|
-
| CLI verb | `refresh-stale` | `fetch all` |
|
|
1091
|
-
| CLI verb | `policy list/explain` | `rule list/explain` |
|
|
1092
|
-
|
|
1093
|
-
**Hook migration.** Legacy event names / DSL methods must be renamed to the textus/3 forms above before a hook will load; see `CHANGELOG.md` §0.11.0 for the full event-rename detail.
|
|
1094
|
-
|
|
1095
|
-
### 16.1 Breaking changes in 0.31.0 (capability-based roles)
|
|
1096
|
-
|
|
1097
|
-
0.31.0 replaced declared per-zone write policies with **derived** authority and renamed the `refresh` transition to `fetch`. These keys/values are no longer accepted:
|
|
1098
|
-
|
|
1099
|
-
| Removed / renamed (≤ 0.30) | 0.31.0 form |
|
|
1100
|
-
|---|---|
|
|
1101
|
-
| `zones[*].write_policy:` | (removed) authority is derived: `role.can ⊇ { verb_for(zone.kind) }` |
|
|
1102
|
-
| `roles[*].kind:` (`accept_authority`/`generator`/`proposer`/`runner`) | `roles[*].can:` (subset of `propose`, `author`, `fetch`, `build`) |
|
|
1103
|
-
| Actors `runner`, `builder` | `automation` (`can: [fetch, build]`) by default |
|
|
1104
|
-
| `rules[*].refresh:` slot | `rules[*].fetch:` slot |
|
|
1105
|
-
| CLI `textus refresh` / `refresh stale` | `textus fetch` / `fetch all` |
|
|
1106
|
-
| `_meta.last_refreshed_at` | `_meta.last_fetched_at` |
|
|
1107
|
-
| Promotion predicate `:human_accept` / `:accept_authority_signed` | `:author_signed` |
|
|
1108
|
-
| Envelope `refreshing` | `fetching` |
|
|
1109
|
-
|
|
1110
|
-
A manifest still declaring `write_policy:` or a role `kind:` is rejected at load. There is no compatibility alias — the breaking change requires a new wire-compatible manifest. (Wire string `textus/3` is unchanged: capabilities are a manifest concept and never appear on the wire.)
|
|
1111
|
-
|
|
1112
|
-
### 16.2 Breaking changes in 0.33.0 (workspace/keep + Setup-1 scaffold)
|
|
1113
|
-
|
|
1114
|
-
0.33.0 adds the fifth coordination primitive (`workspace` zone-kind + `keep` capability), renames the capability `accept` → `author` (and predicate `accept_signed` → `author_signed`), renames zone-kind `origin` → `canon`, and renames the default scaffold zones to the Setup-1 names. These changes affect **manifest files and tooling** only — the `textus/3` wire format is **UNCHANGED** (envelope shape, audit-log schema, key grammar, and the `version: textus/3` field are all identical to 0.32.x).
|
|
1115
|
-
|
|
1116
|
-
**Renames (manifest and predicate vocabulary):**
|
|
1117
|
-
|
|
1118
|
-
| Removed / renamed (≤ 0.32) | 0.33.0 form |
|
|
1119
|
-
|---|---|
|
|
1120
|
-
| Zone-kind `origin` | `canon` |
|
|
1121
|
-
| Capability `accept` | `author` |
|
|
1122
|
-
| Promotion predicate `accept_signed` | `author_signed` |
|
|
1123
|
-
| Default scaffold zone `identity` | `knowledge` (identity keys live under `knowledge.identity.*`) |
|
|
1124
|
-
| Default scaffold zone `working` | `knowledge` (merged into the same `canon` zone) |
|
|
1125
|
-
| Default scaffold zone `intake` | `feeds` |
|
|
1126
|
-
| Default scaffold zone `review` | `proposals` |
|
|
1127
|
-
| Default scaffold zone `output` | `artifacts` |
|
|
1128
|
-
|
|
1129
|
-
**New in 0.33.0:**
|
|
1130
|
-
|
|
1131
|
-
| Addition | Detail |
|
|
1132
|
-
|---|---|
|
|
1133
|
-
| Zone-kind `workspace` | Agent's own durable lane. Required capability: `keep`. Bytes never auto-promote; climb to `canon` only via propose→accept. |
|
|
1134
|
-
| Capability `keep` | Authorizes writes to `workspace` zones. Default scaffold: `agent` holds `[propose, keep]`. |
|
|
1135
|
-
| Default scaffold zone `notebook` | `kind: workspace`, default owner `agent`. |
|
|
1136
|
-
| `owner:` on a zone | OPTIONAL, INFORMATIONAL — not enforced in 0.33.0 (owner-scoped enforcement is deferred). |
|
|
1137
|
-
| `desc:` on a zone | OPTIONAL — surfaces as the `purpose` field in `textus boot` zone rows. |
|
|
1138
|
-
|
|
1139
|
-
**Clarification (not a breaking change):** `accept` and `reject` are **transition verbs** (CLI commands), not capabilities. Both require the `author` capability. This has always been true; 0.33.0 makes it explicit by removing `accept` from the capability vocabulary.
|
|
1140
|
-
|
|
1141
|
-
A manifest declaring `kind: origin` or capability `accept` (in a `can:` list) is rejected at load.
|
|
1142
|
-
|
|
1143
|
-
### 16.3 Breaking changes in 0.35.0 (proposal target-canon + `author_held`)
|
|
1144
|
-
|
|
1145
|
-
0.35.0 constrains a proposal's target to a `canon` zone and renames the anchor-gate predicate. No `textus/3` wire change; no manifest-schema change.
|
|
1146
|
-
|
|
1147
|
-
**Renames (predicate vocabulary):**
|
|
1148
|
-
|
|
1149
|
-
| Removed / renamed (≤ 0.34) | 0.35.0 form |
|
|
1150
|
-
|---|---|
|
|
1151
|
-
| Promotion predicate `author_signed` | `author_held` |
|
|
1152
|
-
|
|
1153
|
-
**New in 0.35.0:**
|
|
1154
|
-
|
|
1155
|
-
| Addition | Detail |
|
|
1156
|
-
|---|---|
|
|
1157
|
-
| Floor predicate `target_is_canon` | On the `accept` base guard. A proposal's `target_key` MUST resolve to a `canon` zone; `accept` refuses any other target with `guard_failed` naming `target_is_canon`. Floor-only — not relaxable via `rules[].guard`. |
|
|
1158
|
-
| `doctor` check `proposal_targets` | Warns on queued proposals whose `target_key` is non-canon (`proposal.target_not_canon`) or unresolvable (`proposal.target_unresolved`). |
|
|
1159
|
-
|
|
1160
|
-
A `rules[].guard` block referencing the predicate by its old name `author_signed` is rejected at load (unknown predicate).
|
|
1161
|
-
|
|
1162
|
-
---
|
|
1163
|
-
|
|
1164
|
-
**Spec word count target:** <2700 words (allowance widened to fit vocabulary axes intro + migration section).
|
|
1165
|
-
**Reviewed against community-testing checklist (idea file §"Community-testing"):** ✅ implementable in a day in TS/Python (four concepts: manifest, schema, envelope, staleness check); ✅ conformance fixtures A–I; ✅ "Why not X?" section present (incl. why no execution); ✅ name picked.
|