@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
|
@@ -11,7 +11,7 @@ This is an operational map. The normative source is the current
|
|
|
11
11
|
| `registry/extensions/*.json` | Host/application types that cannot redefine core primitives. |
|
|
12
12
|
| `src/engine.mjs` | Operation Contract, validation, authorization, leases, idempotency, atomic commit, audit. |
|
|
13
13
|
| `src/runtime.mjs` | Workflow trigger planning and deterministic event/task/run envelopes. |
|
|
14
|
-
| `src/semantic.mjs` | Deterministic
|
|
14
|
+
| `src/semantic.mjs` | Deterministic records/edges, multilingual embeddings, hybrid retrieval, and invariant rendering. |
|
|
15
15
|
| `src/bundle.mjs` | Export, validate, provision, import, portability filtering, link/hash integrity. |
|
|
16
16
|
| `src/registry.mjs` | Registry loading and type/portability resolution. |
|
|
17
17
|
| `src/frontmatter.mjs` | Dependency-free parse/serialize with unknown-field preservation. |
|
|
@@ -35,8 +35,7 @@ document types.
|
|
|
35
35
|
Current core document families include knowledge (`memory`), capability
|
|
36
36
|
(`skill`, `page`), governance (`rule`, `security_role`), work (`task`,
|
|
37
37
|
`workflow`), actors/catalog (`assistant`, `model`), transcripts (`conversation`,
|
|
38
|
-
`run`),
|
|
39
|
-
`release`).
|
|
38
|
+
`run`), and lifecycle/meta (`conflict`, `migration`, `release`, `primitive`).
|
|
40
39
|
|
|
41
40
|
The contract primitives are `operation`, `patch`, `event`, `delete`, and
|
|
42
41
|
`lease`.
|
|
@@ -77,15 +76,15 @@ For a bundle:
|
|
|
77
76
|
7. Internal links resolve after remapping.
|
|
78
77
|
8. Re-import replays without new commits.
|
|
79
78
|
|
|
80
|
-
For a semantic
|
|
79
|
+
For a multilingual semantic projection:
|
|
81
80
|
|
|
82
81
|
1. Records and index hashes are deterministic for identical inputs.
|
|
83
82
|
2. Stable identity, tokens, content hashes, portability, and graph edges are retained.
|
|
84
83
|
3. Private and resource-bound documents are excluded unless explicitly authorized.
|
|
85
|
-
4.
|
|
86
|
-
5.
|
|
87
|
-
6.
|
|
88
|
-
7.
|
|
84
|
+
4. Embedding records retain model identity and vector dimensions.
|
|
85
|
+
5. Lexical and semantic evidence remain separately inspectable.
|
|
86
|
+
6. Runtime rendering preserves all symbolic control fields.
|
|
87
|
+
7. Language selection never widens authorization or portability scope.
|
|
89
88
|
|
|
90
89
|
## Drift checklist
|
|
91
90
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import crypto from 'node:crypto';
|
|
5
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
5
|
|
|
7
6
|
const FALLBACK_UNIVERSAL = ['type', 'title', 'description', 'timestamp'];
|
|
@@ -20,7 +19,7 @@ const FALLBACK_REQUIRED = {
|
|
|
20
19
|
page: ['slug', 'name', 'sandbox_entry'],
|
|
21
20
|
migration: ['migration_id', 'from_version', 'to_version', 'status'],
|
|
22
21
|
release: ['release_id', 'version', 'schema_version', 'summary', 'released_at'],
|
|
23
|
-
|
|
22
|
+
primitive: ['primitive_id', 'namespace', 'version', 'name', 'mutation', 'portability', 'scopes', 'fields'],
|
|
24
23
|
};
|
|
25
24
|
const CONTRACT_TYPES = new Set(['operation', 'patch', 'event', 'delete', 'lease']);
|
|
26
25
|
const APPEND_TYPES = new Set(['conversation', 'run']);
|
|
@@ -120,46 +119,6 @@ function isSafeReferencePath(value) {
|
|
|
120
119
|
value.split('/').every((segment) => segment && segment !== '.' && segment !== '..');
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
function inferVaultRoot(target) {
|
|
124
|
-
let cursor = path.dirname(target);
|
|
125
|
-
while (path.dirname(cursor) !== cursor) {
|
|
126
|
-
if (path.basename(cursor) === 'translations') return path.dirname(cursor);
|
|
127
|
-
cursor = path.dirname(cursor);
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function validateTranslationReference(target, data, registry, kernelRoot, errors, warnings) {
|
|
133
|
-
if (data.type !== 'translation' || isEmpty(data.source_path)) return;
|
|
134
|
-
if (!isSafeReferencePath(data.source_path)) {
|
|
135
|
-
errors.push(`Field 'source_path' must be a safe vault-relative Markdown path.`);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const vaultRoot = inferVaultRoot(target);
|
|
139
|
-
if (!vaultRoot) {
|
|
140
|
-
warnings.push("Cannot resolve translation source outside the canonical translations/<locale>/ layout.");
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const source = path.resolve(vaultRoot, data.source_path);
|
|
144
|
-
const relative = path.relative(vaultRoot, source);
|
|
145
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
146
|
-
errors.push("Translation source_path escapes the vault.");
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (!fs.existsSync(source) || !fs.statSync(source).isFile() || fs.lstatSync(source).isSymbolicLink()) {
|
|
150
|
-
errors.push(`Translation source does not exist as a regular non-symlinked file: ${data.source_path}.`);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const raw = fs.readFileSync(source, 'utf8');
|
|
154
|
-
const parsed = await parseDocument(raw, kernelRoot);
|
|
155
|
-
if (parsed.data.type === 'translation') errors.push('Translations may not target another translation.');
|
|
156
|
-
const sourceDefinition = registry?.document_primitives?.[parsed.data.type];
|
|
157
|
-
const portability = parsed.data.x_portability || sourceDefinition?.portability;
|
|
158
|
-
if (portability !== 'structural') errors.push('Translations may target structural documents only.');
|
|
159
|
-
const hash = `sha256:${crypto.createHash('sha256').update(raw).digest('hex')}`;
|
|
160
|
-
if (data.source_hash !== hash) errors.push(`Translation source_hash is stale; expected '${hash}'.`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
122
|
export async function validateNode(filePath) {
|
|
164
123
|
const target = path.resolve(filePath);
|
|
165
124
|
const kernelRoot = findKernelRoot(path.dirname(target)) || findKernelRoot(process.cwd());
|
|
@@ -210,8 +169,6 @@ export async function validateNode(filePath) {
|
|
|
210
169
|
}
|
|
211
170
|
}
|
|
212
171
|
|
|
213
|
-
await validateTranslationReference(target, data, registry, kernelRoot, errors, warnings);
|
|
214
|
-
|
|
215
172
|
if (APPEND_TYPES.has(data.type) && body.trim() === '') {
|
|
216
173
|
warnings.push(`Append-only primitive '${data.type}' has an empty body.`);
|
|
217
174
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 required = new Set([
|
|
37
|
+
...requiredCapabilities(definition, action),
|
|
38
|
+
...(policyFloors[definition?.qualified_type || definition?.type]?.[action] || []),
|
|
39
|
+
...(context.policyFloor || []),
|
|
40
|
+
]);
|
|
41
|
+
const granted = new Set(principal.capabilities || []);
|
|
42
|
+
if (principal.kind === 'system' && options.systemBypass !== false) return { allowed: true, required: [...required] };
|
|
43
|
+
const missing = [...required].filter((capability) =>
|
|
44
|
+
!granted.has(capability) && !granted.has('*:*') && !granted.has(`${capability.split(':')[0]}:*`));
|
|
45
|
+
if (missing.length) return { allowed: false, required: [...required], missing, reason: `Missing capabilities: ${missing.join(', ')}.` };
|
|
46
|
+
if (context.requiresHumanConfirmation && principal.kind !== 'human') {
|
|
47
|
+
return { allowed: false, required: [...required], reason: 'A verified human confirmation is required.' };
|
|
48
|
+
}
|
|
49
|
+
if (context.requiresHumanConfirmation && context.humanConfirmation !== true) {
|
|
50
|
+
return { allowed: false, required: [...required], reason: 'Explicit human confirmation is required.' };
|
|
51
|
+
}
|
|
52
|
+
if (context.requiredAssurance) {
|
|
53
|
+
const actual = ASSURANCE[principal.authentication.assurance] ?? -1;
|
|
54
|
+
const requiredAssurance = ASSURANCE[context.requiredAssurance] ?? Number.POSITIVE_INFINITY;
|
|
55
|
+
if (actual < requiredAssurance) {
|
|
56
|
+
return { allowed: false, required: [...required], reason: `Authentication step-up to '${context.requiredAssurance}' is required.` };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { allowed: true, required: [...required] };
|
|
60
|
+
};
|
|
61
|
+
}
|
package/src/bundle.mjs
CHANGED
|
@@ -429,7 +429,7 @@ export function provisionBundle(bundle, opts = {}) {
|
|
|
429
429
|
* import(plan, target) → replay (§17.1). Runs each envelope through the engine
|
|
430
430
|
* (§6.3). Idempotent: each envelope's idempotency_key makes a re-run a no-op.
|
|
431
431
|
*/
|
|
432
|
-
export function importBundle(plan, vaultRoot, engine) {
|
|
432
|
+
export async function importBundle(plan, vaultRoot, engine) {
|
|
433
433
|
const dryRunFlags = new Set(plan.map((envelope) => !!envelope.dry_run));
|
|
434
434
|
if (dryRunFlags.size > 1) {
|
|
435
435
|
return {
|
|
@@ -457,7 +457,7 @@ export function importBundle(plan, vaultRoot, engine) {
|
|
|
457
457
|
// Two-phase import: validate the complete plan with in-plan references first.
|
|
458
458
|
// A single invalid document prevents every commit, eliminating partial bundle
|
|
459
459
|
// installation and allowing dry-run to validate cross-file constraints.
|
|
460
|
-
const preflight = plan.map((envelope) => {
|
|
460
|
+
const preflight = await Promise.all(plan.map((envelope) => {
|
|
461
461
|
const preflightKey = 'preflight-' + crypto.createHash('sha256')
|
|
462
462
|
.update(`${envelope.workspace_id}:${envelope.idempotency_key}:${envelope.path}`)
|
|
463
463
|
.digest('hex')
|
|
@@ -467,7 +467,7 @@ export function importBundle(plan, vaultRoot, engine) {
|
|
|
467
467
|
vaultRoot,
|
|
468
468
|
operationOptions
|
|
469
469
|
);
|
|
470
|
-
});
|
|
470
|
+
}));
|
|
471
471
|
if (!preflight.every((result) => result.success)) {
|
|
472
472
|
return { ok: false, committed: 0, wouldCommit: 0, results: preflight };
|
|
473
473
|
}
|
|
@@ -480,7 +480,7 @@ export function importBundle(plan, vaultRoot, engine) {
|
|
|
480
480
|
};
|
|
481
481
|
}
|
|
482
482
|
const results = [];
|
|
483
|
-
for (const env of plan) results.push(engine.processOperation(env, vaultRoot, operationOptions));
|
|
483
|
+
for (const env of plan) results.push(await engine.processOperation(env, vaultRoot, operationOptions));
|
|
484
484
|
const committed = results.filter((r) => r.success && !r.replay && !r.dry_run).length;
|
|
485
485
|
return { ok: results.every((r) => r.success), committed, wouldCommit: 0, results };
|
|
486
486
|
}
|