@synap-core/workspace-templates 0.30.0 → 0.31.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 +12 -4
- package/dist/{define-DqoItRiR.d.ts → define-DHDi8C33.d.ts} +19 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +428 -239
- package/dist/index.js.map +1 -1
- package/dist/wire.d.ts +1 -1
- package/package.json +1 -1
- package/src/brand-library.yaml +0 -6
- package/src/builder-dev-fold.template.test.ts +2 -3
- package/src/builder-dev-playbooks.template.test.ts +7 -5
- package/src/builder.yaml +60 -42
- package/src/business-model.yaml +20 -1
- package/src/civic-france.yaml +9 -0
- package/src/content-creation.yaml +1 -1
- package/src/content-os.yaml +22 -18
- package/src/crm-absorbs-outreach.template.test.ts +1 -1
- package/src/crm-build-plan.template.test.ts +1 -1
- package/src/crm.yaml +5 -14
- package/src/decisions-wave.template.test.ts +7 -5
- package/src/define.primary-surface.test.ts +9 -0
- package/src/define.ts +4 -1
- package/src/finance.yaml +10 -6
- package/src/foundation.yaml +1 -2
- package/src/hr.yaml +3 -2
- package/src/intent.test.ts +43 -2
- package/src/internal-runbook.yaml +0 -1
- package/src/kanban-grouping.test.ts +72 -0
- package/src/legal.yaml +4 -4
- package/src/lifecycle-coverage.test.ts +7 -64
- package/src/lifecycle-declarations.test.ts +70 -74
- package/src/marketing.yaml +4 -3
- package/src/meetings-knowledge.yaml +1 -2
- package/src/operations.yaml +13 -9
- package/src/process-contract.validate.test.ts +40 -55
- package/src/project-management.yaml +2 -2
- package/src/publish-official-lib.test.ts +3 -2
- package/src/research-base.yaml +12 -5
- package/src/research-decision.yaml +18 -2
- package/src/ship-a-change.template.test.ts +3 -2
- package/src/social.yaml +4 -1
- package/src/start.test.ts +22 -0
- package/src/templates.ts +324 -183
- package/src/types.ts +28 -18
- package/src/validate.ts +143 -77
package/src/operations.yaml
CHANGED
|
@@ -18,7 +18,6 @@ meta:
|
|
|
18
18
|
label: Client delivery
|
|
19
19
|
promise: Every client engagement and where it stands.
|
|
20
20
|
suggestFor: [consultant]
|
|
21
|
-
profile: engagement
|
|
22
21
|
views: [engagements]
|
|
23
22
|
example:
|
|
24
23
|
- { title: "Website redesign for Maison Dupont", values: { engagement-status: active, engagement-value: 18000 } }
|
|
@@ -35,6 +34,8 @@ meta:
|
|
|
35
34
|
# What this space PRODUCES (homes): the purpose word, then its kinds/roles.
|
|
36
35
|
intent:
|
|
37
36
|
label: Delivery
|
|
37
|
+
# The ONE primary output (One model decision 3): a delivered engagement (Deliver a client engagement).
|
|
38
|
+
primary: engagement
|
|
38
39
|
produces:
|
|
39
40
|
- profile: engagement
|
|
40
41
|
- profile: deliverable
|
|
@@ -304,19 +305,28 @@ views:
|
|
|
304
305
|
slug: engagements
|
|
305
306
|
type: kanban
|
|
306
307
|
scopeProfileSlug: engagement
|
|
307
|
-
|
|
308
|
+
config:
|
|
309
|
+
groupByField: engagement-status
|
|
308
310
|
description: "Client engagements on a board by status."
|
|
309
311
|
- name: Deliverables
|
|
310
312
|
slug: deliverables
|
|
311
313
|
type: kanban
|
|
312
314
|
scopeProfileSlug: deliverable
|
|
313
|
-
|
|
315
|
+
config:
|
|
316
|
+
groupByField: deliverable-status
|
|
314
317
|
description: "Deliverables on a board by status."
|
|
315
318
|
- name: Contracts
|
|
316
319
|
slug: contracts
|
|
317
320
|
type: table
|
|
318
321
|
scopeProfileSlug: contract
|
|
319
322
|
description: "Contracts with their status, value, and term."
|
|
323
|
+
- name: Contract Board
|
|
324
|
+
slug: contract-board
|
|
325
|
+
type: kanban
|
|
326
|
+
scopeProfileSlug: contract
|
|
327
|
+
description: "Contracts by status — the flow board of Get a contract signed."
|
|
328
|
+
config:
|
|
329
|
+
groupByField: contract-status
|
|
320
330
|
- name: Client Briefings
|
|
321
331
|
slug: client-briefings
|
|
322
332
|
type: list
|
|
@@ -938,8 +948,6 @@ playbooks:
|
|
|
938
948
|
call.
|
|
939
949
|
subjectProfile:
|
|
940
950
|
profileSlug: engagement
|
|
941
|
-
statusProperty: engagement-status
|
|
942
|
-
humanOnlyStatuses: [on-hold, cancelled]
|
|
943
951
|
activators:
|
|
944
952
|
- { on: created, mode: propose }
|
|
945
953
|
params:
|
|
@@ -1018,8 +1026,6 @@ playbooks:
|
|
|
1018
1026
|
close bills it.
|
|
1019
1027
|
subjectProfile:
|
|
1020
1028
|
profileSlug: deliverable
|
|
1021
|
-
statusProperty: deliverable-status
|
|
1022
|
-
humanOnlyStatuses: [not-started, cancelled]
|
|
1023
1029
|
activators:
|
|
1024
1030
|
- { on: enters_status, status: in-progress, mode: propose }
|
|
1025
1031
|
params:
|
|
@@ -1095,8 +1101,6 @@ playbooks:
|
|
|
1095
1101
|
document. Expiry and termination are a person's call.
|
|
1096
1102
|
subjectProfile:
|
|
1097
1103
|
profileSlug: contract
|
|
1098
|
-
statusProperty: contract-status
|
|
1099
|
-
humanOnlyStatuses: [expired, terminated]
|
|
1100
1104
|
activators:
|
|
1101
1105
|
- { on: created, mode: propose }
|
|
1102
1106
|
params:
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* The process contract a playbook declares is checked at authoring time
|
|
3
3
|
* (`checkProcessContract` in validate.ts), because every one of these mistakes
|
|
4
4
|
* is STORED by the pod and then silently never fires:
|
|
5
|
-
* - a `statusProperty`
|
|
6
|
-
*
|
|
7
|
-
* - a `subjectStatus` / activator `status`
|
|
8
|
-
*
|
|
9
|
-
* value no entity ever enters;
|
|
5
|
+
* - a `statusProperty` / `onEnd` / `humanOnlyStatuses` → retired (One model
|
|
6
|
+
* decision 4): the process follows the kind's declared lifecycle;
|
|
7
|
+
* - a `subjectStatus` / activator `status` that is not an option of the
|
|
8
|
+
* kind's declared lifecycle (or a kind with none) → a write the select
|
|
9
|
+
* refuses, or a rule on a value no entity ever enters;
|
|
10
10
|
* - an `onFail.toStage` naming no stage → a rejected gate with nowhere to go;
|
|
11
11
|
* - a `relationToSubject` naming no relation → an edge type nothing renders.
|
|
12
12
|
*
|
|
@@ -24,21 +24,17 @@ import type { WorkspaceYaml } from "./types.js";
|
|
|
24
24
|
import { readYamlTemplates } from "./generate.mjs";
|
|
25
25
|
|
|
26
26
|
const PROCESS_RULES = new Set([
|
|
27
|
-
"playbook-
|
|
28
|
-
"playbook-status-property-unknown",
|
|
29
|
-
"playbook-status-property-not-select",
|
|
27
|
+
"playbook-field-retired",
|
|
30
28
|
"stage-subject-status-unknown",
|
|
31
|
-
"stage-subject-status-unknown-no-
|
|
29
|
+
"stage-subject-status-unknown-no-lifecycle",
|
|
32
30
|
"stage-onfail-unknown",
|
|
33
31
|
"activator-on-invalid",
|
|
34
32
|
"activator-mode-invalid",
|
|
35
33
|
"activator-no-subject",
|
|
36
34
|
"activator-status-required",
|
|
37
35
|
"activator-status-unknown",
|
|
38
|
-
"activator-status-unknown-no-
|
|
36
|
+
"activator-status-unknown-no-lifecycle",
|
|
39
37
|
"activator-status-unexpected",
|
|
40
|
-
"human-only-status-unknown",
|
|
41
|
-
"human-only-status-unknown-no-property",
|
|
42
38
|
"output-relation-no-subject",
|
|
43
39
|
"output-relation-unknown",
|
|
44
40
|
"output-optional-not-boolean",
|
|
@@ -46,10 +42,19 @@ const PROCESS_RULES = new Set([
|
|
|
46
42
|
]);
|
|
47
43
|
|
|
48
44
|
/**
|
|
49
|
-
* `owner` OWNS the shared kind `widget` (with its lifecycle
|
|
45
|
+
* `owner` OWNS the shared kind `widget` (with its declared lifecycle). `proc`
|
|
50
46
|
* requires it and only re-declares `widget` with no properties — so resolving
|
|
51
|
-
* `widget-stage` proves the
|
|
47
|
+
* `widget-stage` proves the lifecycle is found ACROSS the corpus.
|
|
52
48
|
*/
|
|
49
|
+
const WIDGET_LIFECYCLE = {
|
|
50
|
+
property: "widget-stage",
|
|
51
|
+
options: [
|
|
52
|
+
{ key: "New", category: "open" },
|
|
53
|
+
{ key: "Built", category: "active" },
|
|
54
|
+
{ key: "Shipped", category: "done" },
|
|
55
|
+
{ key: "Archived", category: "lost", humanOnly: true },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
53
58
|
const owner = {
|
|
54
59
|
meta: { slug: "owner", name: "Owner", description: "d", icon: "x", color: "gray" },
|
|
55
60
|
workspace: { name: "Owner", description: "d" },
|
|
@@ -58,6 +63,7 @@ const owner = {
|
|
|
58
63
|
slug: "widget",
|
|
59
64
|
displayName: "Widget",
|
|
60
65
|
scope: "shared",
|
|
66
|
+
lifecycle: WIDGET_LIFECYCLE,
|
|
61
67
|
properties: [
|
|
62
68
|
{ slug: "widget-stage", label: "Stage", valueType: "string", inputType: "select", enumValues: ["New", "Built", "Shipped", "Archived"] },
|
|
63
69
|
{ slug: "widget-notes", label: "Notes", valueType: "string", inputType: "text" },
|
|
@@ -72,8 +78,7 @@ function proc(mutate: (pb: Record<string, any>) => void = () => {}): WorkspaceYa
|
|
|
72
78
|
name: "Build Widget",
|
|
73
79
|
goalTemplate: "Build {widget}",
|
|
74
80
|
params: [{ name: "widget", type: "entity", required: true }],
|
|
75
|
-
subjectProfile: { profileSlug: "widget"
|
|
76
|
-
humanOnlyStatuses: ["New", "Archived"],
|
|
81
|
+
subjectProfile: { profileSlug: "widget" },
|
|
77
82
|
activators: [{ on: "enters_status", status: "Built", mode: "propose" }],
|
|
78
83
|
expectedOutputs: [{ kind: "widget", label: "Spin-off", relationToSubject: "spawned_from" }],
|
|
79
84
|
stages: [
|
|
@@ -112,26 +117,16 @@ describe("process contract — the clean fixture is clean", () => {
|
|
|
112
117
|
});
|
|
113
118
|
|
|
114
119
|
describe("process contract — each rule fires on its own fixture", () => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
// Negative control: each assertion goes red with the `playbook-field-retired`
|
|
121
|
+
// loop removed from checkProcessContract.
|
|
122
|
+
it("each retired field is refused (statusProperty, onEnd, humanOnlyStatuses)", () => {
|
|
123
|
+
expect(rulesOf(proc((pb) => (pb.subjectProfile.statusProperty = "widget-stage")))).toEqual([
|
|
124
|
+
"playbook-field-retired",
|
|
118
125
|
]);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
expect(
|
|
123
|
-
rulesOf(
|
|
124
|
-
proc((pb) => {
|
|
125
|
-
pb.subjectProfile.statusProperty = "widget-notes";
|
|
126
|
-
// Without a resolvable vocabulary the statuses are not re-reported.
|
|
127
|
-
})
|
|
128
|
-
)
|
|
129
|
-
).toEqual(["playbook-status-property-not-select"]);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("statusProperty with no subject kind", () => {
|
|
133
|
-
const rules = rulesOf(proc((pb) => (pb.subjectProfile = { statusProperty: "widget-stage" })));
|
|
134
|
-
expect(rules).toContain("playbook-status-property-no-subject");
|
|
126
|
+
expect(rulesOf(proc((pb) => (pb.subjectProfile.onEnd = { cancelled: "New" })))).toEqual([
|
|
127
|
+
"playbook-field-retired",
|
|
128
|
+
]);
|
|
129
|
+
expect(rulesOf(proc((pb) => (pb.humanOnlyStatuses = ["New"])))).toEqual(["playbook-field-retired"]);
|
|
135
130
|
});
|
|
136
131
|
|
|
137
132
|
it("a stage subjectStatus outside the enum", () => {
|
|
@@ -140,17 +135,14 @@ describe("process contract — each rule fires on its own fixture", () => {
|
|
|
140
135
|
]);
|
|
141
136
|
});
|
|
142
137
|
|
|
143
|
-
it("a stage subjectStatus
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
expect(rules).toEqual([
|
|
152
|
-
"stage-subject-status-unknown-no-property",
|
|
153
|
-
"stage-subject-status-unknown-no-property",
|
|
138
|
+
it("a stage subjectStatus on a kind that declares no lifecycle", () => {
|
|
139
|
+
const noLifecycle = structuredClone(owner) as unknown as { profiles: Array<Record<string, unknown>> };
|
|
140
|
+
delete noLifecycle.profiles[0]!.lifecycle;
|
|
141
|
+
const tpl = proc((pb) => (pb.activators = [{ on: "created", mode: "propose" }]));
|
|
142
|
+
const { errors } = validateTemplate(tpl, { corpus: [noLifecycle as unknown as WorkspaceYaml, tpl] });
|
|
143
|
+
expect(errors.filter((e) => PROCESS_RULES.has(e.rule)).map((e) => e.rule)).toEqual([
|
|
144
|
+
"stage-subject-status-unknown-no-lifecycle",
|
|
145
|
+
"stage-subject-status-unknown-no-lifecycle",
|
|
154
146
|
]);
|
|
155
147
|
});
|
|
156
148
|
|
|
@@ -218,7 +210,6 @@ describe("process contract — each rule fires on its own fixture", () => {
|
|
|
218
210
|
proc((pb) => {
|
|
219
211
|
delete pb.subjectProfile;
|
|
220
212
|
delete pb.stages;
|
|
221
|
-
delete pb.humanOnlyStatuses;
|
|
222
213
|
delete pb.expectedOutputs;
|
|
223
214
|
pb.activators = [{ on: "created", mode: "propose" }];
|
|
224
215
|
})
|
|
@@ -226,12 +217,6 @@ describe("process contract — each rule fires on its own fixture", () => {
|
|
|
226
217
|
expect(rules).toEqual(["activator-no-subject"]);
|
|
227
218
|
});
|
|
228
219
|
|
|
229
|
-
it("a humanOnlyStatuses value outside the enum", () => {
|
|
230
|
-
expect(rulesOf(proc((pb) => (pb.humanOnlyStatuses = ["New", "Retired"])))).toEqual([
|
|
231
|
-
"human-only-status-unknown",
|
|
232
|
-
]);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
220
|
it("a playbook-level relationToSubject the corpus does not define", () => {
|
|
236
221
|
expect(rulesOf(proc((pb) => (pb.expectedOutputs[0].relationToSubject = "spawned_by")))).toEqual([
|
|
237
222
|
"output-relation-unknown",
|
|
@@ -277,7 +262,6 @@ describe("process contract — each rule fires on its own fixture", () => {
|
|
|
277
262
|
const strip = (pb: Record<string, any>) => {
|
|
278
263
|
delete pb.subjectProfile;
|
|
279
264
|
delete pb.stages;
|
|
280
|
-
delete pb.humanOnlyStatuses;
|
|
281
265
|
delete pb.activators;
|
|
282
266
|
};
|
|
283
267
|
expect(rulesOf(proc(strip))).toEqual(["output-relation-no-subject"]);
|
|
@@ -340,7 +324,8 @@ describe("process contract — the shipped corpus", () => {
|
|
|
340
324
|
const corpus = readYamlTemplates().map((t) => t.data as WorkspaceYaml);
|
|
341
325
|
const contentOs = corpus.find((t) => t.meta.slug === "content-os")!;
|
|
342
326
|
const produce = contentOs.playbooks!.find((p) => p.name === "Produce Content")!;
|
|
343
|
-
expect(produce.subjectProfile
|
|
327
|
+
expect(produce.subjectProfile).toEqual({ profileSlug: "post" });
|
|
328
|
+
expect(contentOs.profiles.find((p) => p.slug === "post")?.lifecycle?.property).toBe("post-status");
|
|
344
329
|
expect(produce.stages!.filter((s) => s.subjectStatus).length).toBe(6);
|
|
345
330
|
const errs = corpus.flatMap((t) =>
|
|
346
331
|
validateTemplate(t, { corpus })
|
|
@@ -17,6 +17,8 @@ meta:
|
|
|
17
17
|
# What this space PRODUCES (homes): the purpose word, then its kinds/roles.
|
|
18
18
|
intent:
|
|
19
19
|
label: Planning
|
|
20
|
+
# The ONE primary output (One model decision 3): a finished sprint, its one produced kind.
|
|
21
|
+
primary: sprint
|
|
20
22
|
produces:
|
|
21
23
|
- profile: sprint
|
|
22
24
|
|
|
@@ -192,7 +194,6 @@ views:
|
|
|
192
194
|
slug: sprint-board
|
|
193
195
|
type: kanban
|
|
194
196
|
scopeProfileSlug: task
|
|
195
|
-
groupBy: task-status
|
|
196
197
|
description: "Active sprint tasks in Backlog / Todo / In Progress / Review / Done lanes"
|
|
197
198
|
config:
|
|
198
199
|
groupByField: task-status
|
|
@@ -276,7 +277,6 @@ views:
|
|
|
276
277
|
slug: retrospective-board
|
|
277
278
|
type: kanban
|
|
278
279
|
scopeProfileSlug: sprint
|
|
279
|
-
groupBy: sprint-status
|
|
280
280
|
description: "Sprint retrospectives — completed sprints with retrospective notes attached as documents"
|
|
281
281
|
config:
|
|
282
282
|
groupByField: sprint-status
|
|
@@ -77,7 +77,7 @@ describe("plan → execute — the wire", () => {
|
|
|
77
77
|
.playbooks as any[];
|
|
78
78
|
const produce = yamlPlaybooks.find((p) => p.name === "Produce Content");
|
|
79
79
|
// Non-vacuity: the YAML really declares the contract fields compared below.
|
|
80
|
-
expect(produce.subjectProfile
|
|
80
|
+
expect(produce.subjectProfile).toEqual({ profileSlug: "post" });
|
|
81
81
|
expect(produce.activators).toEqual([{ on: "enters_status", status: "Briefed", mode: "propose" }]);
|
|
82
82
|
expect(produce.stages.find((s: any) => s.key === "review").onFail).toEqual({ toStage: "draft" });
|
|
83
83
|
expect(
|
|
@@ -98,7 +98,8 @@ describe("plan → execute — the wire", () => {
|
|
|
98
98
|
"Scheduled",
|
|
99
99
|
"Published",
|
|
100
100
|
]);
|
|
101
|
-
|
|
101
|
+
// Retired from the catalog (One model decision 4): nothing to carry.
|
|
102
|
+
expect(postedProduce.humanOnlyStatuses).toBeUndefined();
|
|
102
103
|
});
|
|
103
104
|
|
|
104
105
|
it("reports a stamp the CP did not store", async () => {
|
package/src/research-base.yaml
CHANGED
|
@@ -29,6 +29,8 @@ meta:
|
|
|
29
29
|
# What this space PRODUCES (homes): the purpose word, then its kinds/roles.
|
|
30
30
|
intent:
|
|
31
31
|
label: Research
|
|
32
|
+
# The ONE primary output (One model decision 3): a finding is what research yields (questions are its input).
|
|
33
|
+
primary: research-finding
|
|
32
34
|
produces:
|
|
33
35
|
- profile: hypothesis
|
|
34
36
|
- profile: research-finding
|
|
@@ -60,6 +62,15 @@ profiles:
|
|
|
60
62
|
icon: help-circle
|
|
61
63
|
color: blue
|
|
62
64
|
description: An open question driving an investigation — the thing research answers.
|
|
65
|
+
# The pod seeds this lifecycle on every pod (ensure-system-lifecycles.ts);
|
|
66
|
+
# declared here to match the seed exactly, so a playbook can follow it.
|
|
67
|
+
lifecycle:
|
|
68
|
+
property: questionStatus
|
|
69
|
+
options:
|
|
70
|
+
- { key: open, category: open }
|
|
71
|
+
- { key: exploring, category: active }
|
|
72
|
+
- { key: answered, category: done }
|
|
73
|
+
- { key: abandoned, category: lost }
|
|
63
74
|
properties:
|
|
64
75
|
- slug: questionStatus
|
|
65
76
|
label: Status
|
|
@@ -280,10 +291,9 @@ views:
|
|
|
280
291
|
slug: research-hypotheses
|
|
281
292
|
type: kanban
|
|
282
293
|
scopeProfileSlug: hypothesis
|
|
283
|
-
groupBy: stance
|
|
284
294
|
description: Candidate theories, grouped by where the evidence has left them.
|
|
285
295
|
config:
|
|
286
|
-
|
|
296
|
+
groupByField: stance
|
|
287
297
|
- name: Findings
|
|
288
298
|
slug: research-findings
|
|
289
299
|
type: table
|
|
@@ -420,8 +430,6 @@ playbooks:
|
|
|
420
430
|
proposal-gated and reviewable.
|
|
421
431
|
subjectProfile:
|
|
422
432
|
profileSlug: question
|
|
423
|
-
statusProperty: questionStatus
|
|
424
|
-
humanOnlyStatuses: [abandoned]
|
|
425
433
|
activators:
|
|
426
434
|
- { on: created, mode: propose }
|
|
427
435
|
params:
|
|
@@ -487,7 +495,6 @@ playbooks:
|
|
|
487
495
|
support.
|
|
488
496
|
subjectProfile:
|
|
489
497
|
profileSlug: research-finding
|
|
490
|
-
statusProperty: status
|
|
491
498
|
activators:
|
|
492
499
|
- { on: enters_status, status: validated, mode: propose }
|
|
493
500
|
params:
|
|
@@ -45,6 +45,15 @@ profiles:
|
|
|
45
45
|
# Pod-wide `question` is a first-class KIND (Decision 1 — the manifest retired
|
|
46
46
|
# w4.convert.question to a keep no-op). Declared as a plain kind to match the
|
|
47
47
|
# seed + research-base and reuse the existing pod-wide row.
|
|
48
|
+
# The pod seeds this lifecycle on every pod (ensure-system-lifecycles.ts);
|
|
49
|
+
# declared here to match the seed exactly, so a playbook can follow it.
|
|
50
|
+
lifecycle:
|
|
51
|
+
property: questionStatus
|
|
52
|
+
options:
|
|
53
|
+
- { key: open, category: open }
|
|
54
|
+
- { key: exploring, category: active }
|
|
55
|
+
- { key: answered, category: done }
|
|
56
|
+
- { key: abandoned, category: lost }
|
|
48
57
|
properties:
|
|
49
58
|
- slug: questionStatus
|
|
50
59
|
label: Status
|
|
@@ -83,6 +92,15 @@ profiles:
|
|
|
83
92
|
# Pod-wide `decision` is a first-class KIND (Decision 1 — w4.convert.decision
|
|
84
93
|
# retired to a keep no-op). Declared as a plain kind to match the seed; the
|
|
85
94
|
# `resolves` / `considered_in` links resolve against the reused pod-wide row.
|
|
95
|
+
# The pod seeds this lifecycle on every pod (ensure-system-lifecycles.ts);
|
|
96
|
+
# declared here to match the seed exactly, so a playbook can follow it.
|
|
97
|
+
lifecycle:
|
|
98
|
+
property: decisionStatus
|
|
99
|
+
options:
|
|
100
|
+
- { key: proposed, category: open }
|
|
101
|
+
- { key: accepted, category: done }
|
|
102
|
+
- { key: superseded, category: lost }
|
|
103
|
+
- { key: rejected, category: lost }
|
|
86
104
|
properties:
|
|
87
105
|
- slug: summary
|
|
88
106
|
label: Summary
|
|
@@ -168,8 +186,6 @@ playbooks:
|
|
|
168
186
|
superseded.
|
|
169
187
|
subjectProfile:
|
|
170
188
|
profileSlug: decision
|
|
171
|
-
statusProperty: decisionStatus
|
|
172
|
-
humanOnlyStatuses: [superseded, rejected]
|
|
173
189
|
activators:
|
|
174
190
|
- { on: created, mode: propose }
|
|
175
191
|
params:
|
|
@@ -67,8 +67,9 @@ const outputs = (type: string) =>
|
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
describe("Ship a change — the process follows the feature's lifecycle", () => {
|
|
70
|
-
it("
|
|
71
|
-
expect(pb.subjectProfile.statusProperty).
|
|
70
|
+
it("follows the feature's lifecycle (names no statusProperty), and every status it writes or reads is an option of it", () => {
|
|
71
|
+
expect(pb.subjectProfile.statusProperty).toBeUndefined();
|
|
72
|
+
expect(pb.subjectProfile.profileSlug).toBe("devplane_feature");
|
|
72
73
|
const options = new Set(feature.lifecycle.options.map((o) => o.key));
|
|
73
74
|
const used = [
|
|
74
75
|
...pb.stages.map((s) => s.subjectStatus).filter(Boolean),
|
package/src/social.yaml
CHANGED
|
@@ -16,6 +16,8 @@ meta:
|
|
|
16
16
|
# What this space PRODUCES (homes): the purpose word, then its kinds/roles.
|
|
17
17
|
intent:
|
|
18
18
|
label: Distribution
|
|
19
|
+
# The ONE primary output (One model decision 3): a published post, its one produced kind.
|
|
20
|
+
primary: post
|
|
19
21
|
produces:
|
|
20
22
|
- profile: post
|
|
21
23
|
|
|
@@ -297,7 +299,8 @@ views:
|
|
|
297
299
|
slug: content-calendar
|
|
298
300
|
type: kanban
|
|
299
301
|
scopeProfileSlug: post
|
|
300
|
-
|
|
302
|
+
config:
|
|
303
|
+
groupByField: post-status
|
|
301
304
|
description: "Posts on a board by status — idea to published."
|
|
302
305
|
- name: Platforms
|
|
303
306
|
slug: platforms
|
package/src/start.test.ts
CHANGED
|
@@ -130,6 +130,28 @@ describe("meta.start — validator rules", () => {
|
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
describe("meta.start — the kind defaults to the primary output (one source)", () => {
|
|
134
|
+
const withPrimary = (start: Partial<TemplateStart>) => {
|
|
135
|
+
const t = make("t", start);
|
|
136
|
+
t.meta.intent = { label: "Sales", primary: "deal", produces: [{ profile: "deal" }] };
|
|
137
|
+
return t;
|
|
138
|
+
};
|
|
139
|
+
// Negative control: goes red with the start-profile-* block removed from checkStart.
|
|
140
|
+
it("an omitted profile reads meta.intent.primary, and the wire carries it", () => {
|
|
141
|
+
const t = withPrimary({ profile: undefined });
|
|
142
|
+
expect(startErrors(t)).toEqual([]);
|
|
143
|
+
expect(toPackageDefinition(t).start?.profile).toBe("deal");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("start-profile-redundant: naming the primary again is a second copy", () => {
|
|
147
|
+
expect(startErrors(withPrimary({ profile: "deal" }))).toEqual(["start-profile-redundant"]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("start-profile-missing: no profile and no primary to default to", () => {
|
|
151
|
+
expect(startErrors(make("t", { profile: undefined }))).toEqual(["start-profile-missing"]);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
133
155
|
describe("meta.start — package door", () => {
|
|
134
156
|
it("carries a top-level start, never in _meta", () => {
|
|
135
157
|
const def = toPackageDefinition(make("t"));
|