@synap-core/workspace-templates 0.10.0 → 0.12.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/dist/index.d.ts +74 -34
- package/dist/index.js +1177 -11
- package/dist/index.js.map +1 -1
- package/package.json +16 -25
- package/src/base.template.test.ts +25 -19
- package/src/base.yaml +12 -2
- package/src/blockchain-ecosystem.yaml +106 -0
- package/src/builder.yaml +19 -1
- package/src/business-model.yaml +351 -0
- package/src/catalog.ts +55 -0
- package/src/civic-france.yaml +102 -0
- package/src/dist-freshness.test.ts +97 -0
- package/src/foundation.yaml +62 -0
- package/src/grants.yaml +259 -1
- package/src/life-os.yaml +14 -0
- package/src/networking.yaml +14 -3
- package/src/seed-obligation.test.ts +181 -0
- package/src/shared-role-ssot.test.ts +4 -1
- package/src/templates.ts +1132 -15
- package/src/the-arch.yaml +4 -0
- package/src/types.ts +32 -34
- package/src/validate-flow.ts +11 -1
- package/src/validate.test.ts +86 -0
- package/src/validate.ts +49 -0
- package/src/__fixtures__/report-automation-flow.json +0 -393
package/src/the-arch.yaml
CHANGED
|
@@ -50,6 +50,10 @@ dependencies:
|
|
|
50
50
|
kind: workspace
|
|
51
51
|
relation: require
|
|
52
52
|
reason: "The engineering / ops dashboard — the build-side view of the company."
|
|
53
|
+
- slug: networking
|
|
54
|
+
kind: workspace
|
|
55
|
+
relation: require
|
|
56
|
+
reason: "The partner-intro layer — need → intro-matcher → intro → billed_as → deal, with the A↔B confidentiality firewall; the Stellar Client-Sync emits needs that depend on it."
|
|
53
57
|
|
|
54
58
|
workspace:
|
|
55
59
|
name: The Arch
|
package/src/types.ts
CHANGED
|
@@ -607,46 +607,44 @@ export interface TemplateExtendsRef {
|
|
|
607
607
|
* PACKAGE_DEPENDENCY_KINDS — the dependency-kind vocabulary, declared ONCE.
|
|
608
608
|
*
|
|
609
609
|
* This tuple exists so consumers can IMPORT the vocabulary instead of
|
|
610
|
-
* hand-copying it. `TemplateDependency` is
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
* fail a build; an import can. Those mirrors live in repos this package does not
|
|
614
|
-
* own, so this tuple does not fix them by existing — it makes the fix possible:
|
|
610
|
+
* hand-copying it. `TemplateDependency` is mirrored by hand in several other
|
|
611
|
+
* places — a comment cannot fail a build, an import can, but these mirrors live
|
|
612
|
+
* in repos this package does not own, so they must match this list EXACTLY:
|
|
615
613
|
*
|
|
616
|
-
* - marketplace `types.ts
|
|
617
|
-
* - onboarding `workspace-templates.ts
|
|
618
|
-
* -
|
|
619
|
-
*
|
|
620
|
-
* - hub-protocol REST `rest/packages.ts
|
|
621
|
-
* -
|
|
622
|
-
*
|
|
614
|
+
* - marketplace `features/marketplace/src/types.ts`
|
|
615
|
+
* - onboarding `features/onboarding/src/{templates/workspace-templates,components/WorkspacePreview}.ts(x)`
|
|
616
|
+
* - browser `InstallPackageDialog.tsx`
|
|
617
|
+
* - synap-backend `package-definition.ts` (`PackageDependencyKind`)
|
|
618
|
+
* - hub-protocol REST `rest/packages.ts` (zod enum)
|
|
619
|
+
* - control-plane-api `db/schema/packages.ts` (`TemplateDependency` type)
|
|
620
|
+
* + `routes/packages.ts` (`DEPENDENCY_KINDS` zod enum)
|
|
623
621
|
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
* `db/schema/packages.ts`). A dependency's `kind` names the TYPE of package it
|
|
629
|
-
* points at, so this vocabulary should track `PACKAGE_TYPES` — meaning the live
|
|
630
|
-
* spelling is `workflow`, not `automation`.
|
|
622
|
+
* These are the 7 literals the wire accepts, in lockstep with the CP publish
|
|
623
|
+
* enum (`DEPENDENCY_KINDS = [...PACKAGE_TYPES, "automation"]`, CP
|
|
624
|
+
* `routes/packages.ts`), the hub install enum (`rest/packages.ts`), and the
|
|
625
|
+
* backend resolver's `PackageDependencyKind` (`@synap/database`).
|
|
631
626
|
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* `
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
* still legally accepts (a published third-party package may carry it; CP's own
|
|
639
|
-
* schema permits it). Narrowing must therefore follow the wire, not lead it.
|
|
627
|
+
* Resolver behaviour (backend `package-dependency-resolver.ts`):
|
|
628
|
+
* - `workspace` → resolve+install; the ONLY kind that may `compose`.
|
|
629
|
+
* - `capability` / `cell` → resolve+install, always `require`d (presence).
|
|
630
|
+
* - `skill` / `workflow` / `view` / `automation` → ACCEPTED and `require`d
|
|
631
|
+
* present, surfaced as `required-absent` (non-fatal) — no standalone by-slug
|
|
632
|
+
* applier on the pod yet; when one grows, give the kind its own branch.
|
|
640
633
|
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
634
|
+
* `'automation'` is legacy vocabulary: CP migration `0049_package_vocabulary`
|
|
635
|
+
* renamed the `automation` package category → `workflow` (which absorbs
|
|
636
|
+
* automation + playbook, `PACKAGE_TYPES` in CP `db/schema/packages.ts`). It
|
|
637
|
+
* stays accepted on the wire — and therefore in this tuple — for back-compat
|
|
638
|
+
* with dependencies published before the rename.
|
|
645
639
|
*/
|
|
646
640
|
export const PACKAGE_DEPENDENCY_KINDS = [
|
|
647
641
|
"workspace",
|
|
648
642
|
"capability",
|
|
649
|
-
|
|
643
|
+
"skill",
|
|
644
|
+
"workflow",
|
|
645
|
+
"view",
|
|
646
|
+
"cell",
|
|
647
|
+
/** @deprecated Legacy alias for `workflow` (CP 0049). Still accepted on the wire. */
|
|
650
648
|
"automation",
|
|
651
649
|
] as const;
|
|
652
650
|
|
|
@@ -658,8 +656,8 @@ export interface TemplateDependency {
|
|
|
658
656
|
slug: string;
|
|
659
657
|
/**
|
|
660
658
|
* What the dependency is. Default `'workspace'`. Only `'workspace'`
|
|
661
|
-
* dependencies can be `compose`d;
|
|
662
|
-
*
|
|
659
|
+
* dependencies can be `compose`d; every other kind is always `require`d
|
|
660
|
+
* present without merging.
|
|
663
661
|
*/
|
|
664
662
|
kind?: PackageDependencyKind;
|
|
665
663
|
/**
|
package/src/validate-flow.ts
CHANGED
|
@@ -667,7 +667,17 @@ function collectLoopBindingRefs(data: unknown): string[] {
|
|
|
667
667
|
return;
|
|
668
668
|
}
|
|
669
669
|
if (isRecord(v)) {
|
|
670
|
-
|
|
670
|
+
// Skip AUTHORING NOTES. An `_`-prefixed key is documentation the runtime
|
|
671
|
+
// never evaluates (`_filterNote` and friends explain what DOWNSTREAM
|
|
672
|
+
// nodes do, and so legitimately quote `{{loop.item...}}` in prose).
|
|
673
|
+
// Reading them as live references made a correct flow fail validation:
|
|
674
|
+
// `stellar-grant-client` was reported as "references {{loop.item}} but is
|
|
675
|
+
// not inside a loop body" purely because its note DESCRIBED the nodes
|
|
676
|
+
// that do. Kept identical in all three copies of this walker.
|
|
677
|
+
for (const [k, x] of Object.entries(v)) {
|
|
678
|
+
if (k.startsWith("_")) continue;
|
|
679
|
+
walk(x);
|
|
680
|
+
}
|
|
671
681
|
}
|
|
672
682
|
};
|
|
673
683
|
walk(data);
|
package/src/validate.test.ts
CHANGED
|
@@ -80,6 +80,92 @@ describe("validateTemplate — clean template", () => {
|
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
describe("validateTemplate — system Knowledge contract", () => {
|
|
84
|
+
const template = (overrides: Record<string, unknown> = {}): WorkspaceYaml =>
|
|
85
|
+
({
|
|
86
|
+
meta: {
|
|
87
|
+
slug: "knowledge-consumer",
|
|
88
|
+
name: "Knowledge consumer",
|
|
89
|
+
description: "d",
|
|
90
|
+
icon: "brain",
|
|
91
|
+
color: "#000000",
|
|
92
|
+
},
|
|
93
|
+
workspace: { name: "Knowledge consumer", description: "d" },
|
|
94
|
+
profiles: [{ slug: "gadget", displayName: "Gadget", properties: [] }],
|
|
95
|
+
views: [
|
|
96
|
+
{
|
|
97
|
+
name: "Knowledge",
|
|
98
|
+
type: "table",
|
|
99
|
+
scopeProfileSlug: "knowledge",
|
|
100
|
+
config: { columns: ["title", "knowledgeForm"] },
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
...overrides,
|
|
104
|
+
}) as WorkspaceYaml;
|
|
105
|
+
|
|
106
|
+
it("allows templates to consume system Knowledge without redeclaring it", () => {
|
|
107
|
+
const result = validateTemplate(
|
|
108
|
+
template({
|
|
109
|
+
seedEntities: [
|
|
110
|
+
{
|
|
111
|
+
profileSlug: "knowledge",
|
|
112
|
+
title: "Write migrations that preserve meaning",
|
|
113
|
+
properties: { knowledgeForm: "insight" },
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(result, JSON.stringify(result.errors)).toEqual({ ok: true, errors: [] });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("rejects a template-owned Knowledge profile", () => {
|
|
123
|
+
const result = validateTemplate(
|
|
124
|
+
template({
|
|
125
|
+
profiles: [
|
|
126
|
+
{
|
|
127
|
+
slug: "knowledge",
|
|
128
|
+
displayName: "Knowledge",
|
|
129
|
+
properties: [
|
|
130
|
+
{
|
|
131
|
+
slug: "ek_type",
|
|
132
|
+
label: "Type",
|
|
133
|
+
valueType: "string",
|
|
134
|
+
inputType: "select",
|
|
135
|
+
enumValues: ["gotcha", "lesson"],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(result.ok).toBe(false);
|
|
144
|
+
expect(
|
|
145
|
+
result.errors.some((error) => error.rule === "system-profile-redefined")
|
|
146
|
+
).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("rejects a legacy or missing Knowledge form on seeded Knowledge", () => {
|
|
150
|
+
const result = validateTemplate(
|
|
151
|
+
template({
|
|
152
|
+
seedEntities: [
|
|
153
|
+
{
|
|
154
|
+
profileSlug: "knowledge",
|
|
155
|
+
title: "Legacy knowledge",
|
|
156
|
+
properties: { ek_type: "gotcha" },
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(result.ok).toBe(false);
|
|
163
|
+
expect(
|
|
164
|
+
result.errors.map((error) => error.rule).sort()
|
|
165
|
+
).toEqual(["knowledge-seed-form-invalid", "knowledge-seed-legacy-form"]);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
83
169
|
// Regression: before wiring `validateFlowDefinition` into validateTemplate, a
|
|
84
170
|
// template automation with a broken FLOW (bogus node type / dangling edge / cycle
|
|
85
171
|
// / typo'd channelType) passed the template gate green and then silently never
|
package/src/validate.ts
CHANGED
|
@@ -159,6 +159,22 @@ export const SYSTEM_PROFILES = new Set([
|
|
|
159
159
|
"report",
|
|
160
160
|
]);
|
|
161
161
|
|
|
162
|
+
/**
|
|
163
|
+
* System-owned profiles whose schema is defined by the pod, rather than by a
|
|
164
|
+
* workspace template. A template may scope a view, link, sidebar item, or seed
|
|
165
|
+
* entity to `knowledge` because it is in {@link SYSTEM_PROFILES}; it must never
|
|
166
|
+
* declare the profile itself. Re-declaring it would turn a shared contract into
|
|
167
|
+
* whichever template happened to be applied first.
|
|
168
|
+
*
|
|
169
|
+
* `knowledgeForm` is intentionally an enum property on the Knowledge kind, not
|
|
170
|
+
* a facet/role: a knowledge entity has exactly one form, while Synap roles are
|
|
171
|
+
* additive. Keep this guard local to the template corpus; the actual profile
|
|
172
|
+
* schema and its migration are owned by the backend seeder.
|
|
173
|
+
*/
|
|
174
|
+
const TEMPLATE_FORBIDDEN_SYSTEM_PROFILE_SLUGS = new Set(["knowledge"]);
|
|
175
|
+
const KNOWLEDGE_FORMS = new Set(["insight", "caution"]);
|
|
176
|
+
const LEGACY_KNOWLEDGE_SEED_FIELDS = new Set(["ek_type", "type"]);
|
|
177
|
+
|
|
162
178
|
// ── Dependency-vocabulary closure (compose vs require semantics) ─────────────
|
|
163
179
|
|
|
164
180
|
/**
|
|
@@ -396,6 +412,20 @@ export function validateTemplate(
|
|
|
396
412
|
const known = (s: string) =>
|
|
397
413
|
slugs.has(s) || SYSTEM_PROFILES.has(s) || baseProfileSlugs.has(s);
|
|
398
414
|
|
|
415
|
+
// ── System-owned Knowledge contract ─────────────────────────────────────
|
|
416
|
+
// `knowledge` is available to every template through SYSTEM_PROFILES. It is
|
|
417
|
+
// not an overlay point: allowing a template to declare it lets a workspace
|
|
418
|
+
// fork its base property definitions, enum vocabulary, or profile row-level
|
|
419
|
+
// metadata. Templates consume it through views/links/seeds only.
|
|
420
|
+
for (const profile of tpl.profiles || []) {
|
|
421
|
+
if (!TEMPLATE_FORBIDDEN_SYSTEM_PROFILE_SLUGS.has(profile.slug)) continue;
|
|
422
|
+
add(
|
|
423
|
+
"system-profile-redefined",
|
|
424
|
+
`profiles.${profile.slug}`,
|
|
425
|
+
`Profile "${profile.slug}" is system-owned. Reference it from views, links, sidebar items, or seedEntities; do not declare template-owned properties or metadata.`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
399
429
|
// Views: structured views need a scope; scoped profiles must resolve.
|
|
400
430
|
for (const v of tpl.views || []) {
|
|
401
431
|
const type = String(v.type || "").toLowerCase();
|
|
@@ -579,6 +609,25 @@ export function validateTemplate(
|
|
|
579
609
|
"seedEntities",
|
|
580
610
|
`seedEntity references unknown profile "${s.profileSlug}"`
|
|
581
611
|
);
|
|
612
|
+
|
|
613
|
+
if (s.profileSlug !== "knowledge") continue;
|
|
614
|
+
const properties = s.properties ?? {};
|
|
615
|
+
for (const field of LEGACY_KNOWLEDGE_SEED_FIELDS) {
|
|
616
|
+
if (!(field in properties)) continue;
|
|
617
|
+
add(
|
|
618
|
+
"knowledge-seed-legacy-form",
|
|
619
|
+
`seedEntities.${s.title}.properties.${field}`,
|
|
620
|
+
`Knowledge seed entities use "knowledgeForm" ("insight" | "caution"), not the legacy "${field}" field.`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
const form = properties.knowledgeForm;
|
|
624
|
+
if (!KNOWLEDGE_FORMS.has(String(form))) {
|
|
625
|
+
add(
|
|
626
|
+
"knowledge-seed-form-invalid",
|
|
627
|
+
`seedEntities.${s.title}.properties.knowledgeForm`,
|
|
628
|
+
`Knowledge seed entities require knowledgeForm to be "insight" or "caution".`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
582
631
|
}
|
|
583
632
|
|
|
584
633
|
// profileSlug referential integrity, part 2 — every OTHER site that names a
|