@gregiteen/ssss-cli 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -4
- package/README.md +42 -84
- package/VERSION +1 -1
- package/conformance/README.md +1 -1
- package/conformance/fixtures.json +4 -48
- package/conformance/reference-bundle.ucw.json +22 -21
- package/docs/help/bundle.md +2 -2
- package/docs/help/overview.md +9 -6
- package/docs/help/semantic.md +5 -4
- package/docs/ssss-spec.md +42 -86
- package/package.json +22 -3
- package/registry/core.json +22 -29
- 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 +331 -0
- package/scripts/conformance.mjs +153 -186
- package/scripts/generate-sbom.mjs +96 -0
- package/scripts/phase9-cross-host-corpus.mjs +223 -0
- package/scripts/ssss.mjs +8 -2
- package/skills/ssss/SKILL.md +32 -208
- 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 +62 -0
- package/src/bundle.mjs +41 -5
- package/src/engine.mjs +101 -503
- package/src/events.mjs +155 -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 +173 -0
- package/src/primitive.mjs +227 -0
- package/src/projections.mjs +133 -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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Verified identity and fail-closed capability authorization contracts. */
|
|
2
|
+
const PRINCIPAL_KINDS = new Set(['human', 'agent', 'service', 'system']);
|
|
3
|
+
const ACTIONS = new Set(['create', 'replace', 'patch', 'append', 'event', 'delete', 'define', 'migrate']);
|
|
4
|
+
const ASSURANCE = { unverified: 0, verified: 1, elevated: 2, hardware: 3 };
|
|
5
|
+
|
|
6
|
+
export function validateVerifiedPrincipal(principal) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
if (!principal || typeof principal !== 'object') errors.push('Verified principal is required.');
|
|
9
|
+
else {
|
|
10
|
+
if (typeof principal.id !== 'string' || !principal.id) errors.push('Principal id is required.');
|
|
11
|
+
if (!PRINCIPAL_KINDS.has(principal.kind)) errors.push(`Principal kind must be one of: ${[...PRINCIPAL_KINDS].join(', ')}.`);
|
|
12
|
+
if (!Array.isArray(principal.workspaceIds)) errors.push('Principal workspaceIds must be an array.');
|
|
13
|
+
if (!principal.authentication || typeof principal.authentication.provider !== 'string') {
|
|
14
|
+
errors.push('Authentication provenance is required.');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { valid: errors.length === 0, errors };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function requiredCapabilities(definition, action) {
|
|
21
|
+
if (!ACTIONS.has(action)) throw new Error(`Unknown authorization action '${action}'.`);
|
|
22
|
+
const declared = definition?.capabilities?.[action];
|
|
23
|
+
if (Array.isArray(declared) && declared.length) return declared;
|
|
24
|
+
const primitive = definition?.qualified_type || definition?.type || 'unknown';
|
|
25
|
+
return [`${primitive}:${action}`];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createCapabilityAuthorizer(options = {}) {
|
|
29
|
+
const policyFloors = options.policyFloors || {};
|
|
30
|
+
return async function authorize({ principal, workspaceId, definition, action, context = {} }) {
|
|
31
|
+
const principalValidation = validateVerifiedPrincipal(principal);
|
|
32
|
+
if (!principalValidation.valid) return { allowed: false, reason: principalValidation.errors.join(' ') };
|
|
33
|
+
if (principal.kind !== 'system' && !principal.workspaceIds.includes(workspaceId)) {
|
|
34
|
+
return { allowed: false, reason: `Principal '${principal.id}' is not scoped to workspace '${workspaceId}'.` };
|
|
35
|
+
}
|
|
36
|
+
const floorKey = definition?.qualified_type || definition?.primitive_id || definition?.type;
|
|
37
|
+
const required = new Set([
|
|
38
|
+
...requiredCapabilities(definition, action),
|
|
39
|
+
...(policyFloors[floorKey]?.[action] || []),
|
|
40
|
+
...(context.policyFloor || []),
|
|
41
|
+
]);
|
|
42
|
+
const granted = new Set(principal.capabilities || []);
|
|
43
|
+
if (principal.kind === 'system' && options.systemBypass !== false) return { allowed: true, required: [...required] };
|
|
44
|
+
const missing = [...required].filter((capability) =>
|
|
45
|
+
!granted.has(capability) && !granted.has('*:*') && !granted.has(`${capability.split(':')[0]}:*`));
|
|
46
|
+
if (missing.length) return { allowed: false, required: [...required], missing, reason: `Missing capabilities: ${missing.join(', ')}.` };
|
|
47
|
+
if (context.requiresHumanConfirmation && principal.kind !== 'human') {
|
|
48
|
+
return { allowed: false, required: [...required], reason: 'A verified human confirmation is required.' };
|
|
49
|
+
}
|
|
50
|
+
if (context.requiresHumanConfirmation && context.humanConfirmation !== true) {
|
|
51
|
+
return { allowed: false, required: [...required], reason: 'Explicit human confirmation is required.' };
|
|
52
|
+
}
|
|
53
|
+
if (context.requiredAssurance) {
|
|
54
|
+
const actual = ASSURANCE[principal.authentication.assurance] ?? -1;
|
|
55
|
+
const requiredAssurance = ASSURANCE[context.requiredAssurance] ?? Number.POSITIVE_INFINITY;
|
|
56
|
+
if (actual < requiredAssurance) {
|
|
57
|
+
return { allowed: false, required: [...required], reason: `Authentication step-up to '${context.requiredAssurance}' is required.` };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { allowed: true, required: [...required] };
|
|
61
|
+
};
|
|
62
|
+
}
|
package/src/bundle.mjs
CHANGED
|
@@ -197,6 +197,17 @@ export function exportBundle(vaultRoot, opts = {}) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
const files = canonicalFiles(kept);
|
|
200
|
+
const hash = contentHash(files);
|
|
201
|
+
const primitiveDeps = Object.keys(inventory).sort();
|
|
202
|
+
const migrationDeps = files
|
|
203
|
+
.map((file) => {
|
|
204
|
+
try {
|
|
205
|
+
const { data } = parseDocument(file.content);
|
|
206
|
+
return data.type === 'migration' ? (data.migration_id || data.slug || file.path) : null;
|
|
207
|
+
} catch { return null; }
|
|
208
|
+
})
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
.sort();
|
|
200
211
|
const manifest = {
|
|
201
212
|
name, description, version,
|
|
202
213
|
exported_at: exportedAt,
|
|
@@ -204,10 +215,16 @@ export function exportBundle(vaultRoot, opts = {}) {
|
|
|
204
215
|
required_extensions: requiredExtensions,
|
|
205
216
|
export_profile: profile,
|
|
206
217
|
primitive_inventory: inventory,
|
|
218
|
+
dependencies: {
|
|
219
|
+
primitives: primitiveDeps,
|
|
220
|
+
extensions: [...requiredExtensions].sort(),
|
|
221
|
+
migrations: migrationDeps,
|
|
222
|
+
integrity: { content_hash: hash, ssss_core_version: core.spec_version },
|
|
223
|
+
},
|
|
207
224
|
provisioning: manifestProvisioning,
|
|
208
225
|
parameters: manifestParameters,
|
|
209
226
|
file_count: files.length,
|
|
210
|
-
provenance: { content_hash:
|
|
227
|
+
provenance: { content_hash: hash, exporter },
|
|
211
228
|
};
|
|
212
229
|
// source_workspace_id is operator-identifying — only carried in a backup (§16.2).
|
|
213
230
|
if (profile === 'backup' && sourceWorkspaceId) manifest.source_workspace_id = sourceWorkspaceId;
|
|
@@ -240,6 +257,25 @@ export function validateBundle(bundle, opts = {}) {
|
|
|
240
257
|
if (!m.primitive_inventory || typeof m.primitive_inventory !== 'object' || Array.isArray(m.primitive_inventory)) {
|
|
241
258
|
errors.push('manifest.primitive_inventory must be an object');
|
|
242
259
|
}
|
|
260
|
+
if (m.dependencies !== undefined) {
|
|
261
|
+
if (!m.dependencies || typeof m.dependencies !== 'object' || Array.isArray(m.dependencies)) {
|
|
262
|
+
errors.push('manifest.dependencies must be an object');
|
|
263
|
+
} else {
|
|
264
|
+
for (const key of ['primitives', 'extensions', 'migrations']) {
|
|
265
|
+
if (!Array.isArray(m.dependencies[key])) errors.push(`manifest.dependencies.${key} must be an array`);
|
|
266
|
+
}
|
|
267
|
+
if (!m.dependencies.integrity || typeof m.dependencies.integrity !== 'object' || Array.isArray(m.dependencies.integrity)) {
|
|
268
|
+
errors.push('manifest.dependencies.integrity must be an object');
|
|
269
|
+
} else if (typeof m.dependencies.integrity.content_hash !== 'string' || !m.dependencies.integrity.content_hash.startsWith('sha256:')) {
|
|
270
|
+
errors.push('manifest.dependencies.integrity.content_hash must be a sha256 digest');
|
|
271
|
+
} else if (
|
|
272
|
+
m.provenance?.content_hash
|
|
273
|
+
&& m.dependencies.integrity.content_hash !== m.provenance.content_hash
|
|
274
|
+
) {
|
|
275
|
+
errors.push('manifest.dependencies.integrity.content_hash must match provenance.content_hash');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
243
279
|
for (const ext of requiredExtensions) {
|
|
244
280
|
if (typeof ext !== 'string' || !ext) {
|
|
245
281
|
errors.push('required extension ids must be non-empty strings');
|
|
@@ -429,7 +465,7 @@ export function provisionBundle(bundle, opts = {}) {
|
|
|
429
465
|
* import(plan, target) → replay (§17.1). Runs each envelope through the engine
|
|
430
466
|
* (§6.3). Idempotent: each envelope's idempotency_key makes a re-run a no-op.
|
|
431
467
|
*/
|
|
432
|
-
export function importBundle(plan, vaultRoot, engine) {
|
|
468
|
+
export async function importBundle(plan, vaultRoot, engine) {
|
|
433
469
|
const dryRunFlags = new Set(plan.map((envelope) => !!envelope.dry_run));
|
|
434
470
|
if (dryRunFlags.size > 1) {
|
|
435
471
|
return {
|
|
@@ -457,7 +493,7 @@ export function importBundle(plan, vaultRoot, engine) {
|
|
|
457
493
|
// Two-phase import: validate the complete plan with in-plan references first.
|
|
458
494
|
// A single invalid document prevents every commit, eliminating partial bundle
|
|
459
495
|
// installation and allowing dry-run to validate cross-file constraints.
|
|
460
|
-
const preflight = plan.map((envelope) => {
|
|
496
|
+
const preflight = await Promise.all(plan.map((envelope) => {
|
|
461
497
|
const preflightKey = 'preflight-' + crypto.createHash('sha256')
|
|
462
498
|
.update(`${envelope.workspace_id}:${envelope.idempotency_key}:${envelope.path}`)
|
|
463
499
|
.digest('hex')
|
|
@@ -467,7 +503,7 @@ export function importBundle(plan, vaultRoot, engine) {
|
|
|
467
503
|
vaultRoot,
|
|
468
504
|
operationOptions
|
|
469
505
|
);
|
|
470
|
-
});
|
|
506
|
+
}));
|
|
471
507
|
if (!preflight.every((result) => result.success)) {
|
|
472
508
|
return { ok: false, committed: 0, wouldCommit: 0, results: preflight };
|
|
473
509
|
}
|
|
@@ -480,7 +516,7 @@ export function importBundle(plan, vaultRoot, engine) {
|
|
|
480
516
|
};
|
|
481
517
|
}
|
|
482
518
|
const results = [];
|
|
483
|
-
for (const env of plan) results.push(engine.processOperation(env, vaultRoot, operationOptions));
|
|
519
|
+
for (const env of plan) results.push(await engine.processOperation(env, vaultRoot, operationOptions));
|
|
484
520
|
const committed = results.filter((r) => r.success && !r.replay && !r.dry_run).length;
|
|
485
521
|
return { ok: results.every((r) => r.success), committed, wouldCommit: 0, results };
|
|
486
522
|
}
|