@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.
- package/README.md +11 -12
- package/VERSION +1 -1
- package/conformance/README.md +1 -1
- package/conformance/fixtures.json +4 -48
- package/conformance/reference-bundle.ucw.json +4 -21
- package/docs/help/bundle.md +2 -2
- package/docs/help/overview.md +4 -4
- package/docs/help/semantic.md +5 -4
- package/docs/ssss-spec.md +32 -77
- package/package.json +22 -3
- package/registry/core.json +21 -28
- package/scripts/build-reference-bundle.mjs +0 -20
- package/scripts/cmd-adapter.mjs +29 -0
- package/scripts/cmd-import.mjs +1 -1
- package/scripts/cmd-migrate.mjs +26 -0
- package/scripts/cmd-new.mjs +5 -5
- package/scripts/cmd-primitive.mjs +48 -0
- package/scripts/cmd-registry.mjs +25 -0
- package/scripts/cmd-semantic.mjs +6 -8
- package/scripts/conformance-09.mjs +199 -0
- package/scripts/conformance.mjs +106 -186
- package/scripts/ssss.mjs +8 -2
- package/skills/ssss/SKILL.md +11 -15
- package/skills/ssss/evals/evals.json +3 -3
- package/skills/ssss/references/contract-reference.md +7 -8
- package/skills/ssss/scripts/validate-schema.mjs +1 -44
- package/src/authorization.mjs +61 -0
- package/src/bundle.mjs +4 -4
- package/src/engine.mjs +101 -503
- package/src/events.mjs +90 -0
- package/src/guard.mjs +32 -0
- package/src/http.mjs +26 -0
- package/src/idempotency.mjs +55 -0
- package/src/kernel.mjs +260 -0
- package/src/leases.mjs +125 -0
- package/src/primitive.mjs +227 -0
- package/src/projections.mjs +62 -0
- package/src/registry.mjs +190 -39
- package/src/semantic.mjs +124 -316
- package/src/ui.mjs +100 -0
- package/src/validator.mjs +87 -0
- package/src/vfs.mjs +203 -0
- package/types/kernel.d.ts +48 -0
- package/docs/help/localization.md +0 -43
- package/scripts/cmd-localize.mjs +0 -44
package/scripts/conformance.mjs
CHANGED
|
@@ -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 {
|
|
30
|
+
import { loadRegistries } from '../src/registry.mjs';
|
|
30
31
|
import { createRunEnvelope, planWorkflowTrigger } from '../src/runtime.mjs';
|
|
31
|
-
import {
|
|
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
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
!
|
|
270
|
-
|
|
271
|
-
|
|
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: '
|
|
324
|
+
path: 'primitives/invalid.md', actor: { role: 'system' },
|
|
309
325
|
content: [
|
|
310
|
-
'---', 'type:
|
|
326
|
+
'---', 'type: primitive', 'title: Invalid Primitive',
|
|
311
327
|
'description: Must fail before the source commits.', 'timestamp: 2026-07-10T00:00:00Z',
|
|
312
|
-
'
|
|
313
|
-
|
|
314
|
-
'
|
|
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
|
-
'
|
|
449
|
-
'
|
|
450
|
-
'
|
|
451
|
-
'
|
|
452
|
-
'
|
|
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
|
|
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
|
-
'
|
|
473
|
-
'
|
|
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
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
|
package/skills/ssss/SKILL.md
CHANGED
|
@@ -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,
|
|
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
|
|
141
|
+
## Multilingual Semantic Runtime
|
|
142
142
|
|
|
143
|
-
Treat semantic indexes and
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
-
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
|
|
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/
|
|
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
|
|
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
|
|
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-
|
|
34
|
-
"assertion": "The skill
|
|
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
|
]
|