@synap-core/workspace-templates 0.10.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 +74 -34
- package/dist/index.js +1177 -11
- package/dist/index.js.map +1 -1
- package/package.json +16 -25
- package/src/base.template.test.ts +25 -19
- package/src/base.yaml +12 -2
- package/src/blockchain-ecosystem.yaml +106 -0
- package/src/builder.yaml +19 -1
- package/src/business-model.yaml +351 -0
- package/src/catalog.ts +55 -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 +259 -1
- package/src/life-os.yaml +14 -0
- package/src/networking.yaml +14 -3
- package/src/seed-obligation.test.ts +181 -0
- package/src/shared-role-ssot.test.ts +4 -1
- package/src/templates.ts +1132 -15
- package/src/the-arch.yaml +4 -0
- package/src/types.ts +32 -34
- 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/src/foundation.yaml
CHANGED
|
@@ -528,6 +528,68 @@ profiles:
|
|
|
528
528
|
valueType: string
|
|
529
529
|
inputType: textarea
|
|
530
530
|
|
|
531
|
+
# ── Roles the BUSINESS MODEL needs, and why they live here ─────────────────
|
|
532
|
+
# `porteur` and `funder` are hats, not kinds: the same person entity is a
|
|
533
|
+
# porteur of one venture and a funder of another, and the hat is revocable
|
|
534
|
+
# while the person is not. They are declared HERE because foundation.yaml is
|
|
535
|
+
# the shared-role SSOT (`shared-role-ssot.test.ts` derives the guarded
|
|
536
|
+
# vocabulary from this file) — declaring them in business-model.yaml instead
|
|
537
|
+
# would fork a second identity for a hat the whole pod refers to.
|
|
538
|
+
- slug: porteur
|
|
539
|
+
displayName: Porteur
|
|
540
|
+
icon: user-check
|
|
541
|
+
color: amber
|
|
542
|
+
description: The person carrying a venture — the one whose commitment, skills, and constraints the model actually rests on. Distinct from `client` (who pays) and `partner` (who collaborates).
|
|
543
|
+
scope: shared
|
|
544
|
+
profileKind: role
|
|
545
|
+
applicableKinds:
|
|
546
|
+
- person
|
|
547
|
+
properties:
|
|
548
|
+
- slug: brings
|
|
549
|
+
label: What They Bring
|
|
550
|
+
valueType: string
|
|
551
|
+
inputType: textarea
|
|
552
|
+
description: The specific competence, network, capital, or legitimacy this person contributes that the model depends on.
|
|
553
|
+
- slug: commitmentLevel
|
|
554
|
+
label: Commitment
|
|
555
|
+
valueType: string
|
|
556
|
+
inputType: select
|
|
557
|
+
enumValues: [full-time, part-time, advisory, intermittent]
|
|
558
|
+
- slug: constraints
|
|
559
|
+
label: Constraints
|
|
560
|
+
valueType: string
|
|
561
|
+
inputType: textarea
|
|
562
|
+
description: What limits this porteur — time, cash runway, geography, non-compete, family. A model that ignores these is fiction.
|
|
563
|
+
- slug: funder
|
|
564
|
+
displayName: Funder
|
|
565
|
+
icon: banknote
|
|
566
|
+
color: green
|
|
567
|
+
description: A source of non-revenue money or subsidy — a bank, a public scheme, an investor, a grantor. A hat worn by a company or a person, never a kind of its own.
|
|
568
|
+
scope: shared
|
|
569
|
+
profileKind: role
|
|
570
|
+
applicableKinds:
|
|
571
|
+
- company
|
|
572
|
+
- person
|
|
573
|
+
properties:
|
|
574
|
+
- slug: fundingType
|
|
575
|
+
label: Funding Type
|
|
576
|
+
valueType: string
|
|
577
|
+
inputType: select
|
|
578
|
+
enumValues: [grant, subsidy, loan, equity, guarantee, prize, other]
|
|
579
|
+
- slug: amountCommitted
|
|
580
|
+
label: Amount Committed
|
|
581
|
+
valueType: number
|
|
582
|
+
inputType: number
|
|
583
|
+
- slug: eligibilityConditions
|
|
584
|
+
label: Eligibility Conditions
|
|
585
|
+
valueType: string
|
|
586
|
+
inputType: textarea
|
|
587
|
+
description: The conditions the venture must satisfy to keep this money. These are the constraints most often discovered too late.
|
|
588
|
+
- slug: decisionDate
|
|
589
|
+
label: Decision Date
|
|
590
|
+
valueType: date
|
|
591
|
+
inputType: date
|
|
592
|
+
|
|
531
593
|
views:
|
|
532
594
|
- name: Mission & Vision
|
|
533
595
|
slug: mission-vision
|
package/src/grants.yaml
CHANGED
|
@@ -43,6 +43,16 @@ dependencies:
|
|
|
43
43
|
kind: workspace
|
|
44
44
|
relation: require
|
|
45
45
|
reason: "Provides the pod-wide client / partner / sponsor role vocabulary this workspace reuses."
|
|
46
|
+
# This template REFERENCES the pod-wide `grant-program` kind (the per-chain
|
|
47
|
+
# funding-program parent) that the blockchain-ecosystem overlay defines. The
|
|
48
|
+
# 3-layer grants spine is program → round → grant_submission: `round` (owned
|
|
49
|
+
# here) `belongs_to` a `grant-program` (owned there). `require` installs that
|
|
50
|
+
# overlay FIRST so `grant-program` exists (pod-wide) before this template
|
|
51
|
+
# provisions the edge + deadline view — same deps-first pattern as foundation.
|
|
52
|
+
- slug: blockchain-ecosystem
|
|
53
|
+
kind: workspace
|
|
54
|
+
relation: require
|
|
55
|
+
reason: "Provides the pod-wide grant-program kind (per-chain funding programs) this workspace's rounds belong to."
|
|
46
56
|
|
|
47
57
|
workspace:
|
|
48
58
|
name: Grants Pipeline
|
|
@@ -873,11 +883,26 @@ views:
|
|
|
873
883
|
- averageAwardAmount
|
|
874
884
|
sortBy: roundNumber
|
|
875
885
|
sortDir: desc
|
|
886
|
+
# Round deadlines on one time axis. Scoped to `round` ONLY: rounds are the
|
|
887
|
+
# deadline SSOT (they carry `submissionDeadline`); `grant-program` carries
|
|
888
|
+
# `nextDeadline`, which the calendar's dateField fallback chain
|
|
889
|
+
# (dueDate/startDate/startTime/date) never resolves, so a program would land
|
|
890
|
+
# dateless in the "Unscheduled" backlog. Plotting programs + rounds on ONE
|
|
891
|
+
# timeline is a deferred UI-phase item. `colorField` (NOT `colorBy` — the
|
|
892
|
+
# calendar adapter reads `colorField`) colors each round by its status.
|
|
893
|
+
- name: Deadlines
|
|
894
|
+
type: calendar
|
|
895
|
+
scopeProfileSlug: round
|
|
896
|
+
description: Upcoming round submission deadlines, on one time axis.
|
|
897
|
+
config:
|
|
898
|
+
dateField: properties.submissionDeadline
|
|
899
|
+
colorField: status
|
|
900
|
+
# No `coverField`: grant-project has no image property (`summary` is prose, so it
|
|
901
|
+
# rendered as a text/blank cover). Fall back to the gallery's clean default cover.
|
|
876
902
|
- name: Awarded Projects
|
|
877
903
|
type: gallery
|
|
878
904
|
scopeProfileSlug: grant-project
|
|
879
905
|
config:
|
|
880
|
-
coverField: summary
|
|
881
906
|
showFields:
|
|
882
907
|
- currentStatus
|
|
883
908
|
- awardedRoundCount
|
|
@@ -907,6 +932,35 @@ entityLinks:
|
|
|
907
932
|
targetProfileSlug: company
|
|
908
933
|
type: submitted_by
|
|
909
934
|
label: Submitted By
|
|
935
|
+
# The application belongs to the engagement that pursued it (contract lives in
|
|
936
|
+
# the composed operations base). Closes the spine on the submission side.
|
|
937
|
+
- sourceProfileSlug: grant_submission
|
|
938
|
+
targetProfileSlug: contract
|
|
939
|
+
type: for_contract
|
|
940
|
+
label: Engagement
|
|
941
|
+
# The commercial bridge: a grant application maps to its fee/commission `deal`,
|
|
942
|
+
# so a grant win is tied to the money. Mirrors networking.yaml's canonical
|
|
943
|
+
# `intro → billed_as → deal` commercial-mapping predicate (same edge type,
|
|
944
|
+
# reused for one consistent meaning across the pod graph). `deal` resolves here
|
|
945
|
+
# because it is a SYSTEM profile (validate.ts `SYSTEM_PROFILES`) — so this needs
|
|
946
|
+
# NO crm compose (grants stays an operations overlay, not a CRM). The deal lives
|
|
947
|
+
# in the CRM workspace; this is a cross-workspace relation, consistent with
|
|
948
|
+
# grant_submission's other cross-domain edges (submitted_by → company).
|
|
949
|
+
- sourceProfileSlug: grant_submission
|
|
950
|
+
targetProfileSlug: deal
|
|
951
|
+
type: billed_as
|
|
952
|
+
label: Billed as
|
|
953
|
+
# 3-layer spine: a round is one cycle OF a long-lived program (SCF #42 → SCF).
|
|
954
|
+
- sourceProfileSlug: round
|
|
955
|
+
targetProfileSlug: grant-program
|
|
956
|
+
type: belongs_to
|
|
957
|
+
label: Program
|
|
958
|
+
# The engagement pursues a program (contract lives in the composed operations
|
|
959
|
+
# base; grant-program in the required blockchain-ecosystem overlay).
|
|
960
|
+
- sourceProfileSlug: contract
|
|
961
|
+
targetProfileSlug: grant-program
|
|
962
|
+
type: pursues
|
|
963
|
+
label: Pursues
|
|
910
964
|
- sourceProfileSlug: grant-project
|
|
911
965
|
targetProfileSlug: round
|
|
912
966
|
type: appears_in
|
|
@@ -923,6 +977,210 @@ entityLinks:
|
|
|
923
977
|
targetProfileSlug: grant_submission
|
|
924
978
|
type: evidenced_by
|
|
925
979
|
label: Evidenced By
|
|
980
|
+
|
|
981
|
+
# The BASE grant playbook — ONE reusable, data-driven process that runs an
|
|
982
|
+
# engagement through ANY program. Chain-specifics (cadence, submissionTracks,
|
|
983
|
+
# trancheModel, nextDeadline) are READ from the `grantProgram` entity at runtime,
|
|
984
|
+
# NOT hardcoded per chain — so one playbook serves Stellar SCF, Optimism RetroPGF,
|
|
985
|
+
# etc. Only a genuinely different-SHAPED program later earns its own override
|
|
986
|
+
# playbook (base + data-driven-override decision). Generalizes the Stellar staged
|
|
987
|
+
# advisor (CP `stellar-grant-client`) into a template. Bound to `contract` (the
|
|
988
|
+
# engagement, per decision — NOT company); targets a `grant-program` param — the
|
|
989
|
+
# 3-layer spine (contract → grant-program → round → grant_submission) made runnable.
|
|
990
|
+
playbooks:
|
|
991
|
+
- name: Grant Process
|
|
992
|
+
description: Run an engagement through a grant program end to end — identify fit, draft the application, submit, track the decision, and deliver the tranches. Reads the program's own cadence / tracks / tranche model so one playbook fits every chain.
|
|
993
|
+
goalTemplate: >-
|
|
994
|
+
Advance this engagement ({contract}) through the grant program
|
|
995
|
+
({grantProgram}) it is pursuing. FIRST read the program entity's own data —
|
|
996
|
+
its cadence, submissionTracks, trancheModel, and nextDeadline — plus the
|
|
997
|
+
client context on the contract, and let THOSE drive the plan: do not assume
|
|
998
|
+
a chain's mechanics, read them from the program. Then work the stages in
|
|
999
|
+
order and keep the client's `grant_submission` card as the durable state of
|
|
1000
|
+
record:
|
|
1001
|
+
|
|
1002
|
+
1. Identify — understand the project and which of the program's
|
|
1003
|
+
submissionTracks fit; set the submission `stage` to `scope`.
|
|
1004
|
+
2. Draft — write the abstract, then the full proposal (products & services,
|
|
1005
|
+
success criteria, GTM, traction); move `stage` `abstract` → `build` and
|
|
1006
|
+
fill the submission's body fields.
|
|
1007
|
+
3. Submit — finalize against the program's nextDeadline and submit; once
|
|
1008
|
+
submitted, set `stage` to `awaiting-results`.
|
|
1009
|
+
4. Awaiting — track the decision and keep `status` current
|
|
1010
|
+
(in-review / waiting-client / needs-document / blocked).
|
|
1011
|
+
5. Awarded — on a decision, set `outcome` (awarded / rejected / partial)
|
|
1012
|
+
and record the awarded amount.
|
|
1013
|
+
6. Delivering — for an award, work the program's trancheModel: fill the
|
|
1014
|
+
tranche deliverables and update trancheCompletion as each milestone ships.
|
|
1015
|
+
|
|
1016
|
+
Never fabricate — leave a field blank rather than guess. Every write is
|
|
1017
|
+
proposal-gated for the user to review.
|
|
1018
|
+
subjectProfile:
|
|
1019
|
+
profileSlug: contract
|
|
1020
|
+
params:
|
|
1021
|
+
- { name: contract, type: entity, profileSlug: contract, label: Engagement, required: true }
|
|
1022
|
+
- { name: grantProgram, type: entity, profileSlug: grant-program, label: Grant Program, required: true }
|
|
1023
|
+
# A grant pursuit is an ENGAGEMENT with a before/during/after lifecycle that
|
|
1024
|
+
# outlives any one working session (drafting, then weeks of waiting on the
|
|
1025
|
+
# funder, then tranche delivery), so this is a PROJECT template: its stages
|
|
1026
|
+
# coordinate a long-running container rather than script one focus session.
|
|
1027
|
+
scope: project
|
|
1028
|
+
# Generic stages, mapped onto grant_submission's own stage/status/outcome +
|
|
1029
|
+
# tranche vocab so the run can maintain the card as it advances.
|
|
1030
|
+
# `category` is the closed rollup bucket (backlog|planned|started|paused|
|
|
1031
|
+
# completed|canceled), chosen by MEANING: `identify` is pre-commitment
|
|
1032
|
+
# scoping (planned); `awaiting` is genuinely blocked on the funder (paused);
|
|
1033
|
+
# `awarded` stays `started` because delivery follows an award — a rejected
|
|
1034
|
+
# pursuit's terminality lives on the submission's own `outcome` field, not
|
|
1035
|
+
# here.
|
|
1036
|
+
stages:
|
|
1037
|
+
- { key: identify, name: Identify, category: planned, description: "Understand the project and which of the program's submission tracks fit — before any writing.", goal: "A clear project understanding and the fitting submission track; submission stage = scope." }
|
|
1038
|
+
- { key: draft, name: Draft, category: started, description: "Write the abstract, then the full proposal, from the client's real material.", goal: "Abstract then full proposal drafted; submission stage abstract → build with body fields filled." }
|
|
1039
|
+
- { key: submit, name: Submit, category: started, description: "Finalize against the program deadline and submit the application.", goal: "Application submitted on time; submission stage = awaiting-results." }
|
|
1040
|
+
- { key: awaiting, name: Awaiting, category: paused, description: "Track the decision and keep the client status current.", goal: "Submission status reflects where the decision stands (in-review / waiting-client / needs-document / blocked)." }
|
|
1041
|
+
- { key: awarded, name: Awarded, category: started, description: "Record the funder's decision and the awarded amount.", goal: "Submission outcome set (awarded / rejected / partial) with the awarded amount." }
|
|
1042
|
+
- { key: delivering, name: Delivering, category: started, description: "Work the program's tranche model — ship and record each tranche.", goal: "Tranche deliverables filled and trancheCompletion updated as milestones ship." }
|
|
1043
|
+
executor: is-agent
|
|
1044
|
+
grants:
|
|
1045
|
+
- entity.create
|
|
1046
|
+
status: active
|
|
1047
|
+
|
|
1048
|
+
# Makes the spine ALIVE: a daily maintenance run that keeps each grant
|
|
1049
|
+
# application's `grant_submission` card current (advances stage + status) — the
|
|
1050
|
+
# durable, kanban-visible state of record. Generalizes the Stellar CP "Client
|
|
1051
|
+
# Sync" automation (read comms → assess → advance submission) into a chain-
|
|
1052
|
+
# AGNOSTIC workspace-template automation: it advances using grant_submission's
|
|
1053
|
+
# OWN stage/status vocab, so it works for any program (no Stellar-specific
|
|
1054
|
+
# assumptions). Same fan-out-safe shape the corpus uses (cron → in-flow `query`
|
|
1055
|
+
# → `loop`), NOT the `inputStrategy:'query'` playbook fan-out (which has the
|
|
1056
|
+
# known gap).
|
|
1057
|
+
automations:
|
|
1058
|
+
- key: grant-submission-sync
|
|
1059
|
+
name: Grant Submission Sync
|
|
1060
|
+
description: >-
|
|
1061
|
+
Once a day, keep each grant application's grant_submission card current —
|
|
1062
|
+
read the submission's recent activity, assess where the pursuit stands, and
|
|
1063
|
+
advance its stage + status so the pipeline stays the durable, kanban-visible
|
|
1064
|
+
state of record.
|
|
1065
|
+
triggerType: cron
|
|
1066
|
+
triggerConfig:
|
|
1067
|
+
expression: "0 8 * * *"
|
|
1068
|
+
# DRAFT by default (deliberate safety): this runs a paid AI assess per
|
|
1069
|
+
# grant_submission every day, so installing the suite must NOT silently start
|
|
1070
|
+
# paid runs. Flip to `active` to let the pipeline self-maintain — same
|
|
1071
|
+
# enabled-off-by-default convention as the radar / CRM-hygiene playbooks.
|
|
1072
|
+
status: draft
|
|
1073
|
+
metadata:
|
|
1074
|
+
templateKey: grant-submission-sync
|
|
1075
|
+
flow:
|
|
1076
|
+
nodes:
|
|
1077
|
+
- id: trigger
|
|
1078
|
+
type: trigger
|
|
1079
|
+
position: { x: 0, y: 0 }
|
|
1080
|
+
data:
|
|
1081
|
+
label: Daily at 08:00
|
|
1082
|
+
triggerType: cron
|
|
1083
|
+
config:
|
|
1084
|
+
expression: "0 8 * * *"
|
|
1085
|
+
|
|
1086
|
+
# READ-ONLY builtin + SELECTION. The grant_submission cards ARE the grant
|
|
1087
|
+
# pursuits (each is created by the `Grant Process` playbook at pursuit
|
|
1088
|
+
# start), so querying them directly is the safe selection: it touches ONLY
|
|
1089
|
+
# real pursuits and never fabricates a card for a non-grant contract.
|
|
1090
|
+
#
|
|
1091
|
+
# ── CREATE-IF-MISSING (deferred, flagged) ────────────────────────────
|
|
1092
|
+
# The Stellar reference does create-if-missing by querying its subject
|
|
1093
|
+
# (companies) and upserting the submission. Generalizing that safely means
|
|
1094
|
+
# selecting "contracts that PURSUE a grant program" — but `entity.query`
|
|
1095
|
+
# filters JSONB PROPERTIES only, it cannot filter by a `pursues` EDGE, and
|
|
1096
|
+
# there is no query-fan-out capability to enumerate edge-linked subjects.
|
|
1097
|
+
# Querying ALL contracts and creating a card per contract would spuriously
|
|
1098
|
+
# create submissions for every non-grant engagement. So creation stays the
|
|
1099
|
+
# `Grant Process` playbook's job (the agent creates the card when a pursuit
|
|
1100
|
+
# begins) and this automation MAINTAINS. Fully-generalized create-if-
|
|
1101
|
+
# missing needs an edge-aware selection capability that does not exist yet.
|
|
1102
|
+
- id: list-submissions
|
|
1103
|
+
type: capability
|
|
1104
|
+
position: { x: 0, y: 120 }
|
|
1105
|
+
data:
|
|
1106
|
+
label: List grant submissions to maintain
|
|
1107
|
+
verbId: entity.query
|
|
1108
|
+
inputMapping:
|
|
1109
|
+
profileSlug: grant_submission
|
|
1110
|
+
limit: "100"
|
|
1111
|
+
|
|
1112
|
+
- id: submissions-loop
|
|
1113
|
+
type: loop
|
|
1114
|
+
position: { x: 0, y: 240 }
|
|
1115
|
+
data:
|
|
1116
|
+
label: For each grant submission
|
|
1117
|
+
iteratorExpression: steps.list-submissions.output.entities
|
|
1118
|
+
itemVariable: submission
|
|
1119
|
+
|
|
1120
|
+
# READ-ONLY builtin. Recent activity on THIS submission's thread — the raw
|
|
1121
|
+
# signal the assess pass reads to place the pursuit's stage/status.
|
|
1122
|
+
- id: read-activity
|
|
1123
|
+
type: messages_query
|
|
1124
|
+
position: { x: 0, y: 360 }
|
|
1125
|
+
data:
|
|
1126
|
+
label: Read the submission's recent activity
|
|
1127
|
+
subjectEntityId: "{{loop.item.id}}"
|
|
1128
|
+
limit: 40
|
|
1129
|
+
|
|
1130
|
+
# READ-ONLY (pure compute). ONE assess pass → STRICT JSON naming the
|
|
1131
|
+
# submission's current stage + status, constrained to grant_submission's
|
|
1132
|
+
# OWN enum vocab so it maps straight back onto the card (chain-agnostic).
|
|
1133
|
+
- id: assess
|
|
1134
|
+
type: capability
|
|
1135
|
+
position: { x: 0, y: 480 }
|
|
1136
|
+
data:
|
|
1137
|
+
label: Assess the submission's stage + status → JSON
|
|
1138
|
+
verbId: ai.generate
|
|
1139
|
+
inputMapping:
|
|
1140
|
+
json: "true"
|
|
1141
|
+
maxTokens: "500"
|
|
1142
|
+
system: >-
|
|
1143
|
+
You are a strict data-extraction function for a grants pipeline.
|
|
1144
|
+
Read a grant application's recent activity and report where the
|
|
1145
|
+
application currently stands. Respond with STRICT JSON and NOTHING
|
|
1146
|
+
else. Never fabricate — if the activity does not clearly indicate a
|
|
1147
|
+
value, keep the submission's current one.
|
|
1148
|
+
prompt: >-
|
|
1149
|
+
The grant submission: {{loop.item.title}}
|
|
1150
|
+
Current stage: {{loop.item.properties.stage}}
|
|
1151
|
+
Current status: {{loop.item.properties.status}}
|
|
1152
|
+
|
|
1153
|
+
Recent activity (oldest first):
|
|
1154
|
+
{{steps.read-activity.output.messages}}
|
|
1155
|
+
|
|
1156
|
+
Report the submission's current state as STRICT JSON, using ONLY
|
|
1157
|
+
these vocabularies and NOTHING else:
|
|
1158
|
+
{
|
|
1159
|
+
"stage": "scope|abstract|build|awaiting-results",
|
|
1160
|
+
"status": "answered|waiting-client|needs-document|in-review|blocked"
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
# GOVERNED WRITE via `output/entity_update` — advances the card's stage +
|
|
1164
|
+
# status (the durable, kanban-visible state). Governed like every output
|
|
1165
|
+
# write: routes to a proposal for review unless auto-approved.
|
|
1166
|
+
- id: advance-submission
|
|
1167
|
+
type: output
|
|
1168
|
+
position: { x: 0, y: 600 }
|
|
1169
|
+
data:
|
|
1170
|
+
label: Advance the submission stage + status
|
|
1171
|
+
outputType: entity_update
|
|
1172
|
+
config:
|
|
1173
|
+
entityId: "{{loop.item.id}}"
|
|
1174
|
+
properties:
|
|
1175
|
+
stage: "{{steps.assess.output.stage}}"
|
|
1176
|
+
status: "{{steps.assess.output.status}}"
|
|
1177
|
+
edges:
|
|
1178
|
+
- { id: gs-trigger-list, source: trigger, target: list-submissions }
|
|
1179
|
+
- { id: gs-list-loop, source: list-submissions, target: submissions-loop }
|
|
1180
|
+
- { id: gs-loop-read, source: submissions-loop, target: read-activity }
|
|
1181
|
+
- { id: gs-read-assess, source: read-activity, target: assess }
|
|
1182
|
+
- { id: gs-assess-advance, source: assess, target: advance-submission }
|
|
1183
|
+
|
|
926
1184
|
app:
|
|
927
1185
|
kind: stored
|
|
928
1186
|
cellKey: entity-detail
|
package/src/life-os.yaml
CHANGED
|
@@ -731,6 +731,16 @@ profiles:
|
|
|
731
731
|
inputType: text
|
|
732
732
|
|
|
733
733
|
views:
|
|
734
|
+
# Knowledge is the pod-owned, validated-memory kind. Notes remain the
|
|
735
|
+
# workspace's capture inbox; this view deliberately reuses the system
|
|
736
|
+
# contract instead of recreating a competing knowledge schema here.
|
|
737
|
+
- name: Knowledge
|
|
738
|
+
slug: knowledge
|
|
739
|
+
type: table
|
|
740
|
+
scopeProfileSlug: knowledge
|
|
741
|
+
description: Validated insights and cautions, with their long-form context and evidence kept in the graph.
|
|
742
|
+
config:
|
|
743
|
+
columns: [title, knowledgeForm]
|
|
734
744
|
- name: All Notes
|
|
735
745
|
type: table
|
|
736
746
|
scopeProfileSlug: note
|
|
@@ -1160,6 +1170,10 @@ layout:
|
|
|
1160
1170
|
appId: dashboard
|
|
1161
1171
|
label: Brain Home
|
|
1162
1172
|
icon: Home
|
|
1173
|
+
- kind: profile
|
|
1174
|
+
profileSlug: knowledge
|
|
1175
|
+
label: Knowledge
|
|
1176
|
+
icon: Brain
|
|
1163
1177
|
- kind: profile
|
|
1164
1178
|
profileSlug: note
|
|
1165
1179
|
label: Notes
|
package/src/networking.yaml
CHANGED
|
@@ -212,6 +212,11 @@ profiles:
|
|
|
212
212
|
description: A company or person that provides a capability The Arch can broker a client need against.
|
|
213
213
|
scope: shared
|
|
214
214
|
profileKind: role
|
|
215
|
+
# Shared supply-role category — the intro-matcher's roster is now
|
|
216
|
+
# entity.query { roleCategory: "provider" }, spanning EVERY role tagged
|
|
217
|
+
# "provider" (this one in a public install; + blockchain-ecosystem's web3
|
|
218
|
+
# supply roles when The Arch installs both). One string, kept in sync there.
|
|
219
|
+
roleCategory: provider
|
|
215
220
|
applicableKinds:
|
|
216
221
|
- company
|
|
217
222
|
- person
|
|
@@ -365,15 +370,21 @@ automations:
|
|
|
365
370
|
limit: "100"
|
|
366
371
|
|
|
367
372
|
# READ-ONLY builtin. The provider roster to match against — companies /
|
|
368
|
-
# people wearing
|
|
373
|
+
# people wearing ANY role tagged `roleCategory: "provider"`. Dynamic by
|
|
374
|
+
# design: a public install matches its `solution-provider`s; The Arch
|
|
375
|
+
# (which also installs blockchain-ecosystem) additionally matches the web3
|
|
376
|
+
# supply roles; any future supply role tagged "provider" auto-joins with
|
|
377
|
+
# ZERO edits here. No `profileSlug` → no per-role hardcoding, no dependency
|
|
378
|
+
# on blockchain-ecosystem (a public installer simply has no web3 roles to
|
|
379
|
+
# match, so nothing leaks).
|
|
369
380
|
- id: list-providers
|
|
370
381
|
type: capability
|
|
371
382
|
position: { x: 0, y: 240 }
|
|
372
383
|
data:
|
|
373
|
-
label: List the
|
|
384
|
+
label: List the provider roster (any roleCategory=provider)
|
|
374
385
|
verbId: entity.query
|
|
375
386
|
inputMapping:
|
|
376
|
-
|
|
387
|
+
roleCategory: provider
|
|
377
388
|
limit: "100"
|
|
378
389
|
|
|
379
390
|
- id: needs-loop
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed-obligation guard.
|
|
3
|
+
*
|
|
4
|
+
* THE BUG THIS EXISTS TO STOP, stated plainly: the applier has supported
|
|
5
|
+
* `seedEntities` and `suggestedRelations` since
|
|
6
|
+
* `create-workspace-from-definition.ts` learned to read them, and for 31 of 31
|
|
7
|
+
* shipped templates that support was exercised exactly zero times. The
|
|
8
|
+
* observable result is a workspace that installs 15 views over 0 entities —
|
|
9
|
+
* every board empty, every chart blank, and the user left to guess what the
|
|
10
|
+
* pack was for. Two real workspaces in the field reproduced this identically,
|
|
11
|
+
* which is the signature of a template defect rather than user behaviour.
|
|
12
|
+
*
|
|
13
|
+
* The rule: a template that ships VIEWS over profiles it OWNS must also ship
|
|
14
|
+
* at least one seed entity, so the workspace opens with something in it.
|
|
15
|
+
*
|
|
16
|
+
* The escape hatch is deliberate and narrow. `SEEDLESS_BY_DESIGN` lists the
|
|
17
|
+
* templates that predate this guard, each with the reason it is still empty.
|
|
18
|
+
* Adding a line there is legal; adding one silently is not, because the list is
|
|
19
|
+
* asserted to be exact — a template that gains seeds must leave the list in the
|
|
20
|
+
* same commit. That is what turns this from a lint into a shrinking backlog.
|
|
21
|
+
*
|
|
22
|
+
* What a seed is NOT: filler. Seeding three fake companies teaches nothing and
|
|
23
|
+
* gets deleted in the first minute. Seed the QUESTIONS, the stages, the
|
|
24
|
+
* checklist — the reusable part of the method. See `business-model.yaml`, which
|
|
25
|
+
* seeds the nine interrogations rather than an example business.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { describe, expect, it } from "vitest";
|
|
29
|
+
import { WORKSPACE_TEMPLATES } from "./templates.js";
|
|
30
|
+
import type { WorkspaceYaml } from "./types.js";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Templates that ship views over their own profiles and NO seed entities.
|
|
34
|
+
*
|
|
35
|
+
* Every entry is a workspace that currently installs empty. Keep the reason
|
|
36
|
+
* honest — "not done yet" is a valid reason and a useful one, because it reads
|
|
37
|
+
* as debt instead of as a decision.
|
|
38
|
+
*/
|
|
39
|
+
const SEEDLESS_BY_DESIGN: Record<string, string> = {
|
|
40
|
+
"agent-fleet": "Pre-dates this guard. Fleet contents are per-pod; needs a seed pass.",
|
|
41
|
+
base: "Pod substrate, not a workspace a user opens. Seeds would land in every workspace.",
|
|
42
|
+
"blockchain-ecosystem": "Pre-dates this guard; needs a seed pass.",
|
|
43
|
+
"brand-library": "Pre-dates this guard; brand assets are inherently per-company.",
|
|
44
|
+
"builder-workspace": "Pre-dates this guard; needs a seed pass.",
|
|
45
|
+
"business-developer": "Pre-dates this guard; overlay on CRM, seeds belong on the base.",
|
|
46
|
+
"civic-france": "Pre-dates this guard; needs a seed pass.",
|
|
47
|
+
communication: "Pre-dates this guard; needs a seed pass.",
|
|
48
|
+
"content-os": "Pre-dates this guard; needs a seed pass.",
|
|
49
|
+
"content-studio": "Pre-dates this guard; needs a seed pass.",
|
|
50
|
+
crm: "Pre-dates this guard; needs a seed pass.",
|
|
51
|
+
"dev-dashboard": "Pre-dates this guard; needs a seed pass.",
|
|
52
|
+
ecosystem: "Pre-dates this guard; needs a seed pass.",
|
|
53
|
+
"enterprise-os": "Pre-dates this guard; needs a seed pass.",
|
|
54
|
+
finance:
|
|
55
|
+
"Pre-dates this guard. Confirmed empty in the field: 15 views, 6 profiles, 0 entities. First candidate for a seed pass — a chart of accounts and a metric set are genuinely reusable.",
|
|
56
|
+
foundation:
|
|
57
|
+
"Pre-dates this guard. Strategic DNA is company-specific, but the INTERROGATION is not — see business-model.yaml, which seeds it as an overlay.",
|
|
58
|
+
grants: "Pre-dates this guard; needs a seed pass.",
|
|
59
|
+
hr: "Pre-dates this guard; needs a seed pass.",
|
|
60
|
+
"internal-runbook": "Pre-dates this guard; needs a seed pass.",
|
|
61
|
+
legal: "Pre-dates this guard; needs a seed pass.",
|
|
62
|
+
"life-os": "Pre-dates this guard; needs a seed pass.",
|
|
63
|
+
"marketing-campaign": "Pre-dates this guard; needs a seed pass.",
|
|
64
|
+
"meetings-knowledge": "Pre-dates this guard; needs a seed pass.",
|
|
65
|
+
networking: "Pre-dates this guard; needs a seed pass.",
|
|
66
|
+
operations: "Pre-dates this guard; needs a seed pass.",
|
|
67
|
+
"outreach-comms": "Pre-dates this guard; needs a seed pass.",
|
|
68
|
+
"project-management": "Pre-dates this guard; needs a seed pass.",
|
|
69
|
+
"research-base":
|
|
70
|
+
"Pre-dates this guard. Radar reproduced the empty-workspace pathology on top of this substrate; seed pass wanted.",
|
|
71
|
+
"research-decision": "Overlay on research-base; the method is a playbook, not entities.",
|
|
72
|
+
social: "Pre-dates this guard; needs a seed pass.",
|
|
73
|
+
"the-arch": "Pre-dates this guard; needs a seed pass.",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Slugs of profiles this template declares itself (owned or re-declared). */
|
|
77
|
+
function declaredProfileSlugs(tpl: WorkspaceYaml): Set<string> {
|
|
78
|
+
const profiles = (tpl as { profiles?: Array<{ slug?: string }> }).profiles ?? [];
|
|
79
|
+
return new Set(profiles.map((p) => p.slug).filter((s): s is string => Boolean(s)));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Views whose scope is a profile the template declares. */
|
|
83
|
+
function viewsOverOwnProfiles(tpl: WorkspaceYaml): number {
|
|
84
|
+
const declared = declaredProfileSlugs(tpl);
|
|
85
|
+
const views =
|
|
86
|
+
(tpl as { views?: Array<{ scopeProfileSlug?: string; scopeProfileSlugs?: string[] }> })
|
|
87
|
+
.views ?? [];
|
|
88
|
+
return views.filter((v) => {
|
|
89
|
+
const scoped = [v.scopeProfileSlug, ...(v.scopeProfileSlugs ?? [])].filter(Boolean);
|
|
90
|
+
return scoped.some((s) => declared.has(s as string));
|
|
91
|
+
}).length;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function seedCount(tpl: WorkspaceYaml): number {
|
|
95
|
+
const t = tpl as {
|
|
96
|
+
seedEntities?: unknown[];
|
|
97
|
+
suggestedEntities?: unknown[];
|
|
98
|
+
};
|
|
99
|
+
return (t.seedEntities?.length ?? 0) + (t.suggestedEntities?.length ?? 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const slugOf = (slug: string, tpl: WorkspaceYaml) =>
|
|
103
|
+
(tpl as { meta?: { slug?: string } }).meta?.slug ?? slug;
|
|
104
|
+
|
|
105
|
+
describe("workspace-templates — seed obligation (corpus)", () => {
|
|
106
|
+
it("every template with views over its own profiles ships seeds, or is declared seedless", () => {
|
|
107
|
+
const undeclared: string[] = [];
|
|
108
|
+
|
|
109
|
+
for (const [key, tpl] of Object.entries(WORKSPACE_TEMPLATES)) {
|
|
110
|
+
const slug = slugOf(key, tpl);
|
|
111
|
+
if (viewsOverOwnProfiles(tpl) === 0) continue;
|
|
112
|
+
if (seedCount(tpl) > 0) continue;
|
|
113
|
+
if (slug in SEEDLESS_BY_DESIGN) continue;
|
|
114
|
+
undeclared.push(
|
|
115
|
+
`${slug}: ships ${viewsOverOwnProfiles(tpl)} view(s) over its own profiles and 0 seed entities. ` +
|
|
116
|
+
`Add seedEntities (seed the METHOD, not fake data), or add "${slug}" to SEEDLESS_BY_DESIGN with a reason.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
expect(
|
|
121
|
+
undeclared,
|
|
122
|
+
`Templates would install a workspace of empty views:\n - ${undeclared.join("\n - ")}`
|
|
123
|
+
).toEqual([]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("the seedless allowlist is exact — a template that gains seeds must leave it", () => {
|
|
127
|
+
const stale: string[] = [];
|
|
128
|
+
|
|
129
|
+
for (const [key, tpl] of Object.entries(WORKSPACE_TEMPLATES)) {
|
|
130
|
+
const slug = slugOf(key, tpl);
|
|
131
|
+
if (!(slug in SEEDLESS_BY_DESIGN)) continue;
|
|
132
|
+
if (seedCount(tpl) > 0) {
|
|
133
|
+
stale.push(`${slug}: now seeds ${seedCount(tpl)} entity(ies) — remove it from SEEDLESS_BY_DESIGN`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const corpusSlugs = new Set(
|
|
138
|
+
Object.entries(WORKSPACE_TEMPLATES).map(([key, tpl]) => slugOf(key, tpl))
|
|
139
|
+
);
|
|
140
|
+
for (const slug of Object.keys(SEEDLESS_BY_DESIGN)) {
|
|
141
|
+
if (!corpusSlugs.has(slug)) {
|
|
142
|
+
stale.push(`${slug}: listed in SEEDLESS_BY_DESIGN but no such template exists`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
expect(stale, `Stale seedless allowlist:\n - ${stale.join("\n - ")}`).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("every seeded relation names entities seeded in the same template", () => {
|
|
150
|
+
// A suggested relation whose ref matches no seeded title fails SILENTLY at
|
|
151
|
+
// apply time — the workspace installs, the edge does not, and nothing says
|
|
152
|
+
// so. Catch the typo here instead of in a user's pod.
|
|
153
|
+
const dangling: string[] = [];
|
|
154
|
+
|
|
155
|
+
for (const [key, tpl] of Object.entries(WORKSPACE_TEMPLATES)) {
|
|
156
|
+
const slug = slugOf(key, tpl);
|
|
157
|
+
const t = tpl as {
|
|
158
|
+
seedEntities?: Array<{ title?: string }>;
|
|
159
|
+
suggestedEntities?: Array<{ title?: string }>;
|
|
160
|
+
suggestedRelations?: Array<{ sourceRef?: string; targetRef?: string; type?: string }>;
|
|
161
|
+
};
|
|
162
|
+
const rels = t.suggestedRelations ?? [];
|
|
163
|
+
if (rels.length === 0) continue;
|
|
164
|
+
|
|
165
|
+
const titles = new Set(
|
|
166
|
+
[...(t.seedEntities ?? []), ...(t.suggestedEntities ?? [])]
|
|
167
|
+
.map((e) => e.title)
|
|
168
|
+
.filter((x): x is string => Boolean(x))
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
for (const rel of rels) {
|
|
172
|
+
for (const ref of [rel.sourceRef, rel.targetRef]) {
|
|
173
|
+
if (!ref || titles.has(ref)) continue;
|
|
174
|
+
dangling.push(`${slug}: relation "${rel.type}" references unseeded entity "${ref}"`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
expect(dangling, `Dangling seeded relations:\n - ${dangling.join("\n - ")}`).toEqual([]);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -122,8 +122,11 @@ describe("workspace-templates — shared ecosystem role SSOT (corpus)", () => {
|
|
|
122
122
|
// Guards the derivation itself: if foundation ever stops declaring its
|
|
123
123
|
// shared roles, rule (a) would silently guard nothing.
|
|
124
124
|
expect(foundation, "foundation template must exist in the corpus").toBeDefined();
|
|
125
|
+
// `porteur` and `funder` join the relationship roles: a business model
|
|
126
|
+
// rests on WHO carries it and WHO funds it, and both are revocable hats on
|
|
127
|
+
// an existing person/company rather than kinds of their own.
|
|
125
128
|
expect([...SHARED_ROLE_SLUGS].sort()).toEqual(
|
|
126
|
-
["churned", "client", "lead", "partner", "sponsor"]
|
|
129
|
+
["churned", "client", "funder", "lead", "partner", "porteur", "sponsor"]
|
|
127
130
|
);
|
|
128
131
|
});
|
|
129
132
|
|