@gregiteen/ssss-cli 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +11 -12
  2. package/VERSION +1 -1
  3. package/conformance/README.md +1 -1
  4. package/conformance/fixtures.json +4 -48
  5. package/conformance/reference-bundle.ucw.json +4 -21
  6. package/docs/help/bundle.md +2 -2
  7. package/docs/help/overview.md +4 -4
  8. package/docs/help/semantic.md +5 -4
  9. package/docs/ssss-spec.md +32 -77
  10. package/package.json +22 -3
  11. package/registry/core.json +21 -28
  12. package/scripts/build-reference-bundle.mjs +0 -20
  13. package/scripts/cmd-adapter.mjs +29 -0
  14. package/scripts/cmd-import.mjs +1 -1
  15. package/scripts/cmd-migrate.mjs +26 -0
  16. package/scripts/cmd-new.mjs +5 -5
  17. package/scripts/cmd-primitive.mjs +48 -0
  18. package/scripts/cmd-registry.mjs +25 -0
  19. package/scripts/cmd-semantic.mjs +6 -8
  20. package/scripts/conformance-09.mjs +199 -0
  21. package/scripts/conformance.mjs +106 -186
  22. package/scripts/ssss.mjs +8 -2
  23. package/skills/ssss/SKILL.md +11 -15
  24. package/skills/ssss/evals/evals.json +3 -3
  25. package/skills/ssss/references/contract-reference.md +7 -8
  26. package/skills/ssss/scripts/validate-schema.mjs +1 -44
  27. package/src/authorization.mjs +61 -0
  28. package/src/bundle.mjs +4 -4
  29. package/src/engine.mjs +101 -503
  30. package/src/events.mjs +90 -0
  31. package/src/guard.mjs +32 -0
  32. package/src/http.mjs +26 -0
  33. package/src/idempotency.mjs +55 -0
  34. package/src/kernel.mjs +260 -0
  35. package/src/leases.mjs +125 -0
  36. package/src/primitive.mjs +227 -0
  37. package/src/projections.mjs +62 -0
  38. package/src/registry.mjs +190 -39
  39. package/src/semantic.mjs +124 -316
  40. package/src/ui.mjs +100 -0
  41. package/src/validator.mjs +87 -0
  42. package/src/vfs.mjs +203 -0
  43. package/types/kernel.d.ts +48 -0
  44. package/docs/help/localization.md +0 -43
  45. package/scripts/cmd-localize.mjs +0 -44
@@ -20,17 +20,25 @@ import { fileURLToPath } from 'node:url';
20
20
  import { createEngine } from '../src/engine.mjs';
21
21
  import {
22
22
  DEFAULT_EXPORTER,
23
+ contentHash,
23
24
  exportBundle,
24
25
  validateBundle,
25
26
  provisionBundle,
26
27
  importBundle,
27
28
  } from '../src/bundle.mjs';
28
29
  import { parseDocument } from '../src/frontmatter.mjs';
29
- import { documentHash, loadRegistries } from '../src/registry.mjs';
30
+ import { loadRegistries } from '../src/registry.mjs';
30
31
  import { createRunEnvelope, planWorkflowTrigger } from '../src/runtime.mjs';
31
- import { buildSemanticIndex, materializeLocale, searchSemanticIndex } from '../src/semantic.mjs';
32
+ import {
33
+ buildSemanticIndex,
34
+ enrichSemanticIndex,
35
+ renderSemanticRecord,
36
+ searchSemanticIndex,
37
+ searchSemanticIndexWithAdapter,
38
+ } from '../src/semantic.mjs';
32
39
  import { auditRegistryFieldUsage } from './audit-registry-field-usage.mjs';
33
40
  import { validateSkills } from './validate-skills.mjs';
41
+ import { runKernel09Conformance } from './conformance-09.mjs';
34
42
 
35
43
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
44
  const FIXTURES = path.resolve(__dirname, '..', 'conformance', 'fixtures.json');
@@ -155,14 +163,14 @@ function validateRegistry() {
155
163
  * (spec §12) without needing a live host. Fixtures run in array order so that
156
164
  * create → patch → delete dependencies resolve.
157
165
  */
158
- function runAgainstEngine(doc) {
166
+ async function runAgainstEngine(doc) {
159
167
  const vault = fs.mkdtempSync(path.join(os.tmpdir(), 'ssss-vault-'));
160
168
  const engine = createEngine();
161
169
  let pass = 0, fail = 0;
162
170
  try {
163
171
  for (const f of doc.fixtures) {
164
172
  const want = f.expected_response || {};
165
- const res = engine.processOperation(f.request, vault);
173
+ const res = await engine.processOperation(f.request, vault);
166
174
  const okSuccess = want.success == null || res.success === want.success;
167
175
  const wantType = want.validation && want.validation.type;
168
176
  const okType = wantType == null || (res.validation && res.validation.type === wantType);
@@ -192,7 +200,7 @@ function runAgainstEngine(doc) {
192
200
  * - no `tenant_private` primitive ever lands (the §5.5 keystone).
193
201
  * Returns true on full pass.
194
202
  */
195
- function runBundleConformance() {
203
+ async function runBundleConformance() {
196
204
  if (!fs.existsSync(REFERENCE_BUNDLE)) {
197
205
  console.log(' ⚠ No reference bundle; run `node scripts/build-reference-bundle.mjs` first.');
198
206
  return false;
@@ -260,15 +268,23 @@ function runBundleConformance() {
260
268
  !unknownExtensionValidation.valid && unknownExtensionValidation.errors.some((e) => e.includes("required extension 'missing-extension'")),
261
269
  unknownExtensionValidation.errors.join('; '));
262
270
 
263
- const invalidTranslationBundle = JSON.parse(JSON.stringify(bundle));
264
- const invalidTranslation = invalidTranslationBundle.files.find((file) =>
265
- parseDocument(file.content).data.type === 'translation');
266
- invalidTranslation.content = invalidTranslation.content.replace('locale: es', 'locale: invalid_locale!');
267
- const invalidTranslationValidation = validateBundle(invalidTranslationBundle, { registryDir: REGISTRY_DIR });
271
+ const invalidPatternBundle = JSON.parse(JSON.stringify(bundle));
272
+ const invalidPattern = invalidPatternBundle.files.find((file) => parseDocument(file.content).data.type === 'rule');
273
+ invalidPattern.path = 'primitives/invalid.md';
274
+ invalidPattern.content = [
275
+ '---', 'type: primitive', 'title: Invalid Primitive', 'description: Invalid namespace pattern.',
276
+ 'timestamp: 2026-07-10T00:00:00Z', 'primitive_id: invalid!:thing', 'namespace: invalid!',
277
+ 'version: 1', 'name: Invalid', 'mutation: replace', 'portability: structural',
278
+ 'scopes: [workspace]', 'fields: [placeholder]', '---', '', 'Invalid.', '',
279
+ ].join('\n');
280
+ invalidPatternBundle.manifest.primitive_inventory.rule--;
281
+ invalidPatternBundle.manifest.primitive_inventory.primitive = 1;
282
+ invalidPatternBundle.manifest.provenance.content_hash = contentHash(invalidPatternBundle.files);
283
+ const invalidPatternValidation = validateBundle(invalidPatternBundle, { registryDir: REGISTRY_DIR });
268
284
  check('bundle validation enforces registry patterns before import',
269
- !invalidTranslationValidation.valid &&
270
- invalidTranslationValidation.errors.some((error) => error.includes('registry pattern')),
271
- invalidTranslationValidation.errors.join('; '));
285
+ !invalidPatternValidation.valid &&
286
+ invalidPatternValidation.errors.some((error) => error.includes('registry pattern')),
287
+ invalidPatternValidation.errors.join('; '));
272
288
 
273
289
  const prov = provisionBundle(bundle, { workspaceId: 'ws-conf', parameters: { business_name: 'Demo', domain: 'demo.example' } });
274
290
  check('provision resolves params + link integrity (no dangling [[links]])', prov.ok,
@@ -277,11 +293,11 @@ function runBundleConformance() {
277
293
  const vault = fs.mkdtempSync(path.join(os.tmpdir(), 'ssss-bundle-'));
278
294
  try {
279
295
  const engine = createEngine({ registryDir: REGISTRY_DIR });
280
- const first = importBundle(prov.plan, vault, engine);
296
+ const first = await importBundle(prov.plan, vault, engine);
281
297
  check('import commits every file', first.ok && first.committed === bundle.files.length,
282
298
  `ok=${first.ok} committed=${first.committed}/${bundle.files.length}`);
283
299
 
284
- const second = importBundle(prov.plan, vault, engine);
300
+ const second = await importBundle(prov.plan, vault, engine);
285
301
  check('re-import is idempotent (0 new commits)', second.ok && second.committed === 0,
286
302
  `committed=${second.committed}`);
287
303
 
@@ -305,17 +321,17 @@ function runBundleConformance() {
305
321
  },
306
322
  {
307
323
  type: 'operation', idempotency_key: 'atomic-invalid', workspace_id: 'ws-atomic',
308
- path: 'translations/bad/rules/atomic.md', actor: { role: 'system' },
324
+ path: 'primitives/invalid.md', actor: { role: 'system' },
309
325
  content: [
310
- '---', 'type: translation', 'title: Invalid Translation',
326
+ '---', 'type: primitive', 'title: Invalid Primitive',
311
327
  'description: Must fail before the source commits.', 'timestamp: 2026-07-10T00:00:00Z',
312
- 'translation_id: invalid-atomic', 'source_path: rules/atomic.md',
313
- `source_hash: ${documentHash(sourceContent)}`, 'locale: invalid_locale!',
314
- 'status: approved', 'translated_fields: [body]', '---', '', 'Translated.', '',
328
+ 'primitive_id: invalid!:thing', 'namespace: invalid!', 'version: 1', 'name: Invalid',
329
+ 'mutation: replace', 'portability: structural', 'scopes: [workspace]',
330
+ 'fields: [placeholder]', '---', '', 'Invalid.', '',
315
331
  ].join('\n'),
316
332
  },
317
333
  ];
318
- const atomicResult = importBundle(badPlan, atomicVault, engine);
334
+ const atomicResult = await importBundle(badPlan, atomicVault, engine);
319
335
  check('two-phase import prevents partial commits on a late invalid envelope',
320
336
  atomicResult.ok === false && atomicResult.committed === 0 &&
321
337
  !fs.existsSync(path.join(atomicVault, 'rules', 'atomic.md')),
@@ -333,7 +349,7 @@ function runBundleConformance() {
333
349
  return pass === checks.length;
334
350
  }
335
351
 
336
- function runOperationContractRegressionConformance() {
352
+ async function runOperationContractRegressionConformance() {
337
353
  const checks = [];
338
354
  const check = (name, cond, detail = '') => { checks.push({ name, cond, detail }); };
339
355
 
@@ -342,7 +358,7 @@ function runOperationContractRegressionConformance() {
342
358
  try {
343
359
  const engine = createEngine({ registryDir: REGISTRY_DIR, leaseStore });
344
360
  const workflowPath = 'workflows/nested.md';
345
- const createRes = engine.processOperation({
361
+ const createRes = await engine.processOperation({
346
362
  type: 'operation',
347
363
  idempotency_key: 'nested-create',
348
364
  workspace_id: 'ws-reg',
@@ -368,7 +384,7 @@ function runOperationContractRegressionConformance() {
368
384
  }, vault);
369
385
  check('nested frontmatter fixture commits before patch', createRes.success === true, JSON.stringify(createRes.validation?.errors || []));
370
386
 
371
- const patchRes = engine.processOperation({
387
+ const patchRes = await engine.processOperation({
372
388
  type: 'patch',
373
389
  idempotency_key: 'nested-patch',
374
390
  workspace_id: 'ws-reg',
@@ -398,13 +414,13 @@ function runOperationContractRegressionConformance() {
398
414
  actor: { role: 'system' },
399
415
  content: '---\ntype: rule\ntitle: Leased\ndescription: Lease test.\ntimestamp: 2026-07-02T00:00:00Z\nname: Leased\n---\nBody.\n',
400
416
  };
401
- const missingLease = engine.processOperation(leasedEnvelope, vault);
417
+ const missingLease = await engine.processOperation(leasedEnvelope, vault);
402
418
  check('lease conflict rejects missing lease_id', missingLease.success === false && missingLease.validation.errors.some((e) => e.includes('leased')));
403
419
 
404
- const mismatchLease = engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-mismatch', lease_id: 'wrong' }, vault);
420
+ const mismatchLease = await engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-mismatch', lease_id: 'wrong' }, vault);
405
421
  check('lease conflict rejects mismatched lease_id', mismatchLease.success === false && mismatchLease.validation.errors.some((e) => e.includes('Lease mismatch')));
406
422
 
407
- const matchedLease = engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-match', lease_id: 'lease-ok' }, vault);
423
+ const matchedLease = await engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-match', lease_id: 'lease-ok' }, vault);
408
424
  check('lease allows matching lease_id', matchedLease.success === true, JSON.stringify(matchedLease.validation?.errors || []));
409
425
 
410
426
  const expiredTarget = 'rules/expired.md';
@@ -413,13 +429,13 @@ function runOperationContractRegressionConformance() {
413
429
  lease_id: 'expired',
414
430
  expires_at: '2000-01-01T00:00:00.000Z',
415
431
  }));
416
- const expiredLease = engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-expired', path: expiredTarget }, vault);
432
+ const expiredLease = await engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-expired', path: expiredTarget }, vault);
417
433
  check('expired lease does not block writes', expiredLease.success === true, JSON.stringify(expiredLease.validation?.errors || []));
418
434
 
419
435
  const unreadableTarget = 'rules/unreadable.md';
420
436
  const unreadablePathKey = crypto.createHash('sha256').update(unreadableTarget).digest('hex');
421
437
  fs.writeFileSync(path.join(leaseDir, `${unreadablePathKey}.lease.json`), '{not json');
422
- const unreadableLease = engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-unreadable', path: unreadableTarget }, vault);
438
+ const unreadableLease = await engine.processOperation({ ...leasedEnvelope, idempotency_key: 'lease-unreadable', path: unreadableTarget }, vault);
423
439
  check('unreadable lease state fails closed', unreadableLease.success === false && unreadableLease.validation.errors.some((e) => e.includes('unreadable')));
424
440
  } finally {
425
441
  fs.rmSync(vault, { recursive: true, force: true });
@@ -435,160 +451,77 @@ function runOperationContractRegressionConformance() {
435
451
  return pass === checks.length;
436
452
  }
437
453
 
438
- function runSemanticLocalizationConformance() {
454
+ async function runSemanticLocalizationConformance() {
439
455
  const checks = [];
440
456
  const check = (name, cond, detail = '') => { checks.push({ name, cond, detail }); };
441
457
  const vault = fs.mkdtempSync(path.join(os.tmpdir(), 'ssss-semantic-'));
442
- const projection = fs.mkdtempSync(path.join(os.tmpdir(), 'ssss-locale-'));
443
458
  try {
444
459
  const engine = createEngine({ registryDir: REGISTRY_DIR });
445
460
  const sourcePath = 'rules/refund-policy.md';
446
461
  const sourceContent = [
447
- '---',
448
- 'type: rule',
449
- 'title: Refund Policy',
450
- 'description: Explains when festival ticket refunds are available.',
451
- 'timestamp: 2026-07-10T00:00:00Z',
452
- 'name: Refund Policy',
453
- 'aliases: [refund terms, cancellation policy]',
454
- 'tags: [tickets, finance]',
455
- 'relations:',
456
- ' - relation: applies_to',
457
- ' target: workflows/box-office.md',
458
- '---',
459
- '',
460
- 'Refunds are available until fourteen days before the event.',
461
- '',
462
+ '---', 'type: rule', 'title: Política de reembolsos',
463
+ 'description: Explica cuándo están disponibles los reembolsos de boletos.',
464
+ 'timestamp: 2026-07-10T00:00:00Z', 'name: Refund Policy', 'language: es',
465
+ 'aliases: [términos de reembolso, política de cancelación]', 'tags: [boletos, finanzas]',
466
+ 'relations:', ' - relation: applies_to', ' target: workflows/box-office.md',
467
+ '---', '', 'Los reembolsos están disponibles hasta catorce días antes del evento.', '',
462
468
  ].join('\n');
463
- const sourceWrite = engine.processOperation({
469
+ const sourceWrite = await engine.processOperation({
464
470
  type: 'operation', idempotency_key: 'sem-source', workspace_id: 'ws-sem',
465
471
  path: sourcePath, content: sourceContent, actor: { role: 'system' },
466
472
  }, vault);
467
- check('structural semantic source commits', sourceWrite.success === true,
468
- JSON.stringify(sourceWrite.validation?.errors || []));
473
+ check('non-English structural source commits', sourceWrite.success === true, JSON.stringify(sourceWrite.validation?.errors || []));
469
474
 
470
475
  const privateContent = [
471
- '---',
472
- 'type: task',
473
- 'title: Private Refund Request',
474
- 'description: Private customer refund task.',
475
- 'timestamp: 2026-07-10T00:00:00Z',
476
- 'priority: high',
477
- 'category: support',
478
- 'status: pending',
479
- '---',
480
- '',
481
- 'Customer account and payment details.',
482
- '',
476
+ '---', 'type: task', 'title: Solicitud privada', 'description: Datos privados del cliente.',
477
+ 'timestamp: 2026-07-10T00:00:00Z', 'priority: high', 'category: support', 'status: pending',
478
+ '---', '', 'Cuenta y detalles de pago.', '',
483
479
  ].join('\n');
484
- const privateWrite = engine.processOperation({
480
+ const privateWrite = await engine.processOperation({
485
481
  type: 'operation', idempotency_key: 'sem-private', workspace_id: 'ws-sem',
486
482
  path: 'tasks/private-refund.md', content: privateContent, actor: { role: 'system' },
487
483
  }, vault);
488
- check('tenant-private semantic source commits', privateWrite.success === true,
489
- JSON.stringify(privateWrite.validation?.errors || []));
490
-
491
- const overlayPath = 'translations/es-MX/rules/refund-policy.md';
492
- const overlayContent = [
493
- '---',
494
- 'type: translation',
495
- 'title: Spanish Refund Policy Translation',
496
- 'description: Approved Spanish presentation overlay for the refund policy.',
497
- 'timestamp: 2026-07-10T00:00:00Z',
498
- 'translation_id: refund-policy-es-mx',
499
- `source_path: ${sourcePath}`,
500
- `source_hash: ${documentHash(sourceContent)}`,
501
- 'locale: es-MX',
502
- 'status: approved',
503
- 'translated_fields: [title, description, body]',
504
- 'translated_title: Política de reembolsos',
505
- 'translated_description: Explica cuándo están disponibles los reembolsos de boletos.',
506
- '---',
507
- '',
508
- 'Los reembolsos están disponibles hasta catorce días antes del evento.',
509
- '',
510
- ].join('\n');
511
- const overlayWrite = engine.processOperation({
512
- type: 'operation', idempotency_key: 'sem-overlay', workspace_id: 'ws-sem',
513
- path: overlayPath, content: overlayContent, actor: { role: 'system' },
514
- }, vault);
515
- check('hash-bound translation overlay commits', overlayWrite.success === true,
516
- JSON.stringify(overlayWrite.validation?.errors || []));
517
-
518
- const staleOverlay = overlayContent.replace(documentHash(sourceContent), `sha256:${'0'.repeat(64)}`)
519
- .replace('translation_id: refund-policy-es-mx', 'translation_id: stale-es-mx');
520
- const staleWrite = engine.processOperation({
521
- type: 'operation', idempotency_key: 'sem-stale', workspace_id: 'ws-sem',
522
- path: 'translations/es-MX/rules/stale.md', content: staleOverlay, actor: { role: 'system' },
523
- }, vault);
524
- check('stale translation source hash is rejected', staleWrite.success === false &&
525
- staleWrite.validation.errors.some((error) => error.includes('Source hash mismatch')),
526
- JSON.stringify(staleWrite.validation?.errors || []));
527
-
528
- const traversalOverlay = overlayContent
529
- .replace('translation_id: refund-policy-es-mx', 'translation_id: traversal-es-mx')
530
- .replace(`source_path: ${sourcePath}`, 'source_path: ../secrets.md');
531
- const traversalWrite = engine.processOperation({
532
- type: 'operation', idempotency_key: 'sem-traversal', workspace_id: 'ws-sem',
533
- path: 'translations/es-MX/rules/traversal.md', content: traversalOverlay, actor: { role: 'system' },
534
- }, vault);
535
- check('translation reference traversal is rejected', traversalWrite.success === false &&
536
- traversalWrite.validation.errors.some((error) => error.includes('safe vault-relative path')),
537
- JSON.stringify(traversalWrite.validation?.errors || []));
484
+ check('tenant-private semantic source commits', privateWrite.success === true, JSON.stringify(privateWrite.validation?.errors || []));
538
485
 
539
- const immutablePatch = engine.processOperation({
540
- type: 'patch', idempotency_key: 'sem-immutable', workspace_id: 'ws-sem',
541
- path: overlayPath, patches: { locale: 'fr' }, actor: { role: 'system' },
542
- }, vault);
543
- check('translation identity fields are immutable', immutablePatch.success === false &&
544
- immutablePatch.validation.errors.some((error) => error.includes("immutable field 'locale'")),
545
- JSON.stringify(immutablePatch.validation?.errors || []));
546
-
547
- const first = buildSemanticIndex(vault, { locale: 'es-MX', registryDir: REGISTRY_DIR });
548
- const second = buildSemanticIndex(vault, { locale: 'es-mx', registryDir: REGISTRY_DIR });
549
- check('semantic index is deterministic and locale-normalized',
550
- first.index_hash === second.index_hash && first.locale === 'es-MX');
486
+ const first = buildSemanticIndex(vault, { registryDir: REGISTRY_DIR });
487
+ const second = buildSemanticIndex(vault, { registryDir: REGISTRY_DIR });
488
+ check('semantic index is deterministic', first.index_hash === second.index_hash);
551
489
  check('safe semantic default excludes tenant-private documents',
552
490
  first.documents.every((document) => document.portability === 'structural') &&
553
491
  !first.documents.some((document) => document.path.startsWith('tasks/')));
554
- check('semantic graph captures explicit relations',
555
- first.edges.some((edge) => edge.source === sourcePath && edge.relation === 'applies_to'));
556
- const matches = searchSemanticIndex(first, 'reembolsos boletos', { limit: 3 });
557
- check('localized semantic search ranks the translated source',
558
- matches[0]?.document.path === sourcePath && matches[0].document.title === 'Política de reembolsos',
559
- JSON.stringify(matches.map((match) => ({ path: match.document.path, score: match.score }))));
492
+ check('semantic graph captures explicit relations', first.edges.some((edge) => edge.source === sourcePath && edge.relation === 'applies_to'));
493
+ const lexical = searchSemanticIndex(first, 'reembolsos boletos', { limit: 3 });
494
+ check('lexical evidence ranks same-language content', lexical[0]?.document.path === sourcePath && lexical[0].evidence.lexical > 0);
495
+
496
+ const embed = async (texts) => texts.map((text) => /refund|reembols/i.test(text) ? [1, 0] : [0, 1]);
497
+ const enriched = await enrichSemanticIndex(first, { embed, model: 'conformance-multilingual-v1' });
498
+ check('embedding provenance records model and dimension', enriched.embedding.model === 'conformance-multilingual-v1' && enriched.embedding.dimension === 2);
499
+ const crossLanguage = await searchSemanticIndexWithAdapter(enriched, 'refund policy', { embed, limit: 3 });
500
+ check('cross-language semantic evidence ranks the authored source',
501
+ crossLanguage[0]?.document.path === sourcePath && crossLanguage[0].evidence.semantic > 0,
502
+ JSON.stringify(crossLanguage.map((match) => ({ path: match.document.path, evidence: match.evidence }))));
503
+
504
+ const rendered = await renderSemanticRecord(first.documents[0], {
505
+ language: 'ja', render: async () => ({ title: '返金ポリシー', description: 'チケット返金の条件。', body: 'イベントの14日前まで返金できます。' }),
506
+ });
507
+ check('runtime rendering changes presentation without changing controls',
508
+ rendered.presentation.language === 'ja' && rendered.presentation.title === '返金ポリシー' &&
509
+ rendered.invariant.type === first.documents[0].type && rendered.invariant.path === sourcePath);
510
+ let forbiddenRejected = false;
511
+ try { await renderSemanticRecord(first.documents[0], { language: 'fr', render: async () => ({ type: 'assistant', title: 'Politique' }) }); }
512
+ catch (error) { forbiddenRejected = error.message.includes('forbidden field'); }
513
+ check('renderer cannot change symbolic controls', forbiddenRejected);
560
514
 
561
515
  const privateIndex = buildSemanticIndex(vault, { includePrivate: true, registryDir: REGISTRY_DIR });
562
- check('private semantic indexing requires explicit opt-in',
563
- privateIndex.documents.some((document) => document.path === 'tasks/private-refund.md'));
564
-
565
- const manifest = materializeLocale(vault, 'es-mx', projection, { registryDir: REGISTRY_DIR });
566
- const localizedContent = fs.readFileSync(path.join(projection, sourcePath), 'utf8');
567
- const localizedDocument = parseDocument(localizedContent);
568
- check('localization projection preserves symbolic control fields',
569
- localizedDocument.data.type === 'rule' && localizedDocument.data.name === 'Refund Policy' &&
570
- localizedDocument.data.title === 'Política de reembolsos' &&
571
- localizedDocument.data.locale === 'es-MX');
572
- check('localization projection excludes private documents by default',
573
- manifest.document_count >= 1 && !fs.existsSync(path.join(projection, 'tasks', 'private-refund.md')));
574
- check('localization publishes a complete projection without staging residue',
575
- fs.existsSync(path.join(projection, '.ssss-projection.json')) &&
576
- !fs.readdirSync(path.dirname(projection)).some((name) =>
577
- name.startsWith(`.${path.basename(projection)}.`) && name.endsWith('.stage')));
578
- let insideRejected = false;
579
- try { materializeLocale(vault, 'es-MX', path.join(vault, 'derived', 'es'), { registryDir: REGISTRY_DIR }); }
580
- catch (error) { insideRejected = error.message.includes('outside the source vault'); }
581
- check('localization output inside the source vault is rejected', insideRejected);
582
-
583
- const invalidRawPath = path.join(vault, 'rules', 'invalid-raw.md');
584
- fs.writeFileSync(invalidRawPath, '---\ntype: rule\nname: Invalid\n---\nBody.\n');
516
+ check('private semantic indexing requires explicit opt-in', privateIndex.documents.some((document) => document.path === 'tasks/private-refund.md'));
517
+
518
+ fs.writeFileSync(path.join(vault, 'rules', 'invalid-raw.md'), '---\ntype: rule\nname: Invalid\n---\nBody.\n');
585
519
  let invalidRawRejected = false;
586
520
  try { buildSemanticIndex(vault, { registryDir: REGISTRY_DIR }); }
587
521
  catch (error) { invalidRawRejected = error.message.includes("Missing required field 'title'"); }
588
522
  check('semantic projection rejects raw documents that violate the registry', invalidRawRejected);
589
523
  } finally {
590
524
  fs.rmSync(vault, { recursive: true, force: true });
591
- fs.rmSync(projection, { recursive: true, force: true });
592
525
  }
593
526
 
594
527
  let pass = 0;
@@ -596,7 +529,7 @@ function runSemanticLocalizationConformance() {
596
529
  if (c.cond) { pass++; console.log(` ✅ ${c.name}`); }
597
530
  else console.log(` ❌ ${c.name}${c.detail ? ` — ${c.detail}` : ''}`);
598
531
  }
599
- console.log(`\n ${pass}/${checks.length} semantic/localization checks passed (§11.9)`);
532
+ console.log(`\n ${pass}/${checks.length} semantic/multilingual rendering checks passed (§11.9)`);
600
533
  return pass === checks.length;
601
534
  }
602
535
 
@@ -688,7 +621,7 @@ function runRegistryExtensionConformance() {
688
621
  return pass === checks.length;
689
622
  }
690
623
 
691
- function runCliSmokeConformance() {
624
+ async function runCliSmokeConformance() {
692
625
  const checks = [];
693
626
  const check = (name, cond, detail = '') => { checks.push({ name, cond, detail }); };
694
627
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ssss-cli-smoke-'));
@@ -708,7 +641,7 @@ function runCliSmokeConformance() {
708
641
  const failures = [];
709
642
  for (const abs of files) {
710
643
  const rel = path.relative(vault, abs);
711
- const res = engine.processOperation({
644
+ const res = await engine.processOperation({
712
645
  type: 'operation',
713
646
  idempotency_key: `scaffold-${rel.replace(/[^A-Za-z0-9._-]/g, '_')}`,
714
647
  workspace_id: 'ws-scaffold',
@@ -732,21 +665,6 @@ function runCliSmokeConformance() {
732
665
  semanticOutput.results?.some((result) => result.document.path === 'rules/welcome.md'),
733
666
  JSON.stringify(semanticOutput.results || []));
734
667
 
735
- const localizedVault = path.join(tmp, 'localized-es');
736
- const localizationOutput = JSON.parse(execFileSync('node', [
737
- path.join(__dirname, 'ssss.mjs'),
738
- 'localize',
739
- vault,
740
- '--locale',
741
- 'es',
742
- '--out',
743
- localizedVault,
744
- ], { encoding: 'utf8' }));
745
- check('ssss localize materializes a safe structural projection',
746
- localizationOutput.locale === 'es' &&
747
- fs.existsSync(path.join(localizedVault, 'rules', 'welcome.md')) &&
748
- !fs.existsSync(path.join(localizedVault, 'tasks', 'first-task.md')));
749
-
750
668
  const dryRunVault = path.join(tmp, 'dry-run-vault');
751
669
  const dryRunOut = execFileSync('node', [
752
670
  path.join(__dirname, 'ssss.mjs'),
@@ -791,7 +709,7 @@ function runCliSmokeConformance() {
791
709
  * workflow frontmatter as the canonical schedule source, then derive trigger,
792
710
  * task, and run envelopes without introducing a scheduler database of record.
793
711
  */
794
- function runRuntimeConformance() {
712
+ async function runRuntimeConformance() {
795
713
  const checks = [];
796
714
  const check = (name, cond, detail = '') => { checks.push({ name, cond, detail }); };
797
715
 
@@ -854,15 +772,15 @@ function runRuntimeConformance() {
854
772
  const vault = fs.mkdtempSync(path.join(os.tmpdir(), 'ssss-runtime-'));
855
773
  try {
856
774
  const engine = createEngine({ registryDir: REGISTRY_DIR });
857
- const eventRes = engine.processOperation(firstPlan.envelopes[0], vault);
858
- const taskRes = engine.processOperation(firstPlan.envelopes[1], vault);
775
+ const eventRes = await engine.processOperation(firstPlan.envelopes[0], vault);
776
+ const taskRes = await engine.processOperation(firstPlan.envelopes[1], vault);
859
777
  check('trigger event commits through Operation Contract', eventRes.success === true,
860
778
  JSON.stringify(eventRes.validation?.errors || []));
861
779
  check('instantiated task commits through Operation Contract', taskRes.success === true,
862
780
  JSON.stringify(taskRes.validation?.errors || []));
863
781
 
864
- const replayEvent = engine.processOperation(secondPlan.envelopes[0], vault);
865
- const replayTask = engine.processOperation(secondPlan.envelopes[1], vault);
782
+ const replayEvent = await engine.processOperation(secondPlan.envelopes[0], vault);
783
+ const replayTask = await engine.processOperation(secondPlan.envelopes[1], vault);
866
784
  check('duplicate daemon tick replays trigger event idempotently', replayEvent.replay != null);
867
785
  check('duplicate daemon tick replays task write idempotently', replayTask.replay != null);
868
786
 
@@ -873,7 +791,7 @@ function runRuntimeConformance() {
873
791
  status: 'queued',
874
792
  startedAt: scheduledFor,
875
793
  });
876
- const runRes = engine.processOperation(runEnvelope, vault);
794
+ const runRes = await engine.processOperation(runEnvelope, vault);
877
795
  check('worker-created run commits through Operation Contract', runRes.success === true,
878
796
  JSON.stringify(runRes.validation?.errors || []));
879
797
  } finally {
@@ -953,22 +871,24 @@ async function main() {
953
871
  process.exit(ok ? 0 : 1);
954
872
  } else if (opts.engine) {
955
873
  console.log('\nRunning fixtures through the reference engine (src/engine.mjs) ...');
956
- const engineOk = runAgainstEngine(doc);
874
+ const engineOk = await runAgainstEngine(doc);
957
875
  console.log('\nRunning workflow runtime conformance (src/runtime.mjs, §11.8) ...');
958
- const runtimeOk = runRuntimeConformance();
876
+ const runtimeOk = await runRuntimeConformance();
959
877
  console.log('\nRunning operation regression conformance (src/engine.mjs, §6/§7) ...');
960
- const operationRegressionOk = runOperationContractRegressionConformance();
878
+ const operationRegressionOk = await runOperationContractRegressionConformance();
961
879
  console.log('\nRunning extension-registry conformance (src/registry.mjs) ...');
962
880
  const extensionRegistryOk = runRegistryExtensionConformance();
963
- console.log('\nRunning semantic/localization conformance (src/semantic.mjs, §11.9) ...');
964
- const semanticOk = runSemanticLocalizationConformance();
881
+ console.log('\nRunning semantic/multilingual rendering conformance (src/semantic.mjs, §11.9) ...');
882
+ const semanticOk = await runSemanticLocalizationConformance();
883
+ console.log('\nRunning SSSS 0.9 kernel/adapter/UI conformance ...');
884
+ const kernel09Ok = await runKernel09Conformance();
965
885
  console.log('\nRunning bundle/provisioning conformance (src/bundle.mjs, §16/§17) ...');
966
- const bundleOk = runBundleConformance();
886
+ const bundleOk = await runBundleConformance();
967
887
  console.log('\nRunning CLI smoke conformance (scripts/ssss.mjs) ...');
968
- const cliSmokeOk = runCliSmokeConformance();
888
+ const cliSmokeOk = await runCliSmokeConformance();
969
889
  process.exit(
970
890
  engineOk && runtimeOk && operationRegressionOk && extensionRegistryOk &&
971
- semanticOk && bundleOk && cliSmokeOk ? 0 : 1
891
+ semanticOk && kernel09Ok && bundleOk && cliSmokeOk ? 0 : 1
972
892
  );
973
893
  } else {
974
894
  console.log('\nℹ No --endpoint/--engine given; ran structural + registry validation only.');
package/scripts/ssss.mjs CHANGED
@@ -36,9 +36,12 @@ Vault → bundle → tenant lifecycle:
36
36
  import <bundle> Replay a bundle/plan into a target vault (idempotent).
37
37
 
38
38
  Authoring & conformance:
39
+ primitive <verb> Create, validate, inspect, migrate, or emit types.
40
+ registry <verb> Compose, lock, or verify an effective registry.
41
+ adapter conformance Run machine-readable adapter contract suites.
42
+ migrate 0.8-to-0.9 Diagnose and plan a non-destructive 0.9 migration.
39
43
  autolink [dir] Generate OKF wiki links across a vault.
40
44
  semantic <vault> Build/query a deterministic semantic projection (§11.9).
41
- localize <vault> Materialize approved locale overlays outside the vault.
42
45
  conformance Run the canonical conformance suite (spec §12).
43
46
  --engine | --endpoint <url> [--token <pat>]
44
47
 
@@ -63,7 +66,10 @@ const MODULES = {
63
66
  provision: './cmd-provision.mjs',
64
67
  import: './cmd-import.mjs',
65
68
  semantic: './cmd-semantic.mjs',
66
- localize: './cmd-localize.mjs',
69
+ primitive: './cmd-primitive.mjs',
70
+ registry: './cmd-registry.mjs',
71
+ adapter: './cmd-adapter.mjs',
72
+ migrate: './cmd-migrate.mjs',
67
73
  help: './cmd-help.mjs',
68
74
  };
69
75
 
@@ -2,7 +2,7 @@
2
2
  type: skill
3
3
  title: SSSS System Manager
4
4
  name: ssss
5
- description: "Inspect, validate, implement, or change SSSS primitives, extension registries, operation envelopes, workflow runtimes, semantic search, translation/localization overlays, bundles, projections, and host adapters. Use for SSSS schema changes, registry collisions, conformance failures, portability/privacy questions, or translatable semantic vault work. MANDATORY: Read this file before editing SSSS files or code."
5
+ description: "Inspect, validate, implement, or change SSSS primitives, registries, kernel commands, VFS/security adapters, events, projections, multilingual semantic runtime, generative UI, bundles, and host adapters. MANDATORY: Read this file before editing SSSS files or code."
6
6
  timestamp: 2026-07-10T00:00:00Z
7
7
  ---
8
8
 
@@ -138,24 +138,20 @@ integrity before import. Preflight the complete plan before the first commit so
138
138
  late invalid envelope cannot leave a partial install. Provisioning produces
139
139
  deterministic Operation Contract envelopes; re-import must be a no-op.
140
140
 
141
- ## Semantic Search and Localization
141
+ ## Multilingual Semantic Runtime
142
142
 
143
- Treat semantic indexes and localized trees as disposable projections. Use
144
- `@gregiteen/ssss-cli/semantic`, `ssss semantic`, and `ssss localize` for the
145
- dependency-free reference behavior.
143
+ Treat semantic indexes and rendered text/UI as disposable projections. Canonical
144
+ documents are authored once in any language; do not create translation documents or
145
+ localized canonical trees.
146
146
 
147
147
  - Index only `structural` documents by default. Include `tenant_private` and
148
148
  `resource_bound` sources only after an explicit authorized opt-in.
149
149
  - Derive stable identity from `semantic_id`, then `resource`, then source path.
150
150
  - Build edges from explicit `relations`, wiki links, and Markdown links.
151
- - Keep projection output outside the source vault; reject symlinks and unsafe paths,
152
- and stage the complete tree before publishing it.
153
- - Model localization as a `translation` document pointing to an existing
154
- structural source and its exact SHA-256 content hash.
155
- - Translate only `title`, `description`, and body. Preserve types, ids, enums,
156
- permissions, status, portability, relations, and paths from the source.
157
- - Apply approved overlays by default. Reject stale hashes, duplicate locale/source
158
- overlays, recursive translations, private sources, and immutable identity edits.
151
+ - Record embedding model identity and vector dimensions and report lexical and
152
+ semantic evidence separately.
153
+ - Render language at runtime through an injected adapter. Preserve primitive IDs,
154
+ field IDs, enum codes, actions, permissions, paths, versions, hashes, and relations.
159
155
 
160
156
  ## Scope and Host Adapters
161
157
 
@@ -172,7 +168,7 @@ architecture.
172
168
  ## Procedure for Changes
173
169
 
174
170
  1. Classify the change: core primitive, contract behavior, runtime behavior,
175
- semantic/localization behavior, bundle/provisioning behavior, projection, or
171
+ semantic/runtime-rendering behavior, bundle/provisioning behavior, projection, or
176
172
  host extension.
177
173
  2. Inspect the registry, spec section, implementation, fixtures, and host bridge
178
174
  for that surface.
@@ -197,7 +193,7 @@ npm test
197
193
 
198
194
  It covers registry/engine parity, shipped-skill conformance, Operation Contract
199
195
  fixtures, workflow runtime behavior, lease/frontmatter regressions,
200
- extension-registry hardening, semantic/localization behavior,
196
+ extension-registry hardening, multilingual semantic behavior,
201
197
  bundle/provisioning round trips, and CLI smoke tests.
202
198
 
203
199
  For host adapters, run the host's named SSSS conformance command as well. Do not
@@ -26,12 +26,12 @@
26
26
  },
27
27
  {
28
28
  "name": "ssss-semantic-default-is-private",
29
- "assertion": "The skill requires structural-only semantic and localization projections by default, with explicit authorization before private or resource-bound indexing.",
29
+ "assertion": "The skill requires structural-only semantic projections by default, with explicit authorization before private or resource-bound indexing.",
30
30
  "severity": "error"
31
31
  },
32
32
  {
33
- "name": "ssss-translations-preserve-controls",
34
- "assertion": "The skill limits translations to title, description, and body; requires an exact structural source hash; and preserves symbolic control fields.",
33
+ "name": "ssss-runtime-rendering-preserves-controls",
34
+ "assertion": "The skill requires runtime multilingual rendering and preserves all symbolic control fields.",
35
35
  "severity": "error"
36
36
  }
37
37
  ]