@synap-core/workspace-templates 0.12.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +509 -0
- package/automations/bookmark-enrichment.automation.json +321 -0
- package/automations/github-pr-events.automation.json +505 -0
- package/automations/mail-feed.automation.json +86 -0
- package/automations/ship-a-change-checks-green.automation.json +106 -0
- package/automations/ship-a-change-checks-red.automation.json +106 -0
- package/automations/ship-a-change-deployed.automation.json +56 -0
- package/automations/ship-a-change-pr-merged.automation.json +110 -0
- package/automations/ship-a-change-pr-opened.automation.json +104 -0
- package/automations/url-bookmark-capture.automation.json +132 -0
- package/capabilities/agency-skills.capability.json +157 -0
- package/capabilities/apify.capability.json +265 -0
- package/capabilities/apollo.capability.json +120 -0
- package/capabilities/arch-backend.capability.json +97 -0
- package/capabilities/arch-client-intelligence.capability.json +401 -0
- package/capabilities/backblaze-b2.capability.json +205 -0
- package/capabilities/cal-com.capability.json +266 -0
- package/capabilities/canva.capability.json +274 -0
- package/capabilities/claude-code-routines.capability.json +172 -0
- package/capabilities/claude-managed-agents.capability.json +215 -0
- package/capabilities/client-comms-report.capability.json +246 -0
- package/capabilities/content-idea-to-production.capability.json +29 -0
- package/capabilities/datagouv.capability.json +315 -0
- package/capabilities/discord-bot.capability.json +157 -0
- package/capabilities/dropbox.capability.json +281 -0
- package/capabilities/exa.capability.json +109 -0
- package/capabilities/fal-ai.capability.json +83 -0
- package/capabilities/fal-media-lab.capability.json +158 -0
- package/capabilities/fireflies.capability.json +191 -0
- package/capabilities/freellmapi.capability.json +114 -0
- package/capabilities/generic-apikey.capability.json +92 -0
- package/capabilities/github-merge.capability.json +99 -0
- package/capabilities/github-read.capability.json +325 -0
- package/capabilities/governance-tighten-recommender.capability.json +81 -0
- package/capabilities/jev.capability.json +226 -0
- package/capabilities/lead-conversion-enrollment.capability.json +246 -0
- package/capabilities/mail-feed.capability.json +277 -0
- package/capabilities/mailgun.capability.json +52 -0
- package/capabilities/nango-google.capability.json +639 -0
- package/capabilities/notion.capability.json +417 -0
- package/capabilities/omniroute.capability.json +137 -0
- package/capabilities/posthog-analytics.capability.json +258 -0
- package/capabilities/postiz.capability.json +467 -0
- package/capabilities/proactive-digest.capability.json +190 -0
- package/capabilities/proton.capability.json +288 -0
- package/capabilities/replicate-rvm.capability.json +97 -0
- package/capabilities/research-methods.capability.json +57 -0
- package/capabilities/shopify.capability.json +395 -0
- package/capabilities/slack.capability.json +369 -0
- package/capabilities/stripe.capability.json +1496 -0
- package/capabilities/telegram-bridge.capability.json +56 -0
- package/capabilities/treg.capability.json +102 -0
- package/capabilities/unipile-linkedin.capability.json +494 -0
- package/capabilities/vercel.capability.json +424 -0
- package/dist/define-DqoItRiR.d.ts +1370 -0
- package/dist/index.d.ts +174 -900
- package/dist/index.js +26447 -24075
- package/dist/index.js.map +1 -1
- package/dist/wire.d.ts +222 -0
- package/dist/wire.js +77 -0
- package/dist/wire.js.map +1 -0
- package/package.json +27 -4
- package/retired.yaml +136 -0
- package/skills/app-factory-build-slice/SKILL.md +70 -0
- package/skills/app-factory-build-slice.catalog.json +17 -0
- package/skills/app-factory-design-canvas/SKILL.md +96 -0
- package/skills/app-factory-design-canvas.catalog.json +17 -0
- package/skills/app-factory-design-review/SKILL.md +105 -0
- package/skills/app-factory-design-review.catalog.json +18 -0
- package/skills/app-factory-research/SKILL.md +123 -0
- package/skills/app-factory-research.catalog.json +17 -0
- package/skills/bookmark-enrichment/SKILL.md +40 -0
- package/skills/carousel-format/carousel-format.skill.json +18 -0
- package/skills/collage-broll/collage-broll.skill.json +20 -0
- package/skills/content-taste/content-taste.skill.json +18 -0
- package/skills/creative-director/creative-director.skill.json +18 -0
- package/skills/design-critique/SKILL.md +138 -0
- package/skills/design-critique.catalog.json +12 -0
- package/skills/designing-with-real-components/SKILL.md +140 -0
- package/skills/designing-with-real-components.catalog.json +12 -0
- package/skills/edit-loop/edit-loop.skill.json +25 -0
- package/skills/facecam-edit/facecam-edit.skill.json +24 -0
- package/skills/mapping-the-server/SKILL.md +54 -0
- package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
- package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
- package/skills/open-design-edit/open-design-edit.skill.json +24 -0
- package/skills/reading-client-progress/SKILL.md +65 -0
- package/skills/routing-to-channels/SKILL.md +54 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/skills/video-direction/video-direction.skill.json +20 -0
- package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
- package/skills/video-toolbox/video-toolbox.skill.json +20 -0
- package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
- package/src/agency-os.yaml +122 -0
- package/src/agent-os-skill-slugs.test.ts +65 -0
- package/src/apify-social-reader.test.ts +317 -0
- package/src/base.yaml +85 -39
- package/src/blockchain-ecosystem.yaml +1 -0
- package/src/brand-library.yaml +525 -14
- package/src/builder-dev-fold.template.test.ts +113 -0
- package/src/builder.yaml +779 -91
- package/src/business-model-catalog-description.test.ts +47 -0
- package/src/business-model-grp.template.test.ts +214 -0
- package/src/business-model.yaml +296 -49
- package/src/catalog-safety.test.ts +198 -0
- package/src/catalog.template-items.test.ts +75 -0
- package/src/catalog.ts +66 -2
- package/src/concept-guard.test.ts +28 -8
- package/src/concept-guard.ts +2 -2
- package/src/content-creation.template.test.ts +52 -0
- package/src/content-creation.yaml +192 -0
- package/src/content-os.edit-loop.template.test.ts +210 -0
- package/src/content-os.publish-due-posts.template.test.ts +261 -0
- package/src/content-os.template.test.ts +349 -0
- package/src/content-os.video-takes.template.test.ts +338 -0
- package/src/content-os.work-a-take.template.test.ts +125 -0
- package/src/content-os.yaml +1873 -39
- package/src/content-studio-retired.template.test.ts +82 -0
- package/src/corpus-flow-validity.test.ts +4 -5
- package/src/crm-absorbs-outreach.template.test.ts +117 -0
- package/src/crm-build-plan.template.test.ts +128 -0
- package/src/crm-lists.template.test.ts +96 -0
- package/src/crm-messages-cadence.template.test.ts +246 -0
- package/src/crm-one-send-door.template.test.ts +65 -0
- package/src/crm.yaml +1563 -181
- package/src/decisions-wave.template.test.ts +186 -0
- package/src/define.ts +146 -17
- package/src/design-to-implementation.template.test.ts +94 -0
- package/src/domain-template-type.test.ts +75 -0
- package/src/ecosystem.yaml +27 -66
- package/src/enterprise-os.yaml +10 -134
- package/src/facecam-edit.audio-studio.test.ts +188 -0
- package/src/fal-generate-image.test.ts +200 -0
- package/src/fal-media-lab-pod-files.test.ts +180 -0
- package/src/finance.yaml +254 -1
- package/src/foundation.yaml +78 -38
- package/src/frame-host-modules.d.mts +6 -0
- package/src/frame-host-modules.mjs +58 -0
- package/src/generate.mjs +9 -3
- package/src/github-merge.test.ts +210 -0
- package/src/grants.yaml +46 -17
- package/src/hr.yaml +39 -6
- package/src/index.ts +36 -1
- package/src/intent.test.ts +161 -0
- package/src/internal-runbook.yaml +19 -1
- package/src/jev.test.ts +356 -0
- package/src/legal.yaml +33 -1
- package/src/life-os.yaml +51 -240
- package/src/lifecycle-coverage.test.ts +124 -0
- package/src/lifecycle-declarations.test.ts +252 -0
- package/src/lifecycle-presets.test.ts +468 -0
- package/src/lifecycle-presets.ts +494 -0
- package/src/lifecycle-stuck.test.ts +46 -0
- package/src/lifecycle-waits.parity.test.ts +319 -0
- package/src/marketing.yaml +108 -4
- package/src/meetings-knowledge.yaml +18 -0
- package/src/networking.yaml +23 -2
- package/src/operations.yaml +304 -4
- package/src/pipeline-views.template.test.ts +88 -0
- package/src/playbook-goal-placeholders.test.ts +57 -0
- package/src/postiz-contract.test.ts +134 -0
- package/src/presentation-declarations.test.ts +174 -0
- package/src/process-contract.validate.test.ts +352 -0
- package/src/project-management.yaml +39 -740
- package/src/publish-body.d.mts +29 -0
- package/src/publish-body.mjs +74 -0
- package/src/publish-body.test.ts +74 -0
- package/src/publish-official-lib.test.ts +176 -0
- package/src/replicate-rvm.test.ts +504 -0
- package/src/research-base.yaml +119 -20
- package/src/research-decision.yaml +60 -3
- package/src/reserved-profile-slugs.test.ts +81 -0
- package/src/retired-goal-kinds.test.ts +52 -0
- package/src/role-reference-and-step-domain.test.ts +192 -0
- package/src/seed-obligation.test.ts +1 -6
- package/src/send-message-maps.test.ts +227 -0
- package/src/shared-role-ssot.test.ts +40 -2
- package/src/ship-a-change.template.test.ts +177 -0
- package/src/skills.test.ts +170 -0
- package/src/social.yaml +85 -27
- package/src/space-packages.freshness.test.ts +97 -0
- package/src/space-packages.generated.ts +40 -0
- package/src/space-packages.test.ts +125 -0
- package/src/space-packages.ts +73 -0
- package/src/start.test.ts +188 -0
- package/src/stripe-capability.test.ts +192 -0
- package/src/task-intents.test.ts +293 -0
- package/src/template-documents.test.ts +158 -0
- package/src/templates.ts +20907 -21464
- package/src/types.ts +475 -61
- package/src/unipile-linkedin-find-profile.test.ts +183 -0
- package/src/unipile-publish-post.test.ts +162 -0
- package/src/validate-flow.drift.test.ts +2 -3
- package/src/validate-flow.ts +22 -2
- package/src/validate-onboarding.test.ts +59 -0
- package/src/validate.test.ts +71 -4
- package/src/validate.ts +1416 -7
- package/src/wire-core.ts +231 -0
- package/src/wire.test.ts +159 -0
- package/src/wire.ts +107 -0
- package/views/bookmark-views-pack.view.json +39 -0
- package/views/event-views-pack.view.json +45 -0
- package/views/task-views-pack.view.json +53 -0
- package/src/agent-fleet.yaml +0 -1645
- package/src/business-developer.template.test.ts +0 -351
- package/src/business-developer.yaml +0 -860
- package/src/content-studio.yaml +0 -361
- package/src/dev-dashboard.yaml +0 -990
- package/src/outreach-comms.yaml +0 -486
- package/src/the-arch.yaml +0 -201
|
@@ -58,6 +58,15 @@ profiles:
|
|
|
58
58
|
icon: git-branch
|
|
59
59
|
color: violet
|
|
60
60
|
description: A candidate option under consideration — argued for and against by sources.
|
|
61
|
+
# The ONE lifecycle (work model L1): every option's category.
|
|
62
|
+
lifecycle:
|
|
63
|
+
property: stance
|
|
64
|
+
options:
|
|
65
|
+
- { key: proposed, category: open }
|
|
66
|
+
- { key: supported, category: done }
|
|
67
|
+
- { key: contested, category: active }
|
|
68
|
+
- { key: refuted, category: lost }
|
|
69
|
+
- { key: superseded, category: lost }
|
|
61
70
|
properties:
|
|
62
71
|
- slug: stance
|
|
63
72
|
label: Stance
|
|
@@ -131,9 +140,10 @@ playbooks:
|
|
|
131
140
|
sources with a reason — never drop the counter-evidence). (4) Weigh the
|
|
132
141
|
options against explicit, named criteria (cost, reversibility, risk, time,
|
|
133
142
|
evidence strength). (5) Record the recommendation as a `decision` entity
|
|
134
|
-
that `resolves` the question — its rationale
|
|
135
|
-
`alternatives` field preserves the options
|
|
136
|
-
evidence would change the decision.
|
|
143
|
+
at the proposed status that `resolves` the question — its rationale
|
|
144
|
+
explains why it won, and its `alternatives` field preserves the options
|
|
145
|
+
that lost and WHY. (6) Note what evidence would change the decision.
|
|
146
|
+
Accepting the decision is a person's call. Every write is proposal-gated.
|
|
137
147
|
subjectProfile:
|
|
138
148
|
profileSlug: question
|
|
139
149
|
params:
|
|
@@ -145,6 +155,44 @@ playbooks:
|
|
|
145
155
|
- exa_search
|
|
146
156
|
- entity.create
|
|
147
157
|
status: active
|
|
158
|
+
expectedOutputs:
|
|
159
|
+
- { kind: decision, label: Recommended decision, relationToSubject: resolves }
|
|
160
|
+
|
|
161
|
+
- name: Decide
|
|
162
|
+
description: Put a proposed decision in front of a person with its options and evidence, and record what they chose.
|
|
163
|
+
goalTemplate: >-
|
|
164
|
+
Bring this decision ({decision}) to a person. Summarise the question it
|
|
165
|
+
resolves, the options that were considered, the evidence for and
|
|
166
|
+
against each, and the recommendation, in one screen. A person accepts or
|
|
167
|
+
rejects it. When a later decision replaces it, the person marks it
|
|
168
|
+
superseded.
|
|
169
|
+
subjectProfile:
|
|
170
|
+
profileSlug: decision
|
|
171
|
+
statusProperty: decisionStatus
|
|
172
|
+
humanOnlyStatuses: [superseded, rejected]
|
|
173
|
+
activators:
|
|
174
|
+
- { on: created, mode: propose }
|
|
175
|
+
params:
|
|
176
|
+
- { name: decision, type: entity, label: Decision, required: true }
|
|
177
|
+
executor: is-agent
|
|
178
|
+
status: active
|
|
179
|
+
stages:
|
|
180
|
+
- key: prepare
|
|
181
|
+
name: Prepare
|
|
182
|
+
category: started
|
|
183
|
+
subjectStatus: proposed
|
|
184
|
+
goal: Summarise the question, the options, the evidence and the recommendation in one screen.
|
|
185
|
+
criteria:
|
|
186
|
+
- key: decision-options-listed
|
|
187
|
+
statement: The decision lists every option considered and why each lost or won.
|
|
188
|
+
check: { kind: judge }
|
|
189
|
+
- key: accept
|
|
190
|
+
name: Accept
|
|
191
|
+
category: completed
|
|
192
|
+
subjectStatus: accepted
|
|
193
|
+
goal: A person accepts the decision.
|
|
194
|
+
gate: { kind: human }
|
|
195
|
+
onFail: { toStage: prepare }
|
|
148
196
|
|
|
149
197
|
actionPlacements:
|
|
150
198
|
- profileSlug: question
|
|
@@ -156,3 +204,12 @@ actionPlacements:
|
|
|
156
204
|
integrations:
|
|
157
205
|
- templateKey: research-methods
|
|
158
206
|
- templateKey: exa
|
|
207
|
+
|
|
208
|
+
# ── taskIntents — WHAT THIS WORKSPACE NEEDS TO FUNCTION ───────────────────
|
|
209
|
+
# Closed vocabulary: `@synap-core/types/capability-intents` (pod migrations
|
|
210
|
+
# 0283/0284). DERIVED from the declared intents of this template's own
|
|
211
|
+
# `integrations` capability packs — not hand-guessed. Checked at authoring
|
|
212
|
+
# time by `validateTemplate` (`task-intent-unknown`) and by
|
|
213
|
+
# `src/task-intents.test.ts`.
|
|
214
|
+
taskIntents:
|
|
215
|
+
- search_external
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A template may never declare a profile on a slug the pod reserves.
|
|
3
|
+
*
|
|
4
|
+
* `project` was declared by builder, life-os and project-management (and the
|
|
5
|
+
* project-management pack) long after migration 0151 moved projects to their
|
|
6
|
+
* own table — every such declaration is refused by `ProfileRepository.create`
|
|
7
|
+
* on install, so it was dead schema at best. `validateTemplate` now refuses it
|
|
8
|
+
* at authoring time (`profile-slug-reserved`), and the catalog validator runs
|
|
9
|
+
* `validateTemplate` for every workspace and pack.
|
|
10
|
+
*
|
|
11
|
+
* The mirror check reads the pod's own reservation module from the sibling
|
|
12
|
+
* synap-backend checkout and skips, with the reason logged, when it is absent.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { dirname, resolve } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { describe, expect, it } from "vitest";
|
|
19
|
+
import { RESERVED_PROFILE_SLUGS, validateTemplate } from "./validate.js";
|
|
20
|
+
import { readYamlTemplates } from "./generate.mjs";
|
|
21
|
+
import type { WorkspaceYaml } from "./types.js";
|
|
22
|
+
|
|
23
|
+
const PKG = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
24
|
+
const BACKEND_RESERVED = resolve(
|
|
25
|
+
PKG,
|
|
26
|
+
"../../../synap-backend/packages/database/src/utils/reserved-profile-slugs.ts"
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const template = (profiles: WorkspaceYaml["profiles"]): WorkspaceYaml =>
|
|
30
|
+
({
|
|
31
|
+
meta: { slug: "reserved-probe", name: "Probe", description: "d", icon: "x", color: "violet" },
|
|
32
|
+
workspace: { name: "Probe", description: "d" },
|
|
33
|
+
profiles,
|
|
34
|
+
}) as unknown as WorkspaceYaml;
|
|
35
|
+
|
|
36
|
+
const reservedErrors = (tpl: WorkspaceYaml) =>
|
|
37
|
+
validateTemplate(tpl).errors.filter((e) => e.rule === "profile-slug-reserved");
|
|
38
|
+
|
|
39
|
+
describe("profile-slug-reserved", () => {
|
|
40
|
+
it("refuses a declared `project` profile, case- and whitespace-insensitively", () => {
|
|
41
|
+
for (const slug of ["project", "Projects", " project "]) {
|
|
42
|
+
const errors = reservedErrors(
|
|
43
|
+
template([{ slug, displayName: "Project", properties: [] }] as unknown as WorkspaceYaml["profiles"])
|
|
44
|
+
);
|
|
45
|
+
expect(errors, slug).toHaveLength(1);
|
|
46
|
+
expect(errors[0].message).toContain("projects table");
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("leaves a free slug that merely contains the word alone", () => {
|
|
51
|
+
const errors = reservedErrors(
|
|
52
|
+
template([{ slug: "grant-project", displayName: "Grant project", properties: [] }] as unknown as WorkspaceYaml["profiles"])
|
|
53
|
+
);
|
|
54
|
+
expect(errors).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Profiles are declared only by workspace YAMLs: the CP pack fragments that
|
|
58
|
+
// also declared them were retired (catalog-lib refuses a `_meta` fragment).
|
|
59
|
+
it("no shipped template declares a reserved slug", () => {
|
|
60
|
+
const yamls = readYamlTemplates().map((t) => ({ file: `src/${t.file}`, data: t.data as WorkspaceYaml }));
|
|
61
|
+
// Non-vacuity: the scan sees the corpus, and the corpus declares profiles.
|
|
62
|
+
expect(yamls.length).toBeGreaterThan(20);
|
|
63
|
+
const declared = yamls.flatMap((t) => (t.data?.profiles ?? []).map((p: { slug: string }) => p.slug));
|
|
64
|
+
expect(declared.length).toBeGreaterThan(100);
|
|
65
|
+
|
|
66
|
+
const offenders = yamls.flatMap((t) =>
|
|
67
|
+
reservedErrors(t.data as WorkspaceYaml).map((e) => `${t.file}: ${e.path}`)
|
|
68
|
+
);
|
|
69
|
+
expect(offenders).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe.skipIf(!existsSync(BACKEND_RESERVED))("RESERVED_PROFILE_SLUGS mirrors the pod's reservation", () => {
|
|
74
|
+
if (!existsSync(BACKEND_RESERVED))
|
|
75
|
+
console.warn(`[reserved-profile-slugs] SKIP: no synap-backend checkout at ${BACKEND_RESERVED}`);
|
|
76
|
+
|
|
77
|
+
it("is the same set as @synap/database reservedProfileSlugs()", async () => {
|
|
78
|
+
const mod = (await import(BACKEND_RESERVED)) as { reservedProfileSlugs: () => readonly string[] };
|
|
79
|
+
expect([...RESERVED_PROFILE_SLUGS].sort()).toEqual([...mod.reservedProfileSlugs()].sort());
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No catalog template declares a goal-like kind (decision D2, 2026-10-23).
|
|
3
|
+
*
|
|
4
|
+
* "Goal" is the user's word for a track (`project_tracks`). The catalog used to
|
|
5
|
+
* ship three entity kinds that read as goals: Life OS `goal`, Enterprise OS
|
|
6
|
+
* `objective`, Project Management `okr`. A pod that installed two of them
|
|
7
|
+
* showed two "Goals". New installs no longer create them; a pod that already
|
|
8
|
+
* has them keeps its entities (profiles are never removed from an installed
|
|
9
|
+
* pod) until the "Make it a goal" conversion is offered.
|
|
10
|
+
*
|
|
11
|
+
* `retired.yaml` tombstones whole templates, not a kind inside a live template,
|
|
12
|
+
* so this list is the kind-level record. A template that needs the idea of a
|
|
13
|
+
* goal links to the pod's goals (tracks); it never re-declares the kind.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, expect, it } from "vitest";
|
|
17
|
+
import { readYamlTemplates } from "./generate.mjs";
|
|
18
|
+
|
|
19
|
+
const RETIRED_GOAL_KINDS = ["goal", "objective", "okr"] as const;
|
|
20
|
+
|
|
21
|
+
type Declared = { slug?: string };
|
|
22
|
+
type Corpus = { file: string; data: { profiles?: Declared[] } };
|
|
23
|
+
|
|
24
|
+
/** `file: slug` for every declared profile whose slug is a retired goal kind. */
|
|
25
|
+
const retiredKindsDeclared = (corpus: Corpus[]): string[] =>
|
|
26
|
+
corpus.flatMap((t) =>
|
|
27
|
+
(t.data?.profiles ?? [])
|
|
28
|
+
.filter((p) => (RETIRED_GOAL_KINDS as readonly string[]).includes(String(p.slug).trim().toLowerCase()))
|
|
29
|
+
.map((p) => `${t.file}: ${p.slug}`)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
describe("retired goal-like kinds", () => {
|
|
33
|
+
it("the scan catches a template that declares one (so the guard is not vacuous)", () => {
|
|
34
|
+
const probe: Corpus[] = [
|
|
35
|
+
{ file: "probe.yaml", data: { profiles: [{ slug: "task" }, { slug: " OKR " }] } },
|
|
36
|
+
{ file: "probe2.yaml", data: { profiles: [{ slug: "goal" }, { slug: "objective" }] } },
|
|
37
|
+
];
|
|
38
|
+
expect(retiredKindsDeclared(probe)).toEqual(["probe.yaml: OKR ", "probe2.yaml: goal", "probe2.yaml: objective"]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Profiles are declared only by workspace YAMLs: the CP pack fragments that
|
|
42
|
+
// also declared them were retired (catalog-lib refuses a `_meta` fragment).
|
|
43
|
+
it("no shipped workspace template declares one", () => {
|
|
44
|
+
const yamls: Corpus[] = readYamlTemplates().map((t) => ({ file: `src/${t.file}`, data: t.data as Corpus["data"] }));
|
|
45
|
+
// Non-vacuity: the scan reads the corpus, and the corpus declares profiles.
|
|
46
|
+
expect(yamls.length).toBeGreaterThan(20);
|
|
47
|
+
const declared = yamls.flatMap((t) => t.data?.profiles ?? []);
|
|
48
|
+
expect(declared.length).toBeGreaterThan(100);
|
|
49
|
+
|
|
50
|
+
expect(retiredKindsDeclared(yamls)).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W4a (concept consolidation) validator rules — each driven with a template
|
|
3
|
+
* built to BREAK it, plus the real corpus passing it.
|
|
4
|
+
*
|
|
5
|
+
* shared-role-reference-no-foundation a template re-declaring/referencing a
|
|
6
|
+
* foundation shared role must reach foundation through its dependencies.
|
|
7
|
+
* shared-role-reference-unknown a reference (no applicableKinds) must
|
|
8
|
+
* name a role foundation actually defines.
|
|
9
|
+
* stage-domain-shape / -unknown / -not-a-domain a step's `domain` is a slug
|
|
10
|
+
* of a workspace template that OWNS a workspace.
|
|
11
|
+
* pack-requires-pack D8: a pack lists workspace templates.
|
|
12
|
+
*
|
|
13
|
+
* NOT covered: that the pod honours a reference (resolves it to the pod-wide
|
|
14
|
+
* row, grants access) — that is the backend apply door, not this package.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, expect, it } from "vitest";
|
|
18
|
+
import { validateTemplate } from "./validate.js";
|
|
19
|
+
import { WORKSPACE_TEMPLATES } from "./templates.js";
|
|
20
|
+
import type { WorkspaceYaml } from "./types.js";
|
|
21
|
+
|
|
22
|
+
const corpus: WorkspaceYaml[] = Object.values(WORKSPACE_TEMPLATES);
|
|
23
|
+
|
|
24
|
+
const tpl = (over: Record<string, unknown>): WorkspaceYaml =>
|
|
25
|
+
({
|
|
26
|
+
meta: {
|
|
27
|
+
slug: "acme",
|
|
28
|
+
name: "Acme",
|
|
29
|
+
description: "d",
|
|
30
|
+
icon: "x",
|
|
31
|
+
color: "#000000",
|
|
32
|
+
},
|
|
33
|
+
workspace: { name: "Acme", description: "d" },
|
|
34
|
+
profiles: [{ slug: "gadget", displayName: "Gadget", properties: [] }],
|
|
35
|
+
...over,
|
|
36
|
+
}) as unknown as WorkspaceYaml;
|
|
37
|
+
|
|
38
|
+
const rules = (t: WorkspaceYaml, prefix: string) =>
|
|
39
|
+
validateTemplate(t, { corpus })
|
|
40
|
+
.errors.filter((e) => e.rule.startsWith(prefix))
|
|
41
|
+
.map((e) => e.rule);
|
|
42
|
+
|
|
43
|
+
const leadReference = {
|
|
44
|
+
slug: "lead",
|
|
45
|
+
displayName: "Lead",
|
|
46
|
+
scope: "shared",
|
|
47
|
+
profileKind: "role",
|
|
48
|
+
properties: [],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("shared-role references need foundation in the dependency chain", () => {
|
|
52
|
+
it("non-vacuity: foundation defines the shared roles the rule reads", () => {
|
|
53
|
+
const roles = (WORKSPACE_TEMPLATES["foundation"]?.profiles ?? []).filter(
|
|
54
|
+
(p) => p.scope === "shared" && p.profileKind === "role"
|
|
55
|
+
);
|
|
56
|
+
expect(roles.map((p) => p.slug)).toEqual(
|
|
57
|
+
expect.arrayContaining(["client", "partner", "lead"])
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("a reference with NO foundation in its chain is refused", () => {
|
|
62
|
+
expect(rules(tpl({ profiles: [leadReference] }), "shared-role-reference")).toEqual([
|
|
63
|
+
"shared-role-reference-no-foundation",
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("a FULL re-declaration of a foundation role without foundation is refused too", () => {
|
|
68
|
+
const full = { ...leadReference, applicableKinds: ["company", "person"] };
|
|
69
|
+
expect(rules(tpl({ profiles: [full] }), "shared-role-reference")).toEqual([
|
|
70
|
+
"shared-role-reference-no-foundation",
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("foundation reached TRANSITIVELY (via crm) satisfies it", () => {
|
|
75
|
+
const t = tpl({
|
|
76
|
+
profiles: [leadReference],
|
|
77
|
+
dependencies: [{ slug: "crm", kind: "workspace", relation: "require" }],
|
|
78
|
+
});
|
|
79
|
+
expect(rules(t, "shared-role-reference")).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("a reference to a role foundation does not define is refused", () => {
|
|
83
|
+
const t = tpl({
|
|
84
|
+
profiles: [{ ...leadReference, slug: "investor", displayName: "Investor" }],
|
|
85
|
+
dependencies: [{ slug: "foundation", kind: "workspace", relation: "require" }],
|
|
86
|
+
});
|
|
87
|
+
expect(rules(t, "shared-role-reference")).toEqual(["shared-role-reference-unknown"]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("a shared role the template OWNS (full body, not foundation's) is left alone", () => {
|
|
91
|
+
const own = {
|
|
92
|
+
slug: "competitor",
|
|
93
|
+
displayName: "Competitor",
|
|
94
|
+
scope: "shared",
|
|
95
|
+
profileKind: "role",
|
|
96
|
+
applicableKinds: ["company"],
|
|
97
|
+
properties: [],
|
|
98
|
+
};
|
|
99
|
+
expect(rules(tpl({ profiles: [own] }), "shared-role-reference")).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("crm and ecosystem now carry references, not bodies", () => {
|
|
103
|
+
for (const slug of ["crm", "ecosystem"]) {
|
|
104
|
+
const roles = (WORKSPACE_TEMPLATES[slug]?.profiles ?? []).filter(
|
|
105
|
+
(p) =>
|
|
106
|
+
p.scope === "shared" &&
|
|
107
|
+
p.profileKind === "role" &&
|
|
108
|
+
["client", "partner", "lead"].includes(p.slug)
|
|
109
|
+
);
|
|
110
|
+
expect(roles.length, `${slug} references no foundation role`).toBeGreaterThan(0);
|
|
111
|
+
for (const r of roles) {
|
|
112
|
+
expect(r.applicableKinds, `${slug}.${r.slug} restates applicableKinds`).toBeUndefined();
|
|
113
|
+
expect(r.description, `${slug}.${r.slug} restates description`).toBeUndefined();
|
|
114
|
+
expect(r.icon, `${slug}.${r.slug} restates icon`).toBeUndefined();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const withStep = (domain: unknown) =>
|
|
121
|
+
tpl({
|
|
122
|
+
playbooks: [
|
|
123
|
+
{
|
|
124
|
+
name: "M",
|
|
125
|
+
goalTemplate: "g",
|
|
126
|
+
scope: "project",
|
|
127
|
+
stages: [{ key: "s", name: "S", category: "started", domain }],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("step domains name a workspace template that owns a workspace", () => {
|
|
133
|
+
it("a real domain template passes", () => {
|
|
134
|
+
expect(rules(withStep("finance"), "stage-domain")).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
it("a workspace id is refused", () => {
|
|
137
|
+
expect(rules(withStep("8f894661-db21-4f6d-ba30-5334f7b67bef"), "stage-domain")).toEqual([
|
|
138
|
+
"stage-domain-shape",
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
it("a display name is refused", () => {
|
|
142
|
+
expect(rules(withStep("Foundation"), "stage-domain")).toEqual(["stage-domain-shape"]);
|
|
143
|
+
});
|
|
144
|
+
it("a slug no template carries is refused", () => {
|
|
145
|
+
expect(rules(withStep("no-such-domain"), "stage-domain")).toEqual(["stage-domain-unknown"]);
|
|
146
|
+
});
|
|
147
|
+
it("an overlay's slug is refused (it never owns a workspace)", () => {
|
|
148
|
+
expect(rules(withStep("business-model"), "stage-domain")).toEqual([
|
|
149
|
+
"stage-domain-not-a-domain",
|
|
150
|
+
]);
|
|
151
|
+
});
|
|
152
|
+
it("a pack's slug is refused", () => {
|
|
153
|
+
expect(rules(withStep("enterprise-os"), "stage-domain")).toEqual([
|
|
154
|
+
"stage-domain-not-a-domain",
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("D8: a pack lists workspace templates, never a pack", () => {
|
|
160
|
+
const pack = (deps: unknown[]) =>
|
|
161
|
+
tpl({ meta: { slug: "p", name: "P", description: "d", icon: "x", color: "#000000", tags: ["suite"] }, dependencies: deps });
|
|
162
|
+
it("pack → pack is refused", () => {
|
|
163
|
+
expect(
|
|
164
|
+
rules(pack([{ slug: "enterprise-os", kind: "workspace", relation: "require" }]), "pack-")
|
|
165
|
+
).toEqual(["pack-requires-pack"]);
|
|
166
|
+
});
|
|
167
|
+
it("pack → workspace templates passes", () => {
|
|
168
|
+
expect(
|
|
169
|
+
rules(pack([{ slug: "foundation", kind: "workspace", relation: "require" }]), "pack-")
|
|
170
|
+
).toEqual([]);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("the real corpus passes every W4a rule", () => {
|
|
175
|
+
it("no template trips a W4a rule", () => {
|
|
176
|
+
expect(corpus.length).toBeGreaterThan(20);
|
|
177
|
+
const hits = corpus.flatMap((t) =>
|
|
178
|
+
validateTemplate(t, { corpus })
|
|
179
|
+
.errors.filter((e) =>
|
|
180
|
+
/^(shared-role-reference|stage-domain|pack-requires-pack)/.test(e.rule)
|
|
181
|
+
)
|
|
182
|
+
.map((e) => `${t.meta?.slug}: ${e.rule} ${e.path}`)
|
|
183
|
+
);
|
|
184
|
+
expect(hits).toEqual([]);
|
|
185
|
+
});
|
|
186
|
+
it("non-vacuity: the corpus HAS step domains and role references to check", () => {
|
|
187
|
+
const domains = corpus.flatMap((t) =>
|
|
188
|
+
(t.playbooks ?? []).flatMap((p) => (p.stages ?? []).map((s) => s.domain))
|
|
189
|
+
).filter(Boolean);
|
|
190
|
+
expect(domains.length).toBeGreaterThanOrEqual(12); // business-model 6 + grants 6
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -37,18 +37,15 @@ import type { WorkspaceYaml } from "./types.js";
|
|
|
37
37
|
* as debt instead of as a decision.
|
|
38
38
|
*/
|
|
39
39
|
const SEEDLESS_BY_DESIGN: Record<string, string> = {
|
|
40
|
-
"agent-fleet": "Pre-dates this guard. Fleet contents are per-pod; needs a seed pass.",
|
|
41
40
|
base: "Pod substrate, not a workspace a user opens. Seeds would land in every workspace.",
|
|
42
41
|
"blockchain-ecosystem": "Pre-dates this guard; needs a seed pass.",
|
|
43
42
|
"brand-library": "Pre-dates this guard; brand assets are inherently per-company.",
|
|
44
43
|
"builder-workspace": "Pre-dates this guard; needs a seed pass.",
|
|
45
|
-
"business-developer": "Pre-dates this guard; overlay on CRM, seeds belong on the base.",
|
|
46
44
|
"civic-france": "Pre-dates this guard; needs a seed pass.",
|
|
47
45
|
communication: "Pre-dates this guard; needs a seed pass.",
|
|
48
46
|
"content-os": "Pre-dates this guard; needs a seed pass.",
|
|
49
|
-
"content-
|
|
47
|
+
"content-creation": "Suite command tower; seeds belong on the constituent workspaces (content-os), same as enterprise-os.",
|
|
50
48
|
crm: "Pre-dates this guard; needs a seed pass.",
|
|
51
|
-
"dev-dashboard": "Pre-dates this guard; needs a seed pass.",
|
|
52
49
|
ecosystem: "Pre-dates this guard; needs a seed pass.",
|
|
53
50
|
"enterprise-os": "Pre-dates this guard; needs a seed pass.",
|
|
54
51
|
finance:
|
|
@@ -64,13 +61,11 @@ const SEEDLESS_BY_DESIGN: Record<string, string> = {
|
|
|
64
61
|
"meetings-knowledge": "Pre-dates this guard; needs a seed pass.",
|
|
65
62
|
networking: "Pre-dates this guard; needs a seed pass.",
|
|
66
63
|
operations: "Pre-dates this guard; needs a seed pass.",
|
|
67
|
-
"outreach-comms": "Pre-dates this guard; needs a seed pass.",
|
|
68
64
|
"project-management": "Pre-dates this guard; needs a seed pass.",
|
|
69
65
|
"research-base":
|
|
70
66
|
"Pre-dates this guard. Radar reproduced the empty-workspace pathology on top of this substrate; seed pass wanted.",
|
|
71
67
|
"research-decision": "Overlay on research-base; the method is a playbook, not entities.",
|
|
72
68
|
social: "Pre-dates this guard; needs a seed pass.",
|
|
73
|
-
"the-arch": "Pre-dates this guard; needs a seed pass.",
|
|
74
69
|
};
|
|
75
70
|
|
|
76
71
|
/** Slugs of profiles this template declares itself (owned or re-declared). */
|