@gregiteen/ssss-cli 0.9.0 → 0.9.2
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 +83 -4
- package/README.md +41 -82
- package/VERSION +1 -1
- package/conformance/reference-bundle.ucw.json +19 -1
- package/docs/help/concurrency.md +45 -0
- package/docs/help/overview.md +8 -4
- package/docs/ssss-spec.md +11 -10
- package/package.json +1 -1
- package/registry/core.json +1 -1
- package/scripts/conformance-09.mjs +250 -8
- package/scripts/conformance.mjs +47 -0
- package/scripts/generate-sbom.mjs +96 -0
- package/scripts/phase9-cross-host-corpus.mjs +223 -0
- package/scripts/release.sh +20 -13
- package/skills/ssss/SKILL.md +32 -204
- package/src/authorization.mjs +2 -1
- package/src/bundle.mjs +37 -1
- package/src/events.mjs +83 -10
- package/src/file-lock.mjs +112 -0
- package/src/idempotency.mjs +15 -7
- package/src/leases.mjs +77 -16
- package/src/projections.mjs +72 -1
- package/src/vfs.mjs +33 -7
|
@@ -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/scripts/release.sh
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
# ============================================================
|
|
3
3
|
# SSSS — Version Release Script
|
|
4
4
|
#
|
|
5
|
-
# Bumps
|
|
5
|
+
# Bumps VERSION, package.json and package-lock.json, and turns the
|
|
6
|
+
# CHANGELOG's [Unreleased] entries into a section for the new version
|
|
7
|
+
# (a stub when [Unreleased] is empty).
|
|
6
8
|
# ============================================================
|
|
7
9
|
|
|
8
10
|
set -euo pipefail
|
|
@@ -20,16 +22,21 @@ if [[ ! "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
|
|
20
22
|
exit 1
|
|
21
23
|
fi
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# Quick sed to insert new version at the top of the changelog
|
|
26
|
-
sed -i.bak "/^## \[.*\]/i \\
|
|
27
|
-
## [$NEW_VERSION] - $(date +%Y-%m-%d)\\
|
|
28
|
-
### Added\\
|
|
29
|
-
- \\
|
|
30
|
-
\\
|
|
31
|
-
" CHANGELOG.md
|
|
25
|
+
cd "$(dirname "$0")/.."
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
echo "$NEW_VERSION" > VERSION
|
|
28
|
+
npm version "$NEW_VERSION" --no-git-tag-version --allow-same-version > /dev/null
|
|
29
|
+
|
|
30
|
+
node --input-type=module -e '
|
|
31
|
+
import fs from "node:fs";
|
|
32
|
+
const [version, date] = process.argv.slice(1);
|
|
33
|
+
const text = fs.readFileSync("CHANGELOG.md", "utf8");
|
|
34
|
+
if (text.includes(`## [${version}]`)) process.exit(0);
|
|
35
|
+
const match = text.match(/^## \[Unreleased\]\n([\s\S]*?)(?=^## \[)/m);
|
|
36
|
+
if (!match) { console.error("CHANGELOG.md has no ## [Unreleased] section followed by a release."); process.exit(1); }
|
|
37
|
+
const entries = match[1].trim() || "### Added\n- ";
|
|
38
|
+
const section = `## [Unreleased]\n\n## [${version}] - ${date}\n${entries}\n\n`;
|
|
39
|
+
fs.writeFileSync("CHANGELOG.md", text.replace(match[0], section));
|
|
40
|
+
' "$NEW_VERSION" "$(date +%Y-%m-%d)"
|
|
41
|
+
|
|
42
|
+
echo "✅ Bumped version to $NEW_VERSION (VERSION, package.json, package-lock.json, CHANGELOG.md)"
|
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 || []);
|
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');
|