@gregiteen/ssss-cli 0.14.0 → 0.14.2
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 +42 -0
- package/VERSION +1 -1
- package/conformance/reference-bundle.ucw.json +1 -1
- package/docs/ssss-spec.md +15 -12
- package/package.json +4 -1
- package/registry/core.json +15 -1
- package/schemas/bundle.schema.json +259 -0
- package/schemas/core-registry.schema.json +386 -0
- package/scripts/audit-registry-field-usage.mjs +3 -0
- package/scripts/check-commit-durability.mjs +1 -0
- package/scripts/cmd-schema.mjs +62 -0
- package/scripts/conformance-09.mjs +147 -0
- package/scripts/ssss.mjs +2 -0
- package/skills/ssss/SKILL.md +2 -2
- package/skills/ssss/core/SKILL.md +3 -3
- package/skills/ssss/core/references/core-registry.json +15 -1
- package/skills/ssss/core/references/ssss-spec.md +127 -19
- package/src/authorization.mjs +31 -3
- package/src/engine.mjs +11 -1
- package/src/kernel.mjs +3 -3
- package/src/registry.mjs +82 -3
- package/src/schema-validator.mjs +134 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://ssss.dev/schemas/core-registry.schema.json",
|
|
4
|
+
"title": "SSSS Core Registry Schema",
|
|
5
|
+
"description": "JSON Schema for SSSS core and extension registries (spec §5).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"registry",
|
|
9
|
+
"spec_version",
|
|
10
|
+
"description",
|
|
11
|
+
"document_primitives"
|
|
12
|
+
],
|
|
13
|
+
"properties": {
|
|
14
|
+
"$schema": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"registry": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"spec_version": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
"description": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
},
|
|
26
|
+
"conformance": {
|
|
27
|
+
"type": "string"
|
|
28
|
+
},
|
|
29
|
+
"document_primitives": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"$ref": "#/$defs/primitiveDefinition"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"contract_primitives": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"additionalProperties": {
|
|
38
|
+
"$ref": "#/$defs/contractPrimitive"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"universal_frontmatter": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["required"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"required": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": { "type": "string" }
|
|
48
|
+
},
|
|
49
|
+
"recommended_okf": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": { "type": "string" }
|
|
52
|
+
},
|
|
53
|
+
"recommended_semantic": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": { "type": "string" }
|
|
56
|
+
},
|
|
57
|
+
"spec_ref": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"portability": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"required": ["classes", "export_profiles"],
|
|
65
|
+
"properties": {
|
|
66
|
+
"description": { "type": "string" },
|
|
67
|
+
"classes": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": { "type": "string" }
|
|
70
|
+
},
|
|
71
|
+
"export_profiles": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"required": ["includes"],
|
|
76
|
+
"properties": {
|
|
77
|
+
"includes": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": { "type": "string" }
|
|
80
|
+
},
|
|
81
|
+
"excludes": {
|
|
82
|
+
"type": "array",
|
|
83
|
+
"items": { "type": "string" }
|
|
84
|
+
},
|
|
85
|
+
"resource_bound": { "type": "string" },
|
|
86
|
+
"note": { "type": "string" }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"conformance": { "type": "string" },
|
|
91
|
+
"spec_ref": { "type": "string" }
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"bundle": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"required": [
|
|
97
|
+
"container",
|
|
98
|
+
"structure",
|
|
99
|
+
"manifest_required_fields",
|
|
100
|
+
"provenance_required_fields"
|
|
101
|
+
],
|
|
102
|
+
"properties": {
|
|
103
|
+
"description": { "type": "string" },
|
|
104
|
+
"container": { "type": "string" },
|
|
105
|
+
"structure": {
|
|
106
|
+
"type": "array",
|
|
107
|
+
"items": { "type": "string" }
|
|
108
|
+
},
|
|
109
|
+
"file_sort": { "type": "string" },
|
|
110
|
+
"manifest_required_fields": {
|
|
111
|
+
"type": "array",
|
|
112
|
+
"items": { "type": "string" }
|
|
113
|
+
},
|
|
114
|
+
"manifest_optional_fields": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": { "type": "string" }
|
|
117
|
+
},
|
|
118
|
+
"provenance_required_fields": {
|
|
119
|
+
"type": "array",
|
|
120
|
+
"items": { "type": "string" }
|
|
121
|
+
},
|
|
122
|
+
"provenance_optional_fields": {
|
|
123
|
+
"type": "array",
|
|
124
|
+
"items": { "type": "string" }
|
|
125
|
+
},
|
|
126
|
+
"content_hash": { "type": "string" },
|
|
127
|
+
"parameter_fields": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"required": ["required"],
|
|
130
|
+
"properties": {
|
|
131
|
+
"required": {
|
|
132
|
+
"type": "array",
|
|
133
|
+
"items": { "type": "string" }
|
|
134
|
+
},
|
|
135
|
+
"optional": {
|
|
136
|
+
"type": "array",
|
|
137
|
+
"items": { "type": "string" }
|
|
138
|
+
},
|
|
139
|
+
"source_enum": {
|
|
140
|
+
"type": "array",
|
|
141
|
+
"items": { "type": "string" }
|
|
142
|
+
},
|
|
143
|
+
"note": { "type": "string" }
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"conformance": { "type": "string" },
|
|
147
|
+
"spec_ref": { "type": "string" }
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"provisioning": {
|
|
151
|
+
"type": "object",
|
|
152
|
+
"required": [
|
|
153
|
+
"verbs",
|
|
154
|
+
"step_required_fields",
|
|
155
|
+
"system_enum",
|
|
156
|
+
"mode_enum"
|
|
157
|
+
],
|
|
158
|
+
"properties": {
|
|
159
|
+
"description": { "type": "string" },
|
|
160
|
+
"verbs": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"additionalProperties": { "type": "string" }
|
|
163
|
+
},
|
|
164
|
+
"step_required_fields": {
|
|
165
|
+
"type": "array",
|
|
166
|
+
"items": { "type": "string" }
|
|
167
|
+
},
|
|
168
|
+
"step_optional_fields": {
|
|
169
|
+
"type": "array",
|
|
170
|
+
"items": { "type": "string" }
|
|
171
|
+
},
|
|
172
|
+
"system_enum": {
|
|
173
|
+
"type": "array",
|
|
174
|
+
"items": { "type": "string" }
|
|
175
|
+
},
|
|
176
|
+
"mode_enum": {
|
|
177
|
+
"type": "array",
|
|
178
|
+
"items": { "type": "string" }
|
|
179
|
+
},
|
|
180
|
+
"edge_relations": {
|
|
181
|
+
"type": "array",
|
|
182
|
+
"items": { "type": "string" }
|
|
183
|
+
},
|
|
184
|
+
"install_modes": {
|
|
185
|
+
"type": "array",
|
|
186
|
+
"items": { "type": "string" }
|
|
187
|
+
},
|
|
188
|
+
"id_remap": { "type": "string" },
|
|
189
|
+
"upgrade": { "type": "string" },
|
|
190
|
+
"conformance": { "type": "string" },
|
|
191
|
+
"spec_ref": { "type": "string" }
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"runtime": {
|
|
195
|
+
"type": "object",
|
|
196
|
+
"required": [
|
|
197
|
+
"trigger_types",
|
|
198
|
+
"misfire_policies",
|
|
199
|
+
"concurrency_policies",
|
|
200
|
+
"task_instantiation_fields",
|
|
201
|
+
"run_statuses",
|
|
202
|
+
"idempotency_basis"
|
|
203
|
+
],
|
|
204
|
+
"properties": {
|
|
205
|
+
"description": { "type": "string" },
|
|
206
|
+
"trigger_types": {
|
|
207
|
+
"type": "array",
|
|
208
|
+
"items": { "type": "string" }
|
|
209
|
+
},
|
|
210
|
+
"misfire_policies": {
|
|
211
|
+
"type": "array",
|
|
212
|
+
"items": { "type": "string" }
|
|
213
|
+
},
|
|
214
|
+
"concurrency_policies": {
|
|
215
|
+
"type": "array",
|
|
216
|
+
"items": { "type": "string" }
|
|
217
|
+
},
|
|
218
|
+
"task_instantiation_fields": {
|
|
219
|
+
"type": "array",
|
|
220
|
+
"items": { "type": "string" }
|
|
221
|
+
},
|
|
222
|
+
"run_statuses": {
|
|
223
|
+
"type": "array",
|
|
224
|
+
"items": { "type": "string" }
|
|
225
|
+
},
|
|
226
|
+
"idempotency_basis": { "type": "string" },
|
|
227
|
+
"spec_ref": { "type": "string" }
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"semantic": {
|
|
231
|
+
"type": "object",
|
|
232
|
+
"required": [
|
|
233
|
+
"version",
|
|
234
|
+
"identity_fields",
|
|
235
|
+
"surface_fields",
|
|
236
|
+
"relation_fields",
|
|
237
|
+
"renderable_fields",
|
|
238
|
+
"language_pattern",
|
|
239
|
+
"safe_default"
|
|
240
|
+
],
|
|
241
|
+
"properties": {
|
|
242
|
+
"version": { "type": "integer" },
|
|
243
|
+
"identity_fields": {
|
|
244
|
+
"type": "array",
|
|
245
|
+
"items": { "type": "string" }
|
|
246
|
+
},
|
|
247
|
+
"surface_fields": {
|
|
248
|
+
"type": "array",
|
|
249
|
+
"items": { "type": "string" }
|
|
250
|
+
},
|
|
251
|
+
"relation_fields": {
|
|
252
|
+
"type": "array",
|
|
253
|
+
"items": { "type": "string" }
|
|
254
|
+
},
|
|
255
|
+
"renderable_fields": {
|
|
256
|
+
"type": "array",
|
|
257
|
+
"items": { "type": "string" }
|
|
258
|
+
},
|
|
259
|
+
"language_pattern": { "type": "string" },
|
|
260
|
+
"safe_default": { "type": "string" },
|
|
261
|
+
"spec_ref": { "type": "string" }
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"reserved_keys": {
|
|
265
|
+
"type": "array",
|
|
266
|
+
"items": { "type": "string" }
|
|
267
|
+
},
|
|
268
|
+
"host_field_prefix": { "type": "string" },
|
|
269
|
+
"spec_ref_reserved": { "type": "string" }
|
|
270
|
+
},
|
|
271
|
+
"$defs": {
|
|
272
|
+
"primitiveDefinition": {
|
|
273
|
+
"type": "object",
|
|
274
|
+
"required": [
|
|
275
|
+
"family",
|
|
276
|
+
"append_only",
|
|
277
|
+
"portability",
|
|
278
|
+
"required_fields"
|
|
279
|
+
],
|
|
280
|
+
"properties": {
|
|
281
|
+
"family": {
|
|
282
|
+
"type": "string"
|
|
283
|
+
},
|
|
284
|
+
"canonical_filename": {
|
|
285
|
+
"type": ["string", "null"]
|
|
286
|
+
},
|
|
287
|
+
"filename_convention": {
|
|
288
|
+
"type": "string"
|
|
289
|
+
},
|
|
290
|
+
"append_only": {
|
|
291
|
+
"type": "boolean"
|
|
292
|
+
},
|
|
293
|
+
"portability": {
|
|
294
|
+
"type": "string",
|
|
295
|
+
"enum": ["structural", "tenant_private", "resource_bound"]
|
|
296
|
+
},
|
|
297
|
+
"required_fields": {
|
|
298
|
+
"type": "array",
|
|
299
|
+
"items": { "type": "string" }
|
|
300
|
+
},
|
|
301
|
+
"required_when": {
|
|
302
|
+
"type": "object",
|
|
303
|
+
"additionalProperties": {
|
|
304
|
+
"type": "array",
|
|
305
|
+
"items": { "type": "string" }
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
"enums": {
|
|
309
|
+
"type": "object",
|
|
310
|
+
"additionalProperties": {
|
|
311
|
+
"type": "array",
|
|
312
|
+
"items": { "type": "string" }
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
"patterns": {
|
|
316
|
+
"type": "object",
|
|
317
|
+
"additionalProperties": {
|
|
318
|
+
"type": "string"
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"immutable_fields": {
|
|
322
|
+
"type": "array",
|
|
323
|
+
"items": { "type": "string" }
|
|
324
|
+
},
|
|
325
|
+
"references": {
|
|
326
|
+
"type": "object",
|
|
327
|
+
"additionalProperties": {
|
|
328
|
+
"type": "object",
|
|
329
|
+
"properties": {
|
|
330
|
+
"allowed_types": {
|
|
331
|
+
"type": "array",
|
|
332
|
+
"items": { "type": "string" }
|
|
333
|
+
},
|
|
334
|
+
"disallowed_types": {
|
|
335
|
+
"type": "array",
|
|
336
|
+
"items": { "type": "string" }
|
|
337
|
+
},
|
|
338
|
+
"allowed_portability": {
|
|
339
|
+
"type": "array",
|
|
340
|
+
"items": { "type": "string" }
|
|
341
|
+
},
|
|
342
|
+
"must_exist": {
|
|
343
|
+
"type": "boolean"
|
|
344
|
+
},
|
|
345
|
+
"hash_field": {
|
|
346
|
+
"type": "string"
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"aliases": {
|
|
352
|
+
"type": "array",
|
|
353
|
+
"items": { "type": "string" }
|
|
354
|
+
},
|
|
355
|
+
"deprecated": {
|
|
356
|
+
"type": "boolean"
|
|
357
|
+
},
|
|
358
|
+
"deprecation_notice": {
|
|
359
|
+
"type": "string"
|
|
360
|
+
},
|
|
361
|
+
"resource": {
|
|
362
|
+
"type": "object",
|
|
363
|
+
"properties": {
|
|
364
|
+
"kind": { "type": "string" },
|
|
365
|
+
"provision_relation": { "type": "string" }
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
"scopes": {
|
|
369
|
+
"type": "array",
|
|
370
|
+
"items": { "type": "string" }
|
|
371
|
+
},
|
|
372
|
+
"spec_ref": {
|
|
373
|
+
"type": "string"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
"contractPrimitive": {
|
|
378
|
+
"type": "object",
|
|
379
|
+
"required": ["purpose", "spec_ref"],
|
|
380
|
+
"properties": {
|
|
381
|
+
"purpose": { "type": "string" },
|
|
382
|
+
"spec_ref": { "type": "string" }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -45,6 +45,7 @@ const ENGINE_ENFORCED_KEYS = [
|
|
|
45
45
|
'immutable_fields',
|
|
46
46
|
'references',
|
|
47
47
|
'append_only',
|
|
48
|
+
'aliases',
|
|
48
49
|
];
|
|
49
50
|
|
|
50
51
|
// Keys that are real and load-bearing but are intentionally consumed
|
|
@@ -58,6 +59,8 @@ const KNOWN_NON_ENGINE_KEYS = new Set([
|
|
|
58
59
|
'portability', // consumed by src/registry.mjs resolvePortability() + src/bundle.mjs export filtering, not write-time validation
|
|
59
60
|
'resource', // extension-defined resource_bound metadata, consumed by src/bundle.mjs provisioning
|
|
60
61
|
'description', // human documentation on a primitive definition, same role as `notes`; never enforced at write time
|
|
62
|
+
'deprecated', // documentation / catalog deprecation status; consumed by tool displays and spec generation, not write-time validation
|
|
63
|
+
'deprecation_notice', // human guidance on migration path for deprecated primitives; consumed by documentation
|
|
61
64
|
// Product-side projection metadata. The SSSS engine never reads these: they are
|
|
62
65
|
// consumed by the host application's own projector to decide which target tables
|
|
63
66
|
// a document feeds and how (see festech.live SsssGenericProjector.ts). Kept in the
|
|
@@ -306,6 +306,7 @@ async function checkRecoveryTriggers(root) {
|
|
|
306
306
|
const untouched = read(live, 'rules/live.md').includes('in flight');
|
|
307
307
|
holder.child.kill('SIGKILL');
|
|
308
308
|
const signal = await holder.exited;
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
309
310
|
const afterKill = await fileKernel(live).recover();
|
|
310
311
|
check('recovery skips an intent whose writer is alive',
|
|
311
312
|
whileAlive.length === 1 && whileAlive[0].outcome === 'in_flight' && untouched, JSON.stringify(whileAlive));
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema export and inspection commands for SSSS.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { parseArgs, die, wantsHelp } from './lib/cli.mjs';
|
|
8
|
+
|
|
9
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const ROOT = path.resolve(HERE, '..');
|
|
11
|
+
const SCHEMAS_DIR = path.resolve(ROOT, 'schemas');
|
|
12
|
+
|
|
13
|
+
const HELP = `Usage: ssss schema core|bundle|export [options]
|
|
14
|
+
|
|
15
|
+
Commands:
|
|
16
|
+
core [--out <file>] Output the Core Registry JSON Schema.
|
|
17
|
+
bundle [--out <file>] Output the .ucw Bundle JSON Schema.
|
|
18
|
+
export [--out <dir>] Export all canonical schemas to a directory (default: ./schemas).
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
function emit(value, output) {
|
|
22
|
+
const text = typeof value === 'string' ? value : `${JSON.stringify(value, null, 2)}\n`;
|
|
23
|
+
if (output) {
|
|
24
|
+
fs.mkdirSync(path.dirname(path.resolve(output)), { recursive: true });
|
|
25
|
+
fs.writeFileSync(path.resolve(output), text, { encoding: 'utf8' });
|
|
26
|
+
} else {
|
|
27
|
+
process.stdout.write(text);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function run(argv) {
|
|
32
|
+
if (wantsHelp(argv)) { console.log(HELP); return; }
|
|
33
|
+
const verb = argv[0];
|
|
34
|
+
const { flags } = parseArgs(argv.slice(1));
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const coreSchemaPath = path.join(SCHEMAS_DIR, 'core-registry.schema.json');
|
|
38
|
+
const bundleSchemaPath = path.join(SCHEMAS_DIR, 'bundle.schema.json');
|
|
39
|
+
|
|
40
|
+
if (verb === 'core') {
|
|
41
|
+
const content = fs.readFileSync(coreSchemaPath, 'utf8');
|
|
42
|
+
emit(content, flags.out);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (verb === 'bundle') {
|
|
46
|
+
const content = fs.readFileSync(bundleSchemaPath, 'utf8');
|
|
47
|
+
emit(content, flags.out);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (verb === 'export') {
|
|
51
|
+
const targetDir = path.resolve(flags.out || './schemas');
|
|
52
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
53
|
+
fs.copyFileSync(coreSchemaPath, path.join(targetDir, 'core-registry.schema.json'));
|
|
54
|
+
fs.copyFileSync(bundleSchemaPath, path.join(targetDir, 'bundle.schema.json'));
|
|
55
|
+
console.log(`Exported canonical schemas to ${targetDir}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
die(`Unknown schema verb '${verb || ''}'.\n${HELP}`);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
die(error.message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -33,6 +33,7 @@ import { MemoryIdempotencyStore, FileIdempotencyStore, runIdempotencyContract }
|
|
|
33
33
|
import { capabilityCovers, createCapabilityAuthorizer } from '../src/authorization.mjs';
|
|
34
34
|
import { createEngine } from '../src/engine.mjs';
|
|
35
35
|
import { validateVerifiedPrincipal } from '../src/authorization.mjs';
|
|
36
|
+
import { validateJsonSchema } from '../src/schema-validator.mjs';
|
|
36
37
|
|
|
37
38
|
async function rejects(promise, pattern) {
|
|
38
39
|
try { await promise; return false; } catch (error) { return pattern.test(error.message); }
|
|
@@ -319,11 +320,109 @@ async function checkCapabilities(root, check) {
|
|
|
319
320
|
await write('lr-1', 'roles/editor/ROLE.md', role('editor', ['"write:rule"']), 'system');
|
|
320
321
|
await write('lr-2', 'roles/owner/ROLE.md', role('owner', ['"write:*"']), 'system');
|
|
321
322
|
await write('lr-3', 'roles/typed/ROLE.md', role('typed', ['"*:assistant"']), 'system');
|
|
323
|
+
await write('lr-rw', 'roles/rolewriter/ROLE.md', role('rolewriter', ['"write:role"']), 'system');
|
|
322
324
|
check('legacy roles: write:<type> grants writes to that type', (await write('lr-4', 'rules/a.md', doc('rule', 'A'), 'editor')).success);
|
|
323
325
|
check('legacy roles: write:<type> denies other types', (await write('lr-5', 'assistants/b/ASSISTANT.md', doc('assistant', 'B'), 'editor')).error?.code === 'forbidden');
|
|
324
326
|
check('legacy roles: *:<type> grants writes to that type', (await write('lr-6', 'assistants/c/ASSISTANT.md', doc('assistant', 'C'), 'typed')).success);
|
|
325
327
|
check('legacy roles: write:* grants writes to every type', (await write('lr-7', 'workflows/d/WORKFLOW.md', doc('workflow', 'D'), 'owner')).success);
|
|
326
328
|
check('legacy roles: an unknown role is forbidden, not promoted', (await write('lr-8', 'rules/e.md', doc('rule', 'E'), 'ghost')).error?.code === 'forbidden');
|
|
329
|
+
check('legacy roles: write:role grants writes to security_role (alias unification)',
|
|
330
|
+
(await write('lr-aliased-1', 'roles/sec/ROLE.md', role('sec', ['"write:rule"']), 'rolewriter')).success);
|
|
331
|
+
|
|
332
|
+
const roleDoc = (name, permissions) => `---\ntype: role\ntitle: "${name}"\ndescription: "Canonical role."\ntimestamp: "2026-09-24T00:00:00Z"\nname: "${name}"\npermissions: [${permissions.join(', ')}]\n---\n`;
|
|
333
|
+
const replaceRes = await write('lr-aliased-replace', 'roles/sec/ROLE.md', roleDoc('sec', ['"write:rule"', '"write:task"']), 'rolewriter');
|
|
334
|
+
check('kernel mutation: replace security_role with role succeeds (alias unification)', replaceRes.success);
|
|
335
|
+
|
|
336
|
+
const illegalTypeChange = await write('lr-illegal-type', 'roles/sec/ROLE.md', doc('assistant', 'SecAsst'), 'owner');
|
|
337
|
+
check('kernel mutation: replace role with assistant fails closed',
|
|
338
|
+
illegalTypeChange.error?.code === 'validation_failed' &&
|
|
339
|
+
illegalTypeChange.error?.message?.includes('Operation may not change an existing document type.'));
|
|
340
|
+
|
|
341
|
+
const convDoc = `---\ntype: conversation\ntitle: "Thread"\ndescription: "Chat transcript."\ntimestamp: "2026-09-24T00:00:00Z"\nthread_id: "t-1"\n---\n\nInitial message.\n`;
|
|
342
|
+
await engine.processOperation({ type: 'operation', workspace_id: 'w', idempotency_key: 'conv-init', path: 'threads/t1/CONVERSATION.md', content: convDoc, actor: { role: 'system' } }, vault);
|
|
343
|
+
const patchRes = await engine.processOperation({
|
|
344
|
+
type: 'patch', workspace_id: 'w', idempotency_key: 'conv-patch-alias', path: 'threads/t1/CONVERSATION.md',
|
|
345
|
+
patches: { type: 'thread', __body__: 'New reply.' },
|
|
346
|
+
actor: { role: 'system' },
|
|
347
|
+
}, vault);
|
|
348
|
+
check('kernel mutation: patch conversation with type thread succeeds (alias unification)', patchRes.success);
|
|
349
|
+
|
|
350
|
+
const regTypes = engine._registry.types;
|
|
351
|
+
check('core registry: release, model, page, conflict declare deprecated: true',
|
|
352
|
+
regTypes.get('release')?.deprecated === true &&
|
|
353
|
+
regTypes.get('model')?.deprecated === true &&
|
|
354
|
+
regTypes.get('page')?.deprecated === true &&
|
|
355
|
+
regTypes.get('conflict')?.deprecated === true);
|
|
356
|
+
|
|
357
|
+
let selfAliasRejected = false;
|
|
358
|
+
try {
|
|
359
|
+
composeRegistries({
|
|
360
|
+
core: {
|
|
361
|
+
registry: 'ssss',
|
|
362
|
+
version: '0.14.0',
|
|
363
|
+
universal_fields: ['type'],
|
|
364
|
+
portability: { classes: { structural: {} } },
|
|
365
|
+
document_primitives: {
|
|
366
|
+
rule: { family: 'core', append_only: false, portability: 'structural', required_fields: ['type'], aliases: ['rule'] },
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
extensions: [],
|
|
370
|
+
});
|
|
371
|
+
} catch (err) {
|
|
372
|
+
selfAliasRejected = err.message.includes('cannot alias itself');
|
|
373
|
+
}
|
|
374
|
+
check('core registry: self-alias on a primitive is rejected', selfAliasRejected);
|
|
375
|
+
|
|
376
|
+
let asymmetricAliasRejected = false;
|
|
377
|
+
try {
|
|
378
|
+
composeRegistries({
|
|
379
|
+
core: {
|
|
380
|
+
registry: 'ssss',
|
|
381
|
+
version: '0.14.0',
|
|
382
|
+
universal_fields: ['type'],
|
|
383
|
+
portability: { classes: { structural: {} } },
|
|
384
|
+
document_primitives: {
|
|
385
|
+
rule: { family: 'core', append_only: false, portability: 'structural', required_fields: ['type'], aliases: ['assistant'] },
|
|
386
|
+
assistant: { family: 'core', append_only: false, portability: 'structural', required_fields: ['type'] },
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
extensions: [],
|
|
390
|
+
});
|
|
391
|
+
} catch (err) {
|
|
392
|
+
asymmetricAliasRejected = err.message.includes('collides with existing primitive');
|
|
393
|
+
}
|
|
394
|
+
check('core registry: non-mutual core alias collision is rejected', asymmetricAliasRejected);
|
|
395
|
+
|
|
396
|
+
const floorAuthorizer = createCapabilityAuthorizer({
|
|
397
|
+
policyFloors: { 'ssss:role': { create: ['governance:elevated'] } },
|
|
398
|
+
});
|
|
399
|
+
const secRoleDef = regTypes.get('security_role');
|
|
400
|
+
const floorBlocked = await floorAuthorizer({
|
|
401
|
+
principal: {
|
|
402
|
+
id: 'u1',
|
|
403
|
+
kind: 'agent',
|
|
404
|
+
workspaceIds: ['w'],
|
|
405
|
+
capabilities: ['ssss:security_role:create'],
|
|
406
|
+
authentication: { provider: 'token', assurance: 'verified' },
|
|
407
|
+
},
|
|
408
|
+
workspaceId: 'w',
|
|
409
|
+
definition: secRoleDef,
|
|
410
|
+
action: 'create',
|
|
411
|
+
});
|
|
412
|
+
const floorAllowed = await floorAuthorizer({
|
|
413
|
+
principal: {
|
|
414
|
+
id: 'u1',
|
|
415
|
+
kind: 'agent',
|
|
416
|
+
workspaceIds: ['w'],
|
|
417
|
+
capabilities: ['ssss:security_role:create', 'governance:elevated'],
|
|
418
|
+
authentication: { provider: 'token', assurance: 'verified' },
|
|
419
|
+
},
|
|
420
|
+
workspaceId: 'w',
|
|
421
|
+
definition: secRoleDef,
|
|
422
|
+
action: 'create',
|
|
423
|
+
});
|
|
424
|
+
check('authorization: policyFloors on ssss:role apply when mutating security_role alias',
|
|
425
|
+
floorBlocked.allowed === false && floorBlocked.missing?.includes('governance:elevated') && floorAllowed.allowed === true);
|
|
327
426
|
|
|
328
427
|
const pathPrincipal = {
|
|
329
428
|
id: 'scoped-user',
|
|
@@ -340,6 +439,32 @@ async function checkCapabilities(root, check) {
|
|
|
340
439
|
(await writeScoped('ps-2', 'rules/engineering/lint.md', doc('rule', 'Lint'))).error?.code === 'forbidden');
|
|
341
440
|
}
|
|
342
441
|
|
|
442
|
+
function checkJsonSchemas(check) {
|
|
443
|
+
const coreRegistry = JSON.parse(fs.readFileSync(path.resolve('registry/core.json'), 'utf8'));
|
|
444
|
+
const coreSchema = JSON.parse(fs.readFileSync(path.resolve('schemas/core-registry.schema.json'), 'utf8'));
|
|
445
|
+
const coreResult = validateJsonSchema(coreRegistry, coreSchema);
|
|
446
|
+
check('schema: registry/core.json validates against schemas/core-registry.schema.json', coreResult.valid, coreResult.errors?.[0]?.message);
|
|
447
|
+
|
|
448
|
+
const bundle = JSON.parse(fs.readFileSync(path.resolve('conformance/reference-bundle.ucw.json'), 'utf8'));
|
|
449
|
+
const bundleSchema = JSON.parse(fs.readFileSync(path.resolve('schemas/bundle.schema.json'), 'utf8'));
|
|
450
|
+
const bundleResult = validateJsonSchema(bundle, bundleSchema);
|
|
451
|
+
check('schema: reference-bundle.ucw.json validates against schemas/bundle.schema.json', bundleResult.valid, bundleResult.errors?.[0]?.message);
|
|
452
|
+
|
|
453
|
+
const badCore = { ...coreRegistry };
|
|
454
|
+
delete badCore.spec_version;
|
|
455
|
+
const badCoreResult = validateJsonSchema(badCore, coreSchema);
|
|
456
|
+
check('schema: core registry schema rejects missing spec_version', !badCoreResult.valid && badCoreResult.errors.some((e) => e.path.includes('spec_version')));
|
|
457
|
+
|
|
458
|
+
const badPortability = JSON.parse(JSON.stringify(coreRegistry));
|
|
459
|
+
badPortability.document_primitives.memory.portability = 'illegal_class';
|
|
460
|
+
const badPortResult = validateJsonSchema(badPortability, coreSchema);
|
|
461
|
+
check('schema: core registry schema rejects invalid portability enum', !badPortResult.valid && badPortResult.errors.some((e) => e.path.includes('portability')));
|
|
462
|
+
|
|
463
|
+
const badBundle = { files: [] };
|
|
464
|
+
const badBundleResult = validateJsonSchema(badBundle, bundleSchema);
|
|
465
|
+
check('schema: bundle schema rejects missing manifest', !badBundleResult.valid && badBundleResult.errors.some((e) => e.path.includes('manifest')));
|
|
466
|
+
}
|
|
467
|
+
|
|
343
468
|
// Resource hooks (spec §6.3): every prepare ends in exactly one finalize or
|
|
344
469
|
// reconcile, and nothing after the event append may undo the commit.
|
|
345
470
|
async function checkResourceLifecycle(check) {
|
|
@@ -432,6 +557,27 @@ async function checkResourceLifecycle(check) {
|
|
|
432
557
|
check('references: a reference to a disallowed type is rejected', wrongType.error?.code === 'validation_failed');
|
|
433
558
|
check('references: a patch cannot repoint a reference at a missing document', repointed.error?.code === 'validation_failed');
|
|
434
559
|
|
|
560
|
+
const withRoleRefs = createValidator({ extensions: [{
|
|
561
|
+
registry: 'acme', extends: 'core',
|
|
562
|
+
document_primitives: {
|
|
563
|
+
grant_doc: {
|
|
564
|
+
family: 'extension', append_only: false, portability: 'structural', required_fields: ['type', 'name', 'role_ref'],
|
|
565
|
+
references: { role_ref: { allowed_types: ['role'] } },
|
|
566
|
+
},
|
|
567
|
+
deny_role_doc: {
|
|
568
|
+
family: 'extension', append_only: false, portability: 'structural', required_fields: ['type', 'name', 'target_ref'],
|
|
569
|
+
references: { target_ref: { disallowed_types: ['role'] } },
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
}] });
|
|
573
|
+
const roleRefKernel = createKernel({ vfs: new MemoryVfs(), eventStore: new MemoryEventStore(), validator: withRoleRefs });
|
|
574
|
+
const writeRef = (key, pathName, content) => roleRefKernel.execute({ type: 'operation', workspace_id: 'w', idempotency_key: key, path: pathName, content }, { principal });
|
|
575
|
+
await writeRef('rr-1', 'roles/admin.md', `---\ntype: security_role\ntitle: "Admin"\ndescription: "D"\ntimestamp: "2026-09-24T00:00:00Z"\nrole_id: "admin"\nname: "Admin"\npermissions: ["*:*"]\n---\n`);
|
|
576
|
+
const allowedViaAlias = await writeRef('rr-2', 'grants/g1.md', `---\ntype: grant_doc\ntitle: "G"\ndescription: "D"\ntimestamp: "2026-09-24T00:00:00Z"\nname: "G"\nrole_ref: "roles/admin.md"\n---\n`);
|
|
577
|
+
const disallowedViaAlias = await writeRef('rr-3', 'denies/d1.md', `---\ntype: deny_role_doc\ntitle: "D"\ndescription: "D"\ntimestamp: "2026-09-24T00:00:00Z"\nname: "D"\ntarget_ref: "roles/admin.md"\n---\n`);
|
|
578
|
+
check('references: allowed_types role accepts aliased security_role target', allowedViaAlias.success);
|
|
579
|
+
check('references: disallowed_types role rejects aliased security_role target', disallowedViaAlias.error?.code === 'validation_failed');
|
|
580
|
+
|
|
435
581
|
// Every failure carries a symbolic code that maps to the §6.5 status table.
|
|
436
582
|
const plain = createKernel({ vfs: new MemoryVfs(), eventStore: new MemoryEventStore(), leaseStore: new MemoryLeaseStore() });
|
|
437
583
|
const missing = await plain.execute({ type: 'patch', workspace_id: 'w', idempotency_key: 'ec-404', path: 'rules/none.md', patches: { name: 'x' } }, { principal });
|
|
@@ -666,6 +812,7 @@ export async function runKernel09Conformance() {
|
|
|
666
812
|
checkFrontmatterSubset(check);
|
|
667
813
|
checkRequiredPresence(check);
|
|
668
814
|
await checkCapabilities(temp, check);
|
|
815
|
+
checkJsonSchemas(check);
|
|
669
816
|
} finally {
|
|
670
817
|
fs.rmSync(temp, { recursive: true, force: true });
|
|
671
818
|
}
|
package/scripts/ssss.mjs
CHANGED
|
@@ -41,6 +41,7 @@ Authoring, wire protocol & conformance:
|
|
|
41
41
|
bridge [options] Execute operations over stdin/stdout JSON (spec §6.8).
|
|
42
42
|
primitive <verb> Create, validate, inspect, migrate, or emit types.
|
|
43
43
|
registry <verb> Compose, lock, or verify an effective registry.
|
|
44
|
+
schema <verb> Output or export canonical JSON Schemas (core, bundle).
|
|
44
45
|
adapter conformance Run machine-readable adapter contract suites.
|
|
45
46
|
migrate 0.8-to-0.9 Diagnose and plan a non-destructive 0.9 migration.
|
|
46
47
|
autolink [dir] Generate OKF wiki links across a vault.
|
|
@@ -72,6 +73,7 @@ const MODULES = {
|
|
|
72
73
|
semantic: './cmd-semantic.mjs',
|
|
73
74
|
primitive: './cmd-primitive.mjs',
|
|
74
75
|
registry: './cmd-registry.mjs',
|
|
76
|
+
schema: './cmd-schema.mjs',
|
|
75
77
|
adapter: './cmd-adapter.mjs',
|
|
76
78
|
migrate: './cmd-migrate.mjs',
|
|
77
79
|
serve: './cmd-serve.mjs',
|