@gregiteen/ssss-cli 0.9.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 +41 -82
- package/VERSION +1 -1
- package/conformance/reference-bundle.ucw.json +19 -1
- package/docs/help/overview.md +6 -3
- package/docs/ssss-spec.md +11 -10
- package/package.json +1 -1
- package/registry/core.json +1 -1
- package/scripts/conformance-09.mjs +139 -7
- package/scripts/conformance.mjs +47 -0
- package/scripts/generate-sbom.mjs +96 -0
- package/scripts/phase9-cross-host-corpus.mjs +223 -0
- package/skills/ssss/SKILL.md +32 -204
- package/src/authorization.mjs +2 -1
- package/src/bundle.mjs +37 -1
- package/src/events.mjs +74 -9
- package/src/leases.mjs +50 -2
- package/src/projections.mjs +72 -1
package/scripts/conformance.mjs
CHANGED
|
@@ -691,6 +691,53 @@ async function runCliSmokeConformance() {
|
|
|
691
691
|
check('ssss import --dry-run reports would-commit counts and leaves target empty',
|
|
692
692
|
dryRunOut.includes('would commit') && dryRunFiles.length === 0,
|
|
693
693
|
dryRunOut.trim());
|
|
694
|
+
|
|
695
|
+
const primitiveOut = JSON.parse(execFileSync('node', [
|
|
696
|
+
path.join(__dirname, 'ssss.mjs'),
|
|
697
|
+
'primitive', 'create',
|
|
698
|
+
'--namespace', 'acme',
|
|
699
|
+
'--name', '顧客予約',
|
|
700
|
+
'--language', 'ja',
|
|
701
|
+
'--fields', JSON.stringify([{ id: 'status', name: '状態', kind: 'enum', values: ['pending', 'confirmed'], required: true }]),
|
|
702
|
+
], { encoding: 'utf8' }));
|
|
703
|
+
check('ssss primitive create emits a stable qualified identity',
|
|
704
|
+
/^acme:p_[a-f0-9]{20}$/.test(primitiveOut.primitive_id),
|
|
705
|
+
JSON.stringify(primitiveOut));
|
|
706
|
+
|
|
707
|
+
const defPath = path.join(tmp, 'definition.json');
|
|
708
|
+
fs.writeFileSync(defPath, `${JSON.stringify(primitiveOut)}\n`);
|
|
709
|
+
const inspectOut = JSON.parse(execFileSync('node', [
|
|
710
|
+
path.join(__dirname, 'ssss.mjs'),
|
|
711
|
+
'primitive', 'inspect',
|
|
712
|
+
defPath,
|
|
713
|
+
], { encoding: 'utf8' }));
|
|
714
|
+
const extensionPath = path.join(tmp, 'extension.json');
|
|
715
|
+
fs.writeFileSync(extensionPath, `${JSON.stringify(inspectOut.registry)}\n`);
|
|
716
|
+
const lockOut = JSON.parse(execFileSync('node', [
|
|
717
|
+
path.join(__dirname, 'ssss.mjs'),
|
|
718
|
+
'registry', 'lock',
|
|
719
|
+
'--extension', extensionPath,
|
|
720
|
+
], { encoding: 'utf8' }));
|
|
721
|
+
check('ssss registry lock emits integrity metadata',
|
|
722
|
+
typeof lockOut.integrity === 'string' && lockOut.integrity.startsWith('sha256:'),
|
|
723
|
+
JSON.stringify(lockOut));
|
|
724
|
+
|
|
725
|
+
const adapterOut = JSON.parse(execFileSync('node', [
|
|
726
|
+
path.join(__dirname, 'ssss.mjs'),
|
|
727
|
+
'adapter', 'conformance',
|
|
728
|
+
], { encoding: 'utf8' }));
|
|
729
|
+
check('ssss adapter conformance report is machine-readable and green',
|
|
730
|
+
adapterOut.passed === true && adapterOut.suites?.vfs_memory?.passed === true,
|
|
731
|
+
JSON.stringify(adapterOut));
|
|
732
|
+
|
|
733
|
+
const migrateOut = JSON.parse(execFileSync('node', [
|
|
734
|
+
path.join(__dirname, 'ssss.mjs'),
|
|
735
|
+
'migrate', '0.8-to-0.9',
|
|
736
|
+
vault,
|
|
737
|
+
], { encoding: 'utf8' }));
|
|
738
|
+
check('ssss migrate 0.8-to-0.9 dry-run emits backup manifest without writes',
|
|
739
|
+
migrateOut.dry_run === true && migrateOut.writes_performed === 0 && Array.isArray(migrateOut.backup_manifest),
|
|
740
|
+
JSON.stringify(migrateOut));
|
|
694
741
|
} finally {
|
|
695
742
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
696
743
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate a minimal SPDX-lite SBOM for @gregiteen/ssss-cli publish artifacts.
|
|
4
|
+
* Dependency-free: packages only what is declared in package.json (zero runtime deps).
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import crypto from 'node:crypto';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
13
|
+
|
|
14
|
+
function walk(dir, out = []) {
|
|
15
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
16
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
17
|
+
const abs = path.join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) walk(abs, out);
|
|
19
|
+
else if (entry.isFile()) out.push(abs);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const files = walk(root).filter(
|
|
25
|
+
(f) =>
|
|
26
|
+
!f.includes(`${path.sep}artifacts${path.sep}`) &&
|
|
27
|
+
!f.endsWith('package-lock.json'),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const packages = [
|
|
31
|
+
{
|
|
32
|
+
name: pkg.name,
|
|
33
|
+
version: pkg.version,
|
|
34
|
+
downloadLocation: `https://registry.npmjs.org/${pkg.name}/-/${pkg.name.split('/').pop()}-${pkg.version}.tgz`,
|
|
35
|
+
filesAnalyzed: true,
|
|
36
|
+
licenseConcluded: pkg.license || 'NOASSERTION',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Zero runtime dependencies by design — still list optional peer/dev if present
|
|
41
|
+
for (const [section, deps] of Object.entries({
|
|
42
|
+
dependencies: pkg.dependencies || {},
|
|
43
|
+
optionalDependencies: pkg.optionalDependencies || {},
|
|
44
|
+
})) {
|
|
45
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
46
|
+
packages.push({
|
|
47
|
+
name,
|
|
48
|
+
version: String(version).replace(/^[\^~]/, ''),
|
|
49
|
+
relationship: section,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fileEntries = files.map((abs) => {
|
|
55
|
+
const rel = path.relative(root, abs).split(path.sep).join('/');
|
|
56
|
+
const buf = fs.readFileSync(abs);
|
|
57
|
+
return {
|
|
58
|
+
fileName: rel,
|
|
59
|
+
checksums: [
|
|
60
|
+
{
|
|
61
|
+
algorithm: 'SHA256',
|
|
62
|
+
checksumValue: crypto.createHash('sha256').update(buf).digest('hex'),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const sbom = {
|
|
69
|
+
spdxVersion: 'SPDX-2.3',
|
|
70
|
+
dataLicense: 'CC0-1.0',
|
|
71
|
+
SPDXID: 'SPDXRef-DOCUMENT',
|
|
72
|
+
name: `${pkg.name}-${pkg.version}`,
|
|
73
|
+
documentNamespace: `https://github.com/gregiteen/ssss/sbom/${pkg.version}`,
|
|
74
|
+
creationInfo: {
|
|
75
|
+
created: new Date().toISOString(),
|
|
76
|
+
creators: ['Tool: ssss-generate-sbom'],
|
|
77
|
+
},
|
|
78
|
+
packages,
|
|
79
|
+
files: fileEntries,
|
|
80
|
+
relationships: [
|
|
81
|
+
{
|
|
82
|
+
spdxElementId: 'SPDXRef-DOCUMENT',
|
|
83
|
+
relationshipType: 'DESCRIBES',
|
|
84
|
+
relatedSpdxElement: 'SPDXRef-Package',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
comment:
|
|
88
|
+
'SSSS reference package is intentionally dependency-free at runtime. Host apps supply adapters.',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const outDir = path.join(root, 'artifacts');
|
|
92
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
93
|
+
const outFile = path.join(outDir, `sbom-${pkg.version}.spdx.json`);
|
|
94
|
+
fs.writeFileSync(outFile, `${JSON.stringify(sbom, null, 2)}\n`);
|
|
95
|
+
console.log(`SBOM written: ${outFile}`);
|
|
96
|
+
console.log(`Packages: ${packages.length}, files hashed: ${fileEntries.length}`);
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Phase 9 — shared cross-host command corpus.
|
|
4
|
+
*
|
|
5
|
+
* Runs a normalized set of Operation Contract envelopes through the package
|
|
6
|
+
* kernel (MemoryVfs + memory durable stores) and prints a machine-readable
|
|
7
|
+
* report. Hosts can re-run the same envelopes via their bridges and compare
|
|
8
|
+
* success/valid/type verdicts against this reference.
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import crypto from 'node:crypto';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { createKernel, canonicalRequestHash } from '../src/kernel.mjs';
|
|
16
|
+
import { FileSystemVfs } from '../src/vfs.mjs';
|
|
17
|
+
import { MemoryEventStore } from '../src/events.mjs';
|
|
18
|
+
import { MemoryIdempotencyStore } from '../src/idempotency.mjs';
|
|
19
|
+
import { MemoryLeaseStore } from '../src/leases.mjs';
|
|
20
|
+
import { createValidator } from '../src/validator.mjs';
|
|
21
|
+
import { loadRegistries } from '../src/registry.mjs';
|
|
22
|
+
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
|
|
25
|
+
function ruleContent(name) {
|
|
26
|
+
return [
|
|
27
|
+
'---',
|
|
28
|
+
'type: rule',
|
|
29
|
+
`title: ${name}`,
|
|
30
|
+
`description: Phase 9 corpus rule ${name}`,
|
|
31
|
+
'timestamp: 2026-07-10T00:00:00Z',
|
|
32
|
+
`name: ${name}`,
|
|
33
|
+
'---',
|
|
34
|
+
'',
|
|
35
|
+
`Body for ${name}.`,
|
|
36
|
+
'',
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function spanishRuleContent() {
|
|
41
|
+
return [
|
|
42
|
+
'---',
|
|
43
|
+
'type: rule',
|
|
44
|
+
'title: Bienvenida',
|
|
45
|
+
'description: Regla estructural de corpus multilingüe',
|
|
46
|
+
'timestamp: 2026-07-10T00:00:00Z',
|
|
47
|
+
'name: bienvenida',
|
|
48
|
+
'---',
|
|
49
|
+
'',
|
|
50
|
+
'Cuerpo en español.',
|
|
51
|
+
'',
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const CORPUS = [
|
|
56
|
+
{
|
|
57
|
+
id: 'corpus-rule-create',
|
|
58
|
+
envelope: {
|
|
59
|
+
type: 'operation',
|
|
60
|
+
workspace_id: 'ws-corpus',
|
|
61
|
+
idempotency_key: 'corpus-rule-1',
|
|
62
|
+
path: 'rules/welcome.md',
|
|
63
|
+
content: ruleContent('welcome'),
|
|
64
|
+
},
|
|
65
|
+
expect: { success: true, valid: true },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'corpus-rule-idempotent-replay',
|
|
69
|
+
envelope: {
|
|
70
|
+
type: 'operation',
|
|
71
|
+
workspace_id: 'ws-corpus',
|
|
72
|
+
idempotency_key: 'corpus-rule-1',
|
|
73
|
+
path: 'rules/welcome.md',
|
|
74
|
+
content: ruleContent('welcome'),
|
|
75
|
+
},
|
|
76
|
+
expect: { success: true, valid: true, replay: true },
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'corpus-rule-idempotent-conflict',
|
|
80
|
+
envelope: {
|
|
81
|
+
type: 'operation',
|
|
82
|
+
workspace_id: 'ws-corpus',
|
|
83
|
+
idempotency_key: 'corpus-rule-1',
|
|
84
|
+
path: 'rules/welcome.md',
|
|
85
|
+
content: ruleContent('welcome-changed'),
|
|
86
|
+
},
|
|
87
|
+
expect: { success: false },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'corpus-dry-run',
|
|
91
|
+
envelope: {
|
|
92
|
+
type: 'operation',
|
|
93
|
+
workspace_id: 'ws-corpus',
|
|
94
|
+
idempotency_key: 'corpus-dry-1',
|
|
95
|
+
path: 'rules/dry.md',
|
|
96
|
+
content: ruleContent('dry'),
|
|
97
|
+
dry_run: true,
|
|
98
|
+
},
|
|
99
|
+
expect: { success: true, valid: true, dry_run: true },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'corpus-multilingual-rule',
|
|
103
|
+
envelope: {
|
|
104
|
+
type: 'operation',
|
|
105
|
+
workspace_id: 'ws-corpus',
|
|
106
|
+
idempotency_key: 'corpus-es-1',
|
|
107
|
+
path: 'rules/bienvenida.md',
|
|
108
|
+
content: spanishRuleContent(),
|
|
109
|
+
},
|
|
110
|
+
expect: { success: true, valid: true },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'corpus-missing-actor-denied',
|
|
114
|
+
envelope: {
|
|
115
|
+
type: 'operation',
|
|
116
|
+
workspace_id: 'ws-corpus',
|
|
117
|
+
idempotency_key: 'corpus-no-actor',
|
|
118
|
+
path: 'rules/no-actor.md',
|
|
119
|
+
content: ruleContent('no-actor'),
|
|
120
|
+
},
|
|
121
|
+
principal: null,
|
|
122
|
+
expect: { success: false },
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
function principal() {
|
|
127
|
+
return {
|
|
128
|
+
id: 'phase9:corpus',
|
|
129
|
+
kind: 'system',
|
|
130
|
+
workspaceIds: ['ws-corpus'],
|
|
131
|
+
capabilities: ['*:*'],
|
|
132
|
+
authentication: { provider: 'phase9-corpus', assurance: 'system' },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function main() {
|
|
137
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ssss-phase9-corpus-'));
|
|
138
|
+
const registrySet = loadRegistries();
|
|
139
|
+
const validator = createValidator({ registrySet });
|
|
140
|
+
const kernel = createKernel({
|
|
141
|
+
vfs: new FileSystemVfs(root),
|
|
142
|
+
eventStore: new MemoryEventStore(),
|
|
143
|
+
leaseStore: new MemoryLeaseStore(),
|
|
144
|
+
idempotencyStore: new MemoryIdempotencyStore(),
|
|
145
|
+
validator,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const results = [];
|
|
149
|
+
for (const caseDef of CORPUS) {
|
|
150
|
+
const p =
|
|
151
|
+
caseDef.principal === null
|
|
152
|
+
? null
|
|
153
|
+
: caseDef.principal || principal();
|
|
154
|
+
let result;
|
|
155
|
+
try {
|
|
156
|
+
if (!p) {
|
|
157
|
+
// Simulate host fail-closed before kernel when identity missing
|
|
158
|
+
result = {
|
|
159
|
+
success: false,
|
|
160
|
+
validation: { valid: false, errors: ['Missing actor'] },
|
|
161
|
+
};
|
|
162
|
+
} else {
|
|
163
|
+
result = await kernel.execute(caseDef.envelope, { principal: p });
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
result = {
|
|
167
|
+
success: false,
|
|
168
|
+
validation: { valid: false, errors: [error.message] },
|
|
169
|
+
error: error.message,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const verdict = {
|
|
174
|
+
id: caseDef.id,
|
|
175
|
+
success: !!result.success,
|
|
176
|
+
valid: !!result.validation?.valid,
|
|
177
|
+
replay: !!result.replay,
|
|
178
|
+
dry_run: !!result.dry_run || !!caseDef.envelope.dry_run,
|
|
179
|
+
type: result.validation?.type || null,
|
|
180
|
+
errors: result.validation?.errors || [],
|
|
181
|
+
request_hash: canonicalRequestHash(caseDef.envelope),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const ok =
|
|
185
|
+
(caseDef.expect.success === undefined ||
|
|
186
|
+
verdict.success === caseDef.expect.success) &&
|
|
187
|
+
(caseDef.expect.valid === undefined ||
|
|
188
|
+
verdict.valid === caseDef.expect.valid) &&
|
|
189
|
+
(caseDef.expect.replay === undefined ||
|
|
190
|
+
verdict.replay === caseDef.expect.replay) &&
|
|
191
|
+
(caseDef.expect.dry_run === undefined ||
|
|
192
|
+
verdict.dry_run === caseDef.expect.dry_run);
|
|
193
|
+
|
|
194
|
+
results.push({ ...verdict, passed: ok });
|
|
195
|
+
console.log(`${ok ? '✅' : '❌'} ${caseDef.id} success=${verdict.success} valid=${verdict.valid}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const report = {
|
|
199
|
+
suite: 'phase9-cross-host-corpus',
|
|
200
|
+
package: '@gregiteen/ssss-cli',
|
|
201
|
+
version: JSON.parse(
|
|
202
|
+
fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'),
|
|
203
|
+
).version,
|
|
204
|
+
generated_at: new Date().toISOString(),
|
|
205
|
+
passed: results.every((r) => r.passed),
|
|
206
|
+
results,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const outDir = path.join(__dirname, '../artifacts');
|
|
210
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
211
|
+
const outFile = path.join(outDir, 'phase9-cross-host-corpus.json');
|
|
212
|
+
fs.writeFileSync(outFile, `${JSON.stringify(report, null, 2)}\n`);
|
|
213
|
+
console.log(`\nReport: ${outFile}`);
|
|
214
|
+
console.log(`Overall: ${report.passed ? 'PASS' : 'FAIL'} (${results.filter((r) => r.passed).length}/${results.length})`);
|
|
215
|
+
|
|
216
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
217
|
+
process.exit(report.passed ? 0 : 1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
main().catch((error) => {
|
|
221
|
+
console.error(error);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
package/skills/ssss/SKILL.md
CHANGED
|
@@ -1,221 +1,49 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: skill
|
|
3
|
-
title: SSSS
|
|
3
|
+
title: SSSS Protocol Reference
|
|
4
4
|
name: ssss
|
|
5
|
-
description: "
|
|
6
|
-
timestamp: 2026-07-
|
|
5
|
+
description: "Use this skill when reading, writing, parsing, validating, or extending SSSS -- the Structured Semantic Syntax System: the SSSS Core Contract API, VFS-native primitives, operation/patch/event envelopes, leases, and projections. MANDATORY: You MUST read the full SKILL.md file before executing."
|
|
6
|
+
timestamp: 2026-07-15T00:00:00Z
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# SSSS — Structured Semantic Syntax System
|
|
9
|
+
# SSSS — Structured Semantic Syntax System Reference
|
|
10
10
|
|
|
11
|
-
SSSS is a
|
|
12
|
-
state is typed Markdown plus append-only events. Databases, indexes, queues,
|
|
13
|
-
dashboards, and search stores are host projections unless explicitly documented
|
|
14
|
-
as external resources.
|
|
11
|
+
You are operating in a repository that implements, extends, or relies on the **Structured Semantic Syntax System (SSSS)**. SSSS is a database-free, Markdown-first schema and mutation contract for AI-agent state.
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
This skill provides the normative rules for interacting with an SSSS-compliant vault or host engine (like Total Recall).
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
## ⚠️ The VFS-First Mandate (Absolute Rule)
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
`registry/core.json`, and `docs/ssss-spec.md` when they exist.
|
|
22
|
-
2. Treat `registry/core.json` as the machine-readable primitive contract and
|
|
23
|
-
`docs/ssss-spec.md` as the normative explanation.
|
|
24
|
-
3. Treat `src/engine.mjs`, `src/runtime.mjs`, `src/semantic.mjs`,
|
|
25
|
-
`src/bundle.mjs`, `src/registry.mjs`, and `src/frontmatter.mjs` as the
|
|
26
|
-
reference behavior.
|
|
27
|
-
4. Treat host schemas and services as adapters or extension registries. Never
|
|
28
|
-
promote host-only fields into the core standard without an explicit spec and
|
|
29
|
-
registry change.
|
|
30
|
-
5. Read the current package name and version instead of hard-coding an alias.
|
|
31
|
-
In this checkout the published package is `@gregiteen/ssss-cli`; the CLI is
|
|
32
|
-
`ssss`.
|
|
17
|
+
**NEVER use raw database inserts, updates, or deletes to mutate application state in an SSSS environment!**
|
|
33
18
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
All persistent state (memories, agents, workflows, tasks) lives in the Virtual File System (VFS) as Markdown files with YAML frontmatter. The VFS is the absolute source of truth.
|
|
20
|
+
- If you are interacting via CLI: Use `npx total-recall remember` or `npx total-recall recall`.
|
|
21
|
+
- If you are building host code: You must route mutations through the SSSS **Operation Contract** engine (which handles idempotency, leasing, and semantic indexing), or use the provided API wrappers. Do not bypass the engine to write files directly.
|
|
37
22
|
|
|
38
|
-
##
|
|
23
|
+
## SSSS Core Concepts
|
|
39
24
|
|
|
40
|
-
|
|
25
|
+
### 1. Primitives (The Data Model)
|
|
26
|
+
Data in SSSS is defined by standard primitives (e.g. `workflow`, `assistant`, `skill`, `memory`). Each primitive has a defined schema (in `registry/core.json` or extension registries) and belongs to a specific Portability Class:
|
|
27
|
+
- `structural`: Logic and system prompts (e.g. rules, workflows). Included in export templates.
|
|
28
|
+
- `tenant_private`: User data (e.g. chats, tasks). NEVER included in templates.
|
|
29
|
+
- `resource_bound`: Requires external setup (e.g. Twilio numbers). Exported only as a requirement.
|
|
41
30
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
6. Skills, examples, README prose, and copied mirrors.
|
|
31
|
+
### 2. The Operation Contract (Envelopes)
|
|
32
|
+
When an SSSS engine processes a write, it receives an **Envelope**:
|
|
33
|
+
- `operation`: Creates or completely replaces a document.
|
|
34
|
+
- `patch`: Merges partial data into a document (e.g. updating task status).
|
|
35
|
+
- `event`: Appends an immutable log entry to a document.
|
|
36
|
+
- `delete`: Tombstones a replace-type document.
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
### 3. Idempotency and Leases
|
|
39
|
+
- **Idempotency**: All mutations must provide an `idempotency_key`. The engine ensures duplicate submissions do not result in duplicate state.
|
|
40
|
+
- **Leases**: Background tasks and workflows use the Lease store to acquire temporary locks on documents to prevent race conditions during execution.
|
|
52
41
|
|
|
53
|
-
|
|
42
|
+
### 4. Bundles (.ucw)
|
|
43
|
+
An SSSS vault can be packaged into a `.ucw` (Universal Containerized Workspace) bundle. A bundle is a portable snapshot containing a manifest, deterministic file paths, and content hashes for cryptographic integrity.
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
documents require the universal fields:
|
|
45
|
+
## Validation
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- `timestamp`
|
|
62
|
-
|
|
63
|
-
Primitive-specific required fields are additive. An SSSS-managed skill also
|
|
64
|
-
requires `name`; a schema-v2 memory has knowledge-graph fields; a workflow
|
|
65
|
-
requires `name`.
|
|
66
|
-
|
|
67
|
-
Unknown frontmatter fields must be preserved for forward compatibility. Never
|
|
68
|
-
drop a nested map, array, or unknown field during parse/serialize or patch.
|
|
69
|
-
|
|
70
|
-
Registry declarations are executable contract. Enforce `required_fields`,
|
|
71
|
-
`required_when`, `enums`, `patterns`, `immutable_fields`, `references`,
|
|
72
|
-
`append_only`, and `portability`. Reject malformed extension definitions,
|
|
73
|
-
symlinked registry files, and core or sibling type collisions during load.
|
|
74
|
-
|
|
75
|
-
## Mutation Contract
|
|
76
|
-
|
|
77
|
-
Agent-generated mutations flow through the Operation Contract:
|
|
78
|
-
|
|
79
|
-
```text
|
|
80
|
-
validate envelope
|
|
81
|
-
-> verify safe workspace/idempotency/actor identifiers
|
|
82
|
-
-> resolve and contain the target path
|
|
83
|
-
-> replay or reject the idempotency key + request hash
|
|
84
|
-
-> authorize with verified actor identity (fail closed)
|
|
85
|
-
-> validate any active lease (fail closed)
|
|
86
|
-
-> validate universal + primitive-specific content
|
|
87
|
-
-> commit atomically
|
|
88
|
-
-> append the audit event
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The mutation envelopes are:
|
|
92
|
-
|
|
93
|
-
- `operation` — create or fully replace a replace-type document.
|
|
94
|
-
- `patch` — merge an existing replace-type document without changing `type`.
|
|
95
|
-
- `event` — append an immutable event record.
|
|
96
|
-
- `delete` — remove a replace-type document and emit an audit event; reject
|
|
97
|
-
append-type targets.
|
|
98
|
-
|
|
99
|
-
Use `dry_run: true` for model-generated preflight. Idempotency is scoped by the
|
|
100
|
-
effective request, not the key alone: the same request replays, while the same
|
|
101
|
-
workspace/key with a different request hash is a conflict.
|
|
102
|
-
|
|
103
|
-
## Append, Lease, and Path Safety
|
|
104
|
-
|
|
105
|
-
- `conversation` and `run` are append-only. Preserve prior bytes and append new
|
|
106
|
-
records; never rewrite or delete them.
|
|
107
|
-
- Events are immutable.
|
|
108
|
-
- Paths must be relative, contained vault paths. Reject absolute paths,
|
|
109
|
-
backslashes, null bytes, empty segments, `.`/`..`, directory targets, and
|
|
110
|
-
symlink escapes.
|
|
111
|
-
- Leases are actor-owned, operation-linked, expiring locks. Missing, mismatched,
|
|
112
|
-
malformed, or unreadable lease state fails closed.
|
|
113
|
-
- Hosts must overwrite caller-supplied actor data with verified session identity
|
|
114
|
-
before authorization.
|
|
115
|
-
|
|
116
|
-
## Workflow Runtime
|
|
117
|
-
|
|
118
|
-
Workflow frontmatter owns the schedule. Cron, daemon, webhook, file watcher, and
|
|
119
|
-
event-loop processes are wake-up mechanisms, not a second scheduling database.
|
|
120
|
-
|
|
121
|
-
Use `@gregiteen/ssss-cli/runtime` to deterministically derive the trigger event,
|
|
122
|
-
task, and run envelopes. Duplicate ticks must replay idempotently. Queues and
|
|
123
|
-
task dashboards are disposable projections over workflow state and events.
|
|
124
|
-
|
|
125
|
-
## Bundles and Portability
|
|
126
|
-
|
|
127
|
-
Every document primitive resolves to one portability class:
|
|
128
|
-
|
|
129
|
-
- `structural` — reusable model shipped in templates and sale bundles.
|
|
130
|
-
- `tenant_private` — operator/customer runtime data; excluded from template and
|
|
131
|
-
sale exports.
|
|
132
|
-
- `resource_bound` — exported only as a requirement declaration; never leak the
|
|
133
|
-
seller's bound resource value.
|
|
134
|
-
|
|
135
|
-
Bundle paths must be unique, safe, and symlink-free. Validate content hashes,
|
|
136
|
-
required extensions, primitive inventory, core version, parameters, and link
|
|
137
|
-
integrity before import. Preflight the complete plan before the first commit so a
|
|
138
|
-
late invalid envelope cannot leave a partial install. Provisioning produces
|
|
139
|
-
deterministic Operation Contract envelopes; re-import must be a no-op.
|
|
140
|
-
|
|
141
|
-
## Multilingual Semantic Runtime
|
|
142
|
-
|
|
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
|
-
|
|
147
|
-
- Index only `structural` documents by default. Include `tenant_private` and
|
|
148
|
-
`resource_bound` sources only after an explicit authorized opt-in.
|
|
149
|
-
- Derive stable identity from `semantic_id`, then `resource`, then source path.
|
|
150
|
-
- Build edges from explicit `relations`, wiki links, and Markdown links.
|
|
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.
|
|
155
|
-
|
|
156
|
-
## Scope and Host Adapters
|
|
157
|
-
|
|
158
|
-
The core standard does not collapse host ownership layers. A host may implement
|
|
159
|
-
global/project or system/account/workspace overlays, but must document precedence
|
|
160
|
-
and preserve isolation. Shared system skills should be inherited through an
|
|
161
|
-
overlay rather than copied into every workspace.
|
|
162
|
-
|
|
163
|
-
When working in Total Recall, use its validated-write and memory-validator
|
|
164
|
-
adapter if those modules exist. In another host, locate its extension registry
|
|
165
|
-
and conformance bridge. Do not quote Total Recall paths as universal SSSS
|
|
166
|
-
architecture.
|
|
167
|
-
|
|
168
|
-
## Procedure for Changes
|
|
169
|
-
|
|
170
|
-
1. Classify the change: core primitive, contract behavior, runtime behavior,
|
|
171
|
-
semantic/runtime-rendering behavior, bundle/provisioning behavior, projection, or
|
|
172
|
-
host extension.
|
|
173
|
-
2. Inspect the registry, spec section, implementation, fixtures, and host bridge
|
|
174
|
-
for that surface.
|
|
175
|
-
3. Make the smallest coherent change across code and canon.
|
|
176
|
-
4. Add positive, negative, replay, and invalid-path cases where applicable.
|
|
177
|
-
5. Run focused validation, then the full conformance gate.
|
|
178
|
-
6. Update skill/reference mirrors only after the executable contract is green.
|
|
179
|
-
|
|
180
|
-
## Validation
|
|
181
|
-
|
|
182
|
-
Validate a Markdown document with the dependency-free skill helper:
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
node skills/ssss/scripts/validate-schema.mjs path/to/document.md
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
In the SSSS reference repository, the required gate is:
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
|
-
npm test
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
It covers registry/engine parity, shipped-skill conformance, Operation Contract
|
|
195
|
-
fixtures, workflow runtime behavior, lease/frontmatter regressions,
|
|
196
|
-
extension-registry hardening, multilingual semantic behavior,
|
|
197
|
-
bundle/provisioning round trips, and CLI smoke tests.
|
|
198
|
-
|
|
199
|
-
For host adapters, run the host's named SSSS conformance command as well. Do not
|
|
200
|
-
substitute a loose YAML parse or a generic typecheck for the SSSS gate.
|
|
201
|
-
|
|
202
|
-
## Do Not
|
|
203
|
-
|
|
204
|
-
- Do not treat an installed skill, copied spec, or README as newer than the live
|
|
205
|
-
registry and conformance suite.
|
|
206
|
-
- Do not use raw file writes for agent-generated vault mutations.
|
|
207
|
-
- Do not make a derived database/index/queue the canonical record.
|
|
208
|
-
- Do not add a primitive in prose without registry and fixture coverage.
|
|
209
|
-
- Do not translate symbolic control fields or index private content by default.
|
|
210
|
-
- Do not materialize a projection inside its source vault.
|
|
211
|
-
- Do not mix document primitives with contract envelopes.
|
|
212
|
-
- Do not claim conformance from HTTP success or file creation alone.
|
|
213
|
-
- Do not publish package names, versions, or exporter identities without reading
|
|
214
|
-
them from current package metadata.
|
|
215
|
-
|
|
216
|
-
## References
|
|
217
|
-
|
|
218
|
-
Read `references/contract-reference.md` for the compact topology, primitive
|
|
219
|
-
families, package entry points, and drift checklist. For normative detail, read
|
|
220
|
-
the exact linked section of `docs/ssss-spec.md` rather than loading a stale
|
|
221
|
-
vendored copy.
|
|
47
|
+
If you are writing tests or schemas in a host repository, ensure that:
|
|
48
|
+
1. `MemoryNodeSchema` validations provide all required SSSS v2 properties (`status`, `created`, `updated`, `last_accessed`, `source`, `decay`).
|
|
49
|
+
2. Schema changes maintain strict compatibility with the Open Knowledge Format (OKF) standard if applicable.
|
package/src/authorization.mjs
CHANGED
|
@@ -33,9 +33,10 @@ export function createCapabilityAuthorizer(options = {}) {
|
|
|
33
33
|
if (principal.kind !== 'system' && !principal.workspaceIds.includes(workspaceId)) {
|
|
34
34
|
return { allowed: false, reason: `Principal '${principal.id}' is not scoped to workspace '${workspaceId}'.` };
|
|
35
35
|
}
|
|
36
|
+
const floorKey = definition?.qualified_type || definition?.primitive_id || definition?.type;
|
|
36
37
|
const required = new Set([
|
|
37
38
|
...requiredCapabilities(definition, action),
|
|
38
|
-
...(policyFloors[
|
|
39
|
+
...(policyFloors[floorKey]?.[action] || []),
|
|
39
40
|
...(context.policyFloor || []),
|
|
40
41
|
]);
|
|
41
42
|
const granted = new Set(principal.capabilities || []);
|