textus 0.43.0 → 0.43.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c2c92f27b720b90f0dfa268174cf9d9ea775b37f92bb38ed90cd9719d7b2626
4
- data.tar.gz: f5018ecfd8d72666d48236d42395e44819766659088bc98b1c69ad4fccf2fce8
3
+ metadata.gz: 2c26661afb6c59f813ccdb737e56666be242a14e3315a100348dd8514a41e78a
4
+ data.tar.gz: 26ff3e7e8cd94a545cdcabe964c6e8c7311fe0179a24a13b0ab298eab7f2bf34
5
5
  SHA512:
6
- metadata.gz: 758b3a9da98edb000100e9b6d1665829d94333f53e38f3a68104d33a4aefd831947e235ecb3048e16b388e0256239dfe6f66708d1253b7bfcbb56360a74350bc
7
- data.tar.gz: d72be31709261f1aff37f00a9b213e03d8121758f61e781bfdd1626eb16122d994d64491f4c37be4c851031e8bd122d9df8d8abd682228e2693162d3861c865d
6
+ metadata.gz: db6b8047ba7d7c79ad78a653944709d7cc7cfd5c22a4baae116e97c8d6fea48c409788c835e1c1f83c7684a58da089b08a0525a44791de92875d1d2bb0c26a76
7
+ data.tar.gz: 26e9d74398110f4d34dd59c837901170e5d80a847268f5edee45010f4793c51e1f47737d5d3c85b0098fa06b1334e00bf1038330a12a1061c9957d045e07638c
data/CHANGELOG.md CHANGED
@@ -9,6 +9,22 @@ 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.43.1 — 2026-06-02 — `boot` agent surface derives from the MCP catalog ([ADR 0056](docs/architecture/decisions/0056-boot-quickstart-speaks-the-mcp-catalog.md))
13
+
14
+ No `textus/3` wire-format change — `boot`'s agent-orientation fields are corrected to match the verbs an MCP agent can actually call.
15
+
16
+ ### Fixed
17
+
18
+ - **`boot.agent_quickstart.read_verbs` had drifted from the MCP catalog ([ADR 0056](docs/architecture/decisions/0056-boot-quickstart-speaks-the-mcp-catalog.md)).** It was a hand-maintained list that advertised CLI-only read verbs an MCP agent cannot call (`audit`, `freshness`, `doctor`) while omitting the ones it can and needs (`schema`, `rules`). It now **derives** from `MCP::Catalog` (`get list pulse schema boot rules`) and is reconciled by a guard spec, so it can no longer advertise an uncallable verb or omit a callable one. This is what led downstream skills to shell out to a CLI for schema discovery instead of calling the `schema` verb.
19
+
20
+ ### Changed
21
+
22
+ - **`boot.agent_protocol.recipes` reference verbs, not CLI strings ([ADR 0056](docs/architecture/decisions/0056-boot-quickstart-speaks-the-mcp-catalog.md)).** Each recipe step names a verb (`get KEY`, `schema KEY`, `put KEY`, `propose KEY`, `fetch_all`) or a plain build step, instead of a `textus …` / `echo … | textus …` shell line — each transport frames the verb itself (CLI vs MCP tool). Keeps shell syntax out of the surface an MCP agent reads. `human_steps` still name `accept` (the author-only transition, not an MCP tool, by design).
23
+
24
+ ### Added
25
+
26
+ - **ADR 0054 (Proposed, no implementation): entry-level `desc`.** Records the decision to add an optional one-line `desc:` to manifest entries — surfaced in `boot.entries`/`list` — turning the manifest into a navigable index so an agent finds the right data without a caller hardcoding the key. Pre-registers ADR 0055 (a `find`/search verb) as the evidence-triggered follow-up. Documentation only in this release.
27
+
12
28
  ## 0.43.0 — 2026-06-02 — Typed `publish:` block + remove `index_filename` ([ADR 0052](docs/architecture/decisions/0052-typed-publish-block.md), [0053](docs/architecture/decisions/0053-remove-index-filename.md))
13
29
 
14
30
  No `textus/3` wire-format change. Two breaking (pre-1.0) changes on the publish/enumeration surface: the two top-level publish keys become one typed `publish:` block, and the unused `index_filename:` enumeration feature is removed. Both fail at load with a migration-pointing message.
data/SPEC.md CHANGED
@@ -197,14 +197,14 @@ entries:
197
197
  path: knowledge/network/org
198
198
  zone: knowledge
199
199
  schema: person
200
- owner: textus:network
200
+ owner: human:network
201
201
  nested: true
202
202
 
203
203
  - key: artifacts.catalogs.people
204
204
  path: artifacts/catalogs/people.md
205
205
  zone: artifacts
206
206
  schema: null
207
- owner: textus:build
207
+ owner: automation:build
208
208
 
209
209
  rules:
210
210
  - match: feeds.**
@@ -404,7 +404,7 @@ A derived entry that is produced by a build tool *outside* textus — `rake`, `j
404
404
  - key: output.catalogs.skills
405
405
  path: output/catalogs/skills.md
406
406
  zone: output
407
- owner: build:catalog-skills
407
+ owner: automation:catalog-skills
408
408
  compute:
409
409
  kind: external
410
410
  command: "rake catalog:skills" # informational; external automation invokes it
@@ -800,7 +800,7 @@ Every successful CLI response (`--output=json`) is a single JSON envelope:
800
800
  "protocol": "textus/3",
801
801
  "key": "knowledge.network.org.jane",
802
802
  "zone": "knowledge",
803
- "owner": "textus:network",
803
+ "owner": "human:network",
804
804
  "path": "/absolute/path/to/.textus/zones/knowledge/network/org/jane.md",
805
805
  "format": "markdown",
806
806
  "_meta": { "name": "jane", "relationship": "peer", "org": "acme" },
@@ -898,7 +898,7 @@ All verbs accept `--output=json` and emit a canonical envelope (success or error
898
898
  ```json
899
899
  {
900
900
  "agent_quickstart": {
901
- "read_verbs": ["boot", "get", "list", "audit", "pulse", "freshness", "doctor"],
901
+ "read_verbs": ["get", "list", "pulse", "schema", "boot", "rules"],
902
902
  "write_verbs": ["put KEY --as=agent --stdin"],
903
903
  "writable_zones": ["proposals"],
904
904
  "propose_zone": "proposals",
@@ -907,6 +907,8 @@ All verbs accept `--output=json` and emit a canonical envelope (success or error
907
907
  }
908
908
  ```
909
909
 
910
+ `read_verbs` is derived from the MCP verb catalog — the verbs the agent can actually call over its transport — so it lists the read/discovery verbs (`schema` for an entry's field shape, `rules` for its freshness/guard policy) and never the CLI-only `audit`/`freshness`/`doctor` (ADR 0056). An agent learns an entry's `_meta` shape by calling the `schema` verb before a `put`/`propose`, not by shelling out to a CLI.
911
+
910
912
  `latest_seq` is the current high-water mark of the audit log; agents should use it as the starting cursor for `pulse`.
911
913
 
912
914
  **`textus pulse` output shape:**
@@ -1,7 +1,7 @@
1
1
  # Textus architecture
2
2
 
3
3
  > **Explanation** · for contributors · **read this first** for orientation before SPEC
4
- > **SSoT for** the Ruby implementation layout (layers, container, ports, read/write/fetch paths) · **reviewed** 2026-05 (v0.35)
4
+ > **SSoT for** the Ruby implementation layout (layers, container, ports, read/write/fetch paths) · **reviewed** 2026-06 (v0.43)
5
5
 
6
6
  ```mermaid
7
7
  flowchart TD
@@ -1,7 +1,7 @@
1
1
  # Conventions
2
2
 
3
3
  > **Reference** · for integrators · **read when** you're shaping a `.textus/` tree and want the idiomatic choices
4
- > **SSoT for** idiomatic key naming, schema design, and automation integration · **reviewed** 2026-05 (v0.35)
4
+ > **SSoT for** idiomatic key naming, schema design, and automation integration · **reviewed** 2026-06 (v0.43)
5
5
 
6
6
  Guidelines for shaping a `.textus/` tree, naming keys, organising schemas, and integrating with build automation. The spec ([`../../SPEC.md`](../../SPEC.md)) defines what's enforceable; this document captures what's *idiomatic*.
7
7
 
@@ -39,11 +39,11 @@ Inside `knowledge/`, group by **domain** (identity, people, projects, decisions,
39
39
 
40
40
  ## Owner strings
41
41
 
42
- The `owner:` field in the manifest is **advisory metadata**, not an ACL. Use it to label *who's expected to write here*:
42
+ The `owner:` field in the manifest is **advisory metadata**, not an ACL. Use it to label *who's expected to write here*. The form is `<archetype>` or `<archetype>:<subject>`; the archetype must be one of `human`, `agent`, `automation` (validated at load — ADR 0045), and the subject is free-form:
43
43
 
44
- - `textus:network` — humans curate
44
+ - `human:network` — humans curate
45
45
  - `agent:planner` — a specific named agent
46
- - `build:catalog-skills` — a specific build job
46
+ - `automation:catalog-skills` — a specific build job
47
47
 
48
48
  Tooling around `git blame` or audit logs may filter on owner; the gem itself only echoes it back in envelopes.
49
49
 
@@ -58,7 +58,7 @@ A derived entry declares a `compute:` block with a `kind:` discriminator. Two ki
58
58
  path: artifacts/catalogs/people.md
59
59
  zone: artifacts
60
60
  schema: null
61
- owner: build:catalog-people
61
+ owner: automation:catalog-people
62
62
  compute:
63
63
  kind: projection
64
64
  select: knowledge.network.org # prefix or list of prefixes
@@ -75,7 +75,7 @@ A derived entry declares a `compute:` block with a `kind:` discriminator. Two ki
75
75
  - key: artifacts.catalogs.skills
76
76
  path: artifacts/catalogs/skills.md
77
77
  zone: artifacts
78
- owner: build:catalog-skills
78
+ owner: automation:catalog-skills
79
79
  compute:
80
80
  kind: external
81
81
  command: "rake catalog:skills" # informational; the automation invokes it
@@ -106,7 +106,7 @@ rules:
106
106
  A typical scheduled-fetch integration shells the `fetch stale` sweep itself:
107
107
 
108
108
  ```sh
109
- textus fetch stale --zone=intake --as=automation # in cron / CI
109
+ textus fetch stale --zone=feeds --as=automation # in cron / CI
110
110
  ```
111
111
 
112
112
  See [`./zones.md` §6](zones.md) for the full intake contract and [`../how-to/writing-hooks.md`](../how-to/writing-hooks.md) for writing custom handlers.
@@ -137,7 +137,7 @@ For multi-writer environments, **always pass `if_etag`** on `put`. The gem treat
137
137
  The application layer is organised around three shapes — `Manifest` as a composition record, a single `Container` capability record handed to every use case, and a split envelope reader/writer. See [ADR 0018](../architecture/decisions/0018-manifest-carving.md), [ADR 0017](../architecture/decisions/0017-envelope-io-split.md), [ADR 0022](../architecture/decisions/0022-container-call-dispatcher.md), and [ADR 0023](../architecture/decisions/0023-uniform-use-case-shape.md).
138
138
 
139
139
  - **`Manifest` is a composition record** (`Data.define(:data, :resolver, :policy, :rules)`). Reach individual concerns through the field accessors: `manifest.data.entries`, `manifest.policy.permission_for(zone)`, `manifest.resolver.resolve(key)`, `manifest.rules.for(key)`.
140
- - **Use cases are plain `(container:, call:)` classes.** Each is a single class under `lib/textus/{read,write,maintenance}/` with `def initialize(container:, call:)` and a `#call(...)` method; verbs are looked up in the static `Textus::Dispatcher::VERBS` table. `Container` is a `Data.define` record bundling the wired ports + manifest (`manifest`, `file_store`, `schemas`, `root`, `audit_log`, `events`, `rpc`, `authorizer`); `Call` is the immutable per-invocation value (`role`, `correlation_id`, `now`, `dry_run`). A use case that emits events derives its `Hooks::Context` from `(container, call)` — nothing is injected. Use cases pull only what they need into ivars; nobody passes the raw `Store` around the application layer. `store.as(role)` returns a `RoleScope` that forwards verbs to the dispatcher.
140
+ - **Use cases are plain `(container:, call:)` classes.** Each is a single class under `lib/textus/{read,write,maintenance}/` with `def initialize(container:, call:)` and a `#call(...)` method; verbs are looked up in the static `Textus::Dispatcher::VERBS` table. `Container` is a `Data.define` record bundling the wired ports + manifest (`manifest`, `file_store`, `schemas`, `root`, `audit_log`, `events`, `rpc`); `Call` is the immutable per-invocation value (`role`, `correlation_id`, `now`, `dry_run`). A use case that emits events derives its `Hooks::Context` from `(container, call)` — nothing is injected. Use cases pull only what they need into ivars; nobody passes the raw `Store` around the application layer. `store.as(role)` returns a `RoleScope` that forwards verbs to the dispatcher.
141
141
  - **Write path is split**: `Envelope::IO::Reader` owns read/parse (existing-uid lookup, raw read, parse), and `Envelope::IO::Writer` owns put/delete/move + the audit-append invariant (every public method's final action is `@audit_log.append(...)`).
142
142
 
143
143
  The user-facing CLI surface, the wire envelope shape, and the protocol version (`textus/3`) are unchanged.
data/lib/textus/boot.rb CHANGED
@@ -127,7 +127,10 @@ module Textus
127
127
  propose_zone = manifest.policy.propose_zone_for(agent_role)
128
128
 
129
129
  {
130
- "read_verbs" => %w[boot get list audit pulse freshness doctor],
130
+ # Derived from the MCP catalog (ADR 0056): the agent's real read surface,
131
+ # so the quickstart can neither advertise a verb the agent cannot call
132
+ # (audit/freshness/doctor are CLI-only) nor omit one it can (schema/rules).
133
+ "read_verbs" => Textus::MCP::Catalog.read_verbs,
131
134
  "write_verbs" => agent_role ? ["put KEY --as=#{agent_role} --stdin"] : [],
132
135
  "writable_zones" => writable_zones,
133
136
  "propose_zone" => propose_zone,
@@ -135,6 +138,10 @@ module Textus
135
138
  }
136
139
  end
137
140
 
141
+ # Recipes reference verbs, not a transport's CLI strings (ADR 0056): every
142
+ # step names a verb the agent can call (each transport frames it — CLI as
143
+ # `textus get KEY`, MCP as the `get` tool) or is a plain build step. This
144
+ # keeps shell lines out of the surface an MCP agent reads.
138
145
  def self.recipes(manifest)
139
146
  queue = manifest.policy.queue_zone
140
147
  feeds = zone_label(manifest, :quarantine, "the quarantine zone")
@@ -142,32 +149,32 @@ module Textus
142
149
  "read" => {
143
150
  "purpose" => "find and read an entry",
144
151
  "steps" => [
145
- "textus list --zone=ZONE --prefix=PREFIX # discover keys",
146
- "textus get KEY # returns envelope JSON",
152
+ "list (zone:, prefix:) discover keys without reading bodies",
153
+ "get KEY returns the entry envelope",
147
154
  ],
148
155
  },
149
156
  "write" => {
150
157
  "purpose" => "create or update an entry",
151
158
  "steps" => [
152
- "textus schema get FAMILY # learn the _meta field shape",
153
- "build an envelope JSON: {_meta: {...}, body: \"...\"}",
154
- "echo ENVELOPE | textus put KEY --as=ROLE --stdin",
159
+ "schema KEY learn the _meta field shape (required, optional, field types) before writing",
160
+ "assemble an envelope: { _meta: {}, body: \"…\" }",
161
+ "put KEY persist it (role-gated); pass if_etag to guard a concurrent edit",
155
162
  ],
156
163
  },
157
164
  "propose" => {
158
165
  "purpose" => "agent suggests a change for human review",
159
166
  "agent_steps" => [
160
- "echo ENVELOPE | textus put #{queue}.KEY --as=agent --stdin",
167
+ "propose KEY writes the change into the #{queue} zone for review",
161
168
  ],
162
169
  "human_steps" => [
163
- "textus accept #{queue}.KEY --as=human # promotes the proposal to its target zone",
170
+ "accept #{queue}.KEY promotes the proposal into its target zone",
164
171
  ],
165
172
  },
166
173
  "fetch" => {
167
- "purpose" => "rebuild stale quarantine-zone caches from their declared actions",
174
+ "purpose" => "refresh stale quarantine-zone caches from their declared intake",
168
175
  "steps" => [
169
- "textus freshness --zone=#{feeds} # report fresh/stale per entry",
170
- "textus fetch stale --zone=#{feeds} --as=automation",
176
+ "pulse its `stale` list names entries past their ttl",
177
+ "fetch_all (zone: #{feeds}) — re-pull the stale entries",
171
178
  ],
172
179
  },
173
180
  }
@@ -11,7 +11,7 @@ module Textus
11
11
  # Contracts of every MCP-surfaced verb, in Dispatcher order.
12
12
  def specs
13
13
  Textus::Dispatcher::VERBS.values
14
- .select { |k| k.respond_to?(:contract?) && k.contract? && k.contract.mcp? }
14
+ .select { |k| mcp_surfaced?(k) }
15
15
  .map(&:contract)
16
16
  end
17
17
 
@@ -25,9 +25,23 @@ module Textus
25
25
  specs.map { |s| s.verb.to_s }
26
26
  end
27
27
 
28
+ # MCP-surfaced read verbs, by Dispatcher class namespace — the agent's
29
+ # real read/discovery surface. `boot.agent_quickstart.read_verbs` derives
30
+ # from this so it can never advertise a verb the agent cannot call, nor
31
+ # omit one it can (ADR 0056). Excludes Write/Maintenance.
32
+ def read_verbs
33
+ Textus::Dispatcher::VERBS
34
+ .select { |_verb, klass| mcp_surfaced?(klass) && klass.name.start_with?("Textus::Read::") }
35
+ .keys.map(&:to_s)
36
+ end
37
+
38
+ def mcp_surfaced?(klass)
39
+ klass.respond_to?(:contract?) && klass.contract? && klass.contract.mcp?
40
+ end
41
+
28
42
  def call(name, session:, store:, args:)
29
43
  klass = Textus::Dispatcher::VERBS[name.to_sym]
30
- raise ToolError.new("unknown tool: #{name}") unless klass.respond_to?(:contract?) && klass.contract? && klass.contract.mcp?
44
+ raise ToolError.new("unknown tool: #{name}") unless klass && mcp_surfaced?(klass)
31
45
 
32
46
  spec = klass.contract
33
47
  pos, kw = map_args(spec, args || {}, session)
@@ -1,4 +1,4 @@
1
1
  module Textus
2
- VERSION = "0.43.0"
2
+ VERSION = "0.43.1"
3
3
  PROTOCOL = "textus/3"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: textus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.43.0
4
+ version: 0.43.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick