@gregiteen/ssss-cli 0.9.3 → 0.9.5

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.
package/docs/ssss-spec.md CHANGED
@@ -4,13 +4,18 @@
4
4
 
5
5
  > This is the canonical, vendor-neutral specification for SSSS. It is the ground
6
6
  > truth on which all SSSS implementations are built. It is intended to be vendored
7
- > byte-for-byte into any repository that implements SSSS (e.g. a compatible host
8
- > Sovereign AI OS, the Total Recall reference kernel).
7
+ > byte-for-byte into any repository that implements SSSS.
9
8
  >
10
9
  > Status: **Stable for 0.9**. The format, type registry, and conformance contract
11
10
  > are settled for this version; until v1.0 a later version MAY still introduce
12
11
  > breaking changes.
13
12
  >
13
+ > Revision: 2026-09-24. Editorial alignment with the 0.9 reference kernel
14
+ > (`@gregiteen/ssss-cli`). This revision documents behavior that already ships:
15
+ > the verified-principal model, the real stage order, the canonical event
16
+ > record, and symbolic error codes. It changes no wire format. Every example
17
+ > document in this file is validated by the conformance suite (§12).
18
+ >
14
19
  > Implementation-specific detail (routes, services, storage backends, deployment)
15
20
  > does **not** belong in this document — it belongs in each implementation's own
16
21
  > `SKILL.md`. This file describes *what SSSS is*, never *how one product wires it up*.
@@ -61,6 +66,15 @@ Defined terms used throughout:
61
66
  | **Contract primitive** | A primitive that exists only as a protocol structure (envelope, lease, event). |
62
67
  | **Projection** | A derived, disposable representation of vault state (e.g. an SQL table, a search index). Never source-of-truth. |
63
68
  | **Agent** | Any human or AI actor issuing operations. |
69
+ | **Envelope** | A JSON command submitted to the Operation Contract: `operation`, `patch`, `event`, or `delete` (§6). |
70
+ | **Kernel** | The component that runs the §6.3 pipeline. It owns stage order and semantics; storage is delegated to adapters. |
71
+ | **Adapter** | A host-supplied implementation of one storage or policy contract: VFS, event store, idempotency store, lease store, authorizer, projections, or resource coordinator (§6.7). |
72
+ | **Verified principal** | The authenticated identity a host attaches to an envelope *outside* the envelope body (§6.6). Never taken from client-supplied fields. |
73
+ | **Capability** | A permission string of the form `<scope>:<action>` held by a principal and required by a primitive (§6.6). |
74
+ | **Qualified type** | A primitive identity prefixed by its registry namespace: `ssss:assistant` for core, `<ext>:<type>` for an extension (§5.6). |
75
+ | **Portability class** | `structural`, `tenant_private`, or `resource_bound`: whether a document may leave the workspace (§5.5). |
76
+ | **Bundle** | A `.ucw` package of vault files plus a manifest (§16). |
77
+ | **Commit point** | The moment a mutation becomes durable: the canonical event is appended to the event log (§6.3). |
64
78
 
65
79
  A host is **conformant** if it satisfies every MUST in this document and passes the
66
80
  conformance fixtures of §12.
@@ -77,7 +91,7 @@ conformance fixtures of §12.
77
91
  | **One mutation contract** | All agent-generated mutations MUST flow through the Operation Contract (§6). Direct, unvalidated writes by agents are forbidden. |
78
92
  | **Deterministic validation** | Every mutation MUST be validated against the primitive's schema before commit. Validation MUST be deterministic — same input, same verdict. |
79
93
  | **Idempotent by key** | Every operation carries an idempotency key. Replays MUST NOT double-apply. |
80
- | **Append-only history** | The event log is immutable. Events are never updated or deleted. |
94
+ | **Append-only history** | The event log is immutable, and so are the records in append-type documents. Neither is ever updated or deleted. |
81
95
  | **Disposable indexes** | Derived indexes and projections are ephemeral caches, fully rebuildable from the vault. They may be deleted at any time. |
82
96
  | **Git-versioned** | The vault is version-controlled. History is provenance. |
83
97
  | **Portable** | A vault is interpretable by any Markdown-capable tool. No host is privileged. |
@@ -90,7 +104,7 @@ conformance fixtures of §12.
90
104
 
91
105
  A document primitive is a UTF-8 Markdown file with two regions:
92
106
 
93
- ```markdown
107
+ ```text
94
108
  ---
95
109
  type: <primitive-type>
96
110
  <frontmatter fields...>
@@ -135,13 +149,21 @@ For stable semantic identity and graph projection, documents SHOULD also include
135
149
  |-------|------|-------------|
136
150
  | `semantic_id` | string | Stable concept identity that survives file moves and localized presentation. |
137
151
  | `relations` | array | Explicit semantic edges to other vault-relative document paths or semantic ids. |
138
- | `locale` | string | BCP-47-style locale of the authored natural-language surface, when known. |
152
+ | `language` | string | BCP-47 tag of the authored natural-language surface, when known (e.g. `ja`, `es-MX`). |
139
153
 
140
154
  `resource`, `tags`, and `aliases` serve both OKF discovery and the semantic layer.
141
- These fields are recommendations, not new universal requirements.
155
+ These fields are recommendations, not new universal requirements. Earlier drafts
156
+ called the last field `locale`; a host SHOULD read `locale` as a fallback when
157
+ `language` is absent, and MUST write `language`.
142
158
 
143
159
  Every primitive type defines its own additional REQUIRED and OPTIONAL fields
144
- (§5.4). To maintain forward compatibility and allow agents scratchpad space, hosts MUST NOT reject documents containing unknown frontmatter keys. Unknown fields MUST be preserved.
160
+ (§5.4). The four universal fields above are REQUIRED **in addition to** each
161
+ type's own list. To maintain forward compatibility and allow agents scratchpad
162
+ space, hosts MUST NOT reject documents containing unknown frontmatter keys.
163
+ Unknown fields MUST be preserved.
164
+
165
+ Directory index files (`index.md`, §4.3) are the one exemption. They carry no
166
+ frontmatter and are not validated as primitives.
145
167
 
146
168
  ### 4.3 Linking & Progressive Disclosure
147
169
 
@@ -164,11 +186,34 @@ canonical filename** that signals the primitive at a glance:
164
186
  | `MODEL.md` | `model` |
165
187
  | `CONVERSATION.md` | `conversation` |
166
188
  | `RUN.md` | `run` |
189
+ | `ROLE.md` | `security_role` (canonically `roles/<role>/ROLE.md`) |
167
190
 
168
191
  Free-standing primitives (e.g. `memory`, `task`) instead use a `kebab-case` slug
169
- filename matching their `slug` field. The frontmatter `type` is always
192
+ filename matching their `slug` field. Governed `primitive` definitions live at
193
+ `primitives/<namespace>/<id>.md`. The frontmatter `type` is always
170
194
  authoritative; the filename is a convention, not a substitute for validation.
171
195
 
196
+ ### 4.5 The Frontmatter Subset
197
+
198
+ Frontmatter is YAML, but a conformant host is only required to accept the subset
199
+ below. A document that stays inside it round-trips identically through every
200
+ conformant parser. The reference parser is dependency-free and implements
201
+ exactly this subset.
202
+
203
+ - **Scalars**: strings (bare, `"double"`, or `'single'` quoted), integers,
204
+ decimals, `true`/`false`, and `null`/`~`. ISO 8601 timestamps are read as
205
+ **strings**, not native dates, so they round-trip byte-for-byte.
206
+ - **Inline arrays**: `[a, b, "c d"]`.
207
+ - **Block arrays**: `- item` lines.
208
+ - **Nested maps**: indented `key: value` blocks.
209
+ - **Arrays of maps**: `- key: value` entries whose further keys are indented
210
+ under the first. A map entry may itself hold an inline array.
211
+ - **Folded strings**: `>` and `>-` block scalars.
212
+
213
+ Anchors, aliases, tags, multi-document streams, and flow maps (`{a: 1}`) are
214
+ outside the subset. A host MAY accept them but MUST NOT require them, and
215
+ authors SHOULD NOT use them in documents that leave the host.
216
+
172
217
  ---
173
218
 
174
219
  ## 5. Primitive Type Registry
@@ -217,8 +262,8 @@ class — notably `resource_bound` for things like domains and phone numbers.
217
262
  Document primitives are either:
218
263
 
219
264
  - **Replace-type** — the whole file represents current state; a write replaces it
220
- entirely (`memory`, `skill`, `assistant`, `workflow`, `model`, `task`,
221
- `conflict`, `primitive`).
265
+ entirely (`memory`, `skill`, `rule`, `security_role`, `task`, `assistant`,
266
+ `workflow`, `model`, `conflict`, `page`, `migration`, `release`, `primitive`).
222
267
  - **Append-type** — the file is an ordered, append-only log; writes add records to
223
268
  the body and MUST NOT rewrite prior records (`conversation`, `run`).
224
269
 
@@ -227,8 +272,10 @@ operation that would rewrite or remove existing records of an append-type docume
227
272
 
228
273
  ### 5.4 Per-Type Schemas
229
274
 
230
- Each schema lists REQUIRED fields. All other fields are OPTIONAL. Examples are
231
- minimal and illustrative.
275
+ Each schema lists the type's REQUIRED fields. These are in addition to the
276
+ universal fields of §4.2 (`type`, `title`, `description`, `timestamp`), which
277
+ every example carries. All other fields are OPTIONAL. Examples are minimal, and
278
+ each one passes the reference validator (§12).
232
279
 
233
280
  #### `memory`
234
281
 
@@ -252,6 +299,8 @@ type: memory
252
299
  slug: prefer-atomic-writes
253
300
  category: patterns
254
301
  title: "Always write files atomically (write-then-rename)"
302
+ description: "Write to a temporary file, then rename it over the target."
303
+ timestamp: 2026-05-16T14:00:00Z
255
304
  status: active
256
305
  schema_version: 2
257
306
  confidence: 0.92
@@ -284,9 +333,11 @@ REQUIRED: `type`, `name`, `description`.
284
333
  ---
285
334
  type: skill
286
335
  name: deploy
336
+ title: "Deploy"
287
337
  description: >-
288
338
  Deploy services with zero downtime. Use when the user mentions deploy,
289
339
  release, ship, or production push.
340
+ timestamp: 2026-05-16T14:00:00Z
290
341
  ---
291
342
 
292
343
  # Deploy
@@ -310,6 +361,9 @@ and prevent duplicate daemon ticks from creating duplicate work (§11.8).
310
361
  ```markdown
311
362
  ---
312
363
  type: task
364
+ title: "Research the payments API"
365
+ description: "Research the payments API and write a reference skill."
366
+ timestamp: 2026-05-16T08:00:00Z
313
367
  priority: 85
314
368
  category: skill-engineering
315
369
  status: pending
@@ -333,8 +387,10 @@ REQUIRED: `type`, `name`.
333
387
  ---
334
388
  type: assistant
335
389
  name: "Support Bot"
390
+ title: "Support Bot"
336
391
  description: "Front-line customer support assistant."
337
- model: "anthropic/claude-opus-4-5"
392
+ timestamp: 2026-05-16T14:00:00Z
393
+ model: "example/large-model"
338
394
  ---
339
395
 
340
396
  ## Instructions
@@ -370,7 +426,9 @@ include:
370
426
  ---
371
427
  type: workflow
372
428
  name: "Daily Digest"
429
+ title: "Daily Digest"
373
430
  description: "Sends a daily summary email."
431
+ timestamp: 2026-05-16T14:00:00Z
374
432
  triggers:
375
433
  - type: cron
376
434
  id: daily-0800
@@ -399,7 +457,9 @@ REQUIRED: `type`, `name`. OPTIONAL: `description`, `scope`.
399
457
  ---
400
458
  type: rule
401
459
  name: "No external links in replies"
460
+ title: "No external links in replies"
402
461
  description: "Customer-facing replies must not contain outbound URLs."
462
+ timestamp: 2026-05-16T14:00:00Z
403
463
  scope: support
404
464
  ---
405
465
 
@@ -408,25 +468,33 @@ Outbound links are stripped from any assistant reply on a support thread.
408
468
 
409
469
  #### `security_role`
410
470
 
411
- A definition of a security role used for role-based access control (RBAC).
412
- It maps a role name to a set of permissions that dictate what an agent or user
413
- can do within the workspace.
471
+ A named set of capabilities used for role-based access control (RBAC). It maps
472
+ a role name to the capabilities an agent or user holding that role is granted
473
+ within the workspace.
414
474
 
415
475
  REQUIRED: `type`, `name`, `permissions` (array). OPTIONAL: `description`.
416
476
 
417
477
  ```markdown
418
478
  ---
419
479
  type: security_role
420
- name: "admin"
421
- description: "Administrator with full access to all workspace primitives."
480
+ name: "support-agent"
481
+ title: "Support agent"
482
+ description: "Can write assistants and events, nothing else."
483
+ timestamp: 2026-05-16T14:00:00Z
422
484
  permissions:
423
- - "read:*"
424
- - "write:*"
485
+ - "ssss:assistant:*"
486
+ - "write:event"
425
487
  ---
426
488
 
427
- Full administrative access to the workspace.
489
+ Maintains support assistants and records support events.
428
490
  ```
429
491
 
492
+ `permissions` holds capability strings (§6.6). A capability names a scope and an
493
+ action; `*` in the action position grants every action on that scope, and `*:*`
494
+ grants everything. A `security_role` is one way for a host to derive a
495
+ principal's capabilities. The kernel only ever sees the resulting capability
496
+ list, never the role name.
497
+
430
498
  #### `model`
431
499
 
432
500
  The definition of an inference model.
@@ -436,9 +504,12 @@ REQUIRED: `type`, `model_id`, `provider`.
436
504
  ```markdown
437
505
  ---
438
506
  type: model
439
- model_id: "anthropic/claude-opus-4-5"
440
- provider: anthropic
441
- display_name: "Claude Opus 4.5"
507
+ title: "Example Large Model"
508
+ description: "A general-purpose long-context model."
509
+ timestamp: 2026-05-16T14:00:00Z
510
+ model_id: "example/large-model"
511
+ provider: example
512
+ display_name: "Example Large Model"
442
513
  ---
443
514
 
444
515
  ## Capabilities
@@ -455,9 +526,12 @@ REQUIRED: `type`, `thread_id`. Typical fields: `workspace_id`, `user_id`, `statu
455
526
  ```markdown
456
527
  ---
457
528
  type: conversation
458
- thread_id: "7f3a2b1c-..."
459
- workspace_id: "..."
460
- user_id: "..."
529
+ title: "Greeting"
530
+ description: "A two-turn support conversation."
531
+ timestamp: 2026-05-16T14:00:05Z
532
+ thread_id: "7f3a2b1c-5d6e-4f70-8a91-b2c3d4e5f607"
533
+ workspace_id: "ws-acme"
534
+ user_id: "user-42"
461
535
  status: active
462
536
  created_at: 2026-05-16T14:00:00Z
463
537
  ---
@@ -482,6 +556,9 @@ REQUIRED: `type`, `run_id`, `workflow_id`. Typical fields: `workspace_id`, `stat
482
556
  ```markdown
483
557
  ---
484
558
  type: run
559
+ title: "Daily Digest run 2026-05-16"
560
+ description: "Execution record for the 08:00 daily digest."
561
+ timestamp: 2026-05-16T08:00:01Z
485
562
  run_id: "run-001"
486
563
  workflow_id: "daily-digest"
487
564
  task_path: "tasks/daily-digest/daily-0800-20260516T080000Z.md"
@@ -503,6 +580,9 @@ REQUIRED: `type`, `conflict_id`, `status` (`pending|resolved`), `new_slug`,
503
580
  ```markdown
504
581
  ---
505
582
  type: conflict
583
+ title: "HTML vs plaintext email"
584
+ description: "A new memory contradicts an existing one about email format."
585
+ timestamp: 2026-05-16T18:30:00Z
506
586
  conflict_id: conflict-2026-05-16-001
507
587
  status: pending
508
588
  new_slug: use-html-email
@@ -520,6 +600,9 @@ REQUIRED: `type`, `slug`, `name`, `sandbox_entry`.
520
600
  ```markdown
521
601
  ---
522
602
  type: page
603
+ title: "Leads Dashboard"
604
+ description: "A sandboxed dashboard of inbound leads."
605
+ timestamp: 2026-05-16T14:00:00Z
523
606
  slug: "leads-portal"
524
607
  name: "Leads Dashboard"
525
608
  icon: "users"
@@ -537,6 +620,8 @@ REQUIRED: `type`, `migration_id`, `from_version`, `to_version`, `status`, `descr
537
620
  ```markdown
538
621
  ---
539
622
  type: migration
623
+ title: "Add vector_embedding field"
624
+ timestamp: 2026-05-16T14:00:00Z
540
625
  migration_id: "mig-v2-to-v3"
541
626
  from_version: 2
542
627
  to_version: 3
@@ -554,6 +639,9 @@ REQUIRED: `type`, `release_id`, `version`, `schema_version`, `summary`, `release
554
639
  ```markdown
555
640
  ---
556
641
  type: release
642
+ title: "Release 3.1.0"
643
+ description: "Adds proposal and migration file types."
644
+ timestamp: 2026-05-16T12:00:00Z
557
645
  release_id: "rel-3.1.0"
558
646
  version: "3.1.0"
559
647
  schema_version: 3
@@ -570,6 +658,48 @@ language without changing the core registry. REQUIRED: `type`, `primitive_id`,
570
658
  Stable primitive, field, enum, capability, and action identifiers are symbolic and
571
659
  language-independent. Labels and descriptions are multilingual presentation data.
572
660
 
661
+ The definition is validated against a meta-schema on write:
662
+
663
+ | Field | Rule |
664
+ |-------|------|
665
+ | `primitive_id` | Qualified (`<namespace>:<id>`), prefix equal to `namespace`. A host MAY derive it from `namespace` + `name` so an author never types one. |
666
+ | `namespace` | Lowercase, `[a-z][a-z0-9_-]{0,63}`. |
667
+ | `version` | Positive integer. `revision`, if present, is a positive integer. |
668
+ | `mutation` | `replace` or `append` (§5.3). |
669
+ | `portability` | A §5.5 class. |
670
+ | `scopes` | Non-empty unique subset of `system`, `account`, `workspace`, `user`. |
671
+ | `fields[]` | Each has a unique symbolic `id`, a non-empty `name` in any language, and a `kind`: `string`, `text`, `number`, `integer`, `boolean`, `datetime`, `date`, `enum` (with unique `values`), `object`, `array`, `reference`, `secret_reference`, or `resource_reference`. |
672
+ | `capabilities` | Optional map of action → list of capability strings. |
673
+ | `aliases` | Optional list of unique safe identifiers that resolve to this primitive. |
674
+
675
+ ```markdown
676
+ ---
677
+ type: primitive
678
+ title: "Booking"
679
+ description: "A customer booking, authored in Japanese."
680
+ timestamp: 2026-05-16T14:00:00Z
681
+ primitive_id: "acme:booking"
682
+ namespace: acme
683
+ version: 1
684
+ name: "顧客予約"
685
+ language: ja
686
+ mutation: replace
687
+ portability: tenant_private
688
+ scopes: [workspace]
689
+ fields:
690
+ - id: reservation_date
691
+ name: "予約日"
692
+ kind: datetime
693
+ required: true
694
+ - id: status
695
+ name: "状態"
696
+ kind: enum
697
+ values: [pending, confirmed]
698
+ capabilities:
699
+ create: ["acme.booking:create"]
700
+ ---
701
+ ```
702
+
573
703
  ### 5.5 Portability Classification
574
704
 
575
705
  A workspace is not one undifferentiated blob. Some of it is the **reusable business
@@ -590,21 +720,65 @@ stored documents, and have no portability class.)
590
720
  | `resource_bound` | Requires a real external resource bound at provision time. | ⚠️ as a **requirement declaration** only — the seller's resource value is stripped |
591
721
 
592
722
  A file MAY override its type's default with an `x_portability` frontmatter field. A host
593
- MUST honor the **most restrictive** of (type default, instance override) — a file may make
594
- itself more private than its type, never less.
723
+ MUST honor the **most restrictive** of (type default, instance override), ordered
724
+ `structural` < `resource_bound` < `tenant_private`. A file may make itself more private
725
+ than its type, never less. An unrecognized override value is ignored.
595
726
 
596
727
  An **export profile** is simply a filter over portability classes:
597
728
 
598
729
  - **`backup`** — all three classes. `tenant_private` MUST be encrypted at rest.
599
730
  - **`template`** / **`sale`** — `structural` verbatim; `resource_bound` reduced to a
600
- requirement declaration (seller's bound value stripped); `tenant_private` **dropped
601
- entirely**.
731
+ requirement declaration; `tenant_private` **dropped entirely**.
732
+
733
+ A requirement declaration is produced mechanically. The type's registry entry MUST
734
+ declare `resource.binds`, the list of fields that hold the bound value. Each of
735
+ those fields is replaced with the literal string `REQUIREMENT`, and the file is
736
+ marked `x_portability: resource_bound`. An exporter MUST fail rather than emit a
737
+ `resource_bound` file whose type declares no `resource.binds`, because it cannot
738
+ know which fields to strip.
602
739
 
603
740
  A host MUST reject a `template`/`sale` export that would emit a `tenant_private` primitive.
604
741
  This single rule is what lets an operator sell a proven business model without ever
605
742
  shipping a customer's data — the data is `tenant_private` by classification, so it
606
743
  physically cannot enter a sale bundle.
607
744
 
745
+ ### 5.6 Registries, Qualified Identities & Extensions
746
+
747
+ The type registry is data: `registry/core.json` defines the core primitives, and an
748
+ **extension registry** adds a namespace of its own. Every primitive has a
749
+ **qualified identity**: `ssss:<type>` for core types and `<registry>:<type>` for an
750
+ extension's. A document MAY declare either the bare or the qualified name in
751
+ `type`. Hosts resolve both, and canonical events always record the qualified one
752
+ (§8.2).
753
+
754
+ A registry entry declares the rules that validation (§9) enforces:
755
+
756
+ | Key | Effect |
757
+ |-----|--------|
758
+ | `required_fields` | Fields that MUST be present and non-empty. |
759
+ | `required_when` | `{ "<field>==<value>": [fields] }`: fields required only when the condition holds. |
760
+ | `enums` | Allowed values per field. Array values are checked element by element. |
761
+ | `patterns` | A Unicode regular expression each value of the field MUST match. |
762
+ | `immutable_fields` | Fields a `patch` MUST NOT change. |
763
+ | `references` | Fields holding vault paths, with `allowed_types`, `disallowed_types`, `allowed_portability`, `must_exist`, and an optional `hash_field` pinning the target's content hash. |
764
+ | `append_only` | `true` makes the type append-type (§5.3). |
765
+ | `portability` | The default class (§5.5). |
766
+ | `capabilities` | Per-action capability requirements (§6.6). |
767
+ | `lease_required` | `true` makes every write to the type require a lease (§7). |
768
+ | `resource.binds` | Fields stripped to a requirement declaration on sale export (§5.5). |
769
+
770
+ Composition rules:
771
+
772
+ - An extension MUST NOT redefine or shadow a core type, a sibling extension's type,
773
+ or an existing alias. A collision is a load-time error, not a runtime preference.
774
+ - An extension MAY declare `requires: { "<extension>": "<semver range>" }`. A host
775
+ MUST refuse to load an extension whose dependencies are missing or out of range.
776
+ - A host MAY pin a composed registry set with an **integrity lock**: a SHA-256 over
777
+ the canonical composition. A lock that no longer matches is drift, and a host
778
+ SHOULD refuse to start on it.
779
+ - Extension registry files MUST be regular files. A host MUST refuse symlinked
780
+ registry files.
781
+
608
782
  ---
609
783
 
610
784
  ## 6. The Operation Contract
@@ -612,91 +786,129 @@ physically cannot enter a sale bundle.
612
786
  All agent-generated mutations to a vault MUST flow through the Operation Contract.
613
787
  An agent MUST NOT write vault files directly.
614
788
 
615
- ### 6.1 The Operation Envelope
789
+ ### 6.1 The Envelope
616
790
 
617
- An operation is a JSON envelope:
791
+ An envelope is a JSON object:
618
792
 
619
793
  ```jsonc
620
794
  {
621
- "type": "operation", // "operation" | "patch" | "event" | "delete"
622
- "idempotency_key": "uuid-v4", // see §6.4
623
- "path": "assistants/bot/ASSISTANT.md", // relative VFS path, no leading "/"
624
- "workspace_id": "uuid", // the vault/workspace scope
625
- "content": "---\ntype: ...", // full file content (operation, event)
626
- "patches": { }, // partial merge (patch only)
627
- "lease_id": "uuid", // OPTIONAL — see §7
628
- "intent": "human description", // OPTIONAL — audit annotation
629
- "dry_run": false, // OPTIONAL — validate without committing
630
- "actor": { // OPTIONAL — the cryptographically verified identity.
631
- "type": "human", // "human" | "ai" | "system"
632
- "role": "admin" // role-based access control hook for the host
633
- }
795
+ "type": "operation", // "operation" | "patch" | "event" | "delete" (§6.2)
796
+ "workspace_id": "ws-acme", // the vault/workspace scope
797
+ "idempotency_key": "b3c1…", // §6.4
798
+ "path": "assistants/bot/ASSISTANT.md", // vault-relative, no leading "/", no "." or ".." segments
799
+ "content": "---\ntype: …", // operation: full document; event: JSON payload string
800
+ "patches": { }, // patch only
801
+ "primitive_type": "assistant", // OPTIONAL — asserts the target's type
802
+ "operation_id": "uuid", // OPTIONAL — client-chosen; generated if absent
803
+ "lease_id": "uuid", // OPTIONAL — §7
804
+ "dry_run": false, // OPTIONAL — validate and authorize without committing
805
+ "intent": "human description" // OPTIONAL — audit annotation, not interpreted
634
806
  }
635
807
  ```
636
808
 
809
+ The envelope carries **no identity**. Who is acting is supplied by the host as a
810
+ verified principal, next to the envelope and never inside it (§6.6). A kernel
811
+ MUST ignore any identity-bearing field a client places in the envelope, including
812
+ the pre-0.9 `actor` object.
813
+
814
+ A `path` MUST be rejected with `invalid_request` if it is empty, absolute, contains
815
+ a backslash or NUL, or has an empty, `.`, or `..` segment. A host MUST also refuse
816
+ to follow a symbolic link out of the vault.
817
+
637
818
  ### 6.2 Envelope Types
638
819
 
639
- | `type` | Semantics | Body field | Required envelope fields |
640
- |--------|-----------|------------|--------------------------|
641
- | `operation` | Full atomic write — creates or fully replaces a file. | `content` | `type`, `idempotency_key`, `path`, `workspace_id`, `content` |
642
- | `patch` | Partial merge into an existing file's frontmatter and/or body. | `patches` | `type`, `idempotency_key`, `path`, `workspace_id`, `patches` |
643
- | `event` | Append-only immutable log entry. Never overwrites. | `content` | `type`, `idempotency_key`, `path`, `workspace_id`, `content` |
644
- | `delete` | Removes a replace-type file from the vault. | — | `type`, `idempotency_key`, `path`, `workspace_id` |
645
-
646
- For `patch`, the `patches` object merges into frontmatter keys; the reserved key
647
- `__body__` replaces or (for append-type documents) appends the Markdown body. For
648
- `event`, `content` MUST be a valid JSON string carrying the event payload.
649
-
650
- For `delete`, the host removes the file at `path` and appends a deletion event to the log
651
- (§8) — the removal is itself an auditable, append-only record, so history is never lost. A
652
- host MUST reject a `delete` targeting an **append-type** document (`conversation`, `run`)
653
- or a non-existent path. A `delete` of an already-deleted path is an idempotent no-op that
654
- returns the original result (§6.4). `delete` is the only contract type that removes vault
655
- state; export and provision (§16) never delete — only a live workspace edit or a migration
656
- (§5.4 `migration`) does.
820
+ | `type` | Semantics | Required fields beyond `type`, `workspace_id`, `idempotency_key`, `path` |
821
+ |--------|-----------|------------|
822
+ | `operation` | Create a document, or fully replace an existing one. | `content` (string) |
823
+ | `patch` | Merge into an existing document's frontmatter and/or body. | `patches` (object) |
824
+ | `event` | Append an immutable entry to the event log (§8). Never touches a document. | none; `content`, if present, MUST be a JSON string |
825
+ | `delete` | Remove a replace-type document. | none |
826
+
827
+ **`operation`** — `content` is the complete document. An `operation` MUST NOT
828
+ change the `type` of an existing document; changing type is a migration
829
+ (§5.4 `migration`). Writing a directory `index.md` (§4.3) is permitted and is
830
+ not validated as a primitive.
831
+
832
+ **`patch`** — keys of `patches` are merged shallowly into the frontmatter: a
833
+ supplied key replaces that key's whole value, and absent keys are untouched. The
834
+ reserved key `__body__` replaces the Markdown body of a replace-type document, or
835
+ is appended to the body of an append-type document (§5.3). A `patch` MUST NOT
836
+ change `type` or any of the type's `immutable_fields`. The merged document is
837
+ validated as a whole before commit.
838
+
839
+ **`event`** — the parsed `content` becomes the event's `payload` (§8.2). The target
840
+ `path` is recorded as the event's `subject` and need not exist.
841
+
842
+ **`delete`** — the host removes the document and records the deletion as an event,
843
+ so history is never lost. A `delete` targeting an append-type document MUST be
844
+ rejected with `validation_failed`. A `delete` of a path that does not exist MUST be
845
+ rejected with `not_found`, unless it is an idempotent replay of an earlier delete
846
+ with the same key, in which case the original result is returned (§6.4). `delete`
847
+ is the only envelope that removes vault state. Export and provision (§16, §17)
848
+ never delete.
657
849
 
658
850
  ### 6.3 The Processing Pipeline
659
851
 
660
- A host MUST process every operation through these ordered stages. Any stage's
661
- failure aborts the operation with no commit.
662
-
663
- 1. **Envelope validation** — `type` is one of the four envelope types
664
- (`operation`/`patch`/`event`/`delete`); required envelope fields present and
665
- well-formed.
666
- 2. **Idempotency check** — if this `idempotency_key` + `workspace_id` +
667
- request hash was already committed within the TTL, return the original result
668
- as a replay (§6.4). If the key and workspace match but the request hash
669
- differs, reject the request with `idempotency_conflict`. Stop.
670
- 3. **Authorization & Actor Identity** — the agent has write access to `workspace_id`.
671
- *Security Mandate*: If a host exposes the Operation Contract directly to untrusted clients (e.g., a `POST /api/ssss` REST endpoint), the host **MUST** irreversibly overwrite the `actor` payload with the user's cryptographically verified session identity. Hosts MUST use this `actor` field to enforce Role-Based Access Control (RBAC) over sensitive or `resource_bound` primitives.
672
- 4. **Lease check** — if the target path is leased, the operation MUST carry a
673
- matching, unexpired `lease_id` (§7).
674
- 5. **Content validation** — for `operation`/`patch`, the resulting file is validated
675
- against its primitive schema (§9). Append-type rewrite attempts are rejected. For
676
- `delete`, the host verifies the target exists and is a replace-type document (append-type
677
- deletes are rejected).
678
- 5.5. **Granular Authorization (RBAC)** — evaluated only if stage 5 passed. Resolve
679
- `actor.role` from the (by now host-verified, per stage 3) envelope. `role: "system"`
680
- is an unconditional bypass (used by trusted internal callers such as the provisioning
681
- pipeline, §17). `role: "admin"` is granted `write:*`/`read:*` unconditionally. Any
682
- other named role's permissions are read from that role's `security_role` document
683
- (canonical location `roles/<role>/ROLE.md`) and checked against the required
684
- permission — `write:<type>` for `operation`/`patch`/`delete` (using the type resolved
685
- in stage 5), or the fixed `write:event` for `event` envelopes (which have no resolved
686
- document type). A permission list satisfies the requirement via an exact match,
687
- `*:<type>`, `write:*`, or `*:*`. **A MISSING `actor.role` MUST be denied, not treated
688
- as any default role** — this is the fail-closed complement to stage 3's mandate: a
689
- host that forgets to overwrite `actor` gets every write rejected, not silently
690
- promoted to admin.
691
- 6. **Commit** — the mutation is applied to the vault atomically. For `delete`, the file is
692
- removed.
693
- 7. **Audit** — an audit entry is appended to the event log (§8).
694
-
695
- A `dry_run` operation runs stages 1–5.5 and then stops: it MUST return the validation
696
- verdict with `success` reflecting validity, and MUST NOT commit (`committed_at` is
697
- `null`).
698
-
699
- ### 6.4 The Operation Response
852
+ A host MUST process every envelope through these stages, in this order. A failure
853
+ at any stage before the commit point aborts the envelope with nothing committed.
854
+ Kernels MUST NOT reorder or skip stages. Adapters (§6.7) implement mechanics only.
855
+
856
+ | # | Stage | On failure |
857
+ |---|-------|------------|
858
+ | 1 | **Envelope & identity** — known `type`, required fields present and well-typed (§6.2); a principal with an `id` and `kind` is attached. Nothing is read before this passes, so an anonymous caller learns nothing about the vault. | `invalid_request`, `unauthorized` |
859
+ | 2 | **Idempotency** — look up (`workspace_id`, `idempotency_key`). Same request hash: return the stored response with `replay: true` and stop. Different hash: stop (§6.4). | `idempotency_conflict` |
860
+ | 3 | **Read current state** — load the target, if any, and its version. An unsafe path fails here. | `invalid_request`, or `internal_error` for unreadable stored state |
861
+ | 4 | **Content validation** — resolve the primitive and validate the resulting document (§9). Enforce type immutability, `immutable_fields`, patch/delete target existence, and append-only rules. | `not_found`, `validation_failed` |
862
+ | 5 | **Authorization** — the verified principal must be complete (§6.6), scoped to `workspace_id`, and hold every capability the primitive and action require. | `unauthorized`, `forbidden` |
863
+ | 6 | **Lease** — if the primitive is `lease_required` or the host requires a lease for this path, verify it (§7). | `lease_conflict` |
864
+ | 7 | **Resource prepare** — if the host coordinates an external resource (a domain, a phone number), reserve it. | `internal_error` |
865
+ | 8 | **Dry-run exit** — if `dry_run`, release anything prepared in stage 7 and return success with `committed_at: null`. | — |
866
+ | 9 | **Commit** — write the document with a compare-and-swap on the version read in stage 3 (create-if-absent for a new document), or remove it for `delete`. `event` envelopes write nothing here. | `version_conflict`, `internal_error` |
867
+ | 10 | **Event append — the commit point.** Append the canonical event (§8.2). If the append fails, undo stage 9 and release stage 7. | `internal_error` |
868
+ | 11 | **Resource finalize** — complete the external effect. A failure here MUST NOT undo the commit. The host reconciles and the response carries a warning. | warning only |
869
+ | 12 | **Projections** — dispatch the event to projections (§10). A failure is a warning, never a rollback. | warning only |
870
+ | 13 | **Record idempotency** — store the response under the key (§6.4). | warning only |
871
+
872
+ **The commit point.** A mutation is committed exactly when its canonical event is
873
+ durably appended. Before that, every failure leaves no trace in the vault. After
874
+ it, nothing may undo the mutation, because the append-only log (§8) already says it
875
+ happened. Work after the commit point is compensated forward, never rolled back.
876
+
877
+ **Resource lifecycle.** If a host coordinates external resources, every successful
878
+ `prepare` MUST be followed by exactly one `finalize` or `reconcile`. `reconcile`
879
+ receives the phase it runs in (`dry_run`, `commit`, or `finalize`), so the host
880
+ can tell a released reservation from a compensation after a committed intent.
881
+
882
+ **Concurrency.** Two envelopes racing on one path are serialized by the stage-9
883
+ compare-and-swap. The loser either finds a winner with the same key and returns
884
+ that winner's result as a replay, or fails with `version_conflict`. Two `event`
885
+ envelopes with the same key are serialized by the event store's uniqueness on
886
+ `event_id` (§8.1).
887
+
888
+ ### 6.4 Idempotency
889
+
890
+ Every envelope carries an `idempotency_key`, scoped to its `workspace_id`.
891
+
892
+ - The **request hash** is SHA-256 over the canonical JSON (keys sorted
893
+ recursively, no insignificant whitespace) of `type`, `workspace_id`,
894
+ `idempotency_key`, `path`, `primitive_type`, `content`, `patches`, and the
895
+ verified principal's `id`. `dry_run`, `lease_id`, `operation_id`, and `intent`
896
+ are excluded, so retrying with a fresh lease is still the same request.
897
+ - Only **successful commits** are recorded. A failed or dry-run envelope records
898
+ nothing, so it may be retried under the same key after the cause is fixed.
899
+ - A recorded key replays: the same request hash returns the original response with
900
+ `replay: true` and changes nothing. A different request hash under the same key
901
+ fails with `idempotency_conflict`.
902
+ - Because the principal is part of the hash, a key replayed by a *different*
903
+ principal is a conflict, never a disclosure of another principal's result.
904
+ - A host MUST retain records for at least 24 hours and MAY retain them
905
+ indefinitely. The reference stores never expire.
906
+
907
+ Keys are opaque strings. Use a random UUID for ad-hoc requests. Use a
908
+ deterministic derivation where retries must converge, as with runtime-created work
909
+ (§11.8) and bundle import (§17).
910
+
911
+ ### 6.5 The Response & Error Codes
700
912
 
701
913
  ```jsonc
702
914
  {
@@ -704,50 +916,132 @@ verdict with `success` reflecting validity, and MUST NOT commit (`committed_at`
704
916
  "type": "operation",
705
917
  "operation_id": "uuid",
706
918
  "path": "assistants/bot/ASSISTANT.md",
707
- "committed_at": "2026-05-16T14:00:00Z", // null for dry_run / failure
919
+ "committed_at": "2026-05-16T14:00:00Z", // null for dry_run and failures
708
920
  "dry_run": false,
921
+ "event_id": "uuid", // the canonical event (§8.2); absent unless committed
922
+ "replay": true, // present only on an idempotent replay
709
923
  "validation": {
710
924
  "valid": true,
711
- "type": "assistant", // resolved primitive type
925
+ "type": "ssss:assistant", // resolved qualified type (§5.6)
712
926
  "errors": [],
713
- "warnings": []
714
- },
715
- "replay": { }, // present only on an idempotent replay
716
- "repair": { } // present only on validation failure — see §9
927
+ "warnings": [] // e.g. a projection or finalize failure
928
+ }
717
929
  }
718
930
  ```
719
931
 
720
- ### 6.5 Error Codes
932
+ A failed response has `success: false`, `committed_at: null`, `validation.valid:
933
+ false`, the `repair` block of §9, and an `error` object:
721
934
 
722
- | Code | Meaning |
723
- |------|---------|
724
- | `400` | Invalid request — malformed envelope, or a path that fails the traversal guard. |
725
- | `401` | Authentication required. |
726
- | `403` | Agent lacks write access to the workspace. |
727
- | `404` | The `patch`/`delete` target does not exist. |
728
- | `409` | Lease conflict, version conflict, or idempotency conflict — path is locked, the supplied lease is invalid/expired, or the same idempotency key was reused for a different request hash. |
729
- | `422` | Validation failure — see `validation.errors` and `repair`. |
730
- | `500` | Internal error — the operation was not committed. |
935
+ ```jsonc
936
+ { "success": false, "error": { "code": "validation_failed", "message": "name: Missing required field 'name'." }, "…": "…" }
937
+ ```
731
938
 
732
- A host need not use HTTP; if it does, these are the canonical status codes.
939
+ `error.code` is exactly one of the symbolic codes below. Clients MUST branch on
940
+ `error.code`, never on message text. A host MAY use any transport. If it uses HTTP,
941
+ it MUST use these statuses, `201` for a commit, and `200` for a replay or dry run:
733
942
 
734
- ---
943
+ | Code | HTTP | Meaning |
944
+ |------|------|---------|
945
+ | `invalid_request` | 400 | Malformed envelope, non-JSON event content, or an unsafe path. |
946
+ | `unauthorized` | 401 | No valid verified principal reached the kernel. |
947
+ | `forbidden` | 403 | The principal is not scoped to the workspace or lacks a required capability. |
948
+ | `not_found` | 404 | The `patch` or `delete` target does not exist. |
949
+ | `lease_conflict` | 409 | A required lease is missing, mismatched, or expired. |
950
+ | `version_conflict` | 409 | The document changed between read and commit, and no same-key winner exists. |
951
+ | `idempotency_conflict` | 409 | The key was already used for a different request. |
952
+ | `validation_failed` | 422 | The document fails §9, or the envelope would change a type, an immutable field, or an append-only document. |
953
+ | `internal_error` | 500 | Unreadable stored state, a failed resource prepare, or a failed commit. Nothing was committed. |
735
954
 
736
- ## 7. Leases — Concurrency Control
955
+ ### 6.6 Identity & Authorization
956
+
957
+ Authorization acts on a **verified principal** that the host attaches *outside* the
958
+ envelope, after authenticating the caller:
959
+
960
+ ```jsonc
961
+ {
962
+ "id": "user-42", // stable, recorded on every event
963
+ "kind": "human", // "human" | "agent" | "service" | "system"
964
+ "workspaceIds": ["ws-acme"], // workspaces this principal may act in
965
+ "capabilities": ["ssss:assistant:*"], // §6.6 capability strings
966
+ "authentication": { "provider": "oidc", "assurance": "verified" }
967
+ }
968
+ ```
737
969
 
738
- A **lease** is a file-level write lock that prevents two agents from racing on the
739
- same path.
970
+ - A host that exposes the Operation Contract to untrusted callers MUST derive the
971
+ principal from its own authentication, never from the request body. This is why
972
+ the envelope carries no identity (§6.1).
973
+ - A missing or malformed principal MUST be denied as `unauthorized`. There is no
974
+ default principal and no anonymous write.
975
+ - A non-`system` principal MUST list the envelope's `workspace_id` in
976
+ `workspaceIds`.
977
+ - The required capabilities are the primitive's registry `capabilities[<action>]`,
978
+ or `<qualified_type>:<action>` if none are declared. The action is one of
979
+ `create`, `replace`, `patch`, `append`, `event`, or `delete`. It is derived from
980
+ the envelope and the target's existence, never supplied by the client. `event`
981
+ envelopes require `write:event`.
982
+ - A granted capability covers a required one if it is equal, if it is `*:*`, or if
983
+ it ends in `:*` and the required capability starts with everything before that
984
+ `*`. So `ssss:*` covers every core primitive and `ssss:assistant:*` covers every
985
+ action on assistants.
986
+ - A `system` principal bypasses capability checks. It exists for trusted internal
987
+ callers such as import (§17). A host MUST NOT grant `system` to anything a remote
988
+ caller controls.
989
+ - A host MAY layer **policy floors** (extra capabilities per primitive and action),
990
+ **step-up** (a minimum `authentication.assurance`), and **human confirmation**
991
+ (a `human` principal plus an explicit confirmation) on top of this. These only
992
+ ever add requirements.
993
+
994
+ **Pre-0.9 role compatibility.** Hosts that still accept the pre-0.9 envelope field
995
+ `actor.role` MUST convert it to a principal before stage 5. `system` becomes a
996
+ `system` principal, `admin` receives `*:*`, and any other role receives the
997
+ `permissions` of its `security_role` document at `roles/<role>/ROLE.md`, with
998
+ `write:<type>` and `*:<type>` expanded to `<qualified_type>:*`, and `write:*`
999
+ expanded to `*:*`. A missing role still MUST be denied, never promoted.
1000
+
1001
+ ### 6.7 Adapter Contracts
1002
+
1003
+ A kernel owns the stages above. A host supplies the mechanics through adapters,
1004
+ each with a shared contract test in the conformance suite (§12):
1005
+
1006
+ | Adapter | Contract |
1007
+ |---------|----------|
1008
+ | **VFS** | `read(path) → {bytes, version, hash} \| null`; `writeAtomic(path, bytes, {version \| ifAbsent})` and `remove(path, {version})`, both compare-and-swap, failing with a version conflict and never a partial write. Rejects unsafe paths and symlink traversal. |
1009
+ | **Event store** | `append(event)` durably and in order, rejecting a duplicate `event_id` across every writer; `replay({cursor, workspaceId})` in append order. |
1010
+ | **Idempotency store** | `get(workspace, key)`; `put(workspace, key, record)`, which rejects a second `put` for the same key across every writer. |
1011
+ | **Lease store** | `acquire`, `verify`, `renew`, `release` per §7, with at most one live lease per target across every writer. |
1012
+ | **Authorizer** | `authorize({principal, workspaceId, definition, action}) → {allowed, reason}`, per §6.6. Fails closed. |
1013
+ | **Projection coordinator** | `dispatch(event)`, `replay(id, {rebuild})`, `detectDrift(id, hash)` per §10. |
1014
+ | **Resource coordinator** | Optional `prepare`, `finalize`, `reconcile`, per the §6.3 resource lifecycle. |
1015
+
1016
+ "Across every writer" means across threads, processes, and hosts sharing the same
1017
+ storage, not just within one instance.
740
1018
 
741
- - A lease is identified by a `lease_id` and scoped to a `(workspace_id, path)` pair.
742
- - At most one active lease MAY exist per `(workspace_id, path)`.
743
- - While a path is leased, an operation targeting it MUST present the matching,
744
- unexpired `lease_id` or be rejected with `409`.
745
- - A lease MUST carry an expiry. An expired lease is treated as absent.
746
- - An agent that acquires a lease MUST release it after its operation completes. A
747
- host SHOULD reclaim expired leases automatically.
1019
+ ---
1020
+
1021
+ ## 7. Leases — Concurrency Control
748
1022
 
749
- Leases are advisory coordination, not security. Authorization (§6.3 stage 3) is the
750
- security boundary.
1023
+ A **lease** is a time-bound write claim on one `(workspace_id, target)` pair. It
1024
+ lets an agent do long work (such as a workflow run, §11.8) without another agent
1025
+ writing the same path underneath it.
1026
+
1027
+ A lease record carries `lease_id`, `workspace_id`, `target`, `principal_id`,
1028
+ `operation_id`, `issued_at`, and `expires_at`.
1029
+
1030
+ - **acquire** — grants a lease if no live lease exists on the target; otherwise it
1031
+ fails. At most one live lease MAY exist per target, across every writer.
1032
+ - **verify** — succeeds only if a live lease exists and its `lease_id`,
1033
+ `principal_id`, and `operation_id` all match the caller. A lease is bound to the
1034
+ principal and the unit of work that took it. Presenting someone else's
1035
+ `lease_id` is not enough. An envelope that writes under a lease therefore
1036
+ carries both `lease_id` and the `operation_id` the lease was acquired with.
1037
+ - **renew** — extends `expires_at` for the verified holder.
1038
+ - **release** — deletes the lease for the verified holder.
1039
+ - An expired lease is treated as absent. A host SHOULD reclaim expired leases.
1040
+
1041
+ Whether a write *requires* a lease is decided by the primitive's `lease_required`
1042
+ flag or by host policy. Unleased writes are otherwise protected by the stage-9
1043
+ compare-and-swap (§6.3). Leases are coordination, not security. Authorization
1044
+ (§6.6) is the security boundary.
751
1045
 
752
1046
  ---
753
1047
 
@@ -758,28 +1052,42 @@ flat physical *log*, and a relational *event graph* layered on top of it.
758
1052
 
759
1053
  ### 8.1 The Log
760
1054
 
761
- - Events are written via `type: event` operations (§6.2).
762
- - An entry, once written, MUST NOT be updated or deleted. There is no UPDATE and no
763
- DELETE. Ordering is arrival order; adjacency in the log is purely temporal.
764
- - Audit entries (pipeline stage 7) are themselves events.
1055
+ - Every committed envelope appends exactly one canonical event (§6.3 stage 10). The
1056
+ event *is* the audit record. There is no separate audit write.
1057
+ - An entry, once written, MUST NOT be updated or deleted.
1058
+ - `event_id` is unique within the log, across every writer. For `event` envelopes,
1059
+ a kernel SHOULD derive `event_id` deterministically from (`workspace_id`,
1060
+ `idempotency_key`). The reference uses UUIDv5 over their JSON array. That way,
1061
+ concurrent duplicates of one event collide on the uniqueness check instead of
1062
+ both landing.
1063
+ - Order is append order within a log.
765
1064
  - The event log is the canonical history of *what happened*; the vault is the
766
1065
  canonical state of *what is true now*. Both are source-of-truth; projections are
767
1066
  not.
768
1067
 
769
1068
  ### 8.2 The Event Record
770
1069
 
771
- An event is a typed record — **not** a document primitive. There is no `EVENT.md`;
772
- events exist only in the log. Each event carries:
1070
+ An event is a typed record, **not** a document primitive. There is no `EVENT.md`;
1071
+ events exist only in the log.
773
1072
 
774
1073
  | Field | Description |
775
1074
  |-------|-------------|
776
- | `event_id` | Stable unique identity — makes the event addressable. |
777
- | `event_type` | The kind of event (e.g. `feedback`, `audit`, `spawn`). |
778
- | `correlation_id` | Groups every event of one logical flow ("saga"), however far apart in the log. |
779
- | `caused_by` | Zero or more `event_id`s of the event(s) that directly caused this one (causation). |
780
- | `subject` | The vault path / record the event is about. |
781
- | `payload` | The event-type-specific body. |
782
- | `ts` | ISO 8601 timestamp. |
1075
+ | `event_id` | Unique identity (§8.1). |
1076
+ | `event_type` | The kind of event. Kernel mutations use `ssss.mutation`. |
1077
+ | `schema_version` | Version of this record shape; currently `1`. |
1078
+ | `timestamp` | ISO 8601 commit time. |
1079
+ | `workspace_id` | The workspace. |
1080
+ | `primitive_id`, `primitive_version` | The qualified type (§5.6) and its version. `ssss:event` for `event` envelopes. |
1081
+ | `action` | `create`, `replace`, `patch`, `append`, `event`, or `delete`. |
1082
+ | `subject` | The vault path the event is about. |
1083
+ | `principal` | The verified principal (§6.6) that caused it. |
1084
+ | `operation_id`, `idempotency_key`, `request_hash` | Ties the event to its envelope (§6.4). |
1085
+ | `correlation_id` | Groups every event of one logical flow ("saga"). Defaults to `operation_id`. |
1086
+ | `causation_id` | The `event_id` that directly caused this one, or `null`. |
1087
+ | `before_hash`, `after_hash` | Content hashes of the target before and after, so the log alone can detect vault drift. |
1088
+ | `changed_fields` | For `patch`, the sorted keys that were patched. |
1089
+ | `resource_status` | `prepared` if a resource coordinator took part (§6.3), else `null`. |
1090
+ | `payload` | The event-type-specific body (for `event` envelopes, the parsed `content`). |
783
1091
 
784
1092
  ### 8.3 The Event Graph
785
1093
 
@@ -788,7 +1096,7 @@ it rated, an investigation to the feedback that triggered it, a fix to the
788
1096
  investigation. These references form a causal **graph** over the flat log.
789
1097
 
790
1098
  The graph is preserved without violating append-only because **edges point backward
791
- only**: a new event records its `caused_by` parents; an existing event is never
1099
+ only**: a new event records its `causation_id` parent; an existing event is never
792
1100
  mutated to record a child. The forward view (an event's children, a full saga tree)
793
1101
  is reconstructed by scanning — so the **event graph is a derived artifact** (§10),
794
1102
  disposable and rebuildable. Relationships are canonical (backward `event_id`
@@ -802,15 +1110,29 @@ slice of the event graph: the saga tree of one coherent unit of work.
802
1110
  ## 9. Validation & Repair
803
1111
 
804
1112
  Validation is **deterministic**: identical input always yields an identical verdict.
805
-
806
- A file is valid if and only if:
807
-
808
- 1. It has well-formed YAML frontmatter.
809
- 2. The frontmatter `type` matches a primitive in the registry (§5).
810
- 3. Every REQUIRED field for that primitive is present and non-empty.
811
- 4. For append-type documents, the operation does not rewrite existing records.
812
-
813
- Hosts MUST NOT reject a file due to the presence of unknown or unrecognized frontmatter keys. Unrecognized keys MUST be silently preserved to support agent scratchpads and forward compatibility with OKF extensions.
1113
+ It is a pure function of the document and the composed registry (§5.6).
1114
+
1115
+ A document is valid if and only if:
1116
+
1117
+ 1. Its frontmatter parses (§4.5) to an object.
1118
+ 2. `type` resolves, bare or qualified, to a primitive in the registry.
1119
+ 3. Every universal field (§4.2) and every `required_fields` entry is present and
1120
+ non-empty. `null`, `""`, and `[]` count as empty.
1121
+ 4. Every `required_when` condition that holds has its fields present and non-empty.
1122
+ 5. Every present field with an `enums` entry takes allowed values only.
1123
+ 6. Every present field with a `patterns` entry matches it.
1124
+ 7. Every `references` field names a safe vault path to an existing document (unless
1125
+ `must_exist: false`) of an allowed type and portability. If a `hash_field` is
1126
+ declared, it equals the target's current content hash.
1127
+ 8. A `primitive` document also satisfies the §5.4 `primitive` meta-schema.
1128
+
1129
+ On top of the document rules, stage 4 (§6.3) rejects an envelope that would change
1130
+ a document's `type`, change an `immutable_fields` value, rewrite an append-type
1131
+ document, or delete one.
1132
+
1133
+ Hosts MUST NOT reject a document because of unknown frontmatter keys. Unknown keys
1134
+ MUST be preserved to support agent scratchpads and forward compatibility with OKF
1135
+ extensions.
814
1136
 
815
1137
  On failure, the host MUST return structured **repair feedback** so an agent can
816
1138
  self-correct without guesswork:
@@ -819,14 +1141,14 @@ self-correct without guesswork:
819
1141
  {
820
1142
  "repair": {
821
1143
  "field_errors": [
822
- { "field": "name", "issue": "Missing required field 'name' for type 'assistant'." }
1144
+ { "field": "name", "issue": "Missing required field 'name'." }
823
1145
  ]
824
1146
  }
825
1147
  }
826
1148
  ```
827
1149
 
828
- A host MAY additionally emit non-blocking `warnings` (e.g. deprecated fields,
829
- low-confidence content). Warnings MUST NOT block a commit.
1150
+ A host MAY also emit non-blocking `warnings` (deprecated fields, low-confidence
1151
+ content, a failed projection). Warnings MUST NOT block a commit.
830
1152
 
831
1153
  ---
832
1154
 
@@ -861,7 +1183,7 @@ directory declaring each projection as disposable and recording provenance:
861
1183
  { "file": "memory-layers.jsonl", "disposable": true },
862
1184
  { "file": "embeddings.json", "disposable": true }
863
1185
  ],
864
- "rebuild_command": "npx total-recall compile"
1186
+ "rebuild_command": "<host-defined rebuild command>"
865
1187
  }
866
1188
  ```
867
1189
 
@@ -960,7 +1282,7 @@ embedding similarity).
960
1282
  Because the control vocabulary is symbolic and never translated (§11.1), a workspace
961
1283
  that wishes to *present* itself in a given human language does so with data, not by
962
1284
  translating keys. The `language_convention` primitive (a `structural`, extension-owned
963
- type — festech ships it) records a workspace's presentation conventions: its default
1285
+ type) records a workspace's presentation conventions: its default
964
1286
  language/locale, formality, date and currency formatting, and terminology preferences.
965
1287
 
966
1288
  It is `structural` and therefore travels in `template`/`sale` bundles (§5.5): a sold
@@ -1127,9 +1449,22 @@ Fixtures are distributed as a JSON document carrying:
1127
1449
 
1128
1450
  The conformance fixture set is the shared test contract between all SSSS
1129
1451
  implementations. A host MUST NOT claim SSSS conformance without passing the current
1130
- fixture set. Hosts implementing workflow daemons SHOULD also run the reference
1131
- runtime checks exposed by `@gregiteen/ssss-cli/runtime`. Hosts exposing semantic
1132
- search or runtime rendering MUST also pass the §11.9 checks.
1452
+ fixture set, including each fixture's `expected_http_status` if it speaks HTTP
1453
+ (§6.5). Beyond the fixtures:
1454
+
1455
+ - Hosts implementing workflow daemons SHOULD run the runtime checks exposed by
1456
+ `@gregiteen/ssss-cli/runtime` (§11.8).
1457
+ - Hosts exposing semantic search or runtime rendering MUST pass the §11.9 checks.
1458
+ - Hosts supplying their own adapters (§6.7) MUST pass the shared adapter
1459
+ contracts: VFS, event store, idempotency store, and lease store, including
1460
+ their multi-process races.
1461
+ - Kernel implementations MUST pass the resource-lifecycle, error-code,
1462
+ append-only, capability, and frontmatter-subset checks.
1463
+
1464
+ The reference suite also checks **this document**: every example document in it
1465
+ must validate, §5.1 must list every core primitive with its registry portability,
1466
+ and the §6.5 table must match the reference error codes. A spec edit that breaks
1467
+ an example fails the build, just like a code change would.
1133
1468
 
1134
1469
  ---
1135
1470
 
@@ -1143,7 +1478,7 @@ fixture set. Hosts implementing workflow daemons SHOULD also run the reference
1143
1478
  | Canonical filename | `UPPERCASE.md` | `ASSISTANT.md` |
1144
1479
  | VFS path | relative, no leading `/`, `/`-separated | `assistants/bot/ASSISTANT.md` |
1145
1480
  | Conflict ID | `conflict-YYYY-MM-DD-NNN` | `conflict-2026-05-16-001` |
1146
- | Idempotency key | UUID v4, min 8 chars | `11111111-1111-...` |
1481
+ | Idempotency key | Opaque string; random UUID for ad-hoc requests, deterministic derivation for runtime work (§6.4, §11.8) | `runtime-run-3f9a…` |
1147
1482
  | Timestamp | ISO 8601, `Z` suffix (UTC) | `2026-05-16T14:03:00Z` |
1148
1483
 
1149
1484
  Slugs and names MAY contain non-ASCII Unicode letters so that non-Latin scripts are
@@ -1231,7 +1566,7 @@ A bundle is a JSON object with three top-level members:
1231
1566
  }
1232
1567
  ```
1233
1568
 
1234
- `files` MUST be sorted by `path` (byte order) so that two exports of the same vault state
1569
+ `files` MUST be sorted by `path` (§16.3 order) so that two exports of the same vault state
1235
1570
  are identical and content-hashable (§16.3, `provenance.content_hash`).
1236
1571
 
1237
1572
  ### 16.2 The Manifest
@@ -1246,30 +1581,40 @@ inside, the resources it must bind at provision time, and provenance.
1246
1581
  | `description` | yes | One-line summary. |
1247
1582
  | `version` | yes | The bundle's own semver, independent of the spec. |
1248
1583
  | `exported_at` | yes | ISO-8601 timestamp of export. |
1249
- | `ssss_core_version` | yes | The `registry/core.json` `spec_version` the bundle targets (e.g. `"0.3"`). A host MUST refuse a bundle whose core version it does not support. |
1250
- | `required_extensions` | yes | Array of extension registry ids the files rely on (e.g. `["festech"]`). Empty array if the bundle uses only core primitives. A host MUST refuse a bundle naming an extension it has not loaded. |
1584
+ | `ssss_core_version` | yes | The `registry/core.json` `spec_version` the bundle targets (e.g. `"0.9"`). A host MUST refuse a bundle whose core version it does not support. |
1585
+ | `required_extensions` | yes | Array of extension registry ids the files rely on (e.g. `["acme"]`). Empty array if the bundle uses only core primitives. A host MUST refuse a bundle naming an extension it has not loaded. |
1251
1586
  | `export_profile` | yes | `backup` \| `template` \| `sale` — the §5.5 profile this bundle was built under. Determines the allowed portability classes. |
1252
1587
  | `primitive_inventory` | yes | Map of primitive `type` → count, over every file in the bundle (replaces the legacy hard-coded `categories`; it is registry-driven, so extension types appear automatically). |
1253
1588
  | `provisioning` | yes | Array of provisioning steps (§17.2) — the ordered, declarative plan for binding this bundle into a live workspace. Empty for a pure `backup` that is restored in place. |
1254
1589
  | `parameters` | no | Array of parameter definitions (§16.5) the importer must resolve (e.g. business name, domain). |
1255
1590
  | `source_workspace_id` | no | Origin workspace; OMITTED or nulled in `template`/`sale` profiles (it is operator-identifying). |
1591
+ | `dependencies` | no | `{ primitives, extensions, migrations, integrity }`: the sorted primitive types and extension ids the files use, the `migration` ids they carry, and `integrity: { content_hash, ssss_core_version }`. If present, `integrity.content_hash` MUST equal `provenance.content_hash`. |
1256
1592
  | `file_count` | yes | Length of `files`; a cheap integrity check. |
1257
1593
  | `provenance` | yes | `{ content_hash, exporter, signature? }` (§16.3). |
1258
1594
 
1259
1595
  `primitive_inventory` supersedes the legacy fixed `categories` object and `provisioning`
1260
1596
  supersedes its `capabilities` booleans: both are now open and registry-driven rather than a
1261
- closed enum, so a bundle full of `festech` extension primitives inventories and provisions
1262
- them without a spec change.
1597
+ closed enum, so a bundle full of extension primitives inventories and provisions them
1598
+ without a spec change.
1263
1599
 
1264
1600
  ### 16.3 Provenance & Integrity
1265
1601
 
1266
1602
  `provenance` makes a bundle verifiable and attributable:
1267
1603
 
1268
- - `content_hash` — a hash (SHA-256, hex, prefixed `sha256:`) over the canonical
1269
- serialization of `files` (path-sorted, §16.1). An importer MUST recompute it and reject
1270
- a bundle whose files do not match. This is what makes a bundle tamper-evident and what a
1271
- marketplace lists against.
1272
- - `exporter` — an identifier for the tool/host that produced the bundle (`"@gregiteen/ssss-cli@0.8.0"`).
1604
+ - `content_hash` — `sha256:` followed by the lowercase hex SHA-256 of the UTF-8 bytes of
1605
+ the **canonical file serialization**, defined as:
1606
+ 1. Take each file as `{ "path": …, "content": … }`, in that key order, dropping any
1607
+ other member (such as a cached `frontmatter`).
1608
+ 2. Sort by `path`, comparing strings by UTF-16 code units. This equals byte order
1609
+ for ASCII paths.
1610
+ 3. Serialize the array as JSON with no insignificant whitespace, escaping strings
1611
+ exactly as ECMAScript `JSON.stringify` does: `"`, `\`, and control characters
1612
+ are escaped, and all other characters, including non-ASCII, are emitted
1613
+ literally.
1614
+
1615
+ An importer MUST recompute it and reject a bundle whose files do not match. This is
1616
+ what makes a bundle tamper-evident and what a marketplace lists against.
1617
+ - `exporter` — an identifier for the tool/host that produced the bundle (`"@gregiteen/ssss-cli@0.9.3"`).
1273
1618
  - `signature` — OPTIONAL detached signature over `content_hash` for a sold bundle, so a
1274
1619
  buyer can verify authorship. Unsigned bundles are valid; signing is a marketplace concern.
1275
1620
 
@@ -1342,9 +1687,10 @@ A **step** is `{ id, label, system, mode, required, notes? }`:
1342
1687
  Dependency and resource relationships between primitives are expressed with the canonical
1343
1688
  **edge relations**: `uses_brand | owns_domain | routes_calls_to | deploys_to | has_surface
1344
1689
  | contains_page | installs_pack | authenticates_account | runs_workflow`. The
1345
- `resource_bound` primitives of §5.5 each declare which relation binds them (festech's
1346
- `domain` → `owns_domain`, `phone_number` → `routes_calls_to`, `integration_connection` →
1347
- `connects_to`), so an importer knows exactly what real-world binding each requires.
1690
+ `resource_bound` primitives of §5.5 each declare which relation binds them (for example,
1691
+ an extension's `domain` → `owns_domain`, `phone_number` → `routes_calls_to`, and
1692
+ `integration_connection` → `connects_to`), so an importer knows exactly what real-world
1693
+ binding each requires.
1348
1694
 
1349
1695
  ### 17.3 Determinism, Id-Remap & Link Integrity
1350
1696
 
@@ -1362,9 +1708,8 @@ cannot resolve a link MUST fail the provision rather than emit a dangling refere
1362
1708
  `installMode` of `optional | recommended | required` (adopted from the legacy
1363
1709
  `WorkspaceMarketplaceRecommendation`). `provision` installs `required` dependencies before
1364
1710
  the bundle itself; `optional`/`recommended` are surfaced to the operator.
1365
- - **Upgrade** — moving an installed bundle from `v2` to a `v1`-conformant shape (or any
1366
- version step) is performed through a `migration` primitive (§5) plus **structural-only**
1367
- `patch` envelopes (§6.2). An upgrade MUST NOT touch `tenant_private` data: it rewrites the
1711
+ - **Upgrade** — moving an installed bundle from one version to the next is performed
1712
+ through a `migration` primitive (§5.4) plus **structural-only** `patch` envelopes (§6.2). An upgrade MUST NOT touch `tenant_private` data: it rewrites the
1368
1713
  structural model, never the operator's private records. This is what lets a sold business
1369
1714
  receive standard updates without the vendor reaching into customer data.
1370
1715
 
@@ -1373,8 +1718,15 @@ cannot resolve a link MUST fail the provision rather than emit a dangling refere
1373
1718
  ## Appendix A — Reserved Frontmatter Keys
1374
1719
 
1375
1720
  The following frontmatter keys are reserved by this spec across all primitives and
1376
- MUST NOT be repurposed by hosts: `type`, `slug`, `schema_version`, `status`,
1377
- `feedback`, `confidence`, `semantic_id`, `relations`, and `language`.
1721
+ MUST NOT be repurposed by hosts:
1722
+
1723
+ - Universal (§4.2): `type`, `title`, `description`, `timestamp`.
1724
+ - Semantic (§4.2, §11): `semantic_id`, `relations`, `language`, `resource`, `tags`,
1725
+ `aliases`, `feedback`, `confidence`.
1726
+ - Lifecycle: `slug`, `schema_version`, `status`.
1727
+ - Portability (§5.5): `x_portability`.
1728
+
1729
+ The patch key `__body__` (§6.2) is reserved and can never be a frontmatter key.
1378
1730
 
1379
1731
  Hosts adding their own frontmatter fields SHOULD prefix them `x_` to remain
1380
1732
  forward-compatible with future spec revisions.