@gregiteen/ssss-cli 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -250,14 +250,14 @@ primitive — it implements the subset its product requires — but any primitiv
250
250
  | `assistant` | actor | structural | The definition of an AI assistant/persona. |
251
251
  | `workflow` | work | structural | A reusable **task template**: a defined procedure. Firing a workflow submits a `task`. |
252
252
  | `trigger` | work | structural | A standalone schedule or event-driven trigger that fires a workflow. |
253
- | `model` | catalog | structural | The definition of an inference model. |
253
+ | `model` | catalog | structural | The definition of an inference model (deprecated). |
254
254
  | `thread` | transcript | tenant_private | An append-only chat transcript. |
255
255
  | `conversation` | transcript | tenant_private | An append-only chat transcript. Legacy alias of `thread`. |
256
256
  | `run` | transcript | tenant_private | An append-only workflow execution record. |
257
- | `conflict` | meta | tenant_private | A record of two contradicting primitives, blocking promotion. |
258
- | `page` | capability | structural | A VFS-native sandboxed custom workspace page. |
257
+ | `conflict` | meta | tenant_private | A record of two contradicting primitives, blocking promotion (deprecated). |
258
+ | `page` | capability | structural | A VFS-native sandboxed custom workspace page (deprecated). |
259
259
  | `migration` | meta | structural | An SSSS schema migration state record. |
260
- | `release` | meta | structural | An SSSS system schema version release record. |
260
+ | `release` | meta | structural | An SSSS system schema version release record (deprecated). |
261
261
  | `primitive` | meta | structural | A governed, versioned definition for a namespaced SSSS primitive. |
262
262
 
263
263
  The **Portability** column is the primitive's default portability class (§5.5); a host
@@ -282,7 +282,7 @@ Document primitives are either:
282
282
  entirely (`memory`, `skill`, `rule`, `security_role`, `task`, `assistant`,
283
283
  `workflow`, `model`, `conflict`, `page`, `migration`, `release`, `primitive`).
284
284
  - **Append-type** — the file is an ordered, append-only log; writes add records to
285
- the body and MUST NOT rewrite prior records (`conversation`, `run`).
285
+ the body and MUST NOT rewrite prior records (`thread`, `conversation`, `run`).
286
286
 
287
287
  Append-type documents MUST be mutated only by appending. A host MUST reject an
288
288
  operation that would rewrite or remove existing records of an append-type document.
@@ -629,7 +629,7 @@ Maintains support assistants and records support events.
629
629
 
630
630
  #### `model`
631
631
 
632
- The definition of an inference model.
632
+ The definition of an inference model. Deprecated: inert catalog primitive; modern hosts configure models through provider runtime settings.
633
633
 
634
634
  REQUIRED: `type`, `model_id`, `provider`.
635
635
 
@@ -731,7 +731,7 @@ Collected 12 messages.
731
731
 
732
732
  #### `conflict`
733
733
 
734
- A record of two contradicting primitives. Blocks promotion until resolved.
734
+ A record of two contradicting primitives. Blocks promotion until resolved. Deprecated: inert meta primitive; memory collision handling is managed by host memory kernels.
735
735
 
736
736
  REQUIRED: `type`, `conflict_id`, `status` (`pending|resolved`), `new_slug`,
737
737
  `existing_slug`, `detected_at`.
@@ -752,7 +752,7 @@ detected_at: 2026-05-16T18:30:00Z
752
752
 
753
753
  #### `page`
754
754
 
755
- A VFS-native sandboxed custom workspace page.
755
+ A VFS-native sandboxed custom workspace page. Deprecated: inert sandbox primitive with iframe assumptions; superseded by asset and modern UI primitives.
756
756
 
757
757
  REQUIRED: `type`, `slug`, `name`, `sandbox_entry`.
758
758
 
@@ -791,7 +791,7 @@ description: "Add vector_embedding field"
791
791
 
792
792
  #### `release`
793
793
 
794
- An SSSS system schema version release.
794
+ An SSSS system schema version release. Deprecated: inert meta primitive; release tracking is managed by bundle and package manifests.
795
795
 
796
796
  REQUIRED: `type`, `release_id`, `version`, `schema_version`, `summary`, `released_at`.
797
797
 
@@ -957,6 +957,7 @@ An envelope is a JSON object:
957
957
  "path": "assistants/bot/ASSISTANT.md", // vault-relative, no leading "/", no "." or ".." segments
958
958
  "content": "---\ntype: …", // operation: full document; event: JSON payload string
959
959
  "patches": { }, // patch only
960
+ "expected_before_hash": "sha256:…", // OPTIONAL — asserts target content hash before mutation (§6.1)
960
961
  "primitive_type": "assistant", // OPTIONAL — asserts the target's type
961
962
  "operation_id": "uuid", // OPTIONAL — client-chosen; generated if absent
962
963
  "lease_id": "uuid", // OPTIONAL — §7
@@ -970,6 +971,14 @@ verified principal, next to the envelope and never inside it (§6.6). A kernel
970
971
  MUST ignore any identity-bearing field a client places in the envelope, including
971
972
  the pre-0.9 `actor` object.
972
973
 
974
+ A client or bundle provisioner MAY include `expected_before_hash` on an
975
+ `operation` or `patch` envelope to assert that the target document has not been
976
+ modified locally since the intended baseline was read. For a new document creation,
977
+ `expected_before_hash` is `null`. For an update, it is the `sha256:...` digest of
978
+ the baseline content. If the target document's current hash diverges from
979
+ `expected_before_hash` (and its content is not already equal to the intended write),
980
+ the kernel rejects the mutation with `version_conflict`.
981
+
973
982
  A `path` MUST be rejected with `invalid_request` if it is empty, absolute, contains
974
983
  a backslash or NUL, or has an empty, `.`, or `..` segment. A host MUST also refuse
975
984
  to follow a symbolic link out of the vault.
@@ -1016,7 +1025,7 @@ Kernels MUST NOT reorder or skip stages. Adapters (§6.7) implement mechanics on
1016
1025
  |---|-------|------------|
1017
1026
  | 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` |
1018
1027
  | 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` |
1019
- | 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 |
1028
+ | 3 | **Read current state & base-version check** — load the target, if any, and its version. An unsafe path fails here with `invalid_request`. If `expected_before_hash` is specified: if the target is absent and `expected_before_hash !== null`, or if the target exists and its hash does not match `expected_before_hash` (and its content differs from the intended write), reject with `version_conflict`. | `invalid_request`, `version_conflict`, or `internal_error` for unreadable stored state |
1020
1029
  | 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` |
1021
1030
  | 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` |
1022
1031
  | 6 | **Lease** — if the primitive is `lease_required` or the host requires a lease for this path, verify it (§7). | `lease_conflict` |
@@ -1181,6 +1190,11 @@ envelope, after authenticating the caller:
1181
1190
  it ends in `:*` and the required capability starts with everything before that
1182
1191
  `*`. So `ssss:*` covers every core primitive and `ssss:assistant:*` covers every
1183
1192
  action on assistants.
1193
+ - A granted capability string MAY include an optional path selector separated by `@`:
1194
+ `<capability>@<glob>` (for example, `ssss:rule:*@rules/marketing/**` or
1195
+ `*:*@capabilities/messaging/**`). When a selector is present, the capability grants
1196
+ permission only when the envelope's target path matches the glob pattern. When omitted,
1197
+ the capability applies workspace-wide.
1184
1198
  - A `system` principal bypasses capability checks. It exists for trusted internal
1185
1199
  callers such as import (§17). A host MUST NOT grant `system` to anything a remote
1186
1200
  caller controls.
@@ -1215,6 +1229,73 @@ each with a shared contract test in the conformance suite (§12):
1215
1229
  "Across every writer" means across threads, processes, and hosts sharing the same
1216
1230
  storage, not just within one instance.
1217
1231
 
1232
+ ### 6.8 HTTP/JSON Wire Protocol & Subprocess Bridge
1233
+
1234
+ To enable portable, cross-language interoperation across diverse runtimes (Node.js, Python,
1235
+ Ruby, Go, Rust), SSSS defines a normative HTTP/JSON wire transport contract and a standard
1236
+ subprocess bridge. A host or engine that exposes SSSS over a network MUST conform to this
1237
+ wire contract.
1238
+
1239
+ #### Endpoint & HTTP Headers
1240
+
1241
+ - **Endpoint**: `POST /v1/operations` (hosts MAY also route `POST /` to this handler for convenience).
1242
+ - **Request Content-Type**: `application/json`.
1243
+ - **Response Content-Type**: `application/json`.
1244
+ - **Authentication**: When remote authentication is required, the client MUST present
1245
+ a bearer token via the `Authorization: Bearer <token>` HTTP header. The host verifies
1246
+ the token and resolves it out-of-band to a verified principal (§6.6). The envelope body
1247
+ itself carries no secret credentials.
1248
+ - **Health check**: A host exposing the HTTP interface SHOULD expose `GET /health`, returning
1249
+ HTTP 200 with `{ "ok": true, "version": "<version>" }`.
1250
+
1251
+ #### Request and Response Wire Contract
1252
+
1253
+ - **Request Body**: An Operation Contract envelope (§6.1, §6.2) as a JSON object:
1254
+ ```json
1255
+ {
1256
+ "type": "operation",
1257
+ "idempotency_key": "00000000-0000-0000-0000-000000000001",
1258
+ "path": "assistants/support-bot.md",
1259
+ "workspace_id": "ws-main",
1260
+ "content": "---\ntype: assistant\nname: Support Bot\ntitle: Support Bot\ndescription: Customer support assistant\ntimestamp: 2026-07-02T00:00:00Z\n---\nHelp customers.\n"
1261
+ }
1262
+ ```
1263
+ Callers attaching an explicit principal context MAY submit:
1264
+ ```json
1265
+ {
1266
+ "envelope": {
1267
+ "type": "operation",
1268
+ "idempotency_key": "00000000-0000-0000-0000-000000000001",
1269
+ "path": "assistants/support-bot.md",
1270
+ "workspace_id": "ws-main",
1271
+ "content": "---\ntype: assistant\nname: Support Bot\ntitle: Support Bot\ndescription: Customer support assistant\ntimestamp: 2026-07-02T00:00:00Z\n---\nHelp customers.\n"
1272
+ },
1273
+ "principal": {
1274
+ "id": "agent-1",
1275
+ "kind": "agent",
1276
+ "workspaceIds": ["ws-main"],
1277
+ "capabilities": ["*:*"],
1278
+ "authentication": { "provider": "token", "assurance": "verified" }
1279
+ }
1280
+ }
1281
+ ```
1282
+ - **Response Body**: The kernel response object (§6.5) serialized as JSON:
1283
+ - On commit: `201 Created` with `{ "success": true, "type": "operation", ... }`.
1284
+ - On idempotent replay or dry run: `200 OK` with `{ "success": true, "replay": true, ... }`.
1285
+ - On error: status mapped from `ERROR_STATUS` (§6.5) with `{ "success": false, "error": { "code": "...", "message": "..." } }`.
1286
+
1287
+ #### Subprocess Bridge Contract (`ssss bridge`)
1288
+
1289
+ For local process orchestration without opening network ports (for example, Python hosts
1290
+ integrating SSSS as a subprocess), the reference CLI provides `ssss bridge`:
1291
+
1292
+ - Reads JSON from standard input (`stdin`). Supports:
1293
+ 1. A single JSON request object.
1294
+ 2. A JSON array of requests (`[...]`).
1295
+ 3. Newline-delimited JSON (`NDJSON`), with one request per line when running with `--stream`.
1296
+ - Writes JSON response objects to standard output (`stdout`) matching the wire contract shape.
1297
+ - Does not alter envelope semantics, schema validation, or error status mappings.
1298
+
1218
1299
  ---
1219
1300
 
1220
1301
  ## 7. Leases — Concurrency Control
@@ -1868,6 +1949,23 @@ of `source: user` or `source: system` — e.g. the phone number, domain, and mai
1868
1949
  must bind. This is how the standard guarantees a sold business says exactly what real-world
1869
1950
  resources it needs without shipping the seller's own.
1870
1951
 
1952
+ #### 16.5.1 Frontmatter Parameter Binding (`x_bind`)
1953
+
1954
+ Values bind exclusively through **frontmatter**, never through free-text string substitution in Markdown body text. A template document declares frontmatter parameter bindings using the reserved `x_bind` mapping:
1955
+
1956
+ ```yaml
1957
+ x_bind:
1958
+ channel_id: support_channel_id
1959
+ notify_email: primary_admin_email
1960
+ ```
1961
+
1962
+ During `provisionBundle` (§17.1):
1963
+ 1. For each `field: parameter_key` entry in `x_bind`, the provisioner resolves the value from the supplied `parameters` dictionary, or the parameter's `defaultValue` if omitted.
1964
+ 2. If a parameter is `required: true` and has no supplied value or default, provisioning fails with `unresolved`.
1965
+ 3. The resolved value MUST match the parameter's declared `type` (`string`, `boolean`, `number`, etc.).
1966
+ 4. The target frontmatter field is assigned the resolved value, and the `x_bind` property is stripped from the provisioned output document.
1967
+ 5. In `template` and `sale` export profiles (§5.5), when `reduceResourceBoundFile()` strips sensitive infrastructure values from `resource.binds` (such as domain names or phone numbers), it automatically emits corresponding `x_bind` bindings targeting the synthesized resource requirement parameters.
1968
+
1871
1969
  ---
1872
1970
 
1873
1971
  ## 17. The Provisioning Contract
@@ -1916,12 +2014,18 @@ binding each requires.
1916
2014
  ### 17.3 Determinism, Id-Remap & Link Integrity
1917
2015
 
1918
2016
  When a bundle is provisioned into a target vault, identifiers (slugs, workspace ids,
1919
- cross-file links) are **remapped** so the imported business is a fresh, self-consistent
1920
- instance rather than a clone aliasing the seller's ids. Remapping MUST preserve link
1921
- integrity: every internal `[[link]]` / relative reference that resolved in the source bundle
1922
- MUST resolve to the remapped target after import. This is the same rewrite engine the
1923
- semantic layer's autolink (§11) uses, run with a bundle-relative id map. An importer that
1924
- cannot resolve a link MUST fail the provision rather than emit a dangling reference.
2017
+ cross-file links, and typed references) are **remapped** so the imported business is a fresh, self-consistent
2018
+ instance rather than a clone aliasing the seller's ids.
2019
+
2020
+ Given a target `workspaceId` and optional `pathPrefix`:
2021
+ 1. **Deterministic ID Map:** Every source identifier or slug derives a deterministic target identifier:
2022
+ `newId = "<pathPrefix>-" + sha256(workspaceId + ":" + sourceId).slice(0, 12)`
2023
+ 2. **Path Prefixing:** Target file paths are placed under the path prefix:
2024
+ `targetPath = pathPrefix ? `${pathPrefix}/${sourcePath}` : sourcePath`
2025
+ 3. **Identifier Fields:** Frontmatter identifier fields (`slug`, `id`, `workflow_id`, `run_id`, `task_id`, `migration_id`) are mapped to their derived `newId`.
2026
+ 4. **Link Rewriting:** Body wiki-links `[[source-slug]]` and relative Markdown links `[title](source-path.md)` are rewritten to the target slug and target relative path.
2027
+ 5. **Registry-Declared Typed References (§9):** Frontmatter fields constrained by `references` (e.g. `asset.resource_ref`, `trigger.target_workflow`) that point to relative paths within the bundle MUST be rewritten to point to their remapped target paths under `pathPrefix`.
2028
+ 6. **Link & Reference Integrity:** Remapping MUST preserve link integrity: every internal `[[link]]`, relative markdown link, and typed frontmatter reference that resolved in the source bundle MUST resolve to the remapped target after import. An importer that cannot resolve a link or reference MUST fail the provision (`danglingLinks`) rather than emit a dangling reference.
1925
2029
 
1926
2030
  ### 17.4 Composition & Upgrade
1927
2031
 
@@ -1946,6 +2050,7 @@ MUST NOT be repurposed by hosts:
1946
2050
  `aliases`, `feedback`, `confidence`.
1947
2051
  - Lifecycle: `slug`, `schema_version`, `status`.
1948
2052
  - Portability (§5.5): `x_portability`.
2053
+ - Parameter Binding (§16.5): `x_bind`.
1949
2054
 
1950
2055
  The patch key `__body__` (§6.2) is reserved and can never be a frontmatter key.
1951
2056
 
@@ -25,14 +25,36 @@ export function requiredCapabilities(definition, action) {
25
25
  return [`${primitive}:${action}`];
26
26
  }
27
27
 
28
+ function matchPathGlob(pattern, filePath) {
29
+ if (!pattern || !filePath) return false;
30
+ if (pattern === filePath || pattern === '**' || pattern === '*') return true;
31
+ const escaped = pattern
32
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
33
+ .replace(/\*\*/g, '___DOUBLE_STAR___')
34
+ .replace(/\*/g, '[^/]*')
35
+ .replace(/___DOUBLE_STAR___/g, '.*');
36
+ return new RegExp(`^${escaped}$`).test(filePath);
37
+ }
38
+
28
39
  /**
29
40
  * Does a granted capability cover a required one? Exact match, `*:*`, or a
30
41
  * trailing-`*` prefix: `ssss:*` covers every core capability and
31
- * `ssss:assistant:*` covers every action on assistants.
42
+ * `ssss:assistant:*` covers every action on assistants. Supports optional
43
+ * path scoping with `@<glob>`, e.g. `ssss:rule:*@rules/marketing/**`.
32
44
  */
33
- export function capabilityCovers(granted, required) {
34
- if (granted === required || granted === '*:*') return true;
35
- return typeof granted === 'string' && granted.endsWith(':*') && required.startsWith(granted.slice(0, -1));
45
+ export function capabilityCovers(granted, required, targetPath = null) {
46
+ let grantCapability = granted;
47
+ let pathPattern = null;
48
+ if (typeof granted === 'string' && granted.includes('@')) {
49
+ const atIndex = granted.indexOf('@');
50
+ grantCapability = granted.slice(0, atIndex);
51
+ pathPattern = granted.slice(atIndex + 1);
52
+ }
53
+ if (pathPattern !== null) {
54
+ if (!targetPath || !matchPathGlob(pathPattern, targetPath)) return false;
55
+ }
56
+ if (grantCapability === required || grantCapability === '*:*') return true;
57
+ return typeof grantCapability === 'string' && grantCapability.endsWith(':*') && required.startsWith(grantCapability.slice(0, -1));
36
58
  }
37
59
 
38
60
  export function createCapabilityAuthorizer(options = {}) {
@@ -43,16 +65,45 @@ export function createCapabilityAuthorizer(options = {}) {
43
65
  if (principal.kind !== 'system' && !principal.workspaceIds.includes(workspaceId)) {
44
66
  return { allowed: false, reason: `Principal '${principal.id}' is not scoped to workspace '${workspaceId}'.` };
45
67
  }
46
- const floorKey = definition?.qualified_type || definition?.primitive_id || definition?.type;
68
+ const namespace = definition?.namespace || 'ssss';
69
+ const aliases = Array.isArray(definition?.aliases) ? definition.aliases : [];
70
+ const floorKeys = new Set(
71
+ [
72
+ definition?.qualified_type,
73
+ definition?.primitive_id,
74
+ definition?.type,
75
+ ...aliases.flatMap((alias) => [
76
+ alias,
77
+ alias.includes(':') ? alias : `${namespace}:${alias}`,
78
+ ]),
79
+ ].filter(Boolean),
80
+ );
81
+ const floorCapabilities = [...floorKeys].flatMap((key) => policyFloors[key]?.[action] || []);
47
82
  const required = new Set([
48
83
  ...requiredCapabilities(definition, action),
49
- ...(policyFloors[floorKey]?.[action] || []),
84
+ ...floorCapabilities,
50
85
  ...(context.policyFloor || []),
51
86
  ]);
52
87
  const granted = new Set(principal.capabilities || []);
53
88
  if (principal.kind === 'system' && options.systemBypass !== false) return { allowed: true, required: [...required] };
89
+ const targetPath = context.path || null;
90
+ const defPrefixes = [...new Set([definition?.qualified_type, definition?.primitive_id, definition?.type].filter(Boolean))];
54
91
  const missing = [...required].filter((capability) =>
55
- ![...granted].some((grant) => capabilityCovers(grant, capability)));
92
+ ![...granted].some((grant) => {
93
+ if (capabilityCovers(grant, capability, targetPath)) return true;
94
+ if (!aliases.length) return false;
95
+ const matchedPrefix = defPrefixes.find((prefix) => capability.startsWith(`${prefix}:`));
96
+ if (!matchedPrefix) return false;
97
+ const actionTail = capability.slice(matchedPrefix.length + 1);
98
+ return aliases.some((alias) => {
99
+ const qualifiedAlias = alias.includes(':') ? alias : `${namespace}:${alias}`;
100
+ const bareAlias = alias.includes(':') ? alias.slice(alias.indexOf(':') + 1) : alias;
101
+ return (
102
+ capabilityCovers(grant, `${qualifiedAlias}:${actionTail}`, targetPath) ||
103
+ capabilityCovers(grant, `${bareAlias}:${actionTail}`, targetPath)
104
+ );
105
+ });
106
+ }));
56
107
  if (missing.length) return { allowed: false, required: [...required], missing, reason: `Missing capabilities: ${missing.join(', ')}.` };
57
108
  if (context.requiresHumanConfirmation && principal.kind !== 'human') {
58
109
  return { allowed: false, required: [...required], reason: 'A verified human confirmation is required.' };