@synap-core/workspace-templates 0.7.2 → 0.7.4

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 (44) hide show
  1. package/dist/index.d.ts +207 -8
  2. package/dist/index.js +2122 -1427
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/agent-fleet.yaml +666 -108
  6. package/src/blockchain-ecosystem.yaml +1 -0
  7. package/src/brand-library.yaml +127 -12
  8. package/src/builder.yaml +116 -12
  9. package/src/business-developer.yaml +102 -18
  10. package/src/catalog.ts +13 -3
  11. package/src/communication.yaml +23 -453
  12. package/src/composition-graph.test.ts +26 -0
  13. package/src/content-os.yaml +103 -10
  14. package/src/content-studio.yaml +1 -0
  15. package/src/crm.yaml +12 -0
  16. package/src/define.ts +8 -8
  17. package/src/dev-dashboard.yaml +43 -9
  18. package/src/domains.test.ts +117 -0
  19. package/src/domains.ts +102 -0
  20. package/src/ecosystem.yaml +21 -4
  21. package/src/enterprise-os.yaml +5 -0
  22. package/src/finance.yaml +46 -5
  23. package/src/foundation.yaml +13 -1
  24. package/src/grants.yaml +1 -0
  25. package/src/hr.yaml +61 -8
  26. package/src/index.ts +29 -0
  27. package/src/internal-runbook.yaml +36 -5
  28. package/src/legal.yaml +97 -12
  29. package/src/life-os.yaml +188 -27
  30. package/src/marketing.yaml +15 -2
  31. package/src/meetings-knowledge.yaml +7 -1
  32. package/src/operations.yaml +351 -0
  33. package/src/outreach-comms.yaml +6 -1
  34. package/src/project-management.yaml +840 -111
  35. package/src/research-base.yaml +516 -0
  36. package/src/research-decision.yaml +158 -0
  37. package/src/shared-role-ssot.test.ts +268 -0
  38. package/src/social.yaml +105 -37
  39. package/src/templates.ts +4573 -4302
  40. package/src/the-arch.yaml +11 -0
  41. package/src/types.ts +9 -0
  42. package/src/validate.test.ts +81 -0
  43. package/src/validate.ts +561 -0
  44. package/src/personal.yaml +0 -177
@@ -5,6 +5,7 @@
5
5
 
6
6
  meta:
7
7
  slug: operations
8
+ domain: company
8
9
  name: Operations
9
10
  description: "Deliver client work end to end — the engagements you're running, the deliverables due inside them, and the contracts that govern them. Won deals from CRM hand off into engagements here."
10
11
  icon: briefcase
@@ -25,6 +26,19 @@ workspace:
25
26
  crm: consumer
26
27
  operations: provider
27
28
 
29
+ # The daily client-briefing automation below enumerates clients with
30
+ # `entity.query { profileSlug: "client" }`, and that verb FAILS CLOSED on an
31
+ # unknown slug (`assertKnownProfileSlug`) — an Operations workspace installed
32
+ # without the pod-wide `client` role would kill the run on its first node. The
33
+ # `client` role is foundation's SSOT (foundation.yaml, `scope: shared`,
34
+ # `profileKind: role`), so require it, exactly like crm.yaml does. `require`,
35
+ # not `compose`: foundation is a real workspace, not an overlay.
36
+ dependencies:
37
+ - slug: foundation
38
+ kind: workspace
39
+ relation: require
40
+ reason: "Provides the pod-wide `client` role the daily client-briefing automation queries."
41
+
28
42
  profiles:
29
43
  - slug: engagement
30
44
  displayName: Engagement
@@ -140,6 +154,65 @@ profiles:
140
154
  valueType: string
141
155
  inputType: textarea
142
156
 
157
+ # The durable OUTPUT of the daily briefing automation. A recap that only ever
158
+ # exists as a chat message is not knowledge — it cannot be queried, ranked, or
159
+ # linked. This kind is what turns the digest into structure: one dated row per
160
+ # client per run, linked to the client entity and readable long after the
161
+ # message scrolled away. Written to the TEAM side only; never client-facing.
162
+ - slug: client-briefing
163
+ displayName: Client Briefing
164
+ icon: newspaper
165
+ color: "#F59E0B"
166
+ description: "A dated internal recap of one client's recent communications — what moved, what's at risk, what's next. For the team, never for the client."
167
+ properties:
168
+ - slug: briefing-client
169
+ label: Client
170
+ valueType: entity_id
171
+ targetProfileSlug: company
172
+ inputType: entity_select
173
+ - slug: briefing-date
174
+ label: Written At
175
+ valueType: date
176
+ inputType: date
177
+ # Idempotency key, NOT display. `output/entity_create`'s `dedupeBy` matches
178
+ # on this exact value, so a redelivered run reuses the row it already wrote
179
+ # instead of appending a second briefing for the same client and tick.
180
+ - slug: briefing-key
181
+ label: Run Key
182
+ valueType: string
183
+ inputType: text
184
+ - slug: briefing-headline
185
+ label: Headline
186
+ valueType: string
187
+ inputType: text
188
+ - slug: briefing-summary
189
+ label: Summary
190
+ valueType: string
191
+ inputType: textarea
192
+ - slug: briefing-risks
193
+ label: Risks
194
+ valueType: string
195
+ inputType: textarea
196
+ - slug: briefing-next-actions
197
+ label: Next Actions
198
+ valueType: string
199
+ inputType: textarea
200
+ - slug: briefing-message-count
201
+ label: Messages Read
202
+ valueType: number
203
+ inputType: number
204
+
205
+ # Schema relations. `briefing_about_client` MUST be declared here: an automation
206
+ # `relation_create` dispatches `graph.link` → `relations.create`, which rejects
207
+ # any type that is neither a system built-in nor a workspace relation def
208
+ # (routers/relations.ts — "Unknown relation type"). The applier find-or-creates
209
+ # the def from this block, so the automation's link resolves.
210
+ entityLinks:
211
+ - sourceProfileSlug: client-briefing
212
+ targetProfileSlug: company
213
+ type: briefing_about_client
214
+ label: Client
215
+
143
216
  # CURATED views — the delivery motion, not one per profile
144
217
  views:
145
218
  - name: Engagements
@@ -159,6 +232,11 @@ views:
159
232
  type: table
160
233
  scopeProfileSlug: contract
161
234
  description: "Contracts with their status, value, and term."
235
+ - name: Client Briefings
236
+ slug: client-briefings
237
+ type: list
238
+ scopeProfileSlug: client-briefing
239
+ description: "The daily internal recaps, newest first."
162
240
 
163
241
  onboarding:
164
242
  goal: >
@@ -220,3 +298,276 @@ bento:
220
298
  - kind: view
221
299
  viewName: Contracts
222
300
  pos: { x: 6, y: 0, w: 6, h: 5 }
301
+
302
+ # ============================================================================
303
+ # Automations — the daily client-briefing digest.
304
+ #
305
+ # WHAT: once a day, for every `client`, read that client's inbound comms,
306
+ # distil the real signal with ONE batched AI call (never per-message), persist
307
+ # a durable `client-briefing` row, and post the recap to the client's TEAM-side
308
+ # channel. Firewall: the recap NEVER lands in the client-facing channel.
309
+ #
310
+ # GOVERNANCE DESIGN (why this run cannot die on a `proposed` verdict):
311
+ # • The three `capability` nodes call ONLY read-only builtins —
312
+ # `entity.query`, `ai.generate`, `channel.resolve` — all in
313
+ # READ_ONLY_BUILTIN_VERBS, so the capability gate AUTO-RUNS them (no grant,
314
+ # no proposal). A `capability` node THROWS on a `proposed` verdict
315
+ # (automation-executor.ts) — these three can never reach it.
316
+ # • `messages_query` + `condition` are pure read / pure eval — never governed.
317
+ # • The two governed WRITES are `output` nodes, NOT `capability` nodes:
318
+ # – `entity_create` returns `{status:"proposed"}` on a propose verdict
319
+ # (it never throws), and `entity.create` is in DEFAULT_AUTO_APPROVE, so
320
+ # a human-owned or agent-owned run auto-approves and writes directly.
321
+ # – `relation_create` dispatches `graph.link`; the `graph.link` builtin is
322
+ # seeded `approved:true` (rung 2.7 → auto) and `relation.create` is in
323
+ # DEFAULT_AUTO_APPROVE, so it auto-executes — never a propose verdict.
324
+ # – `channel_message` is a direct, firewall-checked insert with NO
325
+ # governance gate at all.
326
+ # • Every write also lives INSIDE the loop body, whose per-item try/catch
327
+ # aborts only THAT client's remaining steps on any error — the RUN itself
328
+ # survives regardless.
329
+ #
330
+ # `status: active` is load-bearing: the cron scheduler selects
331
+ # `status="active" AND nextRunAt <= now`. A `draft` never fires — that was the
332
+ # exact bug this automation replaces. `metadata.templateKey` makes re-apply
333
+ # idempotent (the post-workspace applier dedups by name / templateKey).
334
+ #
335
+ # ⚠️ KNOWN BLOCKER — RETURNS ZERO CLIENTS UNTIL A BACKEND FIX LANDS.
336
+ # `client`/`company` entities are POD-WIDE (`workspaceId: null`). This automation
337
+ # runs under a WORKSPACE LENS, and `workspaceLensWhere`
338
+ # (`packages/api/src/utils/user-visible-where.ts`) excludes pod-wide globals from
339
+ # a focused workspace by design (`includeGlobals` defaults falsy — product
340
+ # decision 2026-06-15). So the `entity.query` verb — which resolves
341
+ # `input.workspaceId ?? ctx.workspaceId` and CANNOT express a global lens (its
342
+ # param is `workspaceId: uuid().optional()`) — enumerates ZERO clients under the
343
+ # Operations lens. The `list-clients` node is deliberately left with NO
344
+ # workspace override so it starts working the moment the backend fix (let
345
+ # `entity.query` express a pod-wide/global lens → `workspaceLensWhere` with
346
+ # `null`/`includeGlobals:true`) lands. NO workaround is baked in here — a
347
+ # hardcoded client list or a workspace-scoped copy would defeat the point.
348
+ #
349
+ # LIMITATION (honest): `messages_query` resolves ONLY the EXTERNAL client-comms
350
+ # channel bound to the client entity — team-side chatter ABOUT a client is not
351
+ # in the digest input. The recap is intentionally still posted to the TEAM
352
+ # channel; it summarises the client conversation, not internal discussion.
353
+ # ============================================================================
354
+ automations:
355
+ - key: operations-daily-client-briefing
356
+ name: Daily client briefing
357
+ description: >-
358
+ Operations-native daily client briefing. Writes a durable, dated
359
+ client-briefing ENTITY per client (queryable history, linked to the
360
+ client) from their recent comms, and posts the recap to the client's
361
+ internal TEAM channel (never the client-facing channel). Knowledge-building
362
+ and entity-first — the delivery OS's structured memory of each client. For
363
+ a lighter, portable message-only digest installable on any pod, use the
364
+ client-comms-report capability.
365
+ triggerType: cron
366
+ triggerConfig:
367
+ expression: "0 8 * * *"
368
+ status: active
369
+ metadata:
370
+ templateKey: operations-daily-client-briefing
371
+ # The `list-clients` node uses `scope: pod` — the entity.query pod-wide
372
+ # opt-in. That opt-in must be DEPLOYED for the query to return pod-wide
373
+ # clients; without it a pod-wide client query yields 0 rows and this
374
+ # automation is a silent no-op (fires on cron, loops over nothing).
375
+ requiresPodWideEntityQueryLens: true
376
+ flow:
377
+ nodes:
378
+ - id: trigger
379
+ type: trigger
380
+ position: { x: 0, y: 0 }
381
+ data:
382
+ label: Daily at 08:00
383
+ triggerType: cron
384
+ config:
385
+ expression: "0 8 * * *"
386
+
387
+ # READ-ONLY builtin. Enumerates clients by the pod-wide `client` ROLE
388
+ # (foundation SSOT). `entity.query` fails CLOSED on an unknown slug via
389
+ # `assertKnownProfileSlug` — the `dependencies: require foundation` block
390
+ # above guarantees the `client` role exists, so the first node can't die.
391
+ #
392
+ # `scope: pod` = the EXPLICIT pod-wide opt-in. `client` entities are
393
+ # pod-wide (workspaceId:null); a run's workspace lens hides pod-wide
394
+ # globals by default (workspaceLensWhere, product decision 2026-06-15),
395
+ # so WITHOUT scope:pod this loops over zero clients. scope:pod pins the
396
+ # globals-only, owner-gated lens, returning exactly the caller's pod-wide
397
+ # clients — an explicit request, NOT globals silently bleeding into a
398
+ # focused workspace. Do NOT "fix" a zero result by hardcoding a client
399
+ # list or a workspace-scoped profile — scope:pod IS the fix.
400
+ - id: list-clients
401
+ type: capability
402
+ position: { x: 0, y: 120 }
403
+ data:
404
+ label: List clients (pod-wide `client` role)
405
+ verbId: entity.query
406
+ inputMapping:
407
+ profileSlug: client
408
+ scope: pod
409
+ limit: "100"
410
+
411
+ - id: clients-loop
412
+ type: loop
413
+ position: { x: 0, y: 240 }
414
+ data:
415
+ label: For each client
416
+ iteratorExpression: steps.list-clients.output.entities
417
+ itemVariable: client
418
+
419
+ # READ. Resolves the client's EXTERNAL client-comms channel bound to the
420
+ # client entity and returns its recent window. Team-side chatter is NOT
421
+ # included (see the LIMITATION note above).
422
+ - id: read-comms
423
+ type: messages_query
424
+ position: { x: 0, y: 360 }
425
+ data:
426
+ label: Read this client's recent comms
427
+ subjectEntityId: "{{loop.item.id}}"
428
+ limit: 40
429
+
430
+ # READ-ONLY builtin (pure compute — mutates nothing, auto-runs). ONE
431
+ # batched digest for the whole window; NO per-message AI. Returns STRICT
432
+ # JSON whose fields map onto the client-briefing properties below.
433
+ - id: digest
434
+ type: capability
435
+ position: { x: 0, y: 480 }
436
+ data:
437
+ label: Distil the real signal → JSON
438
+ verbId: ai.generate
439
+ inputMapping:
440
+ json: "true"
441
+ maxTokens: "1200"
442
+ system: >-
443
+ You advise a client-delivery team. You read one client's recent
444
+ inbound messages and surface only what genuinely matters. You
445
+ are firewall-aware: your output is for the INTERNAL team, never
446
+ the client.
447
+ prompt: >-
448
+ Digest the recent communications for the client
449
+ "{{loop.item.title}}" into a concise internal briefing for OUR
450
+ delivery team.
451
+
452
+ PEOPLE GUARD (do not break): distinguish the CLIENT's people
453
+ from OUR team members who also appear in the thread. Never treat
454
+ a teammate as a client contact, lead, or new person. Only real,
455
+ client-side signal counts.
456
+
457
+ Surface ONLY real signal: status changes, decisions,
458
+ deals/opportunities, competitor mentions, new client-side
459
+ leads/contacts, risks/blockers, and deadlines/commitments. Skip
460
+ small talk and noise. Lead with what matters; keep it tight.
461
+
462
+ Respond with STRICT JSON and NOTHING else:
463
+ {
464
+ "hasSignal": "true" or "false" (a STRING),
465
+ "headline": "<one-line summary; empty string when hasSignal is 'false'>",
466
+ "summary": "<a short markdown digest for the team; empty string when 'false'>",
467
+ "risks": "<risks/blockers as short markdown, or empty string>",
468
+ "nextActions": "<recommended next actions as short markdown, or empty string>"
469
+ }
470
+ Set hasSignal to "false" when there is no notable activity (no
471
+ messages, or only chatter).
472
+
473
+ Recent conversation (oldest first, JSON):
474
+ {{steps.read-comms.output.messages}}
475
+
476
+ # PER-ITEM FILTER (continue-semantics). When the digest found no signal,
477
+ # skip the rest of THIS client's body — no briefing row, no post. A
478
+ # `condition` node is a loop-body type; a `switch` is NOT (it would break
479
+ # the loop's contiguous-body chain), so the gate MUST be a `condition`.
480
+ - id: signal-gate
481
+ type: condition
482
+ position: { x: 0, y: 600 }
483
+ data:
484
+ label: Only continue when there's real signal
485
+ expression: "steps.digest.output.hasSignal === true"
486
+
487
+ # GOVERNED WRITE via an `output` node (returns `proposed`, never throws;
488
+ # `entity.create` is auto-approved). Durable, dated log row.
489
+ # `dedupeBy: briefing-key` makes a redelivered run reuse the same row
490
+ # instead of appending a duplicate for the same client + tick.
491
+ - id: write-briefing
492
+ type: output
493
+ position: { x: 0, y: 720 }
494
+ data:
495
+ label: Save the client briefing
496
+ outputType: entity_create
497
+ config:
498
+ profileSlug: client-briefing
499
+ title: "Briefing — {{loop.item.title}}"
500
+ dedupeBy: briefing-key
501
+ properties:
502
+ briefing-key: "{{loop.item.id}}::{{trigger.payload.scheduledAt}}"
503
+ briefing-client: "{{loop.item.id}}"
504
+ briefing-date: "{{trigger.payload.scheduledAt}}"
505
+ briefing-headline: "{{steps.digest.output.headline}}"
506
+ briefing-summary: "{{steps.digest.output.summary}}"
507
+ briefing-risks: "{{steps.digest.output.risks}}"
508
+ briefing-next-actions: "{{steps.digest.output.nextActions}}"
509
+ briefing-message-count: "{{steps.read-comms.output.count}}"
510
+
511
+ # GOVERNED WRITE via `output` node → `graph.link` (auto-executes:
512
+ # builtin approved + relation.create in DEFAULT_AUTO_APPROVE). Links the
513
+ # briefing to its client. The relation TYPE is declared in `entityLinks`
514
+ # above, so `relations.create` accepts it instead of "Unknown relation
515
+ # type". `dedupe` (default) skips a duplicate edge on re-run.
516
+ - id: link-briefing
517
+ type: output
518
+ position: { x: 0, y: 840 }
519
+ data:
520
+ label: Link briefing → client
521
+ outputType: relation_create
522
+ config:
523
+ fromEntityId: "{{steps.write-briefing.output.entityId}}"
524
+ toEntityId: "{{loop.item.id}}"
525
+ relationType: briefing_about_client
526
+
527
+ # READ-ONLY builtin. Resolves the client's TEAM-side channel by
528
+ # branchPurpose (the firewall ROLE), NOT the client-facing channel.
529
+ - id: resolve-team
530
+ type: capability
531
+ position: { x: 0, y: 960 }
532
+ data:
533
+ label: Resolve the client's TEAM channel (FIREWALL)
534
+ verbId: channel.resolve
535
+ inputMapping:
536
+ contextObjectType: entity
537
+ contextObjectId: "{{loop.item.id}}"
538
+ branchPurpose: team
539
+
540
+ # UNGOVERNED direct insert (firewall-checked mirror). Posts to the
541
+ # EXPLICIT resolved team channelId — NOT channelType:'subjectEntity',
542
+ # which needs a run subjectEntityId that CRON runs never carry.
543
+ - id: post-recap
544
+ type: output
545
+ position: { x: 0, y: 1080 }
546
+ data:
547
+ label: Post the recap to the TEAM channel (FIREWALL)
548
+ outputType: channel_message
549
+ config:
550
+ channelId: "{{steps.resolve-team.output.channelId}}"
551
+ content: >-
552
+ **{{loop.item.title}} — daily briefing**
553
+
554
+ {{steps.digest.output.headline}}
555
+
556
+ {{steps.digest.output.summary}}
557
+
558
+ **Risks:** {{steps.digest.output.risks}}
559
+
560
+ **Next:** {{steps.digest.output.nextActions}}
561
+ metadata:
562
+ automationMessage: true
563
+ kind: client-briefing
564
+ edges:
565
+ - { id: e-trigger-list, source: trigger, target: list-clients }
566
+ - { id: e-list-loop, source: list-clients, target: clients-loop }
567
+ - { id: e-loop-read, source: clients-loop, target: read-comms }
568
+ - { id: e-read-digest, source: read-comms, target: digest }
569
+ - { id: e-digest-gate, source: digest, target: signal-gate }
570
+ - { id: e-gate-write, source: signal-gate, target: write-briefing }
571
+ - { id: e-write-link, source: write-briefing, target: link-briefing }
572
+ - { id: e-link-resolve, source: link-briefing, target: resolve-team }
573
+ - { id: e-resolve-post, source: resolve-team, target: post-recap }
@@ -8,6 +8,7 @@
8
8
 
9
9
  meta:
10
10
  slug: outreach-comms
11
+ domain: revenue
11
12
  name: Outreach & Comms
12
13
  description: Personalized LinkedIn and email outreach — sequenced touches against real people and companies, drafted and sent by an agent.
13
14
  icon: send
@@ -189,7 +190,11 @@ playbooks:
189
190
  - { name: target, type: entity, label: Target contact, required: true }
190
191
  - { name: campaign, type: text, label: Campaign name, required: true }
191
192
  - { name: channel, type: choice, label: Channel, defaultValue: linkedin }
192
- - { name: campaignContext, type: text, label: Value prop + call to action }
193
+ - {
194
+ name: campaignContext,
195
+ type: text,
196
+ label: Value prop + call to action,
197
+ }
193
198
  executor: is-agent
194
199
  grants:
195
200
  - linkedin_send_invite