@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/CHANGELOG.md +45 -0
- package/VERSION +1 -1
- package/conformance/fixtures.json +21 -12
- package/conformance/reference-bundle.ucw.json +1 -1
- package/docs/ssss-spec.md +536 -184
- package/package.json +1 -1
- package/scripts/audit-spec-examples.mjs +79 -0
- package/scripts/conformance-09.mjs +156 -3
- package/scripts/conformance.mjs +15 -1
- package/scripts/generate-sbom.mjs +13 -14
- package/src/authorization.mjs +11 -1
- package/src/engine.mjs +23 -4
- package/src/frontmatter.mjs +54 -6
- package/src/http.mjs +20 -4
- package/src/kernel.mjs +131 -47
- package/types/kernel.d.ts +7 -0
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
|
|
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
|
|
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
|
-
```
|
|
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
|
-
| `
|
|
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).
|
|
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.
|
|
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`, `
|
|
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.
|
|
231
|
-
|
|
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
|
-
|
|
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
|
|
412
|
-
|
|
413
|
-
|
|
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: "
|
|
421
|
-
|
|
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
|
-
- "
|
|
424
|
-
- "write
|
|
485
|
+
- "ssss:assistant:*"
|
|
486
|
+
- "write:event"
|
|
425
487
|
---
|
|
426
488
|
|
|
427
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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)
|
|
594
|
-
|
|
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
|
|
601
|
-
|
|
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
|
|
789
|
+
### 6.1 The Envelope
|
|
616
790
|
|
|
617
|
-
An
|
|
791
|
+
An envelope is a JSON object:
|
|
618
792
|
|
|
619
793
|
```jsonc
|
|
620
794
|
{
|
|
621
|
-
"type": "operation",
|
|
622
|
-
"
|
|
623
|
-
"
|
|
624
|
-
"
|
|
625
|
-
"content": "---\ntype:
|
|
626
|
-
"patches": { },
|
|
627
|
-
"
|
|
628
|
-
"
|
|
629
|
-
"
|
|
630
|
-
"
|
|
631
|
-
|
|
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 |
|
|
640
|
-
|
|
641
|
-
| `operation` |
|
|
642
|
-
| `patch` |
|
|
643
|
-
| `event` | Append
|
|
644
|
-
| `delete` |
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
`
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
739
|
-
|
|
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
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
-
|
|
750
|
-
|
|
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
|
-
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
-
|
|
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
|
|
772
|
-
events exist only in the log.
|
|
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` |
|
|
777
|
-
| `event_type` | The kind of event
|
|
778
|
-
| `
|
|
779
|
-
| `
|
|
780
|
-
| `
|
|
781
|
-
| `
|
|
782
|
-
| `
|
|
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 `
|
|
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
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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'
|
|
1144
|
+
{ "field": "name", "issue": "Missing required field 'name'." }
|
|
823
1145
|
]
|
|
824
1146
|
}
|
|
825
1147
|
}
|
|
826
1148
|
```
|
|
827
1149
|
|
|
828
|
-
A host MAY
|
|
829
|
-
|
|
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": "
|
|
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
|
|
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
|
|
1131
|
-
|
|
1132
|
-
|
|
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
|
|
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` (
|
|
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.
|
|
1250
|
-
| `required_extensions` | yes | Array of extension registry ids the files rely on (e.g. `["
|
|
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
|
|
1262
|
-
|
|
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` —
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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 (
|
|
1346
|
-
`domain` → `owns_domain`, `phone_number` → `routes_calls_to`,
|
|
1347
|
-
`connects_to`), so an importer knows exactly what real-world
|
|
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
|
|
1366
|
-
|
|
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:
|
|
1377
|
-
|
|
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.
|