@synap-core/workspace-templates 0.12.0 → 0.23.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.
Files changed (245) hide show
  1. package/README.md +483 -0
  2. package/automations/bookmark-enrichment.automation.json +321 -0
  3. package/automations/github-pr-events.automation.json +505 -0
  4. package/automations/mail-feed.automation.json +86 -0
  5. package/automations/ship-a-change-checks-green.automation.json +106 -0
  6. package/automations/ship-a-change-checks-red.automation.json +106 -0
  7. package/automations/ship-a-change-deployed.automation.json +56 -0
  8. package/automations/ship-a-change-pr-merged.automation.json +110 -0
  9. package/automations/ship-a-change-pr-opened.automation.json +104 -0
  10. package/automations/url-bookmark-capture.automation.json +132 -0
  11. package/capabilities/agency-skills.capability.json +157 -0
  12. package/capabilities/apify.capability.json +265 -0
  13. package/capabilities/apollo.capability.json +120 -0
  14. package/capabilities/arch-backend.capability.json +97 -0
  15. package/capabilities/arch-client-intelligence.capability.json +401 -0
  16. package/capabilities/backblaze-b2.capability.json +205 -0
  17. package/capabilities/cal-com.capability.json +266 -0
  18. package/capabilities/canva.capability.json +274 -0
  19. package/capabilities/claude-code-routines.capability.json +172 -0
  20. package/capabilities/claude-managed-agents.capability.json +215 -0
  21. package/capabilities/client-comms-report.capability.json +246 -0
  22. package/capabilities/content-idea-to-production.capability.json +29 -0
  23. package/capabilities/datagouv.capability.json +315 -0
  24. package/capabilities/discord-bot.capability.json +157 -0
  25. package/capabilities/dropbox.capability.json +281 -0
  26. package/capabilities/exa.capability.json +109 -0
  27. package/capabilities/fal-ai.capability.json +83 -0
  28. package/capabilities/fal-media-lab.capability.json +158 -0
  29. package/capabilities/fireflies.capability.json +191 -0
  30. package/capabilities/freellmapi.capability.json +114 -0
  31. package/capabilities/generic-apikey.capability.json +92 -0
  32. package/capabilities/github-merge.capability.json +99 -0
  33. package/capabilities/github-read.capability.json +325 -0
  34. package/capabilities/governance-tighten-recommender.capability.json +81 -0
  35. package/capabilities/jev.capability.json +226 -0
  36. package/capabilities/lead-conversion-enrollment.capability.json +246 -0
  37. package/capabilities/mail-feed.capability.json +277 -0
  38. package/capabilities/mailgun.capability.json +52 -0
  39. package/capabilities/nango-google.capability.json +639 -0
  40. package/capabilities/notion.capability.json +417 -0
  41. package/capabilities/omniroute.capability.json +137 -0
  42. package/capabilities/posthog-analytics.capability.json +258 -0
  43. package/capabilities/postiz.capability.json +467 -0
  44. package/capabilities/proactive-digest.capability.json +190 -0
  45. package/capabilities/proton.capability.json +288 -0
  46. package/capabilities/replicate-rvm.capability.json +97 -0
  47. package/capabilities/research-methods.capability.json +57 -0
  48. package/capabilities/shopify.capability.json +395 -0
  49. package/capabilities/slack.capability.json +369 -0
  50. package/capabilities/stripe.capability.json +420 -0
  51. package/capabilities/telegram-bridge.capability.json +56 -0
  52. package/capabilities/treg.capability.json +102 -0
  53. package/capabilities/unipile-linkedin.capability.json +471 -0
  54. package/capabilities/vercel.capability.json +424 -0
  55. package/dist/define-CPS0n8BV.d.ts +1331 -0
  56. package/dist/index.d.ts +174 -900
  57. package/dist/index.js +26148 -23931
  58. package/dist/index.js.map +1 -1
  59. package/dist/wire.d.ts +222 -0
  60. package/dist/wire.js +77 -0
  61. package/dist/wire.js.map +1 -0
  62. package/package.json +27 -4
  63. package/packs/bookmark-views-pack.json +33 -0
  64. package/packs/crm-pack.json +76 -0
  65. package/packs/customer-profile.json +28 -0
  66. package/packs/deep-research.playbook.json +30 -0
  67. package/packs/event-views-pack.json +33 -0
  68. package/packs/habit-profile.json +27 -0
  69. package/packs/hr-pack.json +54 -0
  70. package/packs/invoice-profile.json +28 -0
  71. package/packs/knowledge-base-pack.json +56 -0
  72. package/packs/meeting-profile.json +29 -0
  73. package/packs/product-profile.json +30 -0
  74. package/packs/project-management-pack.json +49 -0
  75. package/packs/recipe-profile.json +29 -0
  76. package/packs/task-views-pack.json +39 -0
  77. package/retired.yaml +136 -0
  78. package/skills/app-factory-build-slice/SKILL.md +70 -0
  79. package/skills/app-factory-build-slice.catalog.json +17 -0
  80. package/skills/app-factory-design-canvas/SKILL.md +96 -0
  81. package/skills/app-factory-design-canvas.catalog.json +17 -0
  82. package/skills/app-factory-design-review/SKILL.md +99 -0
  83. package/skills/app-factory-design-review.catalog.json +18 -0
  84. package/skills/app-factory-research/SKILL.md +123 -0
  85. package/skills/app-factory-research.catalog.json +17 -0
  86. package/skills/bookmark-enrichment/SKILL.md +40 -0
  87. package/skills/carousel-format/carousel-format.skill.json +18 -0
  88. package/skills/collage-broll/collage-broll.skill.json +20 -0
  89. package/skills/content-taste/content-taste.skill.json +18 -0
  90. package/skills/creative-director/creative-director.skill.json +18 -0
  91. package/skills/design-critique/SKILL.md +135 -0
  92. package/skills/design-critique.catalog.json +12 -0
  93. package/skills/designing-with-real-components/SKILL.md +140 -0
  94. package/skills/designing-with-real-components.catalog.json +12 -0
  95. package/skills/edit-loop/edit-loop.skill.json +25 -0
  96. package/skills/facecam-edit/facecam-edit.skill.json +24 -0
  97. package/skills/mapping-the-server/SKILL.md +54 -0
  98. package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
  99. package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
  100. package/skills/open-design-edit/open-design-edit.skill.json +24 -0
  101. package/skills/reading-client-progress/SKILL.md +65 -0
  102. package/skills/routing-to-channels/SKILL.md +54 -0
  103. package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
  104. package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
  105. package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
  106. package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
  107. package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
  108. package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
  109. package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
  110. package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
  111. package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
  112. package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
  113. package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
  114. package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
  115. package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
  116. package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
  117. package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
  118. package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
  119. package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
  120. package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
  121. package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
  122. package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
  123. package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
  124. package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
  125. package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
  126. package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
  127. package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
  128. package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
  129. package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
  130. package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
  131. package/skills/video-direction/video-direction.skill.json +20 -0
  132. package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
  133. package/skills/video-toolbox/video-toolbox.skill.json +20 -0
  134. package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
  135. package/src/agency-os.yaml +122 -0
  136. package/src/agent-os-skill-slugs.test.ts +65 -0
  137. package/src/apify-social-reader.test.ts +317 -0
  138. package/src/base.yaml +85 -39
  139. package/src/blockchain-ecosystem.yaml +1 -0
  140. package/src/brand-library.yaml +525 -14
  141. package/src/builder-dev-fold.template.test.ts +113 -0
  142. package/src/builder.yaml +779 -91
  143. package/src/business-model-catalog-description.test.ts +47 -0
  144. package/src/business-model-grp.template.test.ts +214 -0
  145. package/src/business-model.yaml +296 -49
  146. package/src/catalog-safety.test.ts +198 -0
  147. package/src/catalog.template-items.test.ts +75 -0
  148. package/src/catalog.ts +66 -2
  149. package/src/concept-guard.test.ts +28 -8
  150. package/src/concept-guard.ts +2 -2
  151. package/src/content-creation.template.test.ts +52 -0
  152. package/src/content-creation.yaml +192 -0
  153. package/src/content-os.edit-loop.template.test.ts +210 -0
  154. package/src/content-os.publish-due-posts.template.test.ts +261 -0
  155. package/src/content-os.template.test.ts +322 -0
  156. package/src/content-os.video-takes.template.test.ts +338 -0
  157. package/src/content-os.work-a-take.template.test.ts +125 -0
  158. package/src/content-os.yaml +1830 -39
  159. package/src/content-studio-retired.template.test.ts +82 -0
  160. package/src/corpus-flow-validity.test.ts +4 -5
  161. package/src/crm-absorbs-outreach.template.test.ts +117 -0
  162. package/src/crm-build-plan.template.test.ts +128 -0
  163. package/src/crm-lists.template.test.ts +96 -0
  164. package/src/crm-messages-cadence.template.test.ts +164 -0
  165. package/src/crm-one-send-door.template.test.ts +65 -0
  166. package/src/crm.yaml +1456 -181
  167. package/src/decisions-wave.template.test.ts +186 -0
  168. package/src/define.ts +130 -17
  169. package/src/design-to-implementation.template.test.ts +94 -0
  170. package/src/domain-template-type.test.ts +75 -0
  171. package/src/ecosystem.yaml +27 -66
  172. package/src/enterprise-os.yaml +10 -134
  173. package/src/facecam-edit.audio-studio.test.ts +188 -0
  174. package/src/fal-generate-image.test.ts +200 -0
  175. package/src/fal-media-lab-pod-files.test.ts +180 -0
  176. package/src/finance.yaml +254 -1
  177. package/src/foundation.yaml +78 -38
  178. package/src/frame-host-modules.d.mts +6 -0
  179. package/src/frame-host-modules.mjs +58 -0
  180. package/src/generate.mjs +9 -3
  181. package/src/github-merge.test.ts +210 -0
  182. package/src/grants.yaml +46 -17
  183. package/src/hr.yaml +39 -6
  184. package/src/index.ts +36 -1
  185. package/src/intent.test.ts +161 -0
  186. package/src/internal-runbook.yaml +19 -1
  187. package/src/jev.test.ts +356 -0
  188. package/src/legal.yaml +33 -1
  189. package/src/life-os.yaml +51 -240
  190. package/src/lifecycle-coverage.test.ts +124 -0
  191. package/src/lifecycle-declarations.test.ts +252 -0
  192. package/src/lifecycle-presets.test.ts +468 -0
  193. package/src/lifecycle-presets.ts +494 -0
  194. package/src/lifecycle-stuck.test.ts +46 -0
  195. package/src/lifecycle-waits.parity.test.ts +319 -0
  196. package/src/marketing.yaml +108 -4
  197. package/src/meetings-knowledge.yaml +18 -0
  198. package/src/networking.yaml +23 -2
  199. package/src/operations.yaml +304 -4
  200. package/src/pipeline-views.template.test.ts +88 -0
  201. package/src/playbook-goal-placeholders.test.ts +57 -0
  202. package/src/postiz-contract.test.ts +134 -0
  203. package/src/presentation-declarations.test.ts +174 -0
  204. package/src/process-contract.validate.test.ts +352 -0
  205. package/src/project-management.yaml +39 -740
  206. package/src/publish-body.d.mts +29 -0
  207. package/src/publish-body.mjs +74 -0
  208. package/src/publish-body.test.ts +74 -0
  209. package/src/publish-official-lib.test.ts +176 -0
  210. package/src/replicate-rvm.test.ts +504 -0
  211. package/src/research-base.yaml +119 -20
  212. package/src/research-decision.yaml +60 -3
  213. package/src/reserved-profile-slugs.test.ts +83 -0
  214. package/src/retired-goal-kinds.test.ts +58 -0
  215. package/src/role-reference-and-step-domain.test.ts +192 -0
  216. package/src/seed-obligation.test.ts +1 -6
  217. package/src/send-message-maps.test.ts +227 -0
  218. package/src/shared-role-ssot.test.ts +40 -2
  219. package/src/ship-a-change.template.test.ts +177 -0
  220. package/src/skills.test.ts +170 -0
  221. package/src/social.yaml +85 -27
  222. package/src/space-packages.freshness.test.ts +97 -0
  223. package/src/space-packages.generated.ts +40 -0
  224. package/src/space-packages.test.ts +125 -0
  225. package/src/space-packages.ts +73 -0
  226. package/src/start.test.ts +188 -0
  227. package/src/task-intents.test.ts +293 -0
  228. package/src/templates.ts +20851 -21496
  229. package/src/types.ts +443 -60
  230. package/src/unipile-publish-post.test.ts +162 -0
  231. package/src/validate-flow.drift.test.ts +2 -3
  232. package/src/validate-flow.ts +22 -2
  233. package/src/validate-onboarding.test.ts +59 -0
  234. package/src/validate.test.ts +71 -4
  235. package/src/validate.ts +1335 -7
  236. package/src/wire-core.ts +231 -0
  237. package/src/wire.test.ts +159 -0
  238. package/src/wire.ts +107 -0
  239. package/src/agent-fleet.yaml +0 -1645
  240. package/src/business-developer.template.test.ts +0 -351
  241. package/src/business-developer.yaml +0 -860
  242. package/src/content-studio.yaml +0 -361
  243. package/src/dev-dashboard.yaml +0 -990
  244. package/src/outreach-comms.yaml +0 -486
  245. package/src/the-arch.yaml +0 -201
@@ -12,6 +12,32 @@ meta:
12
12
  color: "#0F766E"
13
13
  tags: [operations, delivery, clients, engagements]
14
14
  isPublic: true
15
+ # Onboarding start (meta.start): the "client delivery" card.
16
+ start:
17
+ order: 4
18
+ label: Client delivery
19
+ promise: Every client engagement and where it stands.
20
+ suggestFor: [consultant]
21
+ profile: engagement
22
+ views: [engagements]
23
+ example:
24
+ - { title: "Website redesign for Maison Dupont", values: { engagement-status: active, engagement-value: 18000 } }
25
+ - { title: "Monthly strategy retainer", values: { engagement-status: active, engagement-value: 4500 } }
26
+ - { title: "Brand audit for Nordlys", values: { engagement-status: proposed, engagement-value: 6000 } }
27
+ - { title: "Onboarding workshop for Atelier 9", values: { engagement-status: completed, engagement-value: 2800 } }
28
+ firstSession:
29
+ title: Your live engagements
30
+ goal: "Add the 3 to 5 client engagements you are delivering now, each with its client, a status and a value."
31
+ starters:
32
+ - "I run client projects as a freelancer"
33
+ - "We are a small agency with several retainers"
34
+ - "I deliver workshops and audits to companies"
35
+ # What this space PRODUCES (homes): the purpose word, then its kinds/roles.
36
+ intent:
37
+ label: Delivery
38
+ produces:
39
+ - profile: engagement
40
+ - profile: deliverable
15
41
 
16
42
  workspace:
17
43
  name: Operations
@@ -45,12 +71,21 @@ profiles:
45
71
  icon: briefcase
46
72
  color: "#0F766E"
47
73
  description: "A unit of client work — a project or retainer delivered for a company, with scope, dates, and value."
74
+ # The ONE lifecycle (work model L1): the initiative base without its
75
+ # agreed and ready steps (lifecycle-presets.ts). Putting an engagement on
76
+ # hold and cancelling it are a person's call; completing it is not (the
77
+ # close stage has its own human gate).
78
+ lifecycle:
79
+ property: engagement-status
80
+ adopts: initiative
81
+ drop: [agreed, ready]
82
+ rename: { dropped: cancelled }
83
+ humanOnly: { on-hold: true, completed: false, cancelled: true }
48
84
  properties:
49
85
  - slug: engagement-status
50
86
  label: Status
51
87
  valueType: string
52
88
  inputType: select
53
- enumValues: [proposed, active, on-hold, completed, cancelled]
54
89
  constraints: { defaultValue: proposed }
55
90
  - slug: engagement-client
56
91
  label: Client
@@ -81,18 +116,42 @@ profiles:
81
116
  label: Notes
82
117
  valueType: string
83
118
  inputType: textarea
119
+ # How the engagement is invoiced (founder decision 2026-10-12): once at
120
+ # close (the default — "Deliver a client engagement" creates the closing
121
+ # invoice), or one invoice per accepted deliverable (milestone billing —
122
+ # "Deliver a deliverable" creates it at Accepted). Not a lifecycle.
123
+ - slug: engagement-billing-mode
124
+ label: Billing
125
+ valueType: string
126
+ inputType: select
127
+ enumValues: [at-close, per-deliverable]
128
+ constraints: { defaultValue: at-close }
84
129
 
85
130
  - slug: deliverable
86
131
  displayName: Deliverable
87
132
  icon: package-check
88
133
  color: "#0EA5E9"
89
134
  description: "A concrete output owed inside an engagement — with a due date, an owner, and a status."
135
+ # The ONE lifecycle (work model L1): every option's category.
136
+ lifecycle:
137
+ property: deliverable-status
138
+ options:
139
+ - { key: not-started, category: open }
140
+ - { key: in-progress, category: active }
141
+ - { key: in-review, category: active }
142
+ - { key: delivered, category: active }
143
+ - { key: accepted, category: done }
144
+ # A deliverable the client no longer wants (scope cut, engagement
145
+ # cancelled) ends here, not in `accepted`; dropping it is a person's
146
+ # call, like every `cancelled` (work-item base, plan aac47c4a W5). It is
147
+ # a client decision, not a failure: muted, not the lost default `error`.
148
+ - { key: cancelled, category: lost, tone: textMuted, humanOnly: true }
90
149
  properties:
91
150
  - slug: deliverable-status
92
151
  label: Status
93
152
  valueType: string
94
153
  inputType: select
95
- enumValues: [not-started, in-progress, in-review, delivered, accepted]
154
+ enumValues: [not-started, in-progress, in-review, delivered, accepted, cancelled]
96
155
  constraints: { defaultValue: not-started }
97
156
  - slug: deliverable-engagement
98
157
  label: Engagement
@@ -121,6 +180,16 @@ profiles:
121
180
  icon: file-signature
122
181
  color: "#8B5CF6"
123
182
  description: "The agreement that governs an engagement — its status, value, term, and signed document."
183
+ # The ONE lifecycle (work model L1): every option's category.
184
+ lifecycle:
185
+ property: contract-status
186
+ options:
187
+ - { key: draft, category: open }
188
+ - { key: sent, category: active }
189
+ - { key: signed, category: active }
190
+ - { key: active, category: active }
191
+ - { key: expired, category: done, humanOnly: true }
192
+ - { key: terminated, category: lost, humanOnly: true }
124
193
  properties:
125
194
  - slug: contract-status
126
195
  label: Status
@@ -267,10 +336,12 @@ onboarding:
267
336
  engagement, who owns it, and when it's due.
268
337
  collect:
269
338
  - profileSlug: engagement
339
+ min: 1
270
340
  what: The client engagements live right now — one per real project or retainer, with its client and dates.
271
341
  cardinality: several
272
342
  keyFields: [engagement-status, engagement-client, engagement-end]
273
343
  - profileSlug: deliverable
344
+ min: 1
274
345
  what: The deliverables due inside those engagements, placed at their true status with a due date.
275
346
  cardinality: several
276
347
  keyFields: [deliverable-status, due-date]
@@ -591,8 +662,8 @@ automations:
591
662
  # ══════════════════════════════════════════════════════════════════════════
592
663
  # CRM → Operations HANDOFF PICKUP
593
664
  #
594
- # The other half of business-developer's "Win and hand off". That flow ends by
595
- # attaching the pod-wide `client` role with `handoffStatus`; nothing on the
665
+ # The other half of CRM's "Win a deal" (and The Arch's "Win and hand off",
666
+ # moved there from business-developer 2026-10-12). Both end by attaching the pod-wide `client` role with `handoffStatus`; nothing on the
596
667
  # delivery side ever picked it up, so a won deal became a client and then
597
668
  # nothing happened. This automation is that pickup: once a day it finds
598
669
  # clients marked ready/handed_off that have NO engagement yet, and opens the
@@ -841,3 +912,232 @@ automations:
841
912
  - { id: h-engagement-link, source: create-engagement, target: link-engagement }
842
913
  - { id: h-link-contract, source: link-engagement, target: create-contract }
843
914
  - { id: h-contract-link, source: create-contract, target: link-contract }
915
+
916
+ playbooks:
917
+ - name: Deliver a client engagement
918
+ description: Run one client engagement from kick-off to close — scope, contract, deliverables, final review and the closing invoice — with a person approving the start and the close.
919
+ goalTemplate: >-
920
+ Run this client engagement ({engagement}); each stage moves its status.
921
+ KICK-OFF: read the won deal, the proposal and every message with the
922
+ client; write the scope, owner, start and end dates and value on the
923
+ engagement; create one deliverable per promised output (each with a due
924
+ date and an owner); reuse the draft contract already linked to the
925
+ engagement (the daily client pick-up opens one), and create a draft
926
+ contract only when none is linked.
927
+ START: a person confirms the contract is signed and the work may start.
928
+ DELIVER: track every deliverable to accepted (or cancelled by a person),
929
+ post a short weekly status in this session's room, and flag any
930
+ deliverable past its due date.
931
+ CLOSE: once every deliverable is accepted or cancelled, write the closing review
932
+ (what was delivered, what slipped, what the client said), create the
933
+ closing invoice in Finance at the draft status for the engagement value
934
+ still owed (when the engagement bills per deliverable, subtract every
935
+ milestone invoice already created for it), and propose the renewal or
936
+ next engagement. A person
937
+ approves the close. Pausing or cancelling an engagement is a person's
938
+ call.
939
+ subjectProfile:
940
+ profileSlug: engagement
941
+ statusProperty: engagement-status
942
+ humanOnlyStatuses: [on-hold, cancelled]
943
+ activators:
944
+ - { on: created, mode: propose }
945
+ params:
946
+ - { name: engagement, type: entity, label: Engagement, required: true }
947
+ executor: is-agent
948
+ status: active
949
+ requiredIntents: [list_records, send_message]
950
+ expectedOutputs:
951
+ - { kind: deliverable, label: Deliverables, relationToSubject: relates_to }
952
+ - { kind: contract, label: Contract, relationToSubject: contract_for_engagement }
953
+ - { kind: invoice, label: Closing invoice, relationToSubject: relates_to }
954
+ - { kind: document, label: Closing review }
955
+ metadata:
956
+ lineage: { method: "Agency delivery: kick-off, milestone tracking, close-out review" }
957
+ stages:
958
+ - key: kickoff
959
+ name: Kick-off
960
+ category: planned
961
+ subjectStatus: proposed
962
+ goal: Write scope, owner, dates and value; create the deliverables; reuse the linked draft contract or create one when none exists.
963
+ criteria:
964
+ - key: kickoff-scope
965
+ statement: The engagement's scope, owner, start date, end date and value are set.
966
+ check: { kind: judge }
967
+ - key: kickoff-deliverables
968
+ statement: Every output promised in the proposal exists as a deliverable with a due date and an owner.
969
+ check: { kind: judge }
970
+ - key: kickoff-contract
971
+ statement: A contract is linked to the engagement.
972
+ check: { kind: judge }
973
+ - key: start
974
+ name: Start
975
+ category: started
976
+ subjectStatus: active
977
+ goal: A person confirms the contract is signed and the work may start.
978
+ gate: { kind: human }
979
+ onFail: { toStage: kickoff }
980
+ - key: deliver
981
+ name: Deliver
982
+ category: started
983
+ goal: Track every deliverable to accepted or cancelled and post a weekly status in the room.
984
+ indefinite: true
985
+ criteria:
986
+ - key: deliver-all-accepted
987
+ statement: Every deliverable of the engagement is accepted or cancelled.
988
+ check: { kind: judge }
989
+ - key: deliver-weekly-status
990
+ statement: A status was posted in the room for every week the engagement was active.
991
+ check: { kind: judge }
992
+ - key: close
993
+ name: Close
994
+ category: completed
995
+ subjectStatus: completed
996
+ goal: Write the closing review, create the closing invoice, propose the renewal; a person approves the close.
997
+ gate: { kind: human }
998
+ onFail: { toStage: deliver }
999
+
1000
+ # Starts when a person moves the deliverable to In progress (founder
1001
+ # decision 2026-10-12), not when it is created: a deliverable is usually
1002
+ # created at kick-off, weeks before anyone works on it. Not started is
1003
+ # therefore a person's status, and the first stage writes nothing.
1004
+ - name: Deliver a deliverable
1005
+ description: Take one deliverable from in progress to accepted by the client — plan, produce, review internally, send, get the client's sign-off, and bill it when the engagement bills per deliverable.
1006
+ goalTemplate: >-
1007
+ Deliver this deliverable ({deliverable}); each stage moves its status.
1008
+ PLAN: read the engagement's scope and the client's brief, and write what
1009
+ done looks like for this deliverable. PRODUCE: make it, or break it into
1010
+ tasks for the people who will. REVIEW: check it against what done looks
1011
+ like before anyone outside sees it. SEND: a person approves before it
1012
+ goes to the client; record the date it was delivered. ACCEPTED: a person
1013
+ confirms the client accepted it; if the client asked for changes, go
1014
+ back to producing. Then read the engagement's billing: only when it
1015
+ bills per deliverable, create this deliverable's invoice in Finance at
1016
+ the draft status for its share of the engagement value, linked to the
1017
+ deliverable. When it bills at close, create no invoice; the engagement's
1018
+ close bills it.
1019
+ subjectProfile:
1020
+ profileSlug: deliverable
1021
+ statusProperty: deliverable-status
1022
+ humanOnlyStatuses: [not-started, cancelled]
1023
+ activators:
1024
+ - { on: enters_status, status: in-progress, mode: propose }
1025
+ params:
1026
+ - { name: deliverable, type: entity, label: Deliverable, required: true }
1027
+ executor: is-agent
1028
+ status: active
1029
+ expectedOutputs:
1030
+ - { kind: document, label: Delivered file or link }
1031
+ stages:
1032
+ - key: plan
1033
+ name: Plan
1034
+ category: started
1035
+ goal: Write what done looks like for this deliverable from the engagement scope and the client brief.
1036
+ criteria:
1037
+ - key: plan-definition-of-done
1038
+ statement: The deliverable states what done looks like in checkable terms.
1039
+ check: { kind: judge }
1040
+ - key: produce
1041
+ name: Produce
1042
+ category: started
1043
+ subjectStatus: in-progress
1044
+ goal: Make the deliverable, or split it into tasks with owners.
1045
+ criteria:
1046
+ - key: produce-draft-attached
1047
+ statement: A draft of the deliverable is attached or linked.
1048
+ check: { kind: judge }
1049
+ - key: review
1050
+ name: Internal review
1051
+ category: started
1052
+ subjectStatus: in-review
1053
+ goal: Check the draft against what done looks like before the client sees it.
1054
+ gate: { kind: check }
1055
+ onFail: { toStage: produce }
1056
+ criteria:
1057
+ - key: review-meets-done
1058
+ statement: The draft meets every point of the deliverable's definition of done.
1059
+ check: { kind: judge }
1060
+ - key: send
1061
+ name: Send to client
1062
+ category: started
1063
+ subjectStatus: delivered
1064
+ goal: A person approves sending; record the delivered date.
1065
+ gate: { kind: human }
1066
+ onFail: { toStage: produce }
1067
+ criteria:
1068
+ - key: send-date-recorded
1069
+ statement: The deliverable's delivered date is set.
1070
+ check: { kind: evidence, evidenceKey: delivered-date }
1071
+ - key: accepted
1072
+ name: Accepted
1073
+ category: completed
1074
+ subjectStatus: accepted
1075
+ goal: A person confirms the client accepted the deliverable; when the engagement bills per deliverable, create its draft invoice in Finance.
1076
+ gate: { kind: human }
1077
+ onFail: { toStage: produce }
1078
+ # Milestone billing. Only made when the engagement's billing is
1079
+ # per-deliverable; at-close engagements are billed by the engagement's
1080
+ # close. `optional` (synap-backend 30a9ab85): nobody owes the slot, so
1081
+ # an at-close run never shows it pending; the condition itself lives
1082
+ # in the goal.
1083
+ expectedOutputs:
1084
+ - { kind: invoice, label: Milestone invoice (per-deliverable billing only), relationToSubject: relates_to, optional: true }
1085
+
1086
+ - name: Get a contract signed
1087
+ description: Take a client contract from draft to signed and active, with a person approving what is sent and confirming the signature.
1088
+ goalTemplate: >-
1089
+ Get this contract ({contract}) signed; each stage moves its status.
1090
+ DRAFT: fill the contract from the engagement's scope, value, dates and
1091
+ the agency's standard terms, and list every term that differs from the
1092
+ standard. SEND: a person approves the contract before it goes to the
1093
+ client. SIGNED: ask the person to `provide` the signed file; their
1094
+ approval confirms both sides signed. ACTIVE: set the start and end dates and link the signed
1095
+ document. Expiry and termination are a person's call.
1096
+ subjectProfile:
1097
+ profileSlug: contract
1098
+ statusProperty: contract-status
1099
+ humanOnlyStatuses: [expired, terminated]
1100
+ activators:
1101
+ - { on: created, mode: propose }
1102
+ params:
1103
+ - { name: contract, type: entity, label: Contract, required: true }
1104
+ executor: is-agent
1105
+ status: active
1106
+ expectedOutputs:
1107
+ - { kind: document, label: Signed contract }
1108
+ stages:
1109
+ - key: draft
1110
+ name: Draft
1111
+ category: planned
1112
+ subjectStatus: draft
1113
+ goal: Fill the contract from the engagement and the standard terms; list every non-standard term.
1114
+ criteria:
1115
+ - key: contract-value-dates
1116
+ statement: The contract's value, start date and end date are set.
1117
+ check: { kind: judge }
1118
+ - key: contract-deviations-listed
1119
+ statement: Every term that differs from the agency's standard terms is listed.
1120
+ check: { kind: judge }
1121
+ - key: send
1122
+ name: Send
1123
+ category: started
1124
+ subjectStatus: sent
1125
+ goal: A person approves the contract before it goes to the client.
1126
+ gate: { kind: human }
1127
+ onFail: { toStage: draft }
1128
+ - key: signed
1129
+ name: Signed
1130
+ category: started
1131
+ subjectStatus: signed
1132
+ goal: Ask the person to provide the signed file; their approval confirms both sides signed.
1133
+ gate: { kind: human }
1134
+ onFail: { toStage: send }
1135
+ - key: active
1136
+ name: Active
1137
+ category: completed
1138
+ subjectStatus: active
1139
+ goal: Set the dates and link the signed document on the contract.
1140
+ criteria:
1141
+ - key: contract-document-linked
1142
+ statement: The signed document's link is set on the contract.
1143
+ check: { kind: evidence, evidenceKey: contract-url }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * D3 (work model L3, 2026-10-12): a catalog board over a declared lifecycle is
3
+ * a `pipeline` view — ONE rendering of "things in the columns of their
4
+ * status" (the pod's `lifecycle.board`, the one mover), never a kanban beside
5
+ * it that draws a column per drifted value.
6
+ *
7
+ * Reachability, not shape (guards-and-tests.md): a `pipeline` view whose kind
8
+ * declares NO lifecycle renders nothing at all (PipelineView OMITs an
9
+ * undeclared profile), and one whose config names no kind renders "This
10
+ * pipeline names no kind". So every pipeline view must name its kind in
11
+ * `config.profileSlug` (what `readPipelineConfig` reads), and that kind must
12
+ * declare a lifecycle in the template itself or in a template it depends on
13
+ * (the install resolver puts dependencies first — the lead role's lifecycle
14
+ * lives in foundation).
15
+ *
16
+ * The scanned set is DERIVED from every bundled template; a non-vacuity floor
17
+ * keeps it honest. What it does not see: a lifecycle declared only by a pod
18
+ * (not in the catalog), and a pod whose dependency was later removed.
19
+ */
20
+ import { describe, expect, it } from "vitest";
21
+ import { WORKSPACE_TEMPLATES } from "./templates.js";
22
+
23
+ type View = {
24
+ name: string;
25
+ type: string;
26
+ scopeProfileSlug?: string;
27
+ groupBy?: string;
28
+ config?: Record<string, unknown>;
29
+ };
30
+ type Profile = { slug: string; lifecycle?: { property: string } };
31
+ type Template = {
32
+ meta?: { slug?: string };
33
+ profiles?: Profile[];
34
+ views?: View[];
35
+ dependencies?: Array<{ slug: string }>;
36
+ };
37
+
38
+ const templates = WORKSPACE_TEMPLATES as unknown as Record<string, Template>;
39
+ const bySlug = new Map(
40
+ Object.entries(templates).map(([key, t]) => [t.meta?.slug ?? key, t] as const),
41
+ );
42
+
43
+ /** The lifecycle property a profile declares here or in a dependency (transitively). */
44
+ function lifecycleOf(t: Template, profileSlug: string, seen = new Set<Template>()): string | null {
45
+ if (seen.has(t)) return null;
46
+ seen.add(t);
47
+ const own = t.profiles?.find((p) => p.slug === profileSlug && p.lifecycle)?.lifecycle?.property;
48
+ if (own) return own;
49
+ for (const dep of t.dependencies ?? []) {
50
+ const d = bySlug.get(dep.slug);
51
+ const found = d ? lifecycleOf(d, profileSlug, seen) : null;
52
+ if (found) return found;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ const allViews = Object.values(templates).flatMap((t) => (t.views ?? []).map((v) => ({ t, v })));
58
+ const pipelines = allViews.filter(({ v }) => v.type === "pipeline");
59
+
60
+ describe("catalog pipeline views (D3)", () => {
61
+ it("the scan sees the catalog and the converted boards", () => {
62
+ expect(allViews.length).toBeGreaterThan(50);
63
+ expect(pipelines.map(({ v }) => v.name).sort()).toEqual(
64
+ expect.arrayContaining(["Hiring Pipeline", "Lead Pipeline", "Pipeline"]),
65
+ );
66
+ });
67
+
68
+ it("every pipeline view names a kind that declares a lifecycle", () => {
69
+ const broken = pipelines.flatMap(({ t, v }) => {
70
+ const slug = v.config?.profileSlug;
71
+ if (typeof slug !== "string" || !slug) return [`${t.meta?.slug} / ${v.name}: no config.profileSlug`];
72
+ if (v.scopeProfileSlug && v.scopeProfileSlug !== slug)
73
+ return [`${t.meta?.slug} / ${v.name}: config.profileSlug ≠ scopeProfileSlug`];
74
+ return lifecycleOf(t, slug) ? [] : [`${t.meta?.slug} / ${v.name}: ${slug} declares no lifecycle`];
75
+ });
76
+ expect(broken).toEqual([]);
77
+ });
78
+
79
+ it("no kanban named a pipeline still groups a declared lifecycle (it is a pipeline view)", () => {
80
+ const stray = allViews.flatMap(({ t, v }) => {
81
+ if (v.type !== "kanban" || !/pipeline/i.test(v.name) || !v.scopeProfileSlug) return [];
82
+ const property = lifecycleOf(t, v.scopeProfileSlug);
83
+ const groupBy = v.groupBy ?? v.config?.groupBy ?? v.config?.groupByField;
84
+ return property && groupBy === property ? [`${t.meta?.slug} / ${v.name}`] : [];
85
+ });
86
+ expect(stray).toEqual([]);
87
+ });
88
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Every shipped playbook goal is fillable by a run.
3
+ *
4
+ * The pod's playbook door (`playbooks.create`, which the package installer
5
+ * reaches) REFUSES a goalTemplate whose placeholder no run fills
6
+ * (synap-backend `services/playbooks/goal-references.ts`, 1c619345):
7
+ * - a bare `{{name}}` — the run door substitutes `{name}` / `@{arg:name}`
8
+ * only, so `{{name}}` reached the agent verbatim; it never worked;
9
+ * - a `{name}` / `@{arg:name}` naming no declared param.
10
+ * A template that ships either now fails its own install. This guard fails the
11
+ * template at build time instead, derived over EVERY bundled template.
12
+ *
13
+ * It mirrors the pod's rule (this package cannot import the api), so it proves
14
+ * the templates agree with THAT rule — not that the rule is right. Rooted
15
+ * automation paths (`{{trigger.x}}`, `{{steps.y}}`) are not placeholders and
16
+ * are out of scope; so is braced prose (`{see below}`, a space inside).
17
+ */
18
+
19
+ import { describe, expect, it } from "vitest";
20
+ import { WORKSPACE_TEMPLATES } from "./templates.js";
21
+ // One matcher, shared with validateCatalog (which runs it on EVERY kind's playbooks).
22
+ import { goalPlaceholderProblems } from "../scripts/catalog-lib.mjs";
23
+
24
+
25
+ const goals = Object.entries(WORKSPACE_TEMPLATES).flatMap(([slug, tpl]) =>
26
+ (tpl.playbooks ?? []).map((p) => ({
27
+ where: `${slug} › ${p.name}`,
28
+ goal: p.goalTemplate ?? "",
29
+ declared: new Set((p.params ?? []).map((x) => x.name)),
30
+ }))
31
+ );
32
+
33
+ describe("playbook goals — every placeholder is one a run fills", () => {
34
+ it("non-vacuity: the scan sees real playbooks, and a real {name} placeholder", () => {
35
+ expect(goals.length).toBeGreaterThanOrEqual(15);
36
+ expect(goals.some((g) => /\{[A-Za-z_]\w*\}/.test(g.goal))).toBe(true);
37
+ });
38
+
39
+ it("the matcher catches what it hunts (self-check)", () => {
40
+ expect(goalPlaceholderProblems("Check {{part}} now", new Set(["part"]))).toEqual([
41
+ "{{part}}",
42
+ ]);
43
+ expect(goalPlaceholderProblems("Do {x} and @{arg:y}", new Set(["x"]))).toEqual([
44
+ "@{arg:y}",
45
+ ]);
46
+ expect(
47
+ goalPlaceholderProblems("Since {{trigger.payload.since}} {see below}", new Set())
48
+ ).toEqual([]);
49
+ });
50
+
51
+ it("no shipped playbook goal carries {{name}} or an undeclared {name}", () => {
52
+ const bad = goals.flatMap((g) =>
53
+ goalPlaceholderProblems(g.goal, g.declared).map((p) => `${g.where}: ${p}`)
54
+ );
55
+ expect(bad).toEqual([]);
56
+ });
57
+ });
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The Postiz contract: the property keys the backend's Postiz webhook and the
3
+ * `postiz` capability agree on, pinned where the model is authored.
4
+ *
5
+ * The backend finds a post by `postiz-post-id` (the id Postiz returned when the
6
+ * post was queued) and a post queued on a `platform` account sends that
7
+ * account's `postiz-integration-id` and `postiz-provider`. A rename here would
8
+ * leave the webhook matching nothing, silently, so the keys are pinned on the
9
+ * SSOT template (content-os owns `post` and `platform`) and on every template
10
+ * that re-declares those profiles with the Postiz fields.
11
+ *
12
+ * NOT covered: the backend side of the contract (the webhook route and its
13
+ * lookup), which lives in synap-backend; whether Postiz accepts the body
14
+ * (exercised against the openapi, not a live Postiz).
15
+ */
16
+ import { readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { describe, expect, it } from "vitest";
19
+ import { WORKSPACE_TEMPLATES } from "./templates.js";
20
+ import type { TemplateProfile, WorkspaceYaml } from "./types.js";
21
+
22
+ const POST_KEYS = ["postiz-post-id"];
23
+ const PLATFORM_KEYS = ["postiz-integration-id", "postiz-provider"];
24
+
25
+ const templates = Object.values(WORKSPACE_TEMPLATES) as WorkspaceYaml[];
26
+ const profile = (tpl: WorkspaceYaml | undefined, slug: string): TemplateProfile | undefined =>
27
+ (tpl?.profiles ?? []).find((p) => p.slug === slug);
28
+ const propertyOf = (p: TemplateProfile | undefined, key: string) =>
29
+ (p?.properties ?? []).find((prop) => prop.slug === key);
30
+
31
+ const capability = JSON.parse(
32
+ readFileSync(join(import.meta.dirname, "..", "capabilities", "postiz.capability.json"), "utf8"),
33
+ ) as { spec: { skills: Array<Record<string, any>>; provides?: string[] } };
34
+
35
+ describe("Postiz contract — the keys the webhook and the capability share", () => {
36
+ const contentOs = WORKSPACE_TEMPLATES["content-os"] as WorkspaceYaml | undefined;
37
+
38
+ it.each([
39
+ ["post", POST_KEYS],
40
+ ["platform", PLATFORM_KEYS],
41
+ ] as const)("content-os (the SSOT) declares %s's Postiz keys as text", (slug, keys) => {
42
+ const p = profile(contentOs, slug);
43
+ expect(p, `content-os declares ${slug}`).toBeDefined();
44
+ for (const key of keys) expect(propertyOf(p, key)?.valueType, `${slug}.${key}`).toBe("string");
45
+ });
46
+
47
+ it("every template that re-declares post or platform with a Postiz field declares all of them, as text", () => {
48
+ let seen = 0;
49
+ for (const tpl of templates) {
50
+ for (const [slug, keys] of [
51
+ ["post", POST_KEYS],
52
+ ["platform", PLATFORM_KEYS],
53
+ ] as const) {
54
+ const p = profile(tpl, slug);
55
+ if (!p || !(p.properties ?? []).some((prop) => prop.slug.startsWith("postiz-"))) continue;
56
+ seen += 1;
57
+ for (const key of keys) expect(propertyOf(p, key)?.valueType, `${tpl.meta?.slug}: ${slug}.${key}`).toBe("string");
58
+ }
59
+ }
60
+ // content-os and social each declare both profiles.
61
+ expect(seen).toBeGreaterThanOrEqual(4);
62
+ });
63
+
64
+ it("the capability queues a post that can be written back: postiz_schedule_post serves publish_post and returns the ids", () => {
65
+ const verb = capability.spec.skills.find((s) => s.name === "postiz_schedule_post");
66
+ expect(verb?.intent).toBe("publish_post");
67
+ expect(verb?.kind).toBe("code");
68
+ // A write: an agent's run is a proposal (never metadata.readOnly). The pod's
69
+ // post-gate runner writes `result.postId` onto the post named by the
70
+ // `postEntityId` param (backend `metadata.writeBack` hook, lane pz-be).
71
+ expect(verb?.metadata).toEqual({
72
+ verbType: "write",
73
+ writeBack: { entityParam: "postEntityId", kind: "post", properties: { "postiz-post-id": "postId" } },
74
+ });
75
+ for (const p of ["content", "integrationId", "provider", "postEntityId"]) expect(Object.keys(verb?.parameters ?? {})).toContain(p);
76
+ expect(verb?.code).toMatch(/return \{ postId, integrationId, provider,[^}]*postEntityId/);
77
+ expect(capability.spec.provides).toContain("publish_post");
78
+ });
79
+
80
+ it("the channel list hands back what a platform stores: id and provider", () => {
81
+ const verb = capability.spec.skills.find((s) => s.name === "postiz_list_integrations");
82
+ expect(verb?.metadata).toEqual({ readOnly: true });
83
+ expect(verb?.providerSpec?.responseShape?.item).toMatchObject({ id: "id", provider: "identifier" });
84
+ });
85
+
86
+ it("Produce Content requires publish_post and queues at Scheduled", () => {
87
+ const pb = (contentOs?.playbooks ?? []).find((p: { name?: string }) => p.name === "Produce Content") as
88
+ | { requiredIntents?: string[]; stages?: Array<{ key: string; subjectStatus?: string; goal?: string }> }
89
+ | undefined;
90
+ expect(pb?.requiredIntents).toContain("publish_post");
91
+ const schedule = pb?.stages?.find((s) => s.key === "schedule");
92
+ expect(schedule?.subjectStatus).toBe("Scheduled");
93
+ expect(schedule?.goal).toContain("postiz_schedule_post");
94
+ expect(schedule?.goal).toContain("postiz-post-id");
95
+ });
96
+ });
97
+
98
+ // Intent actions (decision ef0e917c, 2026-10-28): a pod app asks the person's
99
+ // Postiz to publish a post with ONE input `{ text, when? }`. Postiz holds one
100
+ // key for several social channels, so each mapped verb names the channel list
101
+ // as its account source; the pod offers one action per channel and refuses a
102
+ // map whose source is not a read listing these fields.
103
+ describe("Postiz — what a pod app may ask it to publish", () => {
104
+ const skill = (name: string) => capability.spec.skills.find((s) => s.name === name)!;
105
+
106
+ it.each(["postiz_schedule", "postiz_publish"])("%s maps the text and the channel picked from postiz_list_integrations", (name) => {
107
+ expect(skill(name).inputMap).toEqual(
108
+ expect.objectContaining({
109
+ params: expect.objectContaining({
110
+ content: "$.text",
111
+ integrationId: "$account.id",
112
+ provider: "$account.provider",
113
+ }),
114
+ accounts: { verb: "postiz_list_integrations", id: "id", label: "name", provider: "provider" },
115
+ }),
116
+ );
117
+ });
118
+
119
+ it("the account source lists the fields it names", () => {
120
+ const list = skill("postiz_list_integrations");
121
+ expect(list.metadata?.readOnly).toBe(true);
122
+ expect(Object.keys(list.providerSpec.responseShape.item)).toEqual(expect.arrayContaining(["id", "name", "provider"]));
123
+ });
124
+
125
+ it("only the scheduled post reads the time; publishing now never does", () => {
126
+ expect(skill("postiz_schedule").inputMap.params.date).toBe("$.when");
127
+ expect(skill("postiz_publish").inputMap.params).not.toHaveProperty("date");
128
+ });
129
+
130
+ it("drafts, uploads and the media post are not offered to apps (P1 maps text and time only)", () => {
131
+ for (const name of ["postiz_draft", "postiz_schedule_post", "postiz_upload_from_url"])
132
+ expect(skill(name).inputMap, name).toBeUndefined();
133
+ });
134
+ });