@synap-core/workspace-templates 0.11.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 +15 -0
- package/dist/index.js +802 -4
- package/dist/index.js.map +1 -1
- package/package.json +16 -25
- package/src/base.template.test.ts +25 -19
- package/src/base.yaml +10 -0
- package/src/builder.yaml +19 -1
- package/src/business-model.yaml +351 -0
- package/src/catalog.ts +19 -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 +17 -6
- package/src/life-os.yaml +14 -0
- package/src/seed-obligation.test.ts +181 -0
- package/src/shared-role-ssot.test.ts +4 -1
- package/src/templates.ts +765 -3
- 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/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synap-core/workspace-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"packageManager": "pnpm@9.15.4",
|
|
6
5
|
"description": "Canonical workspace definitions — YAML source + typed exports for browser, backend, CLI, and marketplace",
|
|
7
6
|
"exports": {
|
|
8
7
|
".": {
|
|
9
8
|
"types": "./dist/index.d.ts",
|
|
10
|
-
"node": "./dist/index.js",
|
|
11
9
|
"default": "./dist/index.js"
|
|
12
10
|
}
|
|
13
11
|
},
|
|
@@ -15,16 +13,6 @@
|
|
|
15
13
|
"dist",
|
|
16
14
|
"src"
|
|
17
15
|
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"type-check": "tsc --noEmit",
|
|
20
|
-
"build:yaml": "node --experimental-strip-types build.mjs",
|
|
21
|
-
"check:converters": "node scripts/check-converters.mjs",
|
|
22
|
-
"check:concepts": "node scripts/check-concepts.mjs",
|
|
23
|
-
"build": "node --experimental-strip-types build.mjs && tsup && node scripts/check-converters.mjs && node scripts/check-concepts.mjs",
|
|
24
|
-
"provision": "node scripts/provision.mjs",
|
|
25
|
-
"eval": "node scripts/eval-standard.mjs",
|
|
26
|
-
"prepublishOnly": "node --experimental-strip-types build.mjs && tsup && node scripts/check-converters.mjs && node scripts/check-concepts.mjs"
|
|
27
|
-
},
|
|
28
16
|
"dependencies": {
|
|
29
17
|
"zod": "^3.0.0",
|
|
30
18
|
"js-yaml": "^4.1.0"
|
|
@@ -37,15 +25,18 @@
|
|
|
37
25
|
},
|
|
38
26
|
"publishConfig": {
|
|
39
27
|
"registry": "https://registry.npmjs.org",
|
|
40
|
-
"access": "public"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"type-check": "tsc --noEmit",
|
|
32
|
+
"build:yaml": "node --experimental-strip-types build.mjs",
|
|
33
|
+
"check:converters": "node scripts/check-converters.mjs",
|
|
34
|
+
"check:concepts": "node scripts/check-concepts.mjs",
|
|
35
|
+
"build": "node --experimental-strip-types build.mjs && tsup && node scripts/check-converters.mjs && node scripts/check-concepts.mjs",
|
|
36
|
+
"provision": "node scripts/provision.mjs",
|
|
37
|
+
"eval": "node scripts/eval-standard.mjs"
|
|
38
|
+
},
|
|
39
|
+
"main": "./dist/index.js",
|
|
40
|
+
"module": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts"
|
|
42
|
+
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
* seeders be deleted (Wave 4) without silently changing what every workspace
|
|
9
9
|
* gets.
|
|
10
10
|
*
|
|
11
|
-
* FIDELITY MECHANISM.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
11
|
+
* FIDELITY MECHANISM. base.yaml is now the SINGLE source of the report flow —
|
|
12
|
+
* the backend `REPORT_AUTOMATION_FLOW` const it was once snapshotted against was
|
|
13
|
+
* retired, and with it the `__fixtures__/report-automation-flow.json` copy-pin
|
|
14
|
+
* (a copy pinned to a copy caught no upstream drift). The report flow is guarded
|
|
15
|
+
* here directly instead: the prompt-fragment smoke check below proves the product
|
|
16
|
+
* content survived, and `templates-freshness.test.ts` pins templates.ts == the
|
|
17
|
+
* YAML source. The 23-ish relation defs KEEP their byte-for-byte fixture
|
|
18
|
+
* guardrail (`__fixtures__/default-relation-defs.json`).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { readFileSync } from "fs";
|
|
@@ -55,18 +55,13 @@ describe("Task A — report automation, byte-faithful", () => {
|
|
|
55
55
|
expect(automation?.description).toMatch(/Gather this workspace's state/);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it("flow BYTE-MATCHES the REPORT_AUTOMATION_FLOW snapshot", () => {
|
|
59
|
-
const fixture = readFixture("report-automation-flow.json");
|
|
60
|
-
// deep-equal: the compiled template's flow is JSON.stringify(parsed YAML), so
|
|
61
|
-
// a deep-equal against the JSON snapshot of the source const proves the
|
|
62
|
-
// transcription is lossless (nodes, edges, and every prompt string).
|
|
63
|
-
expect(automation?.flow).toEqual(fixture);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
58
|
it("preserves the load-bearing hardened prompt fragments (v13)", () => {
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
59
|
+
// The primary guardrail that the report flow's product content survived in
|
|
60
|
+
// base.yaml. (A byte-for-byte fixture pin used to sit above this, snapshotting
|
|
61
|
+
// the retired backend `REPORT_AUTOMATION_FLOW` const; with that const gone
|
|
62
|
+
// base.yaml is the single source, so a copy-pinned-to-a-copy proved nothing
|
|
63
|
+
// and was dropped. `templates-freshness.test.ts` pins templates.ts == the
|
|
64
|
+
// YAML source.)
|
|
70
65
|
const flowStr = JSON.stringify(automation?.flow);
|
|
71
66
|
for (const marker of [
|
|
72
67
|
"COUNTS — the exact number of records gathered",
|
|
@@ -77,6 +72,17 @@ describe("Task A — report automation, byte-faithful", () => {
|
|
|
77
72
|
expect(flowStr).toContain(marker);
|
|
78
73
|
}
|
|
79
74
|
});
|
|
75
|
+
|
|
76
|
+
it("carries the v15 retry policy on all four AI rounds (regression guard)", () => {
|
|
77
|
+
// v15's fix for the 2026-08-03 finishReason=length incident: each of the four
|
|
78
|
+
// ai.generate rounds (analyze/relate/assemble/summarize) gets ONE retry with a
|
|
79
|
+
// 5s delay. This field dropped SILENTLY once — the 700→2000 fix ported maxTokens
|
|
80
|
+
// into base.yaml but not the retry policy, so it went missing on the reconcile-
|
|
81
|
+
// winning path. Assert it here (base.yaml is the SSOT now) so it can't drift again.
|
|
82
|
+
const flowStr = JSON.stringify(automation?.flow);
|
|
83
|
+
expect((flowStr.match(/"retryDelay":5000/g) ?? []).length).toBe(4);
|
|
84
|
+
expect((flowStr.match(/"maxRetries":1(?=[,}])/g) ?? []).length).toBe(4);
|
|
85
|
+
});
|
|
80
86
|
});
|
|
81
87
|
|
|
82
88
|
describe("Task B — relation defs, full coverage", () => {
|
package/src/base.yaml
CHANGED
|
@@ -458,6 +458,8 @@ automations:
|
|
|
458
458
|
maxTokens: "2000"
|
|
459
459
|
errorHandling:
|
|
460
460
|
continueOnError: true
|
|
461
|
+
maxRetries: 1
|
|
462
|
+
retryDelay: 5000
|
|
461
463
|
- id: relate
|
|
462
464
|
type: capability
|
|
463
465
|
position:
|
|
@@ -540,6 +542,8 @@ automations:
|
|
|
540
542
|
maxTokens: "2000"
|
|
541
543
|
errorHandling:
|
|
542
544
|
continueOnError: true
|
|
545
|
+
maxRetries: 1
|
|
546
|
+
retryDelay: 5000
|
|
543
547
|
- id: assemble
|
|
544
548
|
type: capability
|
|
545
549
|
position:
|
|
@@ -724,6 +728,9 @@ automations:
|
|
|
724
728
|
RELATE ROUND MATERIAL:
|
|
725
729
|
{{steps.relate.output}}
|
|
726
730
|
maxTokens: "2000"
|
|
731
|
+
errorHandling:
|
|
732
|
+
maxRetries: 1
|
|
733
|
+
retryDelay: 5000
|
|
727
734
|
- id: summarize
|
|
728
735
|
type: capability
|
|
729
736
|
position:
|
|
@@ -742,6 +749,9 @@ automations:
|
|
|
742
749
|
Material to summarize (may be an error object if the round failed):
|
|
743
750
|
{{steps.analyze.output}}
|
|
744
751
|
maxTokens: "500"
|
|
752
|
+
errorHandling:
|
|
753
|
+
maxRetries: 1
|
|
754
|
+
retryDelay: 5000
|
|
745
755
|
- id: guard-assembled
|
|
746
756
|
type: guard
|
|
747
757
|
position:
|
package/src/builder.yaml
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Builder workspace template
|
|
2
2
|
# Developer + agent intelligence layer.
|
|
3
|
-
#
|
|
3
|
+
#
|
|
4
|
+
# ✅ SINGLE SOURCE OF TRUTH for the Builder workspace, on every door.
|
|
5
|
+
# Both doors now derive from this file:
|
|
6
|
+
# • apps/devplane/lib/builderWorkspaceDefinition.ts → the devplane onboarding
|
|
7
|
+
# path (trpc.workspaces.createFromDefinition) — since 2026-09-05 that module
|
|
8
|
+
# is a five-line `toWorkspaceDefinition("builder-workspace")` derivation
|
|
9
|
+
# • the registry / marketplace install path — same seam
|
|
10
|
+
# Until 2026-09-05 the TS file held a ~1,150-line hand-typed PEER copy of this
|
|
11
|
+
# workspace and nothing compared the two, which produced five real divergences
|
|
12
|
+
# (four capabilities, a dead-Coolify seed pair, defaultApp/primarySurface, an
|
|
13
|
+
# unprefixed calendar dateField, and this file's playbooks/onboarding). Edit the
|
|
14
|
+
# schema HERE; there is no second copy to keep in sync.
|
|
4
15
|
|
|
5
16
|
meta:
|
|
6
17
|
slug: builder-workspace
|
|
@@ -23,6 +34,13 @@ workspace:
|
|
|
23
34
|
- builder.deployments
|
|
24
35
|
- builder.agents
|
|
25
36
|
- builder.packages
|
|
37
|
+
# Tier-1 signal for `resolveWorkspaceForScope` — must match the DevPlane app
|
|
38
|
+
# manifest's `defaultScope.capability` (browser/…/apps/manifest.ts) and the
|
|
39
|
+
# `workspaceCapabilities` list the devplane door derives from this block.
|
|
40
|
+
# Without it a workspace created through THIS door fails all four resolver tiers
|
|
41
|
+
# (no capability · subtype "builder" ≠ kind "devplane" · name "Builder" ≠ "DevPlane")
|
|
42
|
+
# and the workspace press falls silently to a setup card.
|
|
43
|
+
- devplane.workspace
|
|
26
44
|
|
|
27
45
|
profiles:
|
|
28
46
|
- slug: devplane_app
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# Business Model workspace template
|
|
2
|
+
#
|
|
3
|
+
# WHY THIS TEMPLATE EXISTS (read before adding another strategy pack):
|
|
4
|
+
# The nine-component business-model interrogation (porteur, offer, market,
|
|
5
|
+
# money, means, and the conventions binding them) maps almost entirely onto
|
|
6
|
+
# vocabulary that ALREADY EXISTS in this corpus — `mission`/`vision`/`value`
|
|
7
|
+
# (foundation), `audience`/`pain-point`/`positioning`/`offer` (foundation,
|
|
8
|
+
# shared + pod-scoped), `client`/`partner`/`lead` (foundation shared roles),
|
|
9
|
+
# and the whole IBIS apparatus of `question`/`hypothesis`/`decision`/`source`
|
|
10
|
+
# (research-base + research-decision). Minting a parallel `business-model-*`
|
|
11
|
+
# vocabulary would have been a vocabulary fork — the exact failure
|
|
12
|
+
# `concept-guard.ts` was written to catch.
|
|
13
|
+
#
|
|
14
|
+
# So this pack adds only what is genuinely MISSING, which is three things:
|
|
15
|
+
# 1. `convention` — the binding external rule the model rests on (an
|
|
16
|
+
# eligibility threshold, a deontological duty, a contractual floor). Not
|
|
17
|
+
# a decision (nobody chose it) and not a finding (it is not evidence, it
|
|
18
|
+
# is a constraint). Nothing in the corpus expressed this.
|
|
19
|
+
# 2. `projection` — a quantified scenario with its assumptions attached, so
|
|
20
|
+
# a number can be challenged by challenging what it assumed.
|
|
21
|
+
# 3. `rests_on` / `refutes` — the two edges that make a business model a
|
|
22
|
+
# GRAPH instead of a document. `rests_on` is deliberately NOT `depends_on`
|
|
23
|
+
# or `blocks`: those are acyclic scheduling edges, and a business model is
|
|
24
|
+
# legitimately circular (the offer rests on the means, the means rest on
|
|
25
|
+
# the money, the money rests on the offer). `rests_on` carries support and
|
|
26
|
+
# staleness, not sequencing, so a cycle is information rather than a bug.
|
|
27
|
+
#
|
|
28
|
+
# The porteur and funder ROLES it needs are declared in foundation.yaml, the
|
|
29
|
+
# shared-role SSOT — see the note there.
|
|
30
|
+
|
|
31
|
+
meta:
|
|
32
|
+
slug: business-model
|
|
33
|
+
domain: strategy
|
|
34
|
+
name: Business Model
|
|
35
|
+
description: Interrogate a venture as a model that rests on things — components supported by evidence, bound by real conventions, and quantified by projections whose assumptions stay visible.
|
|
36
|
+
icon: layout-grid
|
|
37
|
+
color: "#0EA5E9"
|
|
38
|
+
tags: [strategy, business-model, grp, ibis, viability]
|
|
39
|
+
isPublic: true
|
|
40
|
+
|
|
41
|
+
workspace:
|
|
42
|
+
name: Foundation
|
|
43
|
+
description: Adds business-model interrogation to the strategic substrate — what the model rests on, which conventions bind it, and which numbers assume what.
|
|
44
|
+
subtype: foundation
|
|
45
|
+
|
|
46
|
+
# ONE compose base only — `validate.ts` requires every compose dependency to
|
|
47
|
+
# equal `workspace.subtype`, which is correct: an overlay is layered into one
|
|
48
|
+
# workspace, and a thing cannot be layered into two. The business model belongs
|
|
49
|
+
# in Foundation, because its components ARE the strategic DNA. The evidence
|
|
50
|
+
# apparatus it borrows (hypothesis, source, finding) lives in the Research
|
|
51
|
+
# workspace and is reached by RELATION, not by merging two lenses into one.
|
|
52
|
+
dependencies:
|
|
53
|
+
- slug: foundation
|
|
54
|
+
relation: compose
|
|
55
|
+
reason: The model's components ARE the foundation vocabulary — mission, audience, pain-point, positioning, offer, and the client/partner/porteur/funder roles. This pack interrogates them; it does not restate them.
|
|
56
|
+
|
|
57
|
+
profiles:
|
|
58
|
+
# ── Re-declared from dependencies, verbatim-compatible ─────────────────────
|
|
59
|
+
# Re-declaring a composed profile is how a pack states what it operates on;
|
|
60
|
+
# the apply layer resolves shared slugs to the ONE pod-wide row. Shapes here
|
|
61
|
+
# are strict subsets of the owner's shape, so no overlay conflict arises.
|
|
62
|
+
- slug: question
|
|
63
|
+
displayName: Question
|
|
64
|
+
icon: help-circle
|
|
65
|
+
profileKind: kind
|
|
66
|
+
properties:
|
|
67
|
+
- slug: questionStatus
|
|
68
|
+
label: Status
|
|
69
|
+
valueType: string
|
|
70
|
+
inputType: select
|
|
71
|
+
enumValues: [open, exploring, answered, abandoned]
|
|
72
|
+
- slug: decision
|
|
73
|
+
displayName: Decision
|
|
74
|
+
icon: gavel
|
|
75
|
+
profileKind: kind
|
|
76
|
+
properties:
|
|
77
|
+
- slug: decisionStatus
|
|
78
|
+
label: Status
|
|
79
|
+
valueType: string
|
|
80
|
+
inputType: select
|
|
81
|
+
enumValues: [proposed, accepted, superseded, rejected]
|
|
82
|
+
|
|
83
|
+
# ── What this pack actually adds ───────────────────────────────────────────
|
|
84
|
+
- slug: convention
|
|
85
|
+
displayName: Convention
|
|
86
|
+
icon: scale
|
|
87
|
+
color: rose
|
|
88
|
+
description: A binding rule the model must satisfy and did not choose — an eligibility threshold, a professional duty, a contractual floor, a platform policy. Distinct from a decision (nobody decided it) and from a finding (it constrains rather than informs).
|
|
89
|
+
profileKind: kind
|
|
90
|
+
properties:
|
|
91
|
+
- slug: rule
|
|
92
|
+
label: The Rule
|
|
93
|
+
valueType: string
|
|
94
|
+
inputType: textarea
|
|
95
|
+
description: State the constraint in the terms the issuer uses, not in a paraphrase. Paraphrase is where eligibility collisions hide.
|
|
96
|
+
- slug: issuer
|
|
97
|
+
label: Issued By
|
|
98
|
+
valueType: string
|
|
99
|
+
inputType: text
|
|
100
|
+
description: Who imposes it — a regulator, a funder, an ordre professionnel, a platform, a counterparty.
|
|
101
|
+
- slug: conventionType
|
|
102
|
+
label: Type
|
|
103
|
+
valueType: string
|
|
104
|
+
inputType: select
|
|
105
|
+
enumValues: [regulatory, deontological, eligibility, contractual, platform, fiscal, other]
|
|
106
|
+
- slug: bindingStatus
|
|
107
|
+
label: Binding Status
|
|
108
|
+
valueType: string
|
|
109
|
+
inputType: select
|
|
110
|
+
enumValues: [verified, assumed, contested, expired]
|
|
111
|
+
description: "`assumed` is the dangerous default — it means nobody has read the source text yet."
|
|
112
|
+
- slug: sourceReference
|
|
113
|
+
label: Source Reference
|
|
114
|
+
valueType: string
|
|
115
|
+
inputType: text
|
|
116
|
+
description: The cahier des charges, article, or guide this comes from. A convention with no reference is an assumption wearing a rule's clothes.
|
|
117
|
+
- slug: expiresAt
|
|
118
|
+
label: Expires / Review By
|
|
119
|
+
valueType: date
|
|
120
|
+
inputType: date
|
|
121
|
+
- slug: consequenceIfBreached
|
|
122
|
+
label: Consequence If Breached
|
|
123
|
+
valueType: string
|
|
124
|
+
inputType: textarea
|
|
125
|
+
|
|
126
|
+
- slug: projection
|
|
127
|
+
displayName: Projection
|
|
128
|
+
icon: trending-up
|
|
129
|
+
color: violet
|
|
130
|
+
description: A quantified scenario with its assumptions attached. The number is never the artifact — the assumptions are, because that is the only part anyone can argue with.
|
|
131
|
+
profileKind: kind
|
|
132
|
+
properties:
|
|
133
|
+
- slug: scenario
|
|
134
|
+
label: Scenario
|
|
135
|
+
valueType: string
|
|
136
|
+
inputType: select
|
|
137
|
+
enumValues: [pessimistic, base, optimistic]
|
|
138
|
+
- slug: horizon
|
|
139
|
+
label: Horizon
|
|
140
|
+
valueType: string
|
|
141
|
+
inputType: select
|
|
142
|
+
enumValues: [month, quarter, year, three-year]
|
|
143
|
+
- slug: assumptions
|
|
144
|
+
label: Assumptions
|
|
145
|
+
valueType: string
|
|
146
|
+
inputType: textarea
|
|
147
|
+
description: Every input this number needs to be true. When one changes, whatever rests on this projection goes stale.
|
|
148
|
+
- slug: revenueEstimate
|
|
149
|
+
label: Revenue Estimate
|
|
150
|
+
valueType: number
|
|
151
|
+
inputType: number
|
|
152
|
+
- slug: costEstimate
|
|
153
|
+
label: Cost Estimate
|
|
154
|
+
valueType: number
|
|
155
|
+
inputType: number
|
|
156
|
+
- slug: breakEvenUnits
|
|
157
|
+
label: Break-Even Units
|
|
158
|
+
valueType: number
|
|
159
|
+
inputType: number
|
|
160
|
+
- slug: confidence
|
|
161
|
+
label: Confidence
|
|
162
|
+
valueType: string
|
|
163
|
+
inputType: select
|
|
164
|
+
enumValues: [low, medium, high]
|
|
165
|
+
|
|
166
|
+
# Relation TYPE vocabulary. Declared here (not as bare entityLinks) because
|
|
167
|
+
# these two carry description + inverse labels that the entity-link path drops.
|
|
168
|
+
relationDefs:
|
|
169
|
+
- slug: rests_on
|
|
170
|
+
displayName: Rests On
|
|
171
|
+
description: "The source depends on the target for its validity — not for its scheduling. Cycles are LEGAL and meaningful here: a business model is mutually supporting by nature, and forcing it into an acyclic dependency graph destroys the information. When the target changes, everything resting on it is stale until re-checked."
|
|
172
|
+
isDirectional: true
|
|
173
|
+
uiHints:
|
|
174
|
+
category: reference
|
|
175
|
+
inverseLabel: Supports
|
|
176
|
+
- slug: refutes
|
|
177
|
+
displayName: Refutes
|
|
178
|
+
description: The source is evidence that the target is false. Kept as a first-class edge so a refuted component stays in the graph with its refutation attached, instead of being quietly deleted — the reason a road was not taken is worth more than the road.
|
|
179
|
+
isDirectional: true
|
|
180
|
+
uiHints:
|
|
181
|
+
category: reference
|
|
182
|
+
inverseLabel: Refuted By
|
|
183
|
+
|
|
184
|
+
entityLinks:
|
|
185
|
+
- sourceProfileSlug: offer
|
|
186
|
+
targetProfileSlug: convention
|
|
187
|
+
type: rests_on
|
|
188
|
+
label: Rests on
|
|
189
|
+
- sourceProfileSlug: offer
|
|
190
|
+
targetProfileSlug: audience
|
|
191
|
+
type: rests_on
|
|
192
|
+
label: Rests on
|
|
193
|
+
- sourceProfileSlug: projection
|
|
194
|
+
targetProfileSlug: offer
|
|
195
|
+
type: rests_on
|
|
196
|
+
label: Rests on
|
|
197
|
+
- sourceProfileSlug: decision
|
|
198
|
+
targetProfileSlug: convention
|
|
199
|
+
type: rests_on
|
|
200
|
+
label: Rests on
|
|
201
|
+
- sourceProfileSlug: decision
|
|
202
|
+
targetProfileSlug: projection
|
|
203
|
+
type: rests_on
|
|
204
|
+
label: Rests on
|
|
205
|
+
- sourceProfileSlug: convention
|
|
206
|
+
targetProfileSlug: offer
|
|
207
|
+
type: refutes
|
|
208
|
+
label: Refutes
|
|
209
|
+
- sourceProfileSlug: convention
|
|
210
|
+
targetProfileSlug: audience
|
|
211
|
+
type: refutes
|
|
212
|
+
label: Refutes
|
|
213
|
+
- sourceProfileSlug: convention
|
|
214
|
+
targetProfileSlug: question
|
|
215
|
+
type: rests_on
|
|
216
|
+
label: Rests on
|
|
217
|
+
|
|
218
|
+
views:
|
|
219
|
+
- name: Conventions
|
|
220
|
+
slug: business-model-conventions
|
|
221
|
+
type: table
|
|
222
|
+
scopeProfileSlug: convention
|
|
223
|
+
description: Every rule the model must satisfy, who imposes it, and whether anyone has actually verified it.
|
|
224
|
+
defaultView: true
|
|
225
|
+
config:
|
|
226
|
+
columns: [title, conventionType, issuer, bindingStatus, expiresAt]
|
|
227
|
+
- name: Unverified Conventions
|
|
228
|
+
slug: business-model-assumed-conventions
|
|
229
|
+
type: list
|
|
230
|
+
scopeProfileSlug: convention
|
|
231
|
+
description: Conventions still marked assumed or contested — the model rests on these and nobody has read the source text.
|
|
232
|
+
config:
|
|
233
|
+
filters:
|
|
234
|
+
- field: bindingStatus
|
|
235
|
+
operator: in
|
|
236
|
+
value: [assumed, contested]
|
|
237
|
+
- name: Projections
|
|
238
|
+
slug: business-model-projections
|
|
239
|
+
type: table
|
|
240
|
+
scopeProfileSlug: projection
|
|
241
|
+
description: Scenarios with their assumptions in the open, so a number can be argued with.
|
|
242
|
+
config:
|
|
243
|
+
columns: [title, scenario, horizon, revenueEstimate, costEstimate, confidence]
|
|
244
|
+
- name: Open Interrogation
|
|
245
|
+
slug: business-model-open-questions
|
|
246
|
+
type: kanban
|
|
247
|
+
scopeProfileSlug: question
|
|
248
|
+
groupBy: questionStatus
|
|
249
|
+
description: The nine component questions and everything they spawned, by status.
|
|
250
|
+
|
|
251
|
+
# ── Seeds: the interrogation, not fake data ────────────────────────────────
|
|
252
|
+
# A strategy template that seeds example companies teaches nothing and gets
|
|
253
|
+
# deleted. What is actually reusable is the QUESTION SET — the workspace should
|
|
254
|
+
# arrive with the model's nine interrogations already open, so the first session
|
|
255
|
+
# has somewhere to start and the kanban is not an empty box.
|
|
256
|
+
seedEntities:
|
|
257
|
+
- profileSlug: question
|
|
258
|
+
title: Who is the porteur, and what does the model rest on them for?
|
|
259
|
+
properties:
|
|
260
|
+
questionStatus: open
|
|
261
|
+
content: Name the person, then name the specific competence, network, capital, or legitimacy the model cannot survive without. If the answer is generic, the model has no porteur — it has a job posting.
|
|
262
|
+
- profileSlug: question
|
|
263
|
+
title: What does the porteur's situation forbid?
|
|
264
|
+
properties:
|
|
265
|
+
questionStatus: open
|
|
266
|
+
content: Runway, hours available, geography, non-compete, dependants, health. Constraints discovered in month four were knowable in week one.
|
|
267
|
+
- profileSlug: question
|
|
268
|
+
title: What is the offer, stated as the problem it ends?
|
|
269
|
+
properties:
|
|
270
|
+
questionStatus: open
|
|
271
|
+
content: Not a description of what is built — a statement of the situation a buyer is in before, and after. If the offer can only be described by its features, the buyer has not been found yet.
|
|
272
|
+
- profileSlug: question
|
|
273
|
+
title: Who exactly buys it, and who merely benefits from it?
|
|
274
|
+
properties:
|
|
275
|
+
questionStatus: open
|
|
276
|
+
content: Link to `audience` entities. Payer, user, and beneficiary are frequently three different parties, and a model that conflates them forecasts revenue from people who never pay.
|
|
277
|
+
- profileSlug: question
|
|
278
|
+
title: Which conventions bind this model, and which are still assumed?
|
|
279
|
+
properties:
|
|
280
|
+
questionStatus: open
|
|
281
|
+
content: Eligibility thresholds, professional duties, platform policies, fiscal regimes. Every convention starts `assumed`; the work is reading the source text and moving it to `verified`. An eligibility collision — where the segment with the most pull sits outside the scheme that would fund it — only becomes visible once the numbers are pulled.
|
|
282
|
+
- profileSlug: question
|
|
283
|
+
title: What does one unit of delivery actually cost to produce?
|
|
284
|
+
properties:
|
|
285
|
+
questionStatus: open
|
|
286
|
+
content: Time, tooling, subcontracting, the unbilled parts. Link the answer to a `projection` so the assumptions stay attached to the number.
|
|
287
|
+
- profileSlug: question
|
|
288
|
+
title: Where does the money come from before revenue does?
|
|
289
|
+
properties:
|
|
290
|
+
questionStatus: open
|
|
291
|
+
content: Attach `funder` roles to the companies or schemes involved, with their eligibility conditions. Subsidy is a credential and a discount; it is rarely a pipeline.
|
|
292
|
+
- profileSlug: question
|
|
293
|
+
title: What means — people, tools, licences, premises — must exist first?
|
|
294
|
+
properties:
|
|
295
|
+
questionStatus: open
|
|
296
|
+
content: The means rest on the money and the money rests on the offer. That circularity is the model, not a planning error; record it with `rests_on` and stop trying to linearise it.
|
|
297
|
+
- profileSlug: question
|
|
298
|
+
title: What observation would refute this model, and by when?
|
|
299
|
+
properties:
|
|
300
|
+
questionStatus: open
|
|
301
|
+
content: Set the falsifiable test now, while it can still be set honestly. A model with no refuting observation is a belief with a spreadsheet attached.
|
|
302
|
+
|
|
303
|
+
# The circularity, declared. These edges deliberately form cycles — that is the
|
|
304
|
+
# point of `rests_on`, and the reason this pack does not use `depends_on`.
|
|
305
|
+
suggestedRelations:
|
|
306
|
+
- sourceRef: What is the offer, stated as the problem it ends?
|
|
307
|
+
targetRef: Who exactly buys it, and who merely benefits from it?
|
|
308
|
+
type: rests_on
|
|
309
|
+
- sourceRef: What is the offer, stated as the problem it ends?
|
|
310
|
+
targetRef: What means — people, tools, licences, premises — must exist first?
|
|
311
|
+
type: rests_on
|
|
312
|
+
- sourceRef: What means — people, tools, licences, premises — must exist first?
|
|
313
|
+
targetRef: Where does the money come from before revenue does?
|
|
314
|
+
type: rests_on
|
|
315
|
+
- sourceRef: Where does the money come from before revenue does?
|
|
316
|
+
targetRef: What is the offer, stated as the problem it ends?
|
|
317
|
+
type: rests_on
|
|
318
|
+
- sourceRef: Where does the money come from before revenue does?
|
|
319
|
+
targetRef: Which conventions bind this model, and which are still assumed?
|
|
320
|
+
type: rests_on
|
|
321
|
+
- sourceRef: Who exactly buys it, and who merely benefits from it?
|
|
322
|
+
targetRef: Which conventions bind this model, and which are still assumed?
|
|
323
|
+
type: rests_on
|
|
324
|
+
- sourceRef: What does one unit of delivery actually cost to produce?
|
|
325
|
+
targetRef: What does the porteur's situation forbid?
|
|
326
|
+
type: rests_on
|
|
327
|
+
- sourceRef: What is the offer, stated as the problem it ends?
|
|
328
|
+
targetRef: Who is the porteur, and what does the model rest on them for?
|
|
329
|
+
type: rests_on
|
|
330
|
+
|
|
331
|
+
playbooks:
|
|
332
|
+
- name: Business Model Interrogation
|
|
333
|
+
description: Walk the nine components as questions, resolve each into a decision backed by evidence, and record the conventions and projections the model rests on — including the cycles.
|
|
334
|
+
goalTemplate: "Interrogate the business model for {venture} and record what it rests on. Steps: (1) Walk each of the nine seeded `question` entities in this workspace; where one is already answered, mark it and move on. (2) Resolve each into a `decision`, keeping the options that lost attached via `refutes` rather than deleting them. Where the evidence itself needs gathering, do that in the Research workspace and link back — do not restate sources here. (3) Every external rule discovered becomes a `convention` with its `issuer` and `sourceReference`; leave `bindingStatus` at `assumed` until the source text has actually been read, then `verified`. (4) Every number becomes a `projection` with its `assumptions` filled in — never a bare figure. (5) Wire the graph with `rests_on` — offer on audience, projection on offer, decision on convention. Do NOT try to make the graph acyclic: if the offer rests on the means and the means rest on the money and the money rests on the offer, record all three. (6) Attach `porteur` and `funder` roles to the real people and organisations instead of creating new entities for them. (7) Close by listing which conventions are still `assumed` and which projections carry `low` confidence — that list is the actual state of the model."
|
|
335
|
+
icon: layout-grid
|
|
336
|
+
|
|
337
|
+
actionPlacements:
|
|
338
|
+
- profileSlug: question
|
|
339
|
+
surface: entity-detail
|
|
340
|
+
kind: playbook
|
|
341
|
+
ref: Business Model Interrogation
|
|
342
|
+
label: Interrogate as model component
|
|
343
|
+
- profileSlug: convention
|
|
344
|
+
surface: entity-detail
|
|
345
|
+
kind: playbook
|
|
346
|
+
ref: Business Model Interrogation
|
|
347
|
+
label: Re-check what rests on this
|
|
348
|
+
|
|
349
|
+
integrations:
|
|
350
|
+
- templateKey: research-methods
|
|
351
|
+
- templateKey: exa
|
package/src/catalog.ts
CHANGED
|
@@ -83,6 +83,13 @@ export interface RemoteEntry {
|
|
|
83
83
|
* never "unknown-so-hide".
|
|
84
84
|
*/
|
|
85
85
|
isPrivate?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* The minimum subscription tier required to INSTALL this package (`solo` | `pro`
|
|
88
|
+
* | `team` | `enterprise`); `null`/absent ⇒ ungated. DISPLAY only here — the real
|
|
89
|
+
* install floor is the pod's `assertPackageTierAccess`. Kept a bare `string` so
|
|
90
|
+
* this pure/shared door stays enum-agnostic (like `category`).
|
|
91
|
+
*/
|
|
92
|
+
requiredTier?: string | null;
|
|
86
93
|
/**
|
|
87
94
|
* Lightweight computed summary of the row's sub-objects (cells), carried on the
|
|
88
95
|
* LIST projection so a surface can surface a matching sub-item without the full
|
|
@@ -149,6 +156,8 @@ export interface CpPackageRow {
|
|
|
149
156
|
icon?: string | null;
|
|
150
157
|
/** Present only on `/mine` rows. `false` ⇒ private. Browse rows omit it. */
|
|
151
158
|
isPublic?: boolean;
|
|
159
|
+
/** The package's `required_tier` gate (`solo`|`pro`|…); `null`/absent ⇒ ungated. */
|
|
160
|
+
requiredTier?: string | null;
|
|
152
161
|
/**
|
|
153
162
|
* Computed sub-object summary from `GET /api/packages` / `/mine` (query-time,
|
|
154
163
|
* not a stored column). Carries `key + name` per cell; never the cell code.
|
|
@@ -180,6 +189,7 @@ export function rowToRemoteEntry(
|
|
|
180
189
|
tags: row.tags ?? [],
|
|
181
190
|
icon: row.icon ?? null,
|
|
182
191
|
isPrivate,
|
|
192
|
+
requiredTier: row.requiredTier ?? null,
|
|
183
193
|
items: row.items,
|
|
184
194
|
// Both list routes strip `definition`; absence ⇒ bundle wins a slug conflict,
|
|
185
195
|
// and the install path fetches the full definition per-slug when needed.
|
|
@@ -202,6 +212,12 @@ interface CatalogEntryBase {
|
|
|
202
212
|
icon?: string | null;
|
|
203
213
|
/** `true` ⇒ not publicly listed (internal bundled template, or private CP row). */
|
|
204
214
|
isPrivate: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Minimum install tier (`solo`|`pro`|…); `null` ⇒ ungated. Bundled templates are
|
|
217
|
+
* always `null` (they ship in-client, never gated). DISPLAY only — a surface
|
|
218
|
+
* shows a "requires <tier>" badge; the real floor is the pod's install check.
|
|
219
|
+
*/
|
|
220
|
+
requiredTier?: string | null;
|
|
205
221
|
/**
|
|
206
222
|
* The entry's sub-object summary (cells). Always present (defaults to `[]`) so a
|
|
207
223
|
* consumer can match sub-items uniformly. Bundled workspace templates carry no
|
|
@@ -289,6 +305,8 @@ function bundledToEntry(tpl: WorkspaceYaml): BundledCatalogEntry {
|
|
|
289
305
|
tags: tpl.meta.tags ?? [],
|
|
290
306
|
icon: tpl.meta.icon,
|
|
291
307
|
isPrivate: tpl.meta.isPublic === false,
|
|
308
|
+
// Bundled templates ship in-client and are never tier-gated.
|
|
309
|
+
requiredTier: null,
|
|
292
310
|
// Bundled workspace templates carry no cells — be honest, not `undefined`.
|
|
293
311
|
items: [],
|
|
294
312
|
template: tpl,
|
|
@@ -306,6 +324,7 @@ function remoteToEntry(row: RemoteEntry): RemoteCatalogEntry {
|
|
|
306
324
|
tags: row.tags ?? [],
|
|
307
325
|
icon: row.icon ?? null,
|
|
308
326
|
isPrivate: row.isPrivate ?? false,
|
|
327
|
+
requiredTier: row.requiredTier ?? null,
|
|
309
328
|
items: row.items ?? [],
|
|
310
329
|
remote: row,
|
|
311
330
|
};
|