@bradheitmann/odin-sentinel 0.4.13 → 0.5.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.
Files changed (89) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +24 -17
  3. package/dist/src/harness-pacing/index.d.ts +10 -0
  4. package/dist/src/harness-pacing/index.js +11 -0
  5. package/dist/src/harness-pacing/index.js.map +1 -0
  6. package/dist/src/harness-pacing/recommend.d.ts +28 -0
  7. package/dist/src/harness-pacing/recommend.js +74 -0
  8. package/dist/src/harness-pacing/recommend.js.map +1 -0
  9. package/dist/src/harness-pacing/schema.d.ts +28 -0
  10. package/dist/src/harness-pacing/schema.js +2 -0
  11. package/dist/src/harness-pacing/schema.js.map +1 -0
  12. package/dist/src/harness-pacing/storage.d.ts +32 -0
  13. package/dist/src/harness-pacing/storage.js +74 -0
  14. package/dist/src/harness-pacing/storage.js.map +1 -0
  15. package/dist/src/mcp/server.js +29 -2
  16. package/dist/src/mcp/server.js.map +1 -1
  17. package/dist/src/odin-watch/backends/cmux.d.ts +6 -0
  18. package/dist/src/odin-watch/backends/cmux.js +39 -0
  19. package/dist/src/odin-watch/backends/cmux.js.map +1 -0
  20. package/dist/src/odin-watch/backends/tmux.d.ts +6 -0
  21. package/dist/src/odin-watch/backends/tmux.js +40 -0
  22. package/dist/src/odin-watch/backends/tmux.js.map +1 -0
  23. package/dist/src/odin-watch/classifier.d.ts +27 -0
  24. package/dist/src/odin-watch/classifier.js +182 -0
  25. package/dist/src/odin-watch/classifier.js.map +1 -0
  26. package/dist/src/odin-watch/index.d.ts +2 -0
  27. package/dist/src/odin-watch/index.js +200 -0
  28. package/dist/src/odin-watch/index.js.map +1 -0
  29. package/dist/src/odin-watch/snapshotter.d.ts +11 -0
  30. package/dist/src/odin-watch/snapshotter.js +2 -0
  31. package/dist/src/odin-watch/snapshotter.js.map +1 -0
  32. package/dist/src/odin-watch/writers.d.ts +8 -0
  33. package/dist/src/odin-watch/writers.js +27 -0
  34. package/dist/src/odin-watch/writers.js.map +1 -0
  35. package/dist/src/protocol/index.d.ts +3 -1
  36. package/dist/src/protocol/index.js +4 -1
  37. package/dist/src/protocol/index.js.map +1 -1
  38. package/dist/src/protocol/repository.d.ts +14 -0
  39. package/dist/src/protocol/repository.js +25 -1
  40. package/dist/src/protocol/repository.js.map +1 -1
  41. package/dist/src/protocol/schemas.d.ts +144 -0
  42. package/dist/src/protocol/schemas.js +23 -0
  43. package/dist/src/protocol/schemas.js.map +1 -1
  44. package/dist/src/protocol/service.d.ts +19 -2
  45. package/dist/src/protocol/service.js +89 -2
  46. package/dist/src/protocol/service.js.map +1 -1
  47. package/dist/src/protocol/surface-layout.d.ts +20 -0
  48. package/dist/src/protocol/surface-layout.js +20 -0
  49. package/dist/src/protocol/surface-layout.js.map +1 -1
  50. package/dist/src/protocol/version.d.ts +2 -2
  51. package/dist/src/protocol/version.js +2 -2
  52. package/dist/src/protocol/version.js.map +1 -1
  53. package/dist/src/utils/execFileNoThrow.d.ts +5 -0
  54. package/dist/src/utils/execFileNoThrow.js +18 -0
  55. package/dist/src/utils/execFileNoThrow.js.map +1 -0
  56. package/docs/adapters/cmux-adapter.md +168 -0
  57. package/docs/adapters/herdr-adapter.md +150 -0
  58. package/docs/adapters/minimux-adapter.md +152 -0
  59. package/docs/adapters/plain-terminal.md +80 -0
  60. package/docs/adapters/tmux-adapter.md +150 -0
  61. package/docs/guides/quick-start.md +7 -7
  62. package/docs/guides/quickstart-prompts.md +4 -4
  63. package/docs/lattice/odin-lattice-design.md +555 -0
  64. package/docs/reference/distribution.md +5 -5
  65. package/docs/reference/public-surface-audit.md +3 -3
  66. package/package.json +7 -5
  67. package/plugins/odin-scp/.claude-plugin/plugin.json +2 -2
  68. package/plugins/odin-scp/README.md +6 -6
  69. package/plugins/odin-scp/skills/odin-scp/CHANGELOG.md +12 -0
  70. package/plugins/odin-scp/skills/odin-scp/SKILL.md +196 -3
  71. package/protocol/SCP.md +2 -2
  72. package/protocol/bootstrap-skill.md +196 -3
  73. package/protocol/closeout.yaml +1 -1
  74. package/protocol/delegation.yaml +1 -1
  75. package/protocol/mission-frontrun/droids-scrutiny-feature-reviewer.md +70 -0
  76. package/protocol/mission-frontrun/orchestrator-contract.md +70 -0
  77. package/protocol/mission-frontrun/scrutiny-feature-reviewer-contract.md +73 -0
  78. package/protocol/mission-frontrun/scrutiny-validator-contract.md +77 -0
  79. package/protocol/mission-frontrun/worker-contract.md +66 -0
  80. package/protocol/model-profiles.yaml +8 -1
  81. package/protocol/receipts/boot-receipt.yaml +13 -0
  82. package/protocol/role-cards/dev-worker.md +74 -0
  83. package/protocol/role-cards/exec-asst.md +83 -0
  84. package/protocol/role-cards/exec-pm.md +66 -0
  85. package/protocol/role-cards/qa-worker.md +71 -0
  86. package/protocol/role-cards/team-pm.md +67 -0
  87. package/protocol/roles.yaml +1 -1
  88. package/protocol/topology.yaml +1 -1
  89. package/scripts/audit/verify-pack.mjs +17 -1
@@ -0,0 +1,555 @@
1
+ # ODIN Lattice Design
2
+
3
+ **Status:** Design-only — no implementation code. This document describes an
4
+ optional future sidecar. The core `@bradheitmann/odin-sentinel` package has zero
5
+ imports of the Lattice and continues to operate without it.
6
+
7
+ **Last revised:** 2026-06-12
8
+
9
+ ---
10
+
11
+ ## Table of Contents
12
+
13
+ 1. [Overview and Positioning](#1-overview-and-positioning)
14
+ 2. [SpacetimeDB v2.5 Design Constraints](#2-spacetimedb-v25-design-constraints)
15
+ 3. [Table Designs](#3-table-designs)
16
+ 4. [Reducer Semantics](#4-reducer-semantics)
17
+ 5. [RLS Read Rules](#5-rls-read-rules)
18
+ 6. [Sequencing and Migration Path](#6-sequencing-and-migration-path)
19
+ 7. [License Obligations](#7-license-obligations)
20
+ 8. [Go/No-Go Criteria Table](#8-gono-go-criteria-table)
21
+ 9. [Package Structure](#9-package-structure)
22
+
23
+ ---
24
+
25
+ ## 1. Overview and Positioning
26
+
27
+ ODIN Lattice is an **optional** SpacetimeDB-backed sidecar that provides two
28
+ capabilities ODIN Sentinel teams cannot get from the file-based v0 data layer:
29
+
30
+ - **Push-based team state visibility.** Instead of PM roles polling file flags
31
+ at a fixed cadence, subscriptions deliver state changes the moment a reducer
32
+ commits them. This eliminates idle-supervision tokens entirely for teams whose
33
+ members have a Lattice connection.
34
+
35
+ - **Bounded collective memory.** A two-table entity/edge model lets agents
36
+ deposit named facts and relationships that survive individual session resets.
37
+ Traversal depth is intentionally capped at one to two hops (see
38
+ [Section 2](#2-spacetimedb-v25-design-constraints) for why deeper traversal
39
+ is out of scope).
40
+
41
+ ### What the Lattice is not
42
+
43
+ The Lattice is not a replacement for the protocol. Boot receipts, delivery
44
+ proofs, and state transitions remain the source of truth; the Lattice is a
45
+ *transport and store* for that same data, not a parallel governance layer.
46
+
47
+ The Lattice is not a dependency of `@bradheitmann/odin-sentinel`. A team that
48
+ never runs a SpacetimeDB module operates identically to a team that does — the
49
+ only difference is whether state changes are pulled (file flags) or pushed
50
+ (WebSocket subscriptions).
51
+
52
+ The Lattice is not a deep knowledge graph. SpacetimeDB v2.5 does not support
53
+ recursive server-side queries. Deep traversal is explicitly out of scope; the
54
+ bounded entity/edge model is the correct design for this constraint.
55
+
56
+ ### Relationship to the v0 data layer
57
+
58
+ The v0 data layer consists of the wake-flag files and compact state ledger files
59
+ produced by `odin-watch`. These files are the deployed reality as of mid-2026.
60
+ Adopting the Lattice is a *transport change* — the same record shapes flow into
61
+ SpacetimeDB rows instead of local files. No schema transformation is required.
62
+
63
+ ---
64
+
65
+ ## 2. SpacetimeDB v2.5 Design Constraints
66
+
67
+ Every design choice in Sections 3–9 is bounded by the following platform facts
68
+ verified as of June 2026.
69
+
70
+ | Constraint | Value / Implication |
71
+ |---|---|
72
+ | Version | v2.5 (stable release line, June 2026) |
73
+ | License | BSL 1.1 — source-available, not open-source; specific production-use restrictions apply; converts to AGPL v3 with linking exception after a change-date period |
74
+ | Self-host model | Single-node binary; multi-node clustering is Maincloud-only (not available on self-hosted deployments) |
75
+ | Storage model | In-memory dataset + WAL commit log + periodic snapshots; the full working dataset must fit in RAM at all times |
76
+ | Local transaction latency | Approximately 10 microseconds for committed writes on loopback |
77
+ | Module languages | Rust, C#, and TypeScript (running on the V8 engine) |
78
+ | Client SDKs | Rust, C#, TypeScript; HTTP API also available |
79
+ | Subscription model | `SELECT *` queries; joins limited to a maximum of two tables; no subqueries in subscription predicates |
80
+ | Recursive queries | Not supported — any traversal deeper than a flat join must be implemented as reducer-side logic (TypeScript running inside the module) |
81
+ | Row-level security | Available; filters applied at subscription time by identity |
82
+ | Identity model | OIDC-compatible; each connected client carries an identity token |
83
+ | AI-agent workloads | Zero documented production deployments as of June 2026 |
84
+
85
+ These constraints are not preferences — they are hard platform limits that rule
86
+ out certain design patterns:
87
+
88
+ - A deep recursive knowledge graph cannot use server-side query traversal.
89
+ Traversal must be reducer-side BFS capped at a small hop count.
90
+ - A team dataset that exceeds available RAM cannot be accommodated on
91
+ self-hosted deployments. Dataset size must be estimated before adoption.
92
+ - Multi-machine teams require Maincloud, not a self-hosted binary.
93
+
94
+ ---
95
+
96
+ ## 3. Table Designs
97
+
98
+ > **Design-intent notice.** The following are design-intent descriptions only.
99
+ > No DDL syntax is used anywhere in this document. No INSERT or SELECT
100
+ > operations appear as implementation targets. These designs are inputs to a
101
+ > future evaluation, not implemented code.
102
+
103
+ The Lattice uses eight tables. Each table is described by its purpose and a
104
+ column-definition table. Columns marked `[pk]` form the primary key; columns
105
+ marked `[idx]` carry a secondary index.
106
+
107
+ ### 3.1 agents
108
+
109
+ Tracks every agent occupant that has registered with the Lattice during the
110
+ current or any prior session included in the working dataset.
111
+
112
+ | Column | Type | Description |
113
+ |---|---|---|
114
+ | agent_id | String [pk] | Stable identifier assigned in the boot receipt (e.g. `dev-e008-sonnet`) |
115
+ | session_id | String [idx] | Session in which this agent was last active |
116
+ | role | String | Role name as declared in the boot receipt (`DEV`, `QA`, `PM`, etc.) |
117
+ | status | String | Current lifecycle state: `BOOTED`, `ACTIVE`, `PARKED`, `CRASHED`, `CLOSED` |
118
+ | substrate | String | Multiplexer surface type: `cmux`, `tmux`, `minimux`, `plain`, or `herdr` |
119
+ | registered_at | U64 | Microsecond epoch timestamp of first registration in this session |
120
+ | last_seen_at | U64 | Microsecond epoch of the most recent reducer write from this agent |
121
+
122
+ Retention: rows are retained for the lifetime of the working dataset. A
123
+ maintenance reducer may archive agents whose `last_seen_at` is older than a
124
+ configurable threshold, writing archived rows to a cold snapshot before removal.
125
+
126
+ ### 3.2 role_slots
127
+
128
+ Describes the planned occupancy layout for a team as declared by the EXEC PM at
129
+ launch. Actual agent registrations in the `agents` table are matched against
130
+ `role_slots` to detect unoccupied or over-occupied positions.
131
+
132
+ | Column | Type | Description |
133
+ |---|---|---|
134
+ | slot_id | String [pk] | Unique slot identifier (e.g. `dev-slot-1`) |
135
+ | session_id | String [idx] | Session this slot belongs to |
136
+ | role | String | Expected role for this slot |
137
+ | tier | String | Tier label: `control` (PM/ODIN), `worker` (DEV/QA), `support` |
138
+ | assigned_agent_id | String | Agent expected to occupy this slot; empty if unassigned |
139
+ | occupied | Bool | True when a matching agent row exists and is `ACTIVE` or `PARKED` |
140
+
141
+ Retention: rows are written at session launch and remain for session duration.
142
+ Slots are not updated in place; a new session creates new slot rows.
143
+
144
+ ### 3.3 receipts
145
+
146
+ Stores boot receipts and delivery receipts as submitted by agents. The receipt
147
+ schema mirrors the structures already enforced by the MCP server — rows are
148
+ validated on write and rejected without mutation if validation fails.
149
+
150
+ | Column | Type | Description |
151
+ |---|---|---|
152
+ | receipt_id | String [pk] | Unique identifier generated by the submitting agent |
153
+ | session_id | String [idx] | Session context |
154
+ | agent_id | String [idx] | Agent that submitted the receipt |
155
+ | receipt_type | String | `BOOT`, `DELIVERY`, `QA_PASS`, `QA_FAIL`, `CLOSEOUT` |
156
+ | payload_sha256 | String | SHA-256 hash of the receipt payload for integrity checking |
157
+ | payload_json | String | Full receipt payload serialized as JSON |
158
+ | submitted_at | U64 | Microsecond epoch of submission |
159
+ | valid | Bool | True if validation passed; False rows are written as rejection evidence |
160
+
161
+ Retention: receipts are append-only and permanent within the working dataset.
162
+ A snapshot-and-purge policy may archive sessions older than N days to free RAM.
163
+
164
+ ### 3.4 deliveries
165
+
166
+ Records confirmed delivery events — moments when a DEV agent declared work
167
+ complete and a receipt was accepted. This table is the atomic source of truth
168
+ for "what has been delivered in this session."
169
+
170
+ | Column | Type | Description |
171
+ |---|---|---|
172
+ | delivery_id | String [pk] | Unique delivery identifier |
173
+ | session_id | String [idx] | Session context |
174
+ | agent_id | String | Delivering agent |
175
+ | scope | String | Description of what was delivered (file path, task label, or slice ID) |
176
+ | receipt_id | String | Foreign reference to the originating receipt row |
177
+ | delivery_proof_type | String | `WAIT_IDLE`, `EXPLICIT_CONFIRM`, or `SCREEN_OBSERVE` |
178
+ | delivered_at | U64 | Microsecond epoch |
179
+
180
+ Retention: append-only. Deliveries are never mutated or deleted within a
181
+ session's working dataset.
182
+
183
+ ### 3.5 state_transitions
184
+
185
+ An audit log of every agent status change. Each row records one edge in an
186
+ agent's lifecycle state machine.
187
+
188
+ | Column | Type | Description |
189
+ |---|---|---|
190
+ | transition_id | String [pk] | Unique transition identifier |
191
+ | session_id | String [idx] | Session context |
192
+ | agent_id | String [idx] | Agent whose status changed |
193
+ | from_status | String | Status before the transition |
194
+ | to_status | String | Status after the transition |
195
+ | triggered_by | String | What caused the transition: `RECEIPT`, `WAKE_EVENT`, `PM_OVERRIDE` |
196
+ | trigger_id | String | ID of the receipt or wake event that triggered this transition |
197
+ | transitioned_at | U64 | Microsecond epoch |
198
+
199
+ Retention: append-only. The full transition log is retained for the session
200
+ lifetime to support forensic review of agent lifecycle events.
201
+
202
+ ### 3.6 wake_events
203
+
204
+ Records every event emitted by `odin-watch` — including observation verdicts,
205
+ intervention recommendations, and delivery proofs detected via screen analysis.
206
+ The wake-flag files written to the directory configured via
207
+ `odin-watch --flag-dir` produce records with exactly the shapes stored here;
208
+ switching from file-based to Lattice-based delivery is a transport change only.
209
+
210
+ | Column | Type | Description |
211
+ |---|---|---|
212
+ | event_id | String [pk] | Unique event identifier |
213
+ | session_id | String [idx] | Session context |
214
+ | agent_id | String [idx] | Agent that the wake event concerns |
215
+ | verdict | String | Classifier output: `IDLE`, `ACTIVE`, `DELIVERY_PROOF`, `CRASHED`, `QA_COMPLETE`, `NEEDS_INPUT` |
216
+ | prompt_budget_class | String | `silent`, `compact`, `diagnostic`, or `forensic` |
217
+ | screen_hash | String | Hash of the terminal snapshot that produced this verdict |
218
+ | compact_state_json | String | Serialized compact state snapshot at event time |
219
+ | observed_at | U64 | Microsecond epoch when odin-watch captured the snapshot |
220
+
221
+ Retention: wake events are append-only. A sliding-window policy may remove
222
+ events older than a configurable horizon (e.g., 48 hours) to bound RAM usage.
223
+
224
+ ### 3.7 entity_nodes
225
+
226
+ One row per named fact or entity in the bounded collective memory. Agents write
227
+ entity nodes to record discoveries, decisions, or references that should survive
228
+ their own session reset.
229
+
230
+ | Column | Type | Description |
231
+ |---|---|---|
232
+ | entity_id | String [pk] | Stable entity identifier (agent-assigned, must be unique) |
233
+ | session_id | String [idx] | Session in which this entity was first written |
234
+ | agent_id | String | Agent that created the entity |
235
+ | entity_type | String | Semantic category: `DECISION`, `ARTIFACT`, `FACT`, `RISK`, `REFERENCE` |
236
+ | label | String | Human-readable name for the entity |
237
+ | summary | String | One-to-three sentence description |
238
+ | confidence | String | `HIGH`, `MEDIUM`, `LOW` — agent's declared confidence in the fact |
239
+ | created_at | U64 | Microsecond epoch |
240
+ | updated_at | U64 | Microsecond epoch of the most recent `upsert_entity` call |
241
+
242
+ Retention: entity nodes are retained until explicitly expired by a maintenance
243
+ reducer or until the dataset reaches a configurable node-count ceiling, at which
244
+ point the oldest low-confidence nodes are evicted first.
245
+
246
+ ### 3.8 entity_edges
247
+
248
+ Directed relationships between entity nodes. An edge from node A to node B with
249
+ a given relation label encodes a typed semantic link. Traversal is limited to
250
+ one to two hops by the reducer-side BFS implementation; deeper traversal is
251
+ intentionally unsupported.
252
+
253
+ | Column | Type | Description |
254
+ |---|---|---|
255
+ | edge_id | String [pk] | Unique edge identifier |
256
+ | session_id | String [idx] | Session in which this edge was written |
257
+ | from_entity_id | String [idx] | Source entity node |
258
+ | to_entity_id | String [idx] | Target entity node |
259
+ | relation | String | Semantic relation label: `BLOCKS`, `DEPENDS_ON`, `REFERENCES`, `CONTRADICTS`, `SUPPORTS` |
260
+ | created_by | String | Agent that wrote this edge |
261
+ | created_at | U64 | Microsecond epoch |
262
+
263
+ Retention: edges are retained alongside their referenced nodes. Evicting a node
264
+ also evicts all edges where that node appears as source or target.
265
+
266
+ ---
267
+
268
+ ## 4. Reducer Semantics
269
+
270
+ Reducers are the only mechanism by which data enters the Lattice. All writes are
271
+ validated; a reducer that receives an invalid input writes a rejection record (or
272
+ writes nothing) and returns without mutating any other table. This mirrors the
273
+ validation posture already enforced by the MCP server for receipt submissions.
274
+
275
+ ### 4.1 submit_receipt
276
+
277
+ When an agent submits a boot receipt or delivery receipt, the `submit_receipt`
278
+ reducer receives the serialized payload and validates it against the receipt
279
+ schema enforced by the MCP server. Specifically, the reducer checks that the
280
+ required fields are present (`receipt_id`, `agent_id`, `session_id`,
281
+ `receipt_type`, and `payload_sha256`), that the SHA-256 hash matches the
282
+ serialized payload, and that `receipt_type` is one of the recognized values. If
283
+ any check fails, the reducer writes a row to `receipts` with `valid = false` as
284
+ a rejection record, then returns. If all checks pass, the reducer writes a row
285
+ with `valid = true` and proceeds to call `transition_state` internally when the
286
+ receipt type implies a lifecycle change (for example, a `BOOT` receipt
287
+ transitions the agent from no prior state to `BOOTED`; a `CLOSEOUT` receipt
288
+ transitions to `CLOSED`).
289
+
290
+ ### 4.2 record_delivery
291
+
292
+ The `record_delivery` reducer validates that a delivery event references an
293
+ existing `receipt_id` in the `receipts` table with `valid = true` and a
294
+ `receipt_type` of `DELIVERY`. It also checks that `delivery_proof_type` is one
295
+ of the recognized values. On success it inserts a row into `deliveries` and
296
+ emits a push notification to any PM subscriber holding an active subscription
297
+ to the `deliveries` table filtered by `session_id`. Failed validation writes
298
+ nothing.
299
+
300
+ ### 4.3 transition_state
301
+
302
+ The `transition_state` reducer enforces the agent lifecycle state machine. It
303
+ accepts a `(agent_id, from_status, to_status, triggered_by, trigger_id)` tuple.
304
+ Before writing, it verifies that the agent's current `status` in the `agents`
305
+ table matches `from_status` — if the agent is already in a different state (for
306
+ example, it was concurrently transitioned by a wake event), the reducer rejects
307
+ the write and returns without mutation. On a valid transition it updates the
308
+ agent's `status` in `agents` and appends a row to `state_transitions`. Because
309
+ SpacetimeDB local transactions are serialized, race conditions between two
310
+ concurrent `transition_state` calls for the same agent are resolved by the
311
+ database's own transaction ordering — the second call will see the post-first
312
+ state and reject if the precondition no longer holds.
313
+
314
+ ### 4.4 raise_wake
315
+
316
+ The `raise_wake` reducer is the entry point for `odin-watch` to deposit a
317
+ classifier verdict. It receives the full wake event payload (agent, verdict,
318
+ prompt budget class, screen hash, and compact state JSON) and writes a row to
319
+ `wake_events`. If the verdict is `CRASHED` or `QA_COMPLETE`, the reducer
320
+ additionally calls `transition_state` to reflect the implied lifecycle change.
321
+ PM subscribers holding a wake-event subscription receive the push immediately
322
+ after the transaction commits, replacing the need to poll file flags.
323
+
324
+ ### 4.5 upsert_entity
325
+
326
+ The `upsert_entity` reducer writes or updates an entity node. On first write, it
327
+ inserts a new row into `entity_nodes` with `created_at` set to the current
328
+ timestamp. On a subsequent call with the same `entity_id`, it updates the
329
+ `summary`, `confidence`, and `updated_at` fields only — the `entity_type`,
330
+ `agent_id`, and `created_at` values are immutable once set. This upsert
331
+ semantics allows agents to refine their understanding of a fact without creating
332
+ duplicate nodes.
333
+
334
+ ### 4.6 link_entities
335
+
336
+ The `link_entities` reducer inserts a directed edge between two entity nodes. It
337
+ first verifies that both `from_entity_id` and `to_entity_id` exist in
338
+ `entity_nodes`. If either is missing, the write is rejected. On success it
339
+ inserts a row into `entity_edges`. Duplicate edges (same source, target, and
340
+ relation) are rejected; the caller must call `upsert_entity` to refresh either
341
+ endpoint if the relationship content changes.
342
+
343
+ ### Graph traversal note
344
+
345
+ Deep graph traversal across multiple hops from `entity_nodes` through
346
+ `entity_edges` and back to `entity_nodes` must be implemented as reducer-side
347
+ TypeScript logic, not as server-side recursive queries. SpacetimeDB v2.5 does
348
+ not support recursive queries. The recommended pattern is a bounded BFS emitted
349
+ by a read-only reducer (a reducer that writes nothing and returns a result set),
350
+ capped at a maximum hop depth of two to avoid traversal cost growth.
351
+
352
+ ---
353
+
354
+ ## 5. RLS Read Rules
355
+
356
+ SpacetimeDB v2.5 applies row-level security at subscription time. The Lattice
357
+ uses identity-scoped filtering organized around `session_id` and `agent_id`.
358
+
359
+ **Session-scoped visibility.** All tables carry a `session_id` column. A
360
+ client's subscription receives only rows whose `session_id` matches the session
361
+ the client authenticated into. Rows from other sessions are not returned even if
362
+ the client holds a wildcard subscription. This is the primary isolation
363
+ boundary.
364
+
365
+ **Agent-scoped write authority.** Reducers validate that the identity on the
366
+ incoming connection matches the `agent_id` being written. A PM agent cannot
367
+ submit a receipt on behalf of a DEV agent; only the DEV agent's own connection
368
+ may call `submit_receipt` with that `agent_id`. This prevents impersonation
369
+ without requiring a separate ACL table.
370
+
371
+ **Read-only control-plane roles.** PM and ODIN roles receive read subscriptions
372
+ to all tables within their session scope. They do not have a higher write
373
+ privilege than worker agents — they simply hold subscriptions to tables (such as
374
+ `wake_events` and `state_transitions`) that trigger push notifications on every
375
+ commit, replacing the polling pattern.
376
+
377
+ **RLS filter sketch.** For each table, the effective subscription predicate is
378
+ `session_id = :caller_session`. For the `agents` and `role_slots` tables an
379
+ additional optional predicate scopes to a single `agent_id` for agents that
380
+ want only their own row pushed. The `entity_nodes` and `entity_edges` tables
381
+ use the same session predicate; there is no per-agent visibility restriction on
382
+ collective memory — all team members can read all entity nodes in their session.
383
+
384
+ **Rejected-write visibility.** Rows in `receipts` with `valid = false` are
385
+ readable by all agents in the session. This is intentional: rejected receipts
386
+ are evidence artifacts, not secrets, and PM and ODIN roles need to see them for
387
+ forensic review.
388
+
389
+ ---
390
+
391
+ ## 6. Sequencing and Migration Path
392
+
393
+ ### Phase 0 (current): File-based v0 data layer
394
+
395
+ `odin-watch` writes two kinds of artifacts to the wake-flag directory configured
396
+ via `odin-watch --flag-dir`:
397
+
398
+ - **Wake-flag files.** One file per agent containing the most recent classifier
399
+ verdict. The PM polls these files at a configured cadence (typically every
400
+ two minutes) to decide whether to intervene.
401
+ - **Compact state ledger files.** Append-only NDJSON files recording the full
402
+ event stream for a session, used for forensic review and session handoffs.
403
+
404
+ This layer is fully deployed and production-validated. No Lattice adoption
405
+ decision is required to operate ODIN Sentinel teams.
406
+
407
+ ### Phase 1: Parallel validation
408
+
409
+ When go/no-go criteria are met (see [Section 8](#8-gono-go-criteria-table)), a
410
+ Lattice module can be started alongside the file-based layer. `odin-watch` writes
411
+ to both sinks simultaneously. The Lattice rows are validated against the file
412
+ records; if they match, confidence in the Lattice's data integrity is
413
+ established. This phase runs for at least three sessions before Phase 2.
414
+
415
+ ### Phase 2: Sink switchover
416
+
417
+ Once parallel validation is complete, `odin-watch` is reconfigured to write only
418
+ to the Lattice. File-based output is preserved as a fallback but is no longer
419
+ the primary sink. PM roles switch from file-poll subscriptions to push
420
+ subscriptions. The compact state ledger continues to be written locally as an
421
+ audit record independent of the Lattice.
422
+
423
+ ### Schema compatibility guarantee
424
+
425
+ Because the Lattice ingests the same record shapes that the v0 file layer
426
+ produces, the migration is a transport change, not a schema change. No
427
+ transformation of historical data is required. The file-based layer remains
428
+ valid indefinitely as a fallback if the Lattice is shut down.
429
+
430
+ ### Schema evolution policy
431
+
432
+ SpacetimeDB v2.5 schema migrations support adding new columns and new tables.
433
+ Removing columns or changing column types requires a module re-deployment and a
434
+ full data migration. The Lattice schema should therefore be treated as
435
+ append-only within a major version: new columns may be added; existing columns
436
+ are immutable. If a breaking change is needed, it is addressed by introducing a
437
+ new table alongside the old one and migrating reads before dropping the old table.
438
+
439
+ ---
440
+
441
+ ## 7. License Obligations
442
+
443
+ ### SpacetimeDB v2.5 — BSL 1.1
444
+
445
+ SpacetimeDB v2.5 is released under the Business Source License 1.1. BSL 1.1 is
446
+ source-available but not open-source. It imposes production-use restrictions
447
+ during the change-date period: specifically, the license specifies a
448
+ "Additional Use Grant" that permits non-production use and evaluation, but
449
+ restricts certain production deployments without a commercial agreement.
450
+
451
+ After the change date, the license converts to AGPL v3 with a linking exception.
452
+ The linking exception is relevant because it allows applications that merely
453
+ communicate with a SpacetimeDB module over the network (including via the
454
+ WebSocket SDK) to retain their own license without being required to adopt AGPL.
455
+ A sidecar package that links against SpacetimeDB client libraries at compile time
456
+ may not benefit from this exception and requires separate legal review.
457
+
458
+ **Obligations for any distributed `@bradheitmann/odin-lattice` package:**
459
+
460
+ - The package README and LICENSE file must disclose that SpacetimeDB is a BSL 1.1
461
+ dependency.
462
+ - Any redistribution of SpacetimeDB binaries or modules bundled with the package
463
+ requires compliance with BSL 1.1 production-use terms.
464
+ - The maintainer must track the SpacetimeDB change date and update the license
465
+ disclosure when the BSL period ends and AGPL v3 takes effect.
466
+
467
+ ### herdr — AGPL-3.0 + Commercial
468
+
469
+ If the Lattice design is extended to ingest data from herdr (the agent-state-aware
470
+ terminal multiplexer), the AGPL-3.0 license applies to any artifact that links
471
+ against herdr libraries. AGPL-3.0 requires that network-accessible services using
472
+ AGPL-licensed code make their source available. A commercial license is available
473
+ from the herdr maintainers for deployments where AGPL terms are incompatible.
474
+
475
+ Legal review is required before any `@bradheitmann/odin-lattice` artifact that
476
+ links against herdr is distributed publicly.
477
+
478
+ ---
479
+
480
+ ## 8. Go/No-Go Criteria Table
481
+
482
+ The Lattice remains in design state until **all** Adopt criteria in the table
483
+ below are satisfied. If **any** Defer condition applies, implementation is
484
+ blocked.
485
+
486
+ | Criterion | Adopt | Defer |
487
+ |---|---|---|
488
+ | Documented AI-agent deployment on SpacetimeDB | At least one verified production or extended-evaluation deployment of an AI-agent workload on SpacetimeDB v2.5 | Zero documented cases — unproven track record for agent workloads |
489
+ | Local RAM budget validated | Team dataset size (agents + receipts + wake events for a full session) measured and confirmed to fit within the target machine's available RAM budget | Dataset size unknown or measured to exceed available RAM |
490
+ | BSL 1.1 legal review complete | Review complete; distribution restrictions understood and addressed (either acceptable for the use case or a commercial agreement is in place) | Review not started or outstanding ambiguity about production-use restrictions |
491
+ | v0 file-based layer production-validated | At least three full-session runs using odin-watch file output with no data-integrity issues reported | File-based layer not yet deployed or fewer than three validated sessions |
492
+ | macOS single-node self-host tested | SpacetimeDB single binary self-hosted on macOS, module deployed, client connected, and round-trip latency measured end-to-end | Self-host not tested on target platform |
493
+ | Schema migration tooling available | A tested procedure exists for adding columns to a live Lattice module without full re-deployment data loss | No tested migration procedure |
494
+ | Multi-machine requirement absent or Maincloud acceptable | Either the team runs on a single machine (self-hosted suffices) or a Maincloud deployment is acceptable for the workload | Team requires multi-machine self-hosted clustering (not available in v2.5) |
495
+
496
+ **Decision rule:** ALL Adopt criteria must be true before beginning
497
+ implementation of the Lattice module. If any Defer condition applies, the Lattice
498
+ stays in design state and `odin-watch` file output remains the production data
499
+ layer.
500
+
501
+ ---
502
+
503
+ ## 9. Package Structure
504
+
505
+ The Lattice is packaged as a separate workspace distribution named
506
+ `@bradheitmann/odin-lattice`. It is never listed as a dependency of
507
+ `@bradheitmann/odin-sentinel`. An operator who does not adopt the Lattice
508
+ experiences no runtime cost, no added package footprint, and no behavior
509
+ difference — the zero-backend, no-required-runtime identity of ODIN Sentinel is
510
+ fully preserved.
511
+
512
+ The expected workspace layout when the package is eventually implemented:
513
+
514
+ **`packages/odin-lattice/`** — workspace root for the package.
515
+
516
+ **`packages/odin-lattice/src/schema/`** — TypeScript type definitions
517
+ describing the shape of each table row as used by the client SDK. These types
518
+ are derived from the table designs in Section 3 and are used for subscription
519
+ result decoding on the client side, not for server-side DDL.
520
+
521
+ **`packages/odin-lattice/src/reducers/`** — Client-side reducer call wrappers
522
+ for `submit_receipt`, `record_delivery`, `transition_state`, `raise_wake`,
523
+ `upsert_entity`, and `link_entities`. Each wrapper serializes the call
524
+ arguments and dispatches them over the SpacetimeDB WebSocket connection. Input
525
+ validation is performed client-side before the call is dispatched, mirroring the
526
+ server-side validation described in Section 4.
527
+
528
+ **`packages/odin-lattice/src/client/`** — A WebSocket client wrapper that
529
+ manages the SpacetimeDB connection lifecycle (connect, authenticate via OIDC,
530
+ subscribe, reconnect on drop). PM and ODIN roles use this wrapper to receive
531
+ push notifications from `wake_events` and `state_transitions` subscriptions.
532
+
533
+ **`packages/odin-lattice/src/bfs/`** — Bounded breadth-first search utilities
534
+ operating on locally-cached `entity_nodes` and `entity_edges` subscription
535
+ results. This is the client-side traversal logic described in Section 4's graph
536
+ traversal note. Traversal depth is capped at two hops by default and
537
+ configurable to a maximum of four.
538
+
539
+ **`packages/odin-lattice/README.md`** — Installation instructions, BSL 1.1
540
+ license disclosure, usage examples, and the go/no-go criteria summary directing
541
+ operators to Section 8 of this document before adopting the package.
542
+
543
+ **`packages/odin-lattice/package.json`** — Package manifest declaring
544
+ `@bradheitmann/odin-lattice` as the package name with zero cross-dependency on
545
+ `@bradheitmann/odin-sentinel`. SpacetimeDB client SDK and any other runtime
546
+ dependencies are listed here, not hoisted into the sentinel package.
547
+
548
+ **`packages/odin-lattice/module/`** — (Populated only when go/no-go criteria
549
+ are met.) The SpacetimeDB TypeScript module source. This directory is empty or
550
+ absent during the design phase. Its presence in the repository signals that the
551
+ adoption decision has been made and implementation has begun.
552
+
553
+ ---
554
+
555
+ *End of ODIN Lattice Design.*
@@ -6,7 +6,7 @@ public protocol semantics change, update them together.
6
6
 
7
7
  ## Current Public Versions
8
8
 
9
- - npm package/server: `0.4.13`
9
+ - npm package/server: `0.5.0`
10
10
  - minimum compatible child MCP version: `0.4.5`
11
11
 
12
12
  Private local skill copies may differ for personal workflow reasons. Release
@@ -18,19 +18,19 @@ private-local divergence from repo-internal public artifact drift.
18
18
  Recommended zero-install path:
19
19
 
20
20
  ```bash
21
- pnpm dlx --package @bradheitmann/odin-sentinel@0.4.13 odin-sentinel-mcp
21
+ pnpm dlx --package @bradheitmann/odin-sentinel@0.5.0 odin-sentinel-mcp
22
22
  ```
23
23
 
24
24
  Supported npm global install:
25
25
 
26
26
  ```bash
27
- npm i -g @bradheitmann/odin-sentinel@0.4.13
27
+ npm i -g @bradheitmann/odin-sentinel@0.5.0
28
28
  ```
29
29
 
30
30
  Supported npx zero-install path:
31
31
 
32
32
  ```bash
33
- npx -y -p @bradheitmann/odin-sentinel@0.4.13 odin-sentinel-mcp
33
+ npx -y -p @bradheitmann/odin-sentinel@0.5.0 odin-sentinel-mcp
34
34
  ```
35
35
 
36
36
  Recommended MCP config:
@@ -40,7 +40,7 @@ Recommended MCP config:
40
40
  "mcpServers": {
41
41
  "odin-sentinel": {
42
42
  "command": "pnpm",
43
- "args": ["dlx", "--package", "@bradheitmann/odin-sentinel@0.4.13", "odin-sentinel-mcp"]
43
+ "args": ["dlx", "--package", "@bradheitmann/odin-sentinel@0.5.0", "odin-sentinel-mcp"]
44
44
  }
45
45
  }
46
46
  }
@@ -41,10 +41,10 @@ languages, or harnesses.
41
41
 
42
42
  ## Current Public Surface
43
43
 
44
- - Public package/server version: `0.4.13`
44
+ - Public package/server version: `0.5.0`
45
45
  - Minimum compatible child MCP version: `0.4.5`
46
- - MCP resources: 13
47
- - MCP tools: 27
46
+ - MCP resources: 18
47
+ - MCP tools: 29
48
48
  - Optional telemetry tools: user-invoked, not automatic collection
49
49
 
50
50
  ## Release Drift Rule
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@bradheitmann/odin-sentinel",
3
- "version": "0.4.13",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "Portable MCP governance protocol for multi-agent terminal teams: 27 tools, 13 resources, readiness gates, ODIN watchers, receipts, delegation, and closeout over stdio.",
7
+ "description": "Portable MCP governance protocol for multi-agent terminal teams: 29 tools, 18 resources, readiness gates, ODIN watchers, receipts, delegation, and closeout over stdio.",
8
8
  "type": "module",
9
9
  "main": "./dist/src/protocol/index.js",
10
10
  "types": "./dist/src/protocol/index.d.ts",
@@ -21,7 +21,8 @@
21
21
  },
22
22
  "bin": {
23
23
  "odin-sentinel": "dist/src/bin/index.js",
24
- "odin-sentinel-mcp": "dist/src/bin/index.js"
24
+ "odin-sentinel-mcp": "dist/src/bin/index.js",
25
+ "odin-watch": "dist/src/odin-watch/index.js"
25
26
  },
26
27
  "files": [
27
28
  ".claude-plugin",
@@ -30,7 +31,8 @@
30
31
  "plugins",
31
32
  "protocol",
32
33
  "templates",
33
- "scripts",
34
+ "scripts/audit",
35
+ "scripts/protocol",
34
36
  "AGENTS.md",
35
37
  "CLAUDE.md",
36
38
  "README.md",
@@ -70,7 +72,7 @@
70
72
  "url": "https://github.com/bradheitmann/odin-sentinel/issues"
71
73
  },
72
74
  "odin": {
73
- "publicVersion": "0.4.13",
75
+ "publicVersion": "0.5.0",
74
76
  "minimumCompatibleChildMcpVersion": "0.4.5"
75
77
  },
76
78
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odin-scp",
3
- "version": "0.4.13",
3
+ "version": "0.5.0",
4
4
  "description": "Operate and improve SCP governance for multi-agent teams: self-bootstrap and teardown of federated pods, generic role topology, EXEC PM / TEAM PM / ODIN separation, boot receipts, terminal locator identity, delegation receipts, terminal/CMUX delivery proof, heartbeat cadence, branch-visible claims, adversarial QA, finish audit. Includes the @bradheitmann/odin-sentinel MCP server wired in automatically.",
5
5
  "author": {
6
6
  "name": "bradheitmann"
@@ -19,7 +19,7 @@
19
19
  "mcpServers": {
20
20
  "odin-sentinel": {
21
21
  "command": "pnpm",
22
- "args": ["dlx", "--package", "@bradheitmann/odin-sentinel@0.4.13", "odin-sentinel-mcp"]
22
+ "args": ["dlx", "--package", "@bradheitmann/odin-sentinel@0.5.0", "odin-sentinel-mcp"]
23
23
  }
24
24
  }
25
25
  }