@gregiteen/ssss-cli 0.7.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/CHANGELOG.md +124 -0
- package/LICENSE +15 -0
- package/README.md +162 -0
- package/VERSION +1 -0
- package/conformance/README.md +72 -0
- package/conformance/fixtures.json +763 -0
- package/conformance/reference-bundle.ucw.json +135 -0
- package/docs/help/autolink.md +15 -0
- package/docs/help/bundle.md +47 -0
- package/docs/help/conformance.md +34 -0
- package/docs/help/export.md +20 -0
- package/docs/help/import.md +19 -0
- package/docs/help/overview.md +33 -0
- package/docs/help/portability.md +27 -0
- package/docs/help/provision.md +19 -0
- package/docs/help/provisioning.md +43 -0
- package/docs/help/roles.md +61 -0
- package/docs/help/runtime.md +78 -0
- package/docs/help/scaffold.md +47 -0
- package/docs/ssss-spec.md +1325 -0
- package/package.json +62 -0
- package/registry/core.json +225 -0
- package/registry/extensions/festech.json +134 -0
- package/scripts/audit-registry-field-usage.mjs +136 -0
- package/scripts/autolink.mjs +341 -0
- package/scripts/build-reference-bundle.mjs +169 -0
- package/scripts/cmd-export.mjs +72 -0
- package/scripts/cmd-help.mjs +60 -0
- package/scripts/cmd-import.mjs +71 -0
- package/scripts/cmd-inspect.mjs +78 -0
- package/scripts/cmd-new.mjs +262 -0
- package/scripts/cmd-provision.mjs +68 -0
- package/scripts/cmd-validate.mjs +43 -0
- package/scripts/conformance.mjs +408 -0
- package/scripts/lib/cli.mjs +92 -0
- package/scripts/release.sh +35 -0
- package/scripts/ssss.mjs +93 -0
- package/scripts/validate-skills.mjs +115 -0
- package/src/bundle.mjs +261 -0
- package/src/engine.mjs +452 -0
- package/src/frontmatter.mjs +109 -0
- package/src/registry.mjs +67 -0
- package/src/runtime.mjs +212 -0
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gregiteen/ssss-cli",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "CLI tools and the reference engine for the Structured Semantic Syntax System",
|
|
5
|
+
"main": "src/engine.mjs",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/engine.mjs",
|
|
8
|
+
"./engine": "./src/engine.mjs",
|
|
9
|
+
"./runtime": "./src/runtime.mjs",
|
|
10
|
+
"./registry": "./src/registry.mjs",
|
|
11
|
+
"./frontmatter": "./src/frontmatter.mjs",
|
|
12
|
+
"./bundle": "./src/bundle.mjs",
|
|
13
|
+
"./conformance/fixtures.json": "./conformance/fixtures.json",
|
|
14
|
+
"./conformance/reference-bundle.ucw.json": "./conformance/reference-bundle.ucw.json",
|
|
15
|
+
"./registry/core.json": "./registry/core.json",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"ssss": "./scripts/ssss.mjs"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src",
|
|
27
|
+
"scripts",
|
|
28
|
+
"registry",
|
|
29
|
+
"conformance",
|
|
30
|
+
"docs/ssss-spec.md",
|
|
31
|
+
"docs/help",
|
|
32
|
+
"VERSION",
|
|
33
|
+
"README.md",
|
|
34
|
+
"CHANGELOG.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"conformance": "node ./scripts/conformance.mjs conformance",
|
|
39
|
+
"test": "node ./scripts/conformance.mjs conformance --engine"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"ssss",
|
|
43
|
+
"agent",
|
|
44
|
+
"okf",
|
|
45
|
+
"markdown",
|
|
46
|
+
"vault",
|
|
47
|
+
"operation-contract",
|
|
48
|
+
"ucw",
|
|
49
|
+
"provisioning"
|
|
50
|
+
],
|
|
51
|
+
"author": "Greg Iteen",
|
|
52
|
+
"license": "ISC",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/gregiteen/ssss.git"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/gregiteen/ssss#readme",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/gregiteen/ssss/issues"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {}
|
|
62
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ssss.dev/registry/v1",
|
|
3
|
+
"registry": "core",
|
|
4
|
+
"spec_version": "0.6",
|
|
5
|
+
"description": "The canonical SSSS core primitive registry. These 14 document primitives and 5 contract primitives are defined by the SSSS specification (docs/ssss-spec.md §5) and are owned by this standard. Every document primitive declares a portability class (§5.5); the registry also defines the workflow runtime contract (§11.8), the `.ucw` bundle format (§16), and the provisioning contract (§17). A conformant host MUST implement any core primitive it uses according to this registry. Applications extend the core with their own primitives via an extension registry (see registry/README.md); extensions MUST NOT redefine a core type.",
|
|
6
|
+
"conformance": "A host is core-conformant for a primitive if a file of that type validates exactly when all required_fields are present and non-empty, and (for append_only types) writes only append.",
|
|
7
|
+
|
|
8
|
+
"document_primitives": {
|
|
9
|
+
"memory": {
|
|
10
|
+
"family": "knowledge",
|
|
11
|
+
"canonical_filename": null,
|
|
12
|
+
"filename_convention": "kebab-case slug matching the `slug` field",
|
|
13
|
+
"append_only": false,
|
|
14
|
+
"portability": "structural",
|
|
15
|
+
"required_fields": ["type", "slug", "category", "title", "status", "schema_version"],
|
|
16
|
+
"required_when": {
|
|
17
|
+
"schema_version==2": ["confidence", "importance", "modality", "subject", "predicate", "object", "sentiment_polarity"]
|
|
18
|
+
},
|
|
19
|
+
"enums": {
|
|
20
|
+
"category": ["invariants", "patterns", "anti-patterns", "preferences", "decisions", "concepts", "facts", "lore"],
|
|
21
|
+
"modality": ["must", "must_not", "should", "should_not", "descriptive", "preference"],
|
|
22
|
+
"sentiment_polarity": ["directive_must", "directive_must_not", "descriptive", "preference"],
|
|
23
|
+
"priority": ["absolute", "high", "normal", "low"]
|
|
24
|
+
},
|
|
25
|
+
"spec_ref": "§5.4 memory"
|
|
26
|
+
},
|
|
27
|
+
"skill": {
|
|
28
|
+
"family": "capability",
|
|
29
|
+
"canonical_filename": "SKILL.md",
|
|
30
|
+
"append_only": false,
|
|
31
|
+
"portability": "structural",
|
|
32
|
+
"required_fields": ["type", "name", "description"],
|
|
33
|
+
"notes": "Compatible with the open Agent Skills standard (name + description). The `type: skill` discriminator is REQUIRED for SSSS-managed manifests.",
|
|
34
|
+
"spec_ref": "§5.4 skill"
|
|
35
|
+
},
|
|
36
|
+
"rule": {
|
|
37
|
+
"family": "governance",
|
|
38
|
+
"canonical_filename": null,
|
|
39
|
+
"append_only": false,
|
|
40
|
+
"portability": "structural",
|
|
41
|
+
"required_fields": ["type", "name"],
|
|
42
|
+
"optional_fields": ["description", "scope"],
|
|
43
|
+
"spec_ref": "§5.4 rule"
|
|
44
|
+
},
|
|
45
|
+
"security_role": {
|
|
46
|
+
"family": "governance",
|
|
47
|
+
"canonical_filename": "ROLE.md",
|
|
48
|
+
"append_only": false,
|
|
49
|
+
"portability": "structural",
|
|
50
|
+
"required_fields": ["type", "name", "permissions"],
|
|
51
|
+
"optional_fields": ["description"],
|
|
52
|
+
"spec_ref": "§5.4 security_role"
|
|
53
|
+
},
|
|
54
|
+
"task": {
|
|
55
|
+
"family": "work",
|
|
56
|
+
"canonical_filename": null,
|
|
57
|
+
"append_only": false,
|
|
58
|
+
"portability": "tenant_private",
|
|
59
|
+
"required_fields": ["type", "priority", "category", "status"],
|
|
60
|
+
"optional_fields": ["workflow_id", "workflow_path", "trigger_id", "trigger_type", "trigger_event_id", "scheduled_for", "dedupe_key", "claim_id", "claimed_by", "claimed_at", "attempts", "next_attempt_at"],
|
|
61
|
+
"enums": { "status": ["pending", "in_progress", "done", "failed"] },
|
|
62
|
+
"spec_ref": "§5.4 task"
|
|
63
|
+
},
|
|
64
|
+
"assistant": {
|
|
65
|
+
"family": "actor",
|
|
66
|
+
"canonical_filename": "ASSISTANT.md",
|
|
67
|
+
"append_only": false,
|
|
68
|
+
"portability": "structural",
|
|
69
|
+
"required_fields": ["type", "name"],
|
|
70
|
+
"spec_ref": "§5.4 assistant"
|
|
71
|
+
},
|
|
72
|
+
"workflow": {
|
|
73
|
+
"family": "work",
|
|
74
|
+
"canonical_filename": "WORKFLOW.md",
|
|
75
|
+
"append_only": false,
|
|
76
|
+
"portability": "structural",
|
|
77
|
+
"required_fields": ["type", "name"],
|
|
78
|
+
"optional_fields": ["workflow_id", "slug", "description", "triggers", "isActive", "priority", "misfire_policy", "concurrency"],
|
|
79
|
+
"spec_ref": "§5.4 workflow"
|
|
80
|
+
},
|
|
81
|
+
"model": {
|
|
82
|
+
"family": "catalog",
|
|
83
|
+
"canonical_filename": "MODEL.md",
|
|
84
|
+
"append_only": false,
|
|
85
|
+
"portability": "structural",
|
|
86
|
+
"required_fields": ["type", "model_id", "provider"],
|
|
87
|
+
"spec_ref": "§5.4 model"
|
|
88
|
+
},
|
|
89
|
+
"conversation": {
|
|
90
|
+
"family": "transcript",
|
|
91
|
+
"canonical_filename": "CONVERSATION.md",
|
|
92
|
+
"append_only": true,
|
|
93
|
+
"portability": "tenant_private",
|
|
94
|
+
"required_fields": ["type", "thread_id"],
|
|
95
|
+
"spec_ref": "§5.4 conversation"
|
|
96
|
+
},
|
|
97
|
+
"run": {
|
|
98
|
+
"family": "transcript",
|
|
99
|
+
"canonical_filename": "RUN.md",
|
|
100
|
+
"append_only": true,
|
|
101
|
+
"portability": "tenant_private",
|
|
102
|
+
"required_fields": ["type", "run_id", "workflow_id"],
|
|
103
|
+
"optional_fields": ["task_id", "task_path", "workspace_id", "status", "step_count", "started_at", "ended_at", "claimed_by", "provenance"],
|
|
104
|
+
"enums": { "status": ["queued", "claimed", "running", "waiting_for_human", "succeeded", "failed", "canceled", "retrying", "dead_letter"] },
|
|
105
|
+
"spec_ref": "§5.4 run"
|
|
106
|
+
},
|
|
107
|
+
"conflict": {
|
|
108
|
+
"family": "meta",
|
|
109
|
+
"canonical_filename": null,
|
|
110
|
+
"append_only": false,
|
|
111
|
+
"portability": "tenant_private",
|
|
112
|
+
"required_fields": ["type", "conflict_id", "status", "new_slug", "existing_slug", "detected_at"],
|
|
113
|
+
"enums": { "status": ["pending", "resolved"] },
|
|
114
|
+
"spec_ref": "§5.4 conflict"
|
|
115
|
+
},
|
|
116
|
+
"page": {
|
|
117
|
+
"family": "capability",
|
|
118
|
+
"canonical_filename": null,
|
|
119
|
+
"append_only": false,
|
|
120
|
+
"portability": "structural",
|
|
121
|
+
"required_fields": ["type", "slug", "name", "sandbox_entry"],
|
|
122
|
+
"spec_ref": "§5.4 page"
|
|
123
|
+
},
|
|
124
|
+
"migration": {
|
|
125
|
+
"family": "meta",
|
|
126
|
+
"canonical_filename": null,
|
|
127
|
+
"append_only": false,
|
|
128
|
+
"portability": "structural",
|
|
129
|
+
"required_fields": ["type", "migration_id", "from_version", "to_version", "status", "description"],
|
|
130
|
+
"spec_ref": "§5.4 migration"
|
|
131
|
+
},
|
|
132
|
+
"release": {
|
|
133
|
+
"family": "meta",
|
|
134
|
+
"canonical_filename": null,
|
|
135
|
+
"append_only": false,
|
|
136
|
+
"portability": "structural",
|
|
137
|
+
"required_fields": ["type", "release_id", "version", "schema_version", "summary", "released_at"],
|
|
138
|
+
"spec_ref": "§5.4 release"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
"contract_primitives": {
|
|
143
|
+
"operation": { "purpose": "Full atomic file write (create or full replace).", "spec_ref": "§6" },
|
|
144
|
+
"patch": { "purpose": "Partial merge into an existing file.", "spec_ref": "§6" },
|
|
145
|
+
"event": { "purpose": "Append-only immutable log entry.", "spec_ref": "§6, §8" },
|
|
146
|
+
"delete": { "purpose": "Tombstoning removal of a replace-type file; rejects append-type targets; emits a deletion event.", "spec_ref": "§6.2" },
|
|
147
|
+
"lease": { "purpose": "A file-level write lock.", "spec_ref": "§7" }
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
"universal_frontmatter": {
|
|
151
|
+
"required": ["type", "title", "description", "timestamp"],
|
|
152
|
+
"recommended_okf": ["resource", "tags", "aliases"],
|
|
153
|
+
"spec_ref": "§4.2"
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
"portability": {
|
|
157
|
+
"description": "Every document primitive declares a portability class. The class governs whether a primitive may leave the workspace in a given export profile (§5.5). Contract primitives (operation/patch/event/lease) are protocol envelopes, not stored documents, and carry no portability. A file MAY override its type's default class with an `x_portability` frontmatter field; a host MUST honor the most restrictive of (type default, instance override).",
|
|
158
|
+
"classes": {
|
|
159
|
+
"structural": "The reusable business model — sellable and templatable. Carries no operator- or customer-specific data. Examples: workflow, assistant, page, skill, model, rule, memory, migration, release.",
|
|
160
|
+
"tenant_private": "The operator's private operational data — customers, transcripts, runtime work. NEVER leaves the workspace in a template or sale bundle; included only in an encrypted backup. Examples: conversation, run, task, conflict, and (via extension) profile / CRM contact / deal.",
|
|
161
|
+
"resource_bound": "Requires a real external resource bound at provision time (a domain, phone number, mailbox, API connection). NEVER ships the seller's resource value; ships only a requirement declaration the buyer satisfies. Defined by extension registries (e.g. festech domain / phone_number / integration_connection)."
|
|
162
|
+
},
|
|
163
|
+
"export_profiles": {
|
|
164
|
+
"backup": { "includes": ["structural", "tenant_private", "resource_bound"], "note": "Complete fidelity copy. tenant_private MUST be encrypted at rest." },
|
|
165
|
+
"template": { "includes": ["structural"], "resource_bound": "requirement_declaration_only", "excludes": ["tenant_private"] },
|
|
166
|
+
"sale": { "includes": ["structural"], "resource_bound": "requirement_declaration_only", "excludes": ["tenant_private"] }
|
|
167
|
+
},
|
|
168
|
+
"conformance": "A host MUST resolve every primitive's portability class from this registry (type default, overridable by `x_portability`). An exporter MUST drop every `tenant_private` primitive from a `template` or `sale` bundle, and MUST reduce every `resource_bound` primitive to a requirement declaration (stripping the seller's bound value) in those profiles. A host MUST reject a `template`/`sale` export that would emit a `tenant_private` primitive.",
|
|
169
|
+
"spec_ref": "§5.5"
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
"bundle": {
|
|
173
|
+
"description": "The `.ucw` bundle format (§16): a transportable package of a vault's files plus a manifest declaring what they are and what they need to run. Produced by an export under an export profile (§5.5), consumed by provision/import (§17).",
|
|
174
|
+
"container": "package.ucw.json",
|
|
175
|
+
"structure": ["manifest", "branding", "files"],
|
|
176
|
+
"file_sort": "files MUST be sorted by path (byte order) so equal vault state yields a byte-identical, content-hashable bundle.",
|
|
177
|
+
"manifest_required_fields": ["name", "description", "version", "exported_at", "ssss_core_version", "required_extensions", "export_profile", "primitive_inventory", "provisioning", "file_count", "provenance"],
|
|
178
|
+
"manifest_optional_fields": ["parameters", "source_workspace_id", "branding"],
|
|
179
|
+
"provenance_required_fields": ["content_hash", "exporter"],
|
|
180
|
+
"provenance_optional_fields": ["signature"],
|
|
181
|
+
"content_hash": "sha256 over the canonical (path-sorted) serialization of files; importer MUST recompute and reject on mismatch.",
|
|
182
|
+
"parameter_fields": {
|
|
183
|
+
"required": ["key", "label", "type", "scope", "source"],
|
|
184
|
+
"optional": ["required", "description", "defaultValue", "options", "dependsOn"],
|
|
185
|
+
"source_enum": ["user", "llm", "graph", "system"],
|
|
186
|
+
"note": "Adopted verbatim from ultrachat WorkspaceTemplateVariableDefinition; resource_bound requirements ship as parameters of source user/system."
|
|
187
|
+
},
|
|
188
|
+
"conformance": "A host MUST refuse a bundle whose `ssss_core_version` it does not support or that names a `required_extensions` entry it has not loaded. An exporter MUST NOT emit a file whose resolved portability class is excluded by `export_profile`. `primitive_inventory` MUST count every file by its registry `type`.",
|
|
189
|
+
"spec_ref": "§16"
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
"provisioning": {
|
|
193
|
+
"description": "The provisioning contract (§17): the deterministic, idempotent verbs that move a bundle between vaults — export, provision, import.",
|
|
194
|
+
"verbs": {
|
|
195
|
+
"export": "vault + profile → bundle. Pure: equal vault state and profile yield a byte-identical bundle.",
|
|
196
|
+
"provision": "bundle + parameters + target → ordered, replayable plan of Operation Contract envelopes plus resource bindings. Supports dry_run.",
|
|
197
|
+
"import": "plan + target → replay envelopes through the engine (§6.3); idempotent via each envelope's idempotency_key."
|
|
198
|
+
},
|
|
199
|
+
"step_required_fields": ["id", "label", "system", "mode", "required"],
|
|
200
|
+
"step_optional_fields": ["notes"],
|
|
201
|
+
"system_enum": ["workspace", "branding", "email", "phone", "domains", "accounts", "deployments", "marketplace", "tabs", "automation"],
|
|
202
|
+
"mode_enum": ["existing", "provision", "install", "generate", "configure"],
|
|
203
|
+
"edge_relations": ["uses_brand", "owns_domain", "routes_calls_to", "deploys_to", "has_surface", "contains_page", "installs_pack", "authenticates_account", "runs_workflow", "connects_to"],
|
|
204
|
+
"install_modes": ["optional", "recommended", "required"],
|
|
205
|
+
"id_remap": "Provision remaps slugs/workspace ids/links into the target; every internal link that resolved in the source MUST resolve post-import or the provision MUST fail.",
|
|
206
|
+
"upgrade": "Version steps run through a `migration` primitive plus structural-only `patch` envelopes; MUST NOT touch tenant_private data.",
|
|
207
|
+
"conformance": "import MUST be idempotent (re-run is a no-op via idempotency_key). A step's `mode` and `system` MUST be drawn from the enums above. Each `resource_bound` primitive (§5.5) declares its binding edge relation.",
|
|
208
|
+
"spec_ref": "§17"
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
"runtime": {
|
|
212
|
+
"description": "Workflow runtime contract (§11.8). A daemon, cron bridge, webhook worker, or agent host reads workflow triggers from the vault and derives Operation Contract envelopes. Scheduler queues, task dashboards, and cursor indexes are projections; the canonical schedule remains workflow frontmatter and the canonical history remains the event log.",
|
|
213
|
+
"trigger_types": ["manual", "cron", "interval", "event", "webhook", "file_change", "condition"],
|
|
214
|
+
"misfire_policies": ["skip", "run_once", "catch_up"],
|
|
215
|
+
"concurrency_policies": ["allow_parallel", "skip_if_running", "enqueue", "replace"],
|
|
216
|
+
"task_instantiation_fields": ["workflow_id", "workflow_path", "trigger_id", "trigger_type", "trigger_event_id", "scheduled_for", "dedupe_key"],
|
|
217
|
+
"run_statuses": ["queued", "claimed", "running", "waiting_for_human", "succeeded", "failed", "canceled", "retrying", "dead_letter"],
|
|
218
|
+
"idempotency_basis": "workspace_id + workflow_id + trigger_id + scheduled_for",
|
|
219
|
+
"spec_ref": "§11.8"
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
"reserved_keys": ["type", "slug", "schema_version", "status", "feedback", "confidence"],
|
|
223
|
+
"host_field_prefix": "x_",
|
|
224
|
+
"spec_ref_reserved": "Appendix A"
|
|
225
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ssss.dev/registry/v1",
|
|
3
|
+
"registry": "festech",
|
|
4
|
+
"extends": "core",
|
|
5
|
+
"spec_version": "0.3",
|
|
6
|
+
"description": "Festech extension registry. Defines the host-specific primitives that festech.live layers on top of the SSSS core (registry/core.json). Lifted from festech's docs/ssss/primitive-registry.json and assigned a portability class (§5.5) so a festech workspace can be exported as a backup/template/sale bundle. Core primitives (assistant, workflow, rule, security_role, skill, model, memory, task, conversation, run, conflict, page, migration, release) are NOT redefined here — they are governed by core.json. An extension MUST NOT redefine a core type.",
|
|
7
|
+
"conformance": "A festech-conformant host MUST resolve portability for these primitives from this registry. The `resource_bound` primitives MUST ship only a requirement declaration (never the seller's bound value) in a template/sale export; the `tenant_private` primitives MUST be dropped from template/sale entirely (§5.5).",
|
|
8
|
+
|
|
9
|
+
"document_primitives": {
|
|
10
|
+
"domain": {
|
|
11
|
+
"family": "resource",
|
|
12
|
+
"canonical_path": "domains/<domain>.md",
|
|
13
|
+
"append_only": false,
|
|
14
|
+
"portability": "resource_bound",
|
|
15
|
+
"resource": { "kind": "domain", "binds": ["domain_name", "registrar"], "provision_relation": "owns_domain" },
|
|
16
|
+
"required_fields": ["type", "domain_name", "registrar", "status", "payment_status"],
|
|
17
|
+
"notes": "The seller's domain is NEVER transferred. In template/sale the bundle declares 'this business needs a domain'; the buyer binds their own at provision (§16)."
|
|
18
|
+
},
|
|
19
|
+
"phone_number": {
|
|
20
|
+
"family": "resource",
|
|
21
|
+
"canonical_path": "telephony/PHONE_SYSTEM.md",
|
|
22
|
+
"append_only": false,
|
|
23
|
+
"portability": "resource_bound",
|
|
24
|
+
"resource": { "kind": "phone", "binds": ["phone_number", "provider"], "provision_relation": "routes_calls_to" },
|
|
25
|
+
"required_fields": ["type", "phone_number", "provider", "status"]
|
|
26
|
+
},
|
|
27
|
+
"integration_connection": {
|
|
28
|
+
"family": "resource",
|
|
29
|
+
"canonical_path": "integrations/<provider>.md",
|
|
30
|
+
"append_only": false,
|
|
31
|
+
"portability": "resource_bound",
|
|
32
|
+
"resource": { "kind": "api_connection", "binds": ["provider"], "provision_relation": "connects_to" },
|
|
33
|
+
"required_fields": ["type", "provider", "status", "capabilities", "verified_at"],
|
|
34
|
+
"notes": "Carries credentials/tokens to a third-party provider — the seller's connection value MUST be stripped; the buyer supplies their own at provision."
|
|
35
|
+
},
|
|
36
|
+
"legal_policy": {
|
|
37
|
+
"family": "governance",
|
|
38
|
+
"canonical_path": "contracts/<id>.md",
|
|
39
|
+
"append_only": false,
|
|
40
|
+
"portability": "structural",
|
|
41
|
+
"required_fields": ["type", "title", "version", "effective_date", "policy_type"]
|
|
42
|
+
},
|
|
43
|
+
"extension": {
|
|
44
|
+
"family": "capability",
|
|
45
|
+
"canonical_path": "extensions/<id>.md",
|
|
46
|
+
"append_only": false,
|
|
47
|
+
"portability": "structural",
|
|
48
|
+
"required_fields": ["type", "extension", "display_name", "status"],
|
|
49
|
+
"notes": "Which extensions a workspace has installed is part of the business model and ships in a sale."
|
|
50
|
+
},
|
|
51
|
+
"media_asset": {
|
|
52
|
+
"family": "asset",
|
|
53
|
+
"canonical_path": "media/assets/<asset-id>.md",
|
|
54
|
+
"append_only": false,
|
|
55
|
+
"portability": "structural",
|
|
56
|
+
"required_fields": ["type", "asset_id", "asset_type", "url"],
|
|
57
|
+
"notes": "Brand/marketing assets are structural. An asset holding customer-uploaded content SHOULD override with x_portability: tenant_private."
|
|
58
|
+
},
|
|
59
|
+
"language_convention": {
|
|
60
|
+
"family": "localization",
|
|
61
|
+
"canonical_path": "languages/<locale>/CONVENTION.md",
|
|
62
|
+
"append_only": false,
|
|
63
|
+
"portability": "structural",
|
|
64
|
+
"required_fields": ["type", "locale", "language_name", "keys"]
|
|
65
|
+
},
|
|
66
|
+
"knowledge": {
|
|
67
|
+
"family": "knowledge",
|
|
68
|
+
"canonical_path": "knowledge/<slug>.md",
|
|
69
|
+
"append_only": false,
|
|
70
|
+
"portability": "structural",
|
|
71
|
+
"required_fields": ["type", "name", "slug"],
|
|
72
|
+
"notes": "The curated knowledge base is a core sellable asset of the business model."
|
|
73
|
+
},
|
|
74
|
+
"knowledge_source": {
|
|
75
|
+
"family": "knowledge",
|
|
76
|
+
"canonical_path": "knowledge/<knowledge-slug>/sources/<slug>.md",
|
|
77
|
+
"append_only": false,
|
|
78
|
+
"portability": "structural",
|
|
79
|
+
"required_fields": ["type", "title", "knowledge_base_slug"],
|
|
80
|
+
"notes": "A source backing a structural knowledge base. A source ingesting private customer documents SHOULD override with x_portability: tenant_private."
|
|
81
|
+
},
|
|
82
|
+
"onboarding_state": {
|
|
83
|
+
"family": "runtime",
|
|
84
|
+
"canonical_path": "onboarding_state.md",
|
|
85
|
+
"append_only": false,
|
|
86
|
+
"portability": "tenant_private",
|
|
87
|
+
"required_fields": ["type", "state_version", "collective_id", "completed_steps", "deferred_steps", "dismissed"]
|
|
88
|
+
},
|
|
89
|
+
"profile": {
|
|
90
|
+
"family": "actor",
|
|
91
|
+
"canonical_path": "PROFILE.md",
|
|
92
|
+
"append_only": false,
|
|
93
|
+
"portability": "tenant_private",
|
|
94
|
+
"required_fields": ["type", "user_id", "locale"]
|
|
95
|
+
},
|
|
96
|
+
"personalization": {
|
|
97
|
+
"family": "actor",
|
|
98
|
+
"canonical_path": "PERSONALIZATION.md",
|
|
99
|
+
"append_only": false,
|
|
100
|
+
"portability": "tenant_private",
|
|
101
|
+
"required_fields": ["type"]
|
|
102
|
+
},
|
|
103
|
+
"model-preferences": {
|
|
104
|
+
"family": "actor",
|
|
105
|
+
"canonical_path": "models/preferences/MODEL-PREFERENCES.md",
|
|
106
|
+
"append_only": false,
|
|
107
|
+
"portability": "tenant_private",
|
|
108
|
+
"required_fields": ["type", "user_id", "workspace_id"]
|
|
109
|
+
},
|
|
110
|
+
"sharing_group": {
|
|
111
|
+
"family": "actor",
|
|
112
|
+
"canonical_path": "sharing-groups/<id>.md",
|
|
113
|
+
"append_only": false,
|
|
114
|
+
"portability": "tenant_private",
|
|
115
|
+
"required_fields": ["type", "name", "color", "owner_user_id"]
|
|
116
|
+
},
|
|
117
|
+
"settings": {
|
|
118
|
+
"family": "config",
|
|
119
|
+
"canonical_path": "SETTINGS.md",
|
|
120
|
+
"append_only": false,
|
|
121
|
+
"portability": "structural",
|
|
122
|
+
"required_fields": ["type"],
|
|
123
|
+
"notes": "Workspace settings configure the business model (structural). Account-scoped settings SHOULD override with x_portability: tenant_private."
|
|
124
|
+
},
|
|
125
|
+
"system_settings": {
|
|
126
|
+
"family": "config",
|
|
127
|
+
"canonical_path": "SYSTEM_SETTINGS.md",
|
|
128
|
+
"append_only": false,
|
|
129
|
+
"portability": "tenant_private",
|
|
130
|
+
"required_fields": ["type"],
|
|
131
|
+
"notes": "Host/operator-level configuration; never transfers to a buyer."
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Registry-parity audit: proves every registry field the reference engine is
|
|
4
|
+
* supposed to enforce at write time is actually referenced in src/engine.mjs.
|
|
5
|
+
*
|
|
6
|
+
* Motivation: registry/core.json declared `required_when` and `enums` on the
|
|
7
|
+
* `memory` primitive for a full release cycle before src/engine.mjs's
|
|
8
|
+
* validateContent() ever read either one — a memory node could omit a
|
|
9
|
+
* conditionally-required field or use an out-of-enum value and still pass
|
|
10
|
+
* conformance. That gap was invisible to normal testing because nothing
|
|
11
|
+
* cross-checked "declared in the registry" against "consumed by the engine."
|
|
12
|
+
*
|
|
13
|
+
* This is deliberately a STATIC, deterministic check (grep the engine source
|
|
14
|
+
* for a reference to each enforcement-relevant key), not an LLM judgment
|
|
15
|
+
* call — the whole point is that this class of drift should never again
|
|
16
|
+
* require a human or an agent to notice it. Wired into every
|
|
17
|
+
* `ssss conformance` run (see scripts/conformance.mjs), not just an
|
|
18
|
+
* agent-invoked skill, so it can't silently regress by nobody remembering to
|
|
19
|
+
* ask.
|
|
20
|
+
*/
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const REGISTRY_DIR = path.resolve(__dirname, '..', 'registry');
|
|
27
|
+
// "The engine" for this audit's purposes is the write-time enforcement path:
|
|
28
|
+
// src/engine.mjs itself plus src/registry.mjs, which engine.mjs delegates
|
|
29
|
+
// type-resolution helpers to (e.g. `append_only` is actually read inside
|
|
30
|
+
// registry.mjs's isAppendType(), not inlined in engine.mjs).
|
|
31
|
+
const ENGINE_FILES = [
|
|
32
|
+
path.resolve(__dirname, '..', 'src', 'engine.mjs'),
|
|
33
|
+
path.resolve(__dirname, '..', 'src', 'registry.mjs'),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// Keys the reference engine is expected to enforce at write time (Stage 5,
|
|
37
|
+
// spec §6.3 + §9). If a primitive declares one, the engine source MUST
|
|
38
|
+
// reference it — otherwise the registry is making a promise the engine
|
|
39
|
+
// doesn't keep.
|
|
40
|
+
const ENGINE_ENFORCED_KEYS = ['required_fields', 'required_when', 'enums', 'append_only'];
|
|
41
|
+
|
|
42
|
+
// Keys that are real and load-bearing but are intentionally consumed
|
|
43
|
+
// elsewhere (export/bundle time, documentation, or the registry loader
|
|
44
|
+
// itself), not by src/engine.mjs's write-time content validation. Extend
|
|
45
|
+
// this list — with a comment saying WHERE the key is actually consumed —
|
|
46
|
+
// rather than silencing a warning by deleting it.
|
|
47
|
+
const KNOWN_NON_ENGINE_KEYS = new Set([
|
|
48
|
+
'family', 'canonical_filename', 'canonical_path', 'filename_convention', 'notes', 'spec_ref',
|
|
49
|
+
'optional_fields', 'type', 'registry',
|
|
50
|
+
'portability', // consumed by src/registry.mjs resolvePortability() + src/bundle.mjs export filtering, not write-time validation
|
|
51
|
+
'resource', // extension-defined resource_bound metadata, consumed by src/bundle.mjs provisioning
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
function loadAllPrimitiveDefs() {
|
|
55
|
+
const defs = [];
|
|
56
|
+
const corePath = path.join(REGISTRY_DIR, 'core.json');
|
|
57
|
+
const core = JSON.parse(fs.readFileSync(corePath, 'utf8'));
|
|
58
|
+
for (const [name, def] of Object.entries(core.document_primitives || {})) {
|
|
59
|
+
defs.push({ name, def, source: 'core.json' });
|
|
60
|
+
}
|
|
61
|
+
const extDir = path.join(REGISTRY_DIR, 'extensions');
|
|
62
|
+
if (fs.existsSync(extDir)) {
|
|
63
|
+
for (const file of fs.readdirSync(extDir).filter((f) => f.endsWith('.json'))) {
|
|
64
|
+
const ext = JSON.parse(fs.readFileSync(path.join(extDir, file), 'utf8'));
|
|
65
|
+
for (const [name, def] of Object.entries(ext.document_primitives || {})) {
|
|
66
|
+
defs.push({ name, def, source: `extensions/${file}` });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return defs;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Does any engine-path source file reference `def.<key>` (or an equivalent literal access)? */
|
|
74
|
+
function engineReferencesKey(engineSources, key) {
|
|
75
|
+
const patterns = [
|
|
76
|
+
new RegExp(`\\bdef\\.${key}\\b`),
|
|
77
|
+
new RegExp(`\\btypeDef\\.${key}\\b`),
|
|
78
|
+
new RegExp(`\\['${key}'\\]`),
|
|
79
|
+
new RegExp(`\\["${key}"\\]`),
|
|
80
|
+
];
|
|
81
|
+
return engineSources.some((src) => patterns.some((re) => re.test(src)));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns an array of problem strings. Empty array = clean.
|
|
86
|
+
*/
|
|
87
|
+
export function auditRegistryFieldUsage() {
|
|
88
|
+
const problems = [];
|
|
89
|
+
const missing = ENGINE_FILES.filter((f) => !fs.existsSync(f));
|
|
90
|
+
if (missing.length) return missing.map((f) => `engine source not found at ${f}`);
|
|
91
|
+
const engineSources = ENGINE_FILES.map((f) => fs.readFileSync(f, 'utf8'));
|
|
92
|
+
|
|
93
|
+
const defs = loadAllPrimitiveDefs();
|
|
94
|
+
const keysSeen = new Set();
|
|
95
|
+
for (const { name, def, source } of defs) {
|
|
96
|
+
for (const key of Object.keys(def)) keysSeen.add(key);
|
|
97
|
+
|
|
98
|
+
for (const key of ENGINE_ENFORCED_KEYS) {
|
|
99
|
+
if (def[key] === undefined) continue;
|
|
100
|
+
if (!engineReferencesKey(engineSources, key)) {
|
|
101
|
+
problems.push(
|
|
102
|
+
`${source}: primitive "${name}" declares "${key}" but src/engine.mjs never references it — ` +
|
|
103
|
+
`the registry is promising enforcement the engine doesn't perform.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Flag any key the engine-enforced list and the known-elsewhere list both
|
|
110
|
+
// fail to account for — a new field was added and nobody classified it yet.
|
|
111
|
+
for (const key of keysSeen) {
|
|
112
|
+
if (ENGINE_ENFORCED_KEYS.includes(key) || KNOWN_NON_ENGINE_KEYS.has(key)) continue;
|
|
113
|
+
problems.push(
|
|
114
|
+
`unclassified registry key "${key}" — add it to ENGINE_ENFORCED_KEYS (if src/engine.mjs should ` +
|
|
115
|
+
`enforce it at write time) or KNOWN_NON_ENGINE_KEYS (with a comment on where it IS consumed) ` +
|
|
116
|
+
`in scripts/audit-registry-field-usage.mjs.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return problems;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
const problems = auditRegistryFieldUsage();
|
|
125
|
+
if (problems.length) {
|
|
126
|
+
console.error('❌ Registry/engine parity audit failed:');
|
|
127
|
+
for (const p of problems) console.error(` • ${p}`);
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
} else {
|
|
130
|
+
console.log('✅ Registry/engine parity: every enforcement-relevant registry key is referenced in src/engine.mjs.');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
135
|
+
main();
|
|
136
|
+
}
|