@synap-core/workspace-templates 0.9.4 → 0.11.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.js CHANGED
@@ -2,7 +2,11 @@
2
2
  var PACKAGE_DEPENDENCY_KINDS = [
3
3
  "workspace",
4
4
  "capability",
5
- /** @deprecated Retired by CP 0049 in favour of `workflow`. See note above. */
5
+ "skill",
6
+ "workflow",
7
+ "view",
8
+ "cell",
9
+ /** @deprecated Legacy alias for `workflow` (CP 0049). Still accepted on the wire. */
6
10
  "automation"
7
11
  ];
8
12
 
@@ -598,7 +602,7 @@ var DATA = {
598
602
  "displayName": "Person",
599
603
  "icon": "user",
600
604
  "color": "#0891B2",
601
- "scope": "WORKSPACE",
605
+ "entityScope": "pod",
602
606
  "description": "Team member or agent owner",
603
607
  "properties": [
604
608
  {
@@ -2736,269 +2740,1122 @@ var DATA = {
2736
2740
  "doneWhen": "The live agents exist with their model/provider, the providers are captured with budgets, key skills are catalogued, and agents are linked to active projects."
2737
2741
  }
2738
2742
  },
2739
- "blockchain-ecosystem": {
2743
+ "base": {
2740
2744
  "meta": {
2741
- "slug": "blockchain-ecosystem",
2742
- "domain": "strategy",
2743
- "name": "Blockchain Ecosystem",
2744
- "description": "Web3 market actors \u2014 market-makers, auditors, dev shops, KOLs \u2014 and how they connect. Overlays the Ecosystem workspace.",
2745
- "icon": "boxes",
2746
- "color": "#8B5CF6",
2745
+ "slug": "base",
2746
+ "domain": "core",
2747
+ "name": "Base",
2748
+ "description": "Universal operational defaults every workspace inherits \u2014 the report automation, the default relation vocabulary, and the default command pack.",
2749
+ "icon": "layers",
2750
+ "color": "#6B7280",
2747
2751
  "tags": [
2748
- "blockchain",
2749
- "web3",
2750
- "ecosystem",
2751
- "market-makers"
2752
+ "core",
2753
+ "defaults",
2754
+ "operational"
2752
2755
  ],
2753
- "isPublic": false
2756
+ "isPublic": true
2754
2757
  },
2755
- "dependencies": [
2756
- {
2757
- "slug": "ecosystem",
2758
- "kind": "workspace",
2759
- "relation": "compose",
2760
- "reason": "Adds web3 market actors + relations onto your Ecosystem."
2761
- }
2762
- ],
2763
2758
  "workspace": {
2764
- "name": "Ecosystem",
2765
- "description": "Web3 market actors and the relationships between them, layered onto your Ecosystem.",
2766
- "subtype": "ecosystem"
2759
+ "name": "Base",
2760
+ "description": "The pod-wide operational defaults \u2014 carried as config so one reconcile door seeds them into every workspace instead of three hardcoded seeders.",
2761
+ "proposalId": "base-v1",
2762
+ "visibility": "pod_visible"
2767
2763
  },
2768
- "profiles": [
2764
+ "profiles": [],
2765
+ "relationDefs": [
2766
+ {
2767
+ "slug": "assigned_to",
2768
+ "displayName": "Assigned To",
2769
+ "description": "Person assigned to task/project",
2770
+ "isDirectional": true,
2771
+ "uiHints": {
2772
+ "category": "workflow"
2773
+ }
2774
+ },
2769
2775
  {
2770
- "slug": "market-maker",
2771
- "displayName": "Market Maker",
2772
- "icon": "candlestick-chart",
2773
- "color": "sky",
2774
- "description": "A liquidity provider \u2014 makes markets and provides depth for a token.",
2775
- "scope": "shared",
2776
- "profileKind": "role",
2777
- "applicableKinds": [
2778
- "company",
2779
- "person"
2780
- ],
2781
- "properties": [
2782
- {
2783
- "slug": "status",
2784
- "label": "Status",
2785
- "valueType": "string",
2786
- "inputType": "select",
2787
- "enumValues": [
2788
- "prospect",
2789
- "active",
2790
- "paused",
2791
- "churned"
2792
- ]
2793
- },
2794
- {
2795
- "slug": "exchanges",
2796
- "label": "Exchanges / Venues",
2797
- "valueType": "array",
2798
- "inputType": "text"
2799
- },
2800
- {
2801
- "slug": "engagementStartDate",
2802
- "label": "Engagement Start",
2803
- "valueType": "date",
2804
- "inputType": "date"
2805
- },
2806
- {
2807
- "slug": "notes",
2808
- "label": "Notes",
2809
- "valueType": "string",
2810
- "inputType": "textarea"
2811
- }
2812
- ]
2776
+ "slug": "blocks",
2777
+ "displayName": "Blocks",
2778
+ "description": "Prevents progress on another task",
2779
+ "isDirectional": true,
2780
+ "uiHints": {
2781
+ "category": "workflow"
2782
+ }
2813
2783
  },
2814
2784
  {
2815
- "slug": "security-auditor",
2816
- "displayName": "Security Auditor",
2817
- "icon": "shield-check",
2818
- "color": "red",
2819
- "description": "A security firm or individual that audits smart contracts and protocols.",
2820
- "scope": "shared",
2821
- "profileKind": "role",
2822
- "applicableKinds": [
2823
- "company",
2824
- "person"
2825
- ],
2826
- "properties": [
2827
- {
2828
- "slug": "status",
2829
- "label": "Status",
2830
- "valueType": "string",
2831
- "inputType": "select",
2832
- "enumValues": [
2833
- "prospect",
2834
- "active",
2835
- "paused",
2836
- "churned"
2837
- ]
2838
- },
2839
- {
2840
- "slug": "specialty",
2841
- "label": "Specialty",
2842
- "valueType": "string",
2843
- "inputType": "text"
2844
- },
2845
- {
2846
- "slug": "engagementStartDate",
2847
- "label": "Engagement Start",
2848
- "valueType": "date",
2849
- "inputType": "date"
2850
- },
2851
- {
2852
- "slug": "notes",
2853
- "label": "Notes",
2854
- "valueType": "string",
2855
- "inputType": "textarea"
2856
- }
2857
- ]
2785
+ "slug": "depends_on",
2786
+ "displayName": "Depends On",
2787
+ "description": "Requires completion of another task",
2788
+ "isDirectional": true,
2789
+ "uiHints": {
2790
+ "category": "workflow"
2791
+ }
2858
2792
  },
2859
2793
  {
2860
- "slug": "dev-shop",
2861
- "displayName": "Dev Shop",
2862
- "icon": "code",
2863
- "color": "violet",
2864
- "description": "An outsourced engineering studio or build partner.",
2865
- "scope": "shared",
2866
- "profileKind": "role",
2867
- "applicableKinds": [
2868
- "company",
2869
- "person"
2870
- ],
2871
- "properties": [
2872
- {
2873
- "slug": "status",
2874
- "label": "Status",
2875
- "valueType": "string",
2876
- "inputType": "select",
2877
- "enumValues": [
2878
- "prospect",
2879
- "active",
2880
- "paused",
2881
- "churned"
2882
- ]
2883
- },
2884
- {
2885
- "slug": "specialty",
2886
- "label": "Specialty",
2887
- "valueType": "string",
2888
- "inputType": "text"
2889
- },
2890
- {
2891
- "slug": "engagementStartDate",
2892
- "label": "Engagement Start",
2893
- "valueType": "date",
2894
- "inputType": "date"
2895
- },
2896
- {
2897
- "slug": "notes",
2898
- "label": "Notes",
2899
- "valueType": "string",
2900
- "inputType": "textarea"
2901
- }
2902
- ]
2794
+ "slug": "relates_to",
2795
+ "displayName": "Relates To",
2796
+ "description": "General relationship between entities",
2797
+ "isDirectional": false,
2798
+ "uiHints": {
2799
+ "category": "reference"
2800
+ }
2903
2801
  },
2904
2802
  {
2905
- "slug": "marketing-agency",
2906
- "displayName": "Marketing Agency",
2907
- "icon": "megaphone",
2908
- "color": "pink",
2909
- "description": "A brand, growth, or content marketing agency.",
2910
- "scope": "shared",
2911
- "profileKind": "role",
2912
- "applicableKinds": [
2913
- "company",
2914
- "person"
2915
- ],
2916
- "properties": [
2917
- {
2918
- "slug": "status",
2919
- "label": "Status",
2920
- "valueType": "string",
2921
- "inputType": "select",
2922
- "enumValues": [
2923
- "prospect",
2924
- "active",
2925
- "paused",
2926
- "churned"
2927
- ]
2928
- },
2929
- {
2930
- "slug": "specialty",
2931
- "label": "Specialty",
2932
- "valueType": "string",
2933
- "inputType": "text"
2934
- },
2935
- {
2936
- "slug": "engagementStartDate",
2937
- "label": "Engagement Start",
2938
- "valueType": "date",
2939
- "inputType": "date"
2940
- },
2941
- {
2942
- "slug": "notes",
2943
- "label": "Notes",
2944
- "valueType": "string",
2945
- "inputType": "textarea"
2946
- }
2947
- ]
2803
+ "slug": "mentions",
2804
+ "displayName": "Mentions",
2805
+ "description": "Referenced in content",
2806
+ "isDirectional": true,
2807
+ "uiHints": {
2808
+ "category": "reference"
2809
+ }
2948
2810
  },
2949
2811
  {
2950
- "slug": "paid-ads-agency",
2951
- "displayName": "Paid-Ads Agency",
2952
- "icon": "badge-dollar-sign",
2953
- "color": "orange",
2954
- "description": "A performance / paid-media buying agency.",
2955
- "scope": "shared",
2956
- "profileKind": "role",
2957
- "applicableKinds": [
2958
- "company",
2959
- "person"
2960
- ],
2961
- "properties": [
2962
- {
2963
- "slug": "status",
2964
- "label": "Status",
2965
- "valueType": "string",
2966
- "inputType": "select",
2967
- "enumValues": [
2968
- "prospect",
2969
- "active",
2970
- "paused",
2971
- "churned"
2972
- ]
2973
- },
2974
- {
2975
- "slug": "channels",
2976
- "label": "Channels",
2977
- "valueType": "array",
2978
- "inputType": "text"
2979
- },
2980
- {
2981
- "slug": "engagementStartDate",
2982
- "label": "Engagement Start",
2983
- "valueType": "date",
2984
- "inputType": "date"
2985
- },
2986
- {
2987
- "slug": "notes",
2988
- "label": "Notes",
2989
- "valueType": "string",
2990
- "inputType": "textarea"
2991
- }
2992
- ]
2812
+ "slug": "links_to",
2813
+ "displayName": "Links To",
2814
+ "description": "Hyperlink or reference",
2815
+ "isDirectional": true,
2816
+ "uiHints": {
2817
+ "category": "reference"
2818
+ }
2993
2819
  },
2994
2820
  {
2995
- "slug": "kol",
2996
- "displayName": "KOL",
2997
- "icon": "star",
2998
- "color": "yellow",
2999
- "description": "A key opinion leader or influencer who amplifies reach.",
2821
+ "slug": "parent_of",
2822
+ "displayName": "Parent Of",
2823
+ "description": "Hierarchical parent relationship",
2824
+ "isDirectional": true,
2825
+ "uiHints": {
2826
+ "category": "hierarchy"
2827
+ }
2828
+ },
2829
+ {
2830
+ "slug": "tagged_with",
2831
+ "displayName": "Tagged With",
2832
+ "description": "Categorization tag",
2833
+ "isDirectional": true,
2834
+ "uiHints": {
2835
+ "category": "reference"
2836
+ }
2837
+ },
2838
+ {
2839
+ "slug": "created_by",
2840
+ "displayName": "Created By",
2841
+ "description": "Author or creator",
2842
+ "isDirectional": true,
2843
+ "uiHints": {
2844
+ "category": "social"
2845
+ }
2846
+ },
2847
+ {
2848
+ "slug": "attended_by",
2849
+ "displayName": "Attended By",
2850
+ "description": "Participant in event",
2851
+ "isDirectional": true,
2852
+ "uiHints": {
2853
+ "category": "social"
2854
+ }
2855
+ },
2856
+ {
2857
+ "slug": "belongs_to_project",
2858
+ "displayName": "Belongs To Project",
2859
+ "description": "Project membership",
2860
+ "isDirectional": true,
2861
+ "uiHints": {
2862
+ "category": "hierarchy"
2863
+ }
2864
+ },
2865
+ {
2866
+ "slug": "founder_brand_of",
2867
+ "displayName": "Founder Brand Of",
2868
+ "description": "A personal-brand project is the founder brand of a company project \u2014 links a person's brand to the company it expresses",
2869
+ "isDirectional": true,
2870
+ "uiHints": {
2871
+ "category": "hierarchy",
2872
+ "inverseLabel": "Expression of"
2873
+ }
2874
+ },
2875
+ {
2876
+ "slug": "references",
2877
+ "displayName": "References",
2878
+ "description": "Cites or refers to",
2879
+ "isDirectional": true,
2880
+ "uiHints": {
2881
+ "category": "reference"
2882
+ }
2883
+ },
2884
+ {
2885
+ "slug": "works_at",
2886
+ "displayName": "Works At",
2887
+ "description": "Person works at an organization",
2888
+ "isDirectional": true,
2889
+ "uiHints": {
2890
+ "category": "social",
2891
+ "inverseLabel": "Employs"
2892
+ }
2893
+ },
2894
+ {
2895
+ "slug": "deal_for",
2896
+ "displayName": "Deal For",
2897
+ "description": "Sales deal associated with a contact",
2898
+ "isDirectional": true,
2899
+ "uiHints": {
2900
+ "category": "workflow",
2901
+ "inverseLabel": "Has Deal"
2902
+ }
2903
+ },
2904
+ {
2905
+ "slug": "advances",
2906
+ "displayName": "Advances",
2907
+ "description": "advances the company's Foundation mission/vision goal",
2908
+ "isDirectional": true,
2909
+ "uiHints": {
2910
+ "category": "workflow",
2911
+ "inverseLabel": "Advanced by"
2912
+ }
2913
+ },
2914
+ {
2915
+ "slug": "met_at",
2916
+ "displayName": "Met at",
2917
+ "description": "Person met at an event or gathering",
2918
+ "isDirectional": true,
2919
+ "uiHints": {
2920
+ "category": "social",
2921
+ "inverseLabel": "Attendees"
2922
+ }
2923
+ },
2924
+ {
2925
+ "slug": "works_on",
2926
+ "displayName": "Works on",
2927
+ "description": "Person contributes to a project",
2928
+ "isDirectional": true,
2929
+ "uiHints": {
2930
+ "category": "workflow",
2931
+ "inverseLabel": "Contributors"
2932
+ }
2933
+ },
2934
+ {
2935
+ "slug": "has_skill",
2936
+ "displayName": "Has skill",
2937
+ "description": "Person possesses a skill or expertise",
2938
+ "isDirectional": true,
2939
+ "uiHints": {
2940
+ "category": "reference",
2941
+ "inverseLabel": "Skilled people"
2942
+ }
2943
+ },
2944
+ {
2945
+ "slug": "affiliated_with",
2946
+ "displayName": "Affiliated with",
2947
+ "description": "Person affiliated with an organization",
2948
+ "isDirectional": true,
2949
+ "uiHints": {
2950
+ "category": "social",
2951
+ "inverseLabel": "Members"
2952
+ }
2953
+ },
2954
+ {
2955
+ "slug": "knows",
2956
+ "displayName": "Knows",
2957
+ "description": "Two people know each other",
2958
+ "isDirectional": false,
2959
+ "uiHints": {
2960
+ "category": "social"
2961
+ }
2962
+ },
2963
+ {
2964
+ "slug": "discussed_with",
2965
+ "displayName": "Discussed with",
2966
+ "description": "Had a discussion or conversation with",
2967
+ "isDirectional": false,
2968
+ "uiHints": {
2969
+ "category": "social"
2970
+ }
2971
+ }
2972
+ ],
2973
+ "commands": [
2974
+ {
2975
+ "title": "Create View from Description",
2976
+ "promptTemplate": "You are helping the user create a new view from a natural language description.\n\n**User's goal:** @{arg:goal}\n\nCreate a proposal for a new view that matches this description. Include:\n- View type (table, kanban, list, etc.)\n- Filters, sorts, and scope\n- Layout configuration\n\nOutput your proposal clearly so it can be turned into a Synap view.",
2977
+ "derivedInputs": [
2978
+ {
2979
+ "name": "goal",
2980
+ "label": "Goal",
2981
+ "type": "text"
2982
+ }
2983
+ ],
2984
+ "outputMode": "view",
2985
+ "canCreateViews": true,
2986
+ "permissionsProfile": "propose_writes"
2987
+ },
2988
+ {
2989
+ "title": "Extract Entities from Text",
2990
+ "promptTemplate": "You are helping the user extract entities and relationships from text.\n\n**User's text or selection:**\n\n@{context:text}\n\nAnalyze this content and propose:\n1. New entities to create (Notes, Tasks, People, Companies, etc.) with their properties\n2. Relationships between entities (blocks, related_to, parent_of, etc.)\n\nOutput proposals that the user can review and approve. Do not create anything directly\u2014always propose.",
2991
+ "derivedInputs": [],
2992
+ "outputMode": "proposal",
2993
+ "canCreateViews": false,
2994
+ "permissionsProfile": "propose_writes"
2995
+ },
2996
+ {
2997
+ "title": "Summarize Selection",
2998
+ "promptTemplate": "You are helping the user get a concise summary of their selection.\n\n**User's text or selection:**\n\n@{context:text}\n\nProvide a clear, structured summary. Highlight key points, dates, and actionable items if relevant. Keep it concise but comprehensive.",
2999
+ "derivedInputs": [],
3000
+ "outputMode": "text",
3001
+ "canCreateViews": false,
3002
+ "permissionsProfile": "read_only"
3003
+ },
3004
+ {
3005
+ "title": "Analyze Entities",
3006
+ "promptTemplate": "You are helping the user analyze selected entities.\n\n**Current entity context:**\n\n@{context:entity}\n\nAnalyze this entity and provide:\n- Overview and key properties\n- Insights, gaps, or opportunities\n- Suggestions for relationships or next steps\n\nBe concise and actionable.",
3007
+ "derivedInputs": [],
3008
+ "outputMode": "text",
3009
+ "canCreateViews": false,
3010
+ "permissionsProfile": "read_only"
3011
+ },
3012
+ {
3013
+ "title": "Create Task from Text",
3014
+ "promptTemplate": "You are helping the user turn text into tasks.\n\n**User's text (meeting notes, pasted content, or selection):**\n\n@{context:text}\n\nExtract actionable tasks. For each task propose:\n- Title\n- Optional: due date, priority, assignee, status\n- Link to source if applicable\n\nOutput proposals only\u2014do not create entities directly. The user will review and approve.",
3015
+ "derivedInputs": [],
3016
+ "outputMode": "proposal",
3017
+ "canCreateViews": false,
3018
+ "permissionsProfile": "propose_writes"
3019
+ },
3020
+ {
3021
+ "title": "Create Event from Text",
3022
+ "promptTemplate": "You are helping the user turn text into events.\n\n**User's text (meeting notes, calendar copy, or selection):**\n\n@{context:text}\n\nExtract events with dates/times. For each event propose:\n- Title\n- Start time and end time\n- Optional: description, tags\n\nOutput proposals only\u2014do not create entities directly. The user will review and approve.",
3023
+ "derivedInputs": [],
3024
+ "outputMode": "proposal",
3025
+ "canCreateViews": false,
3026
+ "permissionsProfile": "propose_writes"
3027
+ }
3028
+ ],
3029
+ "automations": [
3030
+ {
3031
+ "name": "Generate report",
3032
+ "description": `Gather this workspace's state, interpret it over three AI rounds, and write a report entity. Run it as-is for a general report, or pass a prompt (e.g. "focus on our clients") to steer what it is about.`,
3033
+ "triggerType": "manual",
3034
+ "triggerConfig": {},
3035
+ "status": "active",
3036
+ "flow": {
3037
+ "nodes": [
3038
+ {
3039
+ "id": "trigger",
3040
+ "type": "trigger",
3041
+ "position": {
3042
+ "x": 0,
3043
+ "y": 200
3044
+ },
3045
+ "data": {
3046
+ "triggerType": "manual",
3047
+ "label": "Run report",
3048
+ "config": {}
3049
+ }
3050
+ },
3051
+ {
3052
+ "id": "now",
3053
+ "type": "compute",
3054
+ "position": {
3055
+ "x": 200,
3056
+ "y": 200
3057
+ },
3058
+ "data": {
3059
+ "label": "Timestamp",
3060
+ "operation": "now"
3061
+ }
3062
+ },
3063
+ {
3064
+ "id": "gather-tasks",
3065
+ "type": "query",
3066
+ "position": {
3067
+ "x": 420,
3068
+ "y": 20
3069
+ },
3070
+ "data": {
3071
+ "label": "Tasks",
3072
+ "profileSlug": "task",
3073
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3074
+ "orderBy": "updatedAt",
3075
+ "orderDir": "desc",
3076
+ "limit": 25,
3077
+ "errorHandling": {
3078
+ "continueOnError": true
3079
+ }
3080
+ }
3081
+ },
3082
+ {
3083
+ "id": "gather-notes",
3084
+ "type": "query",
3085
+ "position": {
3086
+ "x": 420,
3087
+ "y": 140
3088
+ },
3089
+ "data": {
3090
+ "label": "Notes",
3091
+ "profileSlug": "note",
3092
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3093
+ "orderBy": "updatedAt",
3094
+ "orderDir": "desc",
3095
+ "limit": 15,
3096
+ "errorHandling": {
3097
+ "continueOnError": true
3098
+ }
3099
+ }
3100
+ },
3101
+ {
3102
+ "id": "gather-people",
3103
+ "type": "query",
3104
+ "position": {
3105
+ "x": 420,
3106
+ "y": 260
3107
+ },
3108
+ "data": {
3109
+ "label": "People",
3110
+ "profileSlug": "person",
3111
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3112
+ "orderBy": "updatedAt",
3113
+ "orderDir": "desc",
3114
+ "limit": 15,
3115
+ "errorHandling": {
3116
+ "continueOnError": true
3117
+ }
3118
+ }
3119
+ },
3120
+ {
3121
+ "id": "gather-companies",
3122
+ "type": "query",
3123
+ "position": {
3124
+ "x": 420,
3125
+ "y": 380
3126
+ },
3127
+ "data": {
3128
+ "label": "Companies",
3129
+ "profileSlug": "company",
3130
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3131
+ "orderBy": "updatedAt",
3132
+ "orderDir": "desc",
3133
+ "limit": 15,
3134
+ "errorHandling": {
3135
+ "continueOnError": true
3136
+ }
3137
+ }
3138
+ },
3139
+ {
3140
+ "id": "project-tasks",
3141
+ "type": "transform",
3142
+ "position": {
3143
+ "x": 540,
3144
+ "y": 20
3145
+ },
3146
+ "data": {
3147
+ "label": "Project tasks",
3148
+ "expression": "{{steps.gather-tasks.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 status={{item.properties.status}} \xB7 priority={{item.properties.priority}} \xB7 due={{item.properties.dueDate}} \xB7 updated={{item.updatedAt}}",
3149
+ "errorHandling": {
3150
+ "continueOnError": true
3151
+ }
3152
+ }
3153
+ },
3154
+ {
3155
+ "id": "project-notes",
3156
+ "type": "transform",
3157
+ "position": {
3158
+ "x": 540,
3159
+ "y": 140
3160
+ },
3161
+ "data": {
3162
+ "label": "Project notes",
3163
+ "expression": "{{steps.gather-notes.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 updated={{item.updatedAt}}",
3164
+ "errorHandling": {
3165
+ "continueOnError": true
3166
+ }
3167
+ }
3168
+ },
3169
+ {
3170
+ "id": "project-people",
3171
+ "type": "transform",
3172
+ "position": {
3173
+ "x": 540,
3174
+ "y": 260
3175
+ },
3176
+ "data": {
3177
+ "label": "Project people",
3178
+ "expression": "{{steps.gather-people.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 email={{item.properties.email}} \xB7 lastInteraction={{item.properties.lastInteractionAt}} \xB7 updated={{item.updatedAt}}",
3179
+ "errorHandling": {
3180
+ "continueOnError": true
3181
+ }
3182
+ }
3183
+ },
3184
+ {
3185
+ "id": "project-companies",
3186
+ "type": "transform",
3187
+ "position": {
3188
+ "x": 540,
3189
+ "y": 380
3190
+ },
3191
+ "data": {
3192
+ "label": "Project companies",
3193
+ "expression": "{{steps.gather-companies.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 industry={{item.properties.industry}} \xB7 location={{item.properties.location}} \xB7 updated={{item.updatedAt}}",
3194
+ "errorHandling": {
3195
+ "continueOnError": true
3196
+ }
3197
+ }
3198
+ },
3199
+ {
3200
+ "id": "analyze",
3201
+ "type": "capability",
3202
+ "position": {
3203
+ "x": 660,
3204
+ "y": 200
3205
+ },
3206
+ "data": {
3207
+ "label": "Analyze",
3208
+ "verbId": "ai.generate",
3209
+ "verbKind": "read",
3210
+ "inputMapping": {
3211
+ "system": 'You are the ANALYST round of a Synap workspace report.\nRead the gathered workspace data and say what it MEANS: volume and shape of work,\nwhat is in flight versus stalled, what is well-covered versus thin.\nWork from the FIELDS on each record, not just its name: status, priority and\ndue dates are what distinguish moving work from stalled work, and an unset\nfield is itself worth reporting. Listing or counting what exists is NOT a\nfinding \u2014 say what the state of it means.\nDATA INTEGRITY CHECK \u2014 do this BEFORE interpreting anything. Each kind below states how many records were found, then lists them. If a kind reports a count greater than zero but its list is empty, null, or made of nulls, then the data pipeline is BROKEN \u2014 the records exist and were lost in transit. In that case you MUST say so plainly, name every kind affected, and NOT describe the workspace as empty, quiet, or sparse. Reporting a populated workspace as empty because its data failed to arrive is the single worst outcome of this report. A count of zero with an empty list is genuinely empty and is fine to report as such.\nYou are interpreting data you did NOT collect. Every claim must trace to a row in the GATHERED DATA below. Never invent entities, ids, counts, or dates. A step whose value looks like {"error": "..."} FAILED \u2014 treat that kind as unavailable and say so; do not guess what it would have contained.\nEVERY NUMBER YOU WRITE MUST BE COPIED FROM THE COUNTS BLOCK. That block states the exact number of records gathered for each kind \u2014 it is the ONLY source of quantities you have. Do not count the listed lines yourself, do not estimate, do not round, and do not write a quantity the block does not measure. Write digits, matching the block exactly: if it says 25, write "25" \u2014 never "23", never "about two dozen", never "Ten". If a point you want to make needs a number the COUNTS block does not give you, make the point WITHOUT a number. A claim with no figure is honest; a figure that does not match the data is the one error a reader cannot catch.\nNEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.\nSTEER HANDLING \u2014 non-negotiable. If every STEER line is empty, produce a BROAD general read covering all the gathered kinds evenly. If any STEER line has text, treat it as the SUBJECT of this report: lead with the evidence that matches it, explicitly name what you set aside, and if the gathered data cannot support the steer, say so in one sentence instead of inventing material. Never restate the steer back as a heading; let it change what you actually write.\nLINK THE RECORDS YOU NAME. Every line of GATHERED DATA is `<id> \xB7 <title>`. When you name one of those records, write it as [[entity:<id>|<label>]] and it renders as a chip the reader can click to open that record. Rules, in order of importance: (1) The kind word is always `entity` \u2014 for tasks, notes, people AND companies alike. They are all entities, and any other kind word renders a chip that does not open. (2) Use only an id that appears VERBATIM in the gathered data. Copy it; never construct, shorten, complete or guess one. (3) If you are unsure of an id, write the plain label with no brackets. A wrong id is a dead chip, and a dead chip is worse than plain text. (4) The label is free text but must not contain `]`; the id must not contain `:` or `|`. A marker breaking either rule renders as raw literal text. (5) EVERY record you name in prose MUST carry a chip on its FIRST mention \u2014 this is what makes a named record a live object the reader can open rather than a string. Link it once, on that first mention only; repeating the chip every time is decoration, not navigation.\nOutput PLAIN PROSE \u2014 short paragraphs and bullets, with [[entity:\u2026]] chips\ninline wherever you name a record. No markdown headings, no ::: directive\nblocks, no code fences. Under 300 words. Another round will format this.',
3212
+ "prompt": 'STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS \u2014 the exact number of records gathered for each kind. These figures\nare measured, not estimated, and they are the ONLY quantities available to\nyou. Each line also gives the cap the gather was limited to.\n- Tasks: {{steps.gather-tasks.output.count}} gathered (cap 25)\n- Notes: {{steps.gather-notes.output.count}} gathered (cap 15)\n- People: {{steps.gather-people.output.count}} gathered (cap 15)\n- Companies: {{steps.gather-companies.output.count}} gathered (cap 15)\nREADING A COUNT AGAINST ITS CAP: a count BELOW the cap means every record of\nthat kind is listed below \u2014 the set is COMPLETE and you may describe it as\nsuch. A count EQUAL to the cap means the gather stopped at the cap and more\nrecords exist that you cannot see \u2014 describe that kind as "at least N" and\nnever as complete, and never claim something does not exist merely because\nit is absent from the list.\n\nTime window: only records updated on or after {{trigger.payload.since}}\n\nGATHERED DATA \u2014 one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> \xB7 <title>` followed by ` \xB7 <field>=<value>` pairs carrying that\nrecord\'s real state. An EMPTY value means the field is not set on that\nrecord \u2014 that is a fact you may report (an unset status or a missing due\ndate is a finding), and never a value to guess at. Recent does not mean\nimportant.\nTasks \u2014 {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes \u2014 {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople \u2014 {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies \u2014 {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}',
3213
+ "maxTokens": "2000"
3214
+ },
3215
+ "errorHandling": {
3216
+ "continueOnError": true
3217
+ }
3218
+ }
3219
+ },
3220
+ {
3221
+ "id": "relate",
3222
+ "type": "capability",
3223
+ "position": {
3224
+ "x": 900,
3225
+ "y": 200
3226
+ },
3227
+ "data": {
3228
+ "label": "Patterns",
3229
+ "verbId": "ai.generate",
3230
+ "verbKind": "read",
3231
+ "inputMapping": {
3232
+ "system": 'You are the PATTERNS round of a Synap workspace report.\nYou receive the raw gathered data AND the analyst\'s read of it. Your job is what\nthe analyst did NOT do: surface RELATIONSHIPS and things worth attention \u2014\nclusters, repeated themes, people or companies that recur across kinds, gaps\nwhere a kind is conspicuously empty, and anything that looks like it needs a\ndecision. Prefer three sharp observations over ten shallow ones.\nYOUR SCOPE IS WHAT CONNECTS RECORDS, not what they individually are. Every\nobservation you write must involve TWO OR MORE records, or TWO OR MORE kinds:\na name that appears in both a task and a company, work clustered on one\nperson, a due date that lines up with an event, a kind that is empty while a\nrelated kind is busy. Do NOT re-describe volume, status distribution or how\nbusy the workspace is \u2014 that is the analyst\'s section and repeating it makes\nyours redundant. Do not summarise or restate the analyst\'s read.\nNEVER RETURN AN EMPTY ANSWER. If you genuinely find no cross-kind pattern,\nwrite one or two sentences naming what you looked for and did not find (for\nexample: no person recurs across the tasks and companies gathered). That is a\nreal finding and the reader needs it. Silence is not an available outcome.\nDATA INTEGRITY CHECK \u2014 do this BEFORE interpreting anything. Each kind below states how many records were found, then lists them. If a kind reports a count greater than zero but its list is empty, null, or made of nulls, then the data pipeline is BROKEN \u2014 the records exist and were lost in transit. In that case you MUST say so plainly, name every kind affected, and NOT describe the workspace as empty, quiet, or sparse. Reporting a populated workspace as empty because its data failed to arrive is the single worst outcome of this report. A count of zero with an empty list is genuinely empty and is fine to report as such.\nYou are interpreting data you did NOT collect. Every claim must trace to a row in the GATHERED DATA below. Never invent entities, ids, counts, or dates. A step whose value looks like {"error": "..."} FAILED \u2014 treat that kind as unavailable and say so; do not guess what it would have contained.\nEVERY NUMBER YOU WRITE MUST BE COPIED FROM THE COUNTS BLOCK. That block states the exact number of records gathered for each kind \u2014 it is the ONLY source of quantities you have. Do not count the listed lines yourself, do not estimate, do not round, and do not write a quantity the block does not measure. Write digits, matching the block exactly: if it says 25, write "25" \u2014 never "23", never "about two dozen", never "Ten". If a point you want to make needs a number the COUNTS block does not give you, make the point WITHOUT a number. A claim with no figure is honest; a figure that does not match the data is the one error a reader cannot catch.\nNEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.\nSTEER HANDLING \u2014 non-negotiable. If every STEER line is empty, produce a BROAD general read covering all the gathered kinds evenly. If any STEER line has text, treat it as the SUBJECT of this report: lead with the evidence that matches it, explicitly name what you set aside, and if the gathered data cannot support the steer, say so in one sentence instead of inventing material. Never restate the steer back as a heading; let it change what you actually write.\nLINK THE RECORDS YOU NAME. Every line of GATHERED DATA is `<id> \xB7 <title>`. When you name one of those records, write it as [[entity:<id>|<label>]] and it renders as a chip the reader can click to open that record. Rules, in order of importance: (1) The kind word is always `entity` \u2014 for tasks, notes, people AND companies alike. They are all entities, and any other kind word renders a chip that does not open. (2) Use only an id that appears VERBATIM in the gathered data. Copy it; never construct, shorten, complete or guess one. (3) If you are unsure of an id, write the plain label with no brackets. A wrong id is a dead chip, and a dead chip is worse than plain text. (4) The label is free text but must not contain `]`; the id must not contain `:` or `|`. A marker breaking either rule renders as raw literal text. (5) EVERY record you name in prose MUST carry a chip on its FIRST mention \u2014 this is what makes a named record a live object the reader can open rather than a string. Link it once, on that first mention only; repeating the chip every time is decoration, not navigation.\nOutput PLAIN PROSE, under 250 words, with [[entity:\u2026]] chips inline. No\nheadings, no ::: directive blocks, no code fences.',
3233
+ "prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS \u2014 the exact number of records gathered for each kind. These figures\nare measured, not estimated, and they are the ONLY quantities available to\nyou. Each line also gives the cap the gather was limited to.\n- Tasks: {{steps.gather-tasks.output.count}} gathered (cap 25)\n- Notes: {{steps.gather-notes.output.count}} gathered (cap 15)\n- People: {{steps.gather-people.output.count}} gathered (cap 15)\n- Companies: {{steps.gather-companies.output.count}} gathered (cap 15)\nREADING A COUNT AGAINST ITS CAP: a count BELOW the cap means every record of\nthat kind is listed below \u2014 the set is COMPLETE and you may describe it as\nsuch. A count EQUAL to the cap means the gather stopped at the cap and more\nrecords exist that you cannot see \u2014 describe that kind as \"at least N\" and\nnever as complete, and never claim something does not exist merely because\nit is absent from the list.\n\nTime window: only records updated on or after {{trigger.payload.since}}\n\nGATHERED DATA \u2014 one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> \xB7 <title>` followed by ` \xB7 <field>=<value>` pairs carrying that\nrecord's real state. An EMPTY value means the field is not set on that\nrecord \u2014 that is a fact you may report (an unset status or a missing due\ndate is a finding), and never a value to guess at. Recent does not mean\nimportant.\nTasks \u2014 {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes \u2014 {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople \u2014 {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies \u2014 {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}\n\nANALYST'S READ \u2014 already written and already in the report. Do NOT\nrestate, summarise or re-analyse it. It is here so you do not repeat it:\n{{steps.analyze.output}}\n\nYOUR TASK \u2014 cross-kind patterns ONLY. Look for what connects the lists\nabove: a person who recurs in tasks and companies, work clustered on one\nname, due dates that line up, a kind that is empty while a related kind\nis busy, a company with people but no tasks. Write only observations\nthat span TWO OR MORE records or kinds. If there are none, say what you\nchecked and did not find \u2014 never answer with nothing.",
3234
+ "maxTokens": "2000"
3235
+ },
3236
+ "errorHandling": {
3237
+ "continueOnError": true
3238
+ }
3239
+ }
3240
+ },
3241
+ {
3242
+ "id": "assemble",
3243
+ "type": "capability",
3244
+ "position": {
3245
+ "x": 1140,
3246
+ "y": 200
3247
+ },
3248
+ "data": {
3249
+ "label": "Assemble",
3250
+ "verbId": "ai.generate",
3251
+ "verbKind": "read",
3252
+ "inputMapping": {
3253
+ "system": `You are the ASSEMBLER round of a Synap workspace report. You compose the FINAL
3254
+ document body in Synap-flavoured markdown. You add no new facts \u2014 you arrange,
3255
+ title, and format what the earlier rounds produced.
3256
+
3257
+ NEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.
3258
+
3259
+ NUMBERS: you were given no COUNTS block, so you may not originate a quantity
3260
+ at all. Reproduce a figure ONLY by copying one that already appears in the
3261
+ round material, digit for digit. Never total, re-count, convert to words, or
3262
+ put a number in the title or in a ## heading that is not already in the
3263
+ material beneath it.
3264
+
3265
+ SYNAP MARKDOWN SYNTAX \u2014 these rules are absolute:
3266
+ 1. A section is a FOUR-COLON container that must be opened and closed:
3267
+ ::::synap-section{agent="analyst" round="analyze" confidence="0.8"}
3268
+ ...content...
3269
+ ::::
3270
+ 2. Leaf embeds use THREE colons and MUST BE CLOSED with their own ::: line.
3271
+ There is NO self-closing form. An unclosed embed swallows every line that
3272
+ follows it until the section ends, and that content is LOST. Always write
3273
+ the closing ::: immediately after the opening line:
3274
+ :::synap-cell{instanceId="..."}
3275
+ :::
3276
+ :::synap-view{viewId="..."}
3277
+ :::
3278
+ :::synap-entity{id="..."}
3279
+ :::
3280
+ 3. The outer container must always have STRICTLY MORE colons than anything nested
3281
+ inside it. Four outside, three inside. If you ever nest a four-colon block in a
3282
+ four-colon block the section closes early and the rest of the report leaks out.
3283
+ 4. ATTRIBUTE VALUES MUST BE QUOTED. An UNQUOTED value ends at the first } it
3284
+ contains, which silently truncates the attribute \u2014 so always quote. Use
3285
+ DOUBLE quotes for a plain value, and SINGLE quotes when the value is itself
3286
+ a JSON object, so the JSON's own double quotes survive:
3287
+ :::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"task","groupBy":"status"}'}
3288
+ :::
3289
+ A single-quoted value may contain both " and } \u2014 those are only special to
3290
+ an unquoted value. Write the JSON compact, on one line, with no trailing
3291
+ comma; malformed JSON renders the cell with empty config.
3292
+ 5. The ONLY section attributes that survive are: id, agent, round, skills,
3293
+ confidence, stepRunId, nodeId, status. Anything else is silently dropped.
3294
+ 6. confidence is a 0-1 ratio written as a decimal string, e.g. confidence="0.8".
3295
+ Never a percentage.
3296
+ 7. CHIPS ARE CONTENT \u2014 COPY THEM THROUGH VERBATIM. The round material you were
3297
+ given already contains [[entity:<id>|<label>]] markers. Reproduce every one
3298
+ EXACTLY as written: same brackets, same id, same label. Do NOT tidy them into
3299
+ plain text, do NOT rewrite or re-case an id, do NOT convert them to markdown
3300
+ links. You are told you add no new FACTS; a chip is not a fact, it is the
3301
+ earlier round's finding made navigable, and stripping it removes the only
3302
+ clickable thing in the report while leaving the prose looking fine.
3303
+ You may add a chip ONLY by copying a marker that already appears in the
3304
+ material. Never invent an id, and never use a kind word other than \`entity\` \u2014
3305
+ this report is READ-ONLY prose, and either mistake produces a dead chip.
3306
+ 8. NEVER EMIT :::synap-view, AND NEVER EMIT :::synap-cell WITH AN instanceId.
3307
+ Both need an id you have not been given and cannot obtain, and a made-up
3308
+ reference produces a permanent broken block in a document a human reads.
3309
+ 9. YOU MAY EMIT A CHART :::synap-cell \u2014 from this allowlist ONLY, configured
3310
+ through cellProps. These cells fetch their own data at render time, so they
3311
+ need no id, and they make the report a live surface instead of a text file:
3312
+ - :::synap-cell{cellKey="stat-card" cellProps='{"profileSlug":"<kind>","aggregation":"count","timePeriod":"week","label":"<Kind> created this week"}'}
3313
+ - :::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"<kind>","groupBy":"<property>","label":"<the finding this shows>"}'}
3314
+ - :::synap-cell{cellKey="chart-bar" cellProps='{"profileSlug":"<kind>","mode":"category","groupBy":"<property>","label":"<the finding this shows>"}'}
3315
+ Rules, all mandatory:
3316
+ (a) profileSlug must be one of the kinds gathered for THIS report: task,
3317
+ note, person, company. A chart over any other kind is a claim this
3318
+ report cannot back \u2014 the data was never read.
3319
+ (b) groupBy / field must name a property that plausibly exists on that
3320
+ kind. If you are not confident one does, use the stat-card line above
3321
+ EXACTLY as written \u2014 profileSlug, aggregation, timePeriod AND a label
3322
+ naming the window. Never drop timePeriod and never label a windowed
3323
+ count with a bare kind name: the card counts records CREATED in that
3324
+ window, so "Tasks" over a weekly count is a false statement about
3325
+ the workspace.
3326
+ (c) Close EVERY embed with its own ::: line, per rule 2. An unclosed embed
3327
+ swallows the rest of the section.
3328
+ (d) EDITORIAL RULE \u2014 embed to SHOW what a sentence cannot: a distribution,
3329
+ a ratio, a count. Never to decorate. At most 2-3 in the whole report,
3330
+ placed inside the section whose prose they support. A chart that merely
3331
+ restates the sentence beside it is noise, and noise is worse than
3332
+ nothing here. If no gathered kind has a distribution worth showing,
3333
+ emit no cells at all \u2014 that is a correct report, not a lesser one.
3334
+ (e) AT MOST ONE cell per section, and NEVER at the top of a section.
3335
+ Place it immediately AFTER the sentence making the claim it evidences,
3336
+ so the reader has the claim before the picture. A chart that opens a
3337
+ section makes the reader decode a graphic to find out what the section
3338
+ is about, which is the delay the ## heading and the claim sentence
3339
+ exist to remove.
3340
+ (f) The label must state the FINDING, not the dimension. Write
3341
+ label:"Half the open tasks are blocked", never label:"Distribution",
3342
+ "Status" or "Tasks by status". Always set label explicitly: with none,
3343
+ a pie chart titles itself "Distribution" and a bar chart "Breakdown" \u2014
3344
+ words true of every such chart ever drawn, which tell the reader
3345
+ nothing they cannot already see in the shapes.
3346
+ (g) A CHART IS A LIVE VIEW, THE PROSE IS A SNAPSHOT. These cells run
3347
+ their own query when the report is OPENED, which may be long after
3348
+ this text was written \u2014 so a sentence quoting a count and a chart
3349
+ measuring the same thing WILL eventually disagree, and the reader
3350
+ cannot tell which is stale. Never write a specific number in a
3351
+ sentence that sits beside a chart of the same data: let the chart
3352
+ carry the number and let the sentence carry the claim
3353
+ ("most open tasks are stalled at review", not "12 of 19 are stalled").
3354
+ And if the sentence already gives the number the chart would show,
3355
+ DROP THE CHART \u2014 one of them is redundant, and the sentence is the one
3356
+ that cannot go stale against itself.
3357
+ Everything else this report needs is ::::synap-section containers plus
3358
+ [[entity:\u2026]] chips inline.
3359
+
3360
+ 10. EVERY ::::synap-section MUST OPEN WITH A ## CLAIM HEADING \u2014 its first
3361
+ line, before any prose. The heading states WHAT THE SECTION FOUND, not
3362
+ what produced it: "## Half the open tasks are blocked", never
3363
+ "## Analysis", "## Analyze round" or "## Findings". Under 10 words, no
3364
+ trailing period, no markdown or [[entity:\u2026]] chip inside the heading
3365
+ itself \u2014 put the chip in the prose beneath it. Write exactly one ## per
3366
+ section. This heading is the section's title everywhere it is read: the
3367
+ slide deck sets it as the slide headline (with no heading it falls back
3368
+ to the round and agent names, which the slide already shows underneath),
3369
+ and the scrolling reader has no section titles at all without it. A
3370
+ section whose heading merely names the round tells the reader nothing
3371
+ they cannot already see.
3372
+
3373
+ STRUCTURE: one ::::synap-section per round, in order \u2014 analyze, then relate.
3374
+ Open with a single "# " title line ABOVE the first section, and open each
3375
+ section with its own ## claim heading per rule 10.
3376
+
3377
+ STEER HANDLING: if the STEER lines are empty, title the report generically
3378
+ ("Workspace report \u2014 <period>") and keep the rounds in their given order. If any
3379
+ STEER line has text, name that subject in the title and lead each section with
3380
+ the material that answers it. Never add facts the rounds did not give you.
3381
+
3382
+ MISSING ROUNDS: if a round's material looks like {"error": "..."} or is empty,
3383
+ you MUST still emit its section, with status="failed", containing one visible
3384
+ sentence naming what is missing. NEVER silently produce a shorter report \u2014 a
3385
+ reader must be able to see that a round did not run. A failed section is NOT
3386
+ exempt from rule 10: give it a ## heading that states what is missing
3387
+ ("## The patterns round produced nothing"), because it still occupies a slide
3388
+ and still needs a headline.
3389
+
3390
+ WORKED EXAMPLE of correct output:
3391
+ # Workspace report \u2014 July 2026
3392
+
3393
+ ::::synap-section{agent="analyst" round="analyze" confidence="0.8"}
3394
+ ## Most open tasks are stalled at review
3395
+
3396
+ Open tasks are piling up untouched since the sprint opened, and the great
3397
+ majority are sitting in one status \u2014 the oldest is
3398
+ [[entity:3f2a91c4-7b10-4e55-9c02-8ad1f6e4b2d7|Migrate the billing worker]].
3399
+ Notes are thin.
3400
+ :::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"task","groupBy":"status","label":"Review is where open tasks accumulate"}'}
3401
+ :::
3402
+ ::::
3403
+
3404
+ ::::synap-section{agent="analyst" round="relate" status="failed" confidence="0.2"}
3405
+ ## The patterns round produced nothing
3406
+
3407
+ This section is missing: the patterns round failed and produced no material.
3408
+ ::::
3409
+
3410
+ Be CONCISE \u2014 your entire output must fit in 2000 tokens. Emit ONLY the markdown
3411
+ document. No preamble, no explanation, no code fences around the whole thing.`,
3412
+ "prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nReport generated at: {{steps.now.output.result}}\n\nANALYZE ROUND MATERIAL:\n{{steps.analyze.output}}\n\nRELATE ROUND MATERIAL:\n{{steps.relate.output}}",
3413
+ "maxTokens": "2000"
3414
+ }
3415
+ }
3416
+ },
3417
+ {
3418
+ "id": "summarize",
3419
+ "type": "capability",
3420
+ "position": {
3421
+ "x": 1380,
3422
+ "y": 200
3423
+ },
3424
+ "data": {
3425
+ "label": "One-line summary",
3426
+ "verbId": "ai.generate",
3427
+ "verbKind": "read",
3428
+ "inputMapping": {
3429
+ "system": 'Write ONE plain sentence, under 200 characters, stating what this report found.\nNo markdown, no directives, no quotes, no leading label. Just the sentence.\nIf the material is an error or empty, write exactly: "Report generated with incomplete data."',
3430
+ "prompt": "Material to summarize (may be an error object if the round failed):\n{{steps.analyze.output}}",
3431
+ "maxTokens": "500"
3432
+ }
3433
+ }
3434
+ },
3435
+ {
3436
+ "id": "guard-assembled",
3437
+ "type": "guard",
3438
+ "position": {
3439
+ "x": 1620,
3440
+ "y": 200
3441
+ },
3442
+ "data": {
3443
+ "label": "Body assembled",
3444
+ "checks": [
3445
+ {
3446
+ "path": "steps.assemble.output",
3447
+ "minLength": 200,
3448
+ "message": "The assembler produced no usable body (under 200 characters) \u2014 refusing to write an empty report."
3449
+ },
3450
+ {
3451
+ "path": "steps.assemble.output.error",
3452
+ "exists": false,
3453
+ "message": "The assembler round failed \u2014 refusing to write a report without a body."
3454
+ }
3455
+ ]
3456
+ }
3457
+ },
3458
+ {
3459
+ "id": "create-report",
3460
+ "type": "output",
3461
+ "position": {
3462
+ "x": 1860,
3463
+ "y": 200
3464
+ },
3465
+ "data": {
3466
+ "label": "Create report",
3467
+ "outputType": "entity_create",
3468
+ "config": {
3469
+ "profileSlug": "report",
3470
+ "title": "Workspace report \u2014 {{steps.now.output.result}}",
3471
+ "properties": {
3472
+ "reportPeriod": "{{steps.now.output.result}}",
3473
+ "generatedAt": "{{steps.now.output.result}}",
3474
+ "reportStatus": "ready",
3475
+ "summary": "{{steps.summarize.output}}",
3476
+ "reportSources": [
3477
+ "Tasks",
3478
+ "Notes",
3479
+ "People",
3480
+ "Companies"
3481
+ ]
3482
+ },
3483
+ "body": "{{steps.assemble.output}}",
3484
+ "systemData": {
3485
+ "renderer": {
3486
+ "kind": "cell",
3487
+ "cellKey": "{{trigger.payload.renderer}}"
3488
+ }
3489
+ }
3490
+ }
3491
+ }
3492
+ }
3493
+ ],
3494
+ "edges": [
3495
+ {
3496
+ "id": "e-trigger-now",
3497
+ "source": "trigger",
3498
+ "target": "now"
3499
+ },
3500
+ {
3501
+ "id": "e-now-tasks",
3502
+ "source": "now",
3503
+ "target": "gather-tasks"
3504
+ },
3505
+ {
3506
+ "id": "e-now-notes",
3507
+ "source": "now",
3508
+ "target": "gather-notes"
3509
+ },
3510
+ {
3511
+ "id": "e-now-people",
3512
+ "source": "now",
3513
+ "target": "gather-people"
3514
+ },
3515
+ {
3516
+ "id": "e-now-companies",
3517
+ "source": "now",
3518
+ "target": "gather-companies"
3519
+ },
3520
+ {
3521
+ "id": "e-tasks-project",
3522
+ "source": "gather-tasks",
3523
+ "target": "project-tasks"
3524
+ },
3525
+ {
3526
+ "id": "e-notes-project",
3527
+ "source": "gather-notes",
3528
+ "target": "project-notes"
3529
+ },
3530
+ {
3531
+ "id": "e-people-project",
3532
+ "source": "gather-people",
3533
+ "target": "project-people"
3534
+ },
3535
+ {
3536
+ "id": "e-companies-project",
3537
+ "source": "gather-companies",
3538
+ "target": "project-companies"
3539
+ },
3540
+ {
3541
+ "id": "e-tasks-analyze",
3542
+ "source": "project-tasks",
3543
+ "target": "analyze"
3544
+ },
3545
+ {
3546
+ "id": "e-notes-analyze",
3547
+ "source": "project-notes",
3548
+ "target": "analyze"
3549
+ },
3550
+ {
3551
+ "id": "e-people-analyze",
3552
+ "source": "project-people",
3553
+ "target": "analyze"
3554
+ },
3555
+ {
3556
+ "id": "e-companies-analyze",
3557
+ "source": "project-companies",
3558
+ "target": "analyze"
3559
+ },
3560
+ {
3561
+ "id": "e-analyze-relate",
3562
+ "source": "analyze",
3563
+ "target": "relate"
3564
+ },
3565
+ {
3566
+ "id": "e-relate-assemble",
3567
+ "source": "relate",
3568
+ "target": "assemble"
3569
+ },
3570
+ {
3571
+ "id": "e-assemble-summarize",
3572
+ "source": "assemble",
3573
+ "target": "summarize"
3574
+ },
3575
+ {
3576
+ "id": "e-summarize-guard",
3577
+ "source": "summarize",
3578
+ "target": "guard-assembled"
3579
+ },
3580
+ {
3581
+ "id": "e-guard-create",
3582
+ "source": "guard-assembled",
3583
+ "target": "create-report"
3584
+ }
3585
+ ]
3586
+ }
3587
+ }
3588
+ ]
3589
+ },
3590
+ "blockchain-ecosystem": {
3591
+ "meta": {
3592
+ "slug": "blockchain-ecosystem",
3593
+ "domain": "strategy",
3594
+ "name": "Blockchain Ecosystem",
3595
+ "description": "Web3 market actors \u2014 market-makers, auditors, dev shops, KOLs \u2014 and how they connect. Overlays the Ecosystem workspace.",
3596
+ "icon": "boxes",
3597
+ "color": "#8B5CF6",
3598
+ "tags": [
3599
+ "blockchain",
3600
+ "web3",
3601
+ "ecosystem",
3602
+ "market-makers"
3603
+ ],
3604
+ "isPublic": false
3605
+ },
3606
+ "dependencies": [
3607
+ {
3608
+ "slug": "ecosystem",
3609
+ "kind": "workspace",
3610
+ "relation": "compose",
3611
+ "reason": "Adds web3 market actors + relations onto your Ecosystem."
3612
+ }
3613
+ ],
3614
+ "workspace": {
3615
+ "name": "Ecosystem",
3616
+ "description": "Web3 market actors and the relationships between them, layered onto your Ecosystem.",
3617
+ "subtype": "ecosystem"
3618
+ },
3619
+ "profiles": [
3620
+ {
3621
+ "slug": "market-maker",
3622
+ "displayName": "Market Maker",
3623
+ "icon": "candlestick-chart",
3624
+ "color": "sky",
3625
+ "description": "A liquidity provider \u2014 makes markets and provides depth for a token.",
3626
+ "scope": "shared",
3627
+ "profileKind": "role",
3628
+ "roleCategory": "provider",
3629
+ "applicableKinds": [
3630
+ "company",
3631
+ "person"
3632
+ ],
3633
+ "properties": [
3634
+ {
3635
+ "slug": "status",
3636
+ "label": "Status",
3637
+ "valueType": "string",
3638
+ "inputType": "select",
3639
+ "enumValues": [
3640
+ "prospect",
3641
+ "active",
3642
+ "paused",
3643
+ "churned"
3644
+ ]
3645
+ },
3646
+ {
3647
+ "slug": "exchanges",
3648
+ "label": "Exchanges / Venues",
3649
+ "valueType": "array",
3650
+ "inputType": "text"
3651
+ },
3652
+ {
3653
+ "slug": "engagementStartDate",
3654
+ "label": "Engagement Start",
3655
+ "valueType": "date",
3656
+ "inputType": "date"
3657
+ },
3658
+ {
3659
+ "slug": "notes",
3660
+ "label": "Notes",
3661
+ "valueType": "string",
3662
+ "inputType": "textarea"
3663
+ }
3664
+ ]
3665
+ },
3666
+ {
3667
+ "slug": "security-auditor",
3668
+ "displayName": "Security Auditor",
3669
+ "icon": "shield-check",
3670
+ "color": "red",
3671
+ "description": "A security firm or individual that audits smart contracts and protocols.",
3672
+ "scope": "shared",
3673
+ "profileKind": "role",
3674
+ "roleCategory": "provider",
3675
+ "applicableKinds": [
3676
+ "company",
3677
+ "person"
3678
+ ],
3679
+ "properties": [
3680
+ {
3681
+ "slug": "status",
3682
+ "label": "Status",
3683
+ "valueType": "string",
3684
+ "inputType": "select",
3685
+ "enumValues": [
3686
+ "prospect",
3687
+ "active",
3688
+ "paused",
3689
+ "churned"
3690
+ ]
3691
+ },
3692
+ {
3693
+ "slug": "specialty",
3694
+ "label": "Specialty",
3695
+ "valueType": "string",
3696
+ "inputType": "text"
3697
+ },
3698
+ {
3699
+ "slug": "engagementStartDate",
3700
+ "label": "Engagement Start",
3701
+ "valueType": "date",
3702
+ "inputType": "date"
3703
+ },
3704
+ {
3705
+ "slug": "notes",
3706
+ "label": "Notes",
3707
+ "valueType": "string",
3708
+ "inputType": "textarea"
3709
+ }
3710
+ ]
3711
+ },
3712
+ {
3713
+ "slug": "dev-shop",
3714
+ "displayName": "Dev Shop",
3715
+ "icon": "code",
3716
+ "color": "violet",
3717
+ "description": "An outsourced engineering studio or build partner.",
3718
+ "scope": "shared",
3719
+ "profileKind": "role",
3720
+ "roleCategory": "provider",
3721
+ "applicableKinds": [
3722
+ "company",
3723
+ "person"
3724
+ ],
3725
+ "properties": [
3726
+ {
3727
+ "slug": "status",
3728
+ "label": "Status",
3729
+ "valueType": "string",
3730
+ "inputType": "select",
3731
+ "enumValues": [
3732
+ "prospect",
3733
+ "active",
3734
+ "paused",
3735
+ "churned"
3736
+ ]
3737
+ },
3738
+ {
3739
+ "slug": "specialty",
3740
+ "label": "Specialty",
3741
+ "valueType": "string",
3742
+ "inputType": "text"
3743
+ },
3744
+ {
3745
+ "slug": "engagementStartDate",
3746
+ "label": "Engagement Start",
3747
+ "valueType": "date",
3748
+ "inputType": "date"
3749
+ },
3750
+ {
3751
+ "slug": "notes",
3752
+ "label": "Notes",
3753
+ "valueType": "string",
3754
+ "inputType": "textarea"
3755
+ }
3756
+ ]
3757
+ },
3758
+ {
3759
+ "slug": "marketing-agency",
3760
+ "displayName": "Marketing Agency",
3761
+ "icon": "megaphone",
3762
+ "color": "pink",
3763
+ "description": "A brand, growth, or content marketing agency.",
3764
+ "scope": "shared",
3765
+ "profileKind": "role",
3766
+ "roleCategory": "provider",
3767
+ "applicableKinds": [
3768
+ "company",
3769
+ "person"
3770
+ ],
3771
+ "properties": [
3772
+ {
3773
+ "slug": "status",
3774
+ "label": "Status",
3775
+ "valueType": "string",
3776
+ "inputType": "select",
3777
+ "enumValues": [
3778
+ "prospect",
3779
+ "active",
3780
+ "paused",
3781
+ "churned"
3782
+ ]
3783
+ },
3784
+ {
3785
+ "slug": "specialty",
3786
+ "label": "Specialty",
3787
+ "valueType": "string",
3788
+ "inputType": "text"
3789
+ },
3790
+ {
3791
+ "slug": "engagementStartDate",
3792
+ "label": "Engagement Start",
3793
+ "valueType": "date",
3794
+ "inputType": "date"
3795
+ },
3796
+ {
3797
+ "slug": "notes",
3798
+ "label": "Notes",
3799
+ "valueType": "string",
3800
+ "inputType": "textarea"
3801
+ }
3802
+ ]
3803
+ },
3804
+ {
3805
+ "slug": "paid-ads-agency",
3806
+ "displayName": "Paid-Ads Agency",
3807
+ "icon": "badge-dollar-sign",
3808
+ "color": "orange",
3809
+ "description": "A performance / paid-media buying agency.",
3810
+ "scope": "shared",
3811
+ "profileKind": "role",
3812
+ "roleCategory": "provider",
3813
+ "applicableKinds": [
3814
+ "company",
3815
+ "person"
3816
+ ],
3817
+ "properties": [
3818
+ {
3819
+ "slug": "status",
3820
+ "label": "Status",
3821
+ "valueType": "string",
3822
+ "inputType": "select",
3823
+ "enumValues": [
3824
+ "prospect",
3825
+ "active",
3826
+ "paused",
3827
+ "churned"
3828
+ ]
3829
+ },
3830
+ {
3831
+ "slug": "channels",
3832
+ "label": "Channels",
3833
+ "valueType": "array",
3834
+ "inputType": "text"
3835
+ },
3836
+ {
3837
+ "slug": "engagementStartDate",
3838
+ "label": "Engagement Start",
3839
+ "valueType": "date",
3840
+ "inputType": "date"
3841
+ },
3842
+ {
3843
+ "slug": "notes",
3844
+ "label": "Notes",
3845
+ "valueType": "string",
3846
+ "inputType": "textarea"
3847
+ }
3848
+ ]
3849
+ },
3850
+ {
3851
+ "slug": "kol",
3852
+ "displayName": "KOL",
3853
+ "icon": "star",
3854
+ "color": "yellow",
3855
+ "description": "A key opinion leader or influencer who amplifies reach.",
3000
3856
  "scope": "shared",
3001
3857
  "profileKind": "role",
3858
+ "roleCategory": "provider",
3002
3859
  "applicableKinds": [
3003
3860
  "company",
3004
3861
  "person"
@@ -3035,6 +3892,100 @@ var DATA = {
3035
3892
  "inputType": "textarea"
3036
3893
  }
3037
3894
  ]
3895
+ },
3896
+ {
3897
+ "slug": "grant-program",
3898
+ "displayName": "Grant Program",
3899
+ "icon": "landmark",
3900
+ "color": "emerald",
3901
+ "description": "A per-chain funding program (e.g. Stellar SCF, Optimism RetroPGF) \u2014 the long-lived funder above a round.",
3902
+ "profileKind": "kind",
3903
+ "entityScope": "pod",
3904
+ "scope": "shared",
3905
+ "properties": [
3906
+ {
3907
+ "slug": "blockchain",
3908
+ "label": "Blockchain / Chain",
3909
+ "valueType": "string",
3910
+ "inputType": "text"
3911
+ },
3912
+ {
3913
+ "slug": "programName",
3914
+ "label": "Program Name",
3915
+ "valueType": "string",
3916
+ "inputType": "text"
3917
+ },
3918
+ {
3919
+ "slug": "programType",
3920
+ "label": "Program Type",
3921
+ "valueType": "string",
3922
+ "inputType": "select",
3923
+ "enumValues": [
3924
+ "direct",
3925
+ "retroactive",
3926
+ "quadratic",
3927
+ "rfp"
3928
+ ]
3929
+ },
3930
+ {
3931
+ "slug": "status",
3932
+ "label": "Status",
3933
+ "valueType": "string",
3934
+ "inputType": "select",
3935
+ "enumValues": [
3936
+ "active",
3937
+ "paused",
3938
+ "retired"
3939
+ ]
3940
+ },
3941
+ {
3942
+ "slug": "url",
3943
+ "label": "Program URL",
3944
+ "valueType": "string",
3945
+ "inputType": "url"
3946
+ },
3947
+ {
3948
+ "slug": "cadence",
3949
+ "label": "Cadence",
3950
+ "valueType": "string",
3951
+ "inputType": "text",
3952
+ "placeholder": "every 6 weeks"
3953
+ },
3954
+ {
3955
+ "slug": "nextDeadline",
3956
+ "label": "Next Deadline",
3957
+ "valueType": "date",
3958
+ "inputType": "date"
3959
+ },
3960
+ {
3961
+ "slug": "submissionTracks",
3962
+ "label": "Submission Tracks",
3963
+ "valueType": "array",
3964
+ "inputType": "text"
3965
+ },
3966
+ {
3967
+ "slug": "trancheModel",
3968
+ "label": "Tranche Model",
3969
+ "valueType": "string",
3970
+ "inputType": "text",
3971
+ "placeholder": "4 tranches / 90 days"
3972
+ },
3973
+ {
3974
+ "slug": "defaultPlaybook",
3975
+ "label": "Default Playbook",
3976
+ "valueType": "string",
3977
+ "inputType": "text",
3978
+ "constraints": {
3979
+ "defaultValue": "Grant Process"
3980
+ }
3981
+ },
3982
+ {
3983
+ "slug": "description",
3984
+ "label": "Description",
3985
+ "valueType": "string",
3986
+ "inputType": "textarea"
3987
+ }
3988
+ ]
3038
3989
  }
3039
3990
  ],
3040
3991
  "entityLinks": [
@@ -6796,17 +7747,16 @@ var DATA = {
6796
7747
  },
6797
7748
  "profiles": [
6798
7749
  {
6799
- "slug": "crm-lead",
6800
- "displayName": "CRM Lead",
6801
- "icon": "user-round-search",
6802
- "color": "amber",
6803
- "description": "A person or company actively being qualified or contacted by this CRM workspace.",
6804
- "scope": "WORKSPACE",
7750
+ "slug": "lead",
7751
+ "displayName": "Lead",
7752
+ "icon": "user-plus",
7753
+ "color": "yellow",
7754
+ "description": "An early-pipeline prospect \u2014 not yet a client, being qualified or nurtured.",
7755
+ "scope": "shared",
6805
7756
  "profileKind": "role",
6806
- "semanticSlug": "crm.lead",
6807
7757
  "applicableKinds": [
6808
- "person",
6809
- "company"
7758
+ "company",
7759
+ "person"
6810
7760
  ],
6811
7761
  "properties": [
6812
7762
  {
@@ -6903,17 +7853,16 @@ var DATA = {
6903
7853
  ]
6904
7854
  },
6905
7855
  {
6906
- "slug": "crm-client",
6907
- "displayName": "CRM Client",
6908
- "icon": "badge-check",
7856
+ "slug": "client",
7857
+ "displayName": "Client",
7858
+ "icon": "briefcase",
6909
7859
  "color": "emerald",
6910
- "description": "A CRM lead converted by a won commercial proposal and ready for handoff.",
6911
- "scope": "WORKSPACE",
7860
+ "description": "An active commercial relationship \u2014 created when a deal is won.",
7861
+ "scope": "shared",
6912
7862
  "profileKind": "role",
6913
- "semanticSlug": "crm.client",
6914
7863
  "applicableKinds": [
6915
- "person",
6916
- "company"
7864
+ "company",
7865
+ "person"
6917
7866
  ],
6918
7867
  "properties": [
6919
7868
  {
@@ -7278,7 +8227,7 @@ var DATA = {
7278
8227
  "description": "Research and qualify one CRM lead, then make the next action explicit without creating a commercial proposal automatically.",
7279
8228
  "goalTemplate": "Work on CRM lead ({lead}). Verify and enrich only factual information that can be supported, assess fit, and update its CRM lifecycle stage, ICP score, qualification notes, outreach channels, and next action. Do not create a deal simply because the lead is qualified. If a commercial proposal is warranted, explain why and ask for or propose that next step separately.",
7280
8229
  "subjectProfile": {
7281
- "profileSlug": "crm-lead"
8230
+ "profileSlug": "lead"
7282
8231
  },
7283
8232
  "params": [
7284
8233
  {
@@ -7381,7 +8330,7 @@ var DATA = {
7381
8330
  },
7382
8331
  {
7383
8332
  "path": "steps.read-deal.output.entity.properties.sourcedByUserId",
7384
- "exists": true,
8333
+ "minLength": 1,
7385
8334
  "message": "Set the business developer who sourced this proposal before handing it off."
7386
8335
  },
7387
8336
  {
@@ -7451,7 +8400,7 @@ var DATA = {
7451
8400
  "checks": [
7452
8401
  {
7453
8402
  "path": "steps.read-party.output.entity.facetSlugs",
7454
- "arrayIncludes": "crm-lead",
8403
+ "arrayIncludes": "lead",
7455
8404
  "message": "The commercial counterparty must still be a CRM lead before it becomes a CRM client."
7456
8405
  }
7457
8406
  ]
@@ -7714,7 +8663,7 @@ var DATA = {
7714
8663
  "outputType": "facet_attach",
7715
8664
  "config": {
7716
8665
  "entityId": "{{steps.read-party.output.entity.id}}",
7717
- "facetSlug": "crm-client",
8666
+ "facetSlug": "client",
7718
8667
  "properties": {
7719
8668
  "becameClientAt": "{{steps.handoff-time.output.result}}",
7720
8669
  "handoffStatus": "handed_off"
@@ -7734,7 +8683,7 @@ var DATA = {
7734
8683
  "outputType": "facet_update",
7735
8684
  "config": {
7736
8685
  "entityId": "{{steps.read-party.output.entity.id}}",
7737
- "facetSlug": "crm-lead",
8686
+ "facetSlug": "lead",
7738
8687
  "properties": {
7739
8688
  "lifecycleStage": "converted",
7740
8689
  "convertedAt": "{{steps.handoff-time.output.result}}"
@@ -7877,7 +8826,7 @@ var DATA = {
7877
8826
  "label": "Qualify lead",
7878
8827
  "when": {
7879
8828
  "requiredFacetSlugs": [
7880
- "crm-lead"
8829
+ "lead"
7881
8830
  ]
7882
8831
  }
7883
8832
  },
@@ -7889,7 +8838,7 @@ var DATA = {
7889
8838
  "label": "Qualify lead",
7890
8839
  "when": {
7891
8840
  "requiredFacetSlugs": [
7892
- "crm-lead"
8841
+ "lead"
7893
8842
  ]
7894
8843
  }
7895
8844
  },
@@ -9211,7 +10160,8 @@ var DATA = {
9211
10160
  "scopeProfileSlug": "post",
9212
10161
  "description": "Your editorial calendar \u2014 every post on the day it publishes.",
9213
10162
  "config": {
9214
- "dateField": "properties.publish-date"
10163
+ "dateField": "properties.publish-date",
10164
+ "focusKind": "post"
9215
10165
  }
9216
10166
  },
9217
10167
  {
@@ -9356,6 +10306,82 @@ var DATA = {
9356
10306
  }
9357
10307
  ]
9358
10308
  },
10309
+ "profileEntityBentoTemplates": {
10310
+ "series": {
10311
+ "blocks": [
10312
+ {
10313
+ "id": "series-eb-header",
10314
+ "kind": "widget",
10315
+ "widgetType": "entity-header",
10316
+ "pos": {
10317
+ "x": 0,
10318
+ "y": 0,
10319
+ "w": 12,
10320
+ "h": 2
10321
+ }
10322
+ },
10323
+ {
10324
+ "id": "series-eb-props",
10325
+ "kind": "widget",
10326
+ "widgetType": "entity-properties",
10327
+ "pos": {
10328
+ "x": 0,
10329
+ "y": 2,
10330
+ "w": 5,
10331
+ "h": 8
10332
+ }
10333
+ },
10334
+ {
10335
+ "id": "series-eb-curriculum",
10336
+ "kind": "widget",
10337
+ "widgetType": "series-curriculum",
10338
+ "pos": {
10339
+ "x": 5,
10340
+ "y": 2,
10341
+ "w": 7,
10342
+ "h": 8
10343
+ }
10344
+ }
10345
+ ]
10346
+ },
10347
+ "pillar": {
10348
+ "blocks": [
10349
+ {
10350
+ "id": "pillar-eb-header",
10351
+ "kind": "widget",
10352
+ "widgetType": "entity-header",
10353
+ "pos": {
10354
+ "x": 0,
10355
+ "y": 0,
10356
+ "w": 12,
10357
+ "h": 2
10358
+ }
10359
+ },
10360
+ {
10361
+ "id": "pillar-eb-props",
10362
+ "kind": "widget",
10363
+ "widgetType": "entity-properties",
10364
+ "pos": {
10365
+ "x": 0,
10366
+ "y": 2,
10367
+ "w": 5,
10368
+ "h": 8
10369
+ }
10370
+ },
10371
+ {
10372
+ "id": "pillar-eb-chapter",
10373
+ "kind": "widget",
10374
+ "widgetType": "pillar-chapter",
10375
+ "pos": {
10376
+ "x": 5,
10377
+ "y": 2,
10378
+ "w": 7,
10379
+ "h": 8
10380
+ }
10381
+ }
10382
+ ]
10383
+ }
10384
+ },
9359
10385
  "entityLinks": [
9360
10386
  {
9361
10387
  "sourceProfileSlug": "post",
@@ -10085,11 +11111,12 @@ var DATA = {
10085
11111
  ],
10086
11112
  "isPublic": true
10087
11113
  },
10088
- "app": {
10089
- "kind": "url",
10090
- "appId": "crm",
10091
- "url": "https://crm.synap.live",
10092
- "title": "CRM"
11114
+ "layout": {
11115
+ "primarySurface": {
11116
+ "kind": "url",
11117
+ "url": "https://crm.synap.live",
11118
+ "title": "CRM"
11119
+ }
10093
11120
  },
10094
11121
  "dependencies": [
10095
11122
  {
@@ -10126,6 +11153,7 @@ var DATA = {
10126
11153
  "icon": "user",
10127
11154
  "color": "amber",
10128
11155
  "description": "A prospect, stakeholder, or contact.",
11156
+ "entityScope": "pod",
10129
11157
  "properties": [
10130
11158
  {
10131
11159
  "slug": "email",
@@ -10211,7 +11239,7 @@ var DATA = {
10211
11239
  "icon": "building-2",
10212
11240
  "color": "#10B981",
10213
11241
  "description": "An organisation being qualified or sold to.",
10214
- "scope": "WORKSPACE",
11242
+ "entityScope": "pod",
10215
11243
  "properties": [
10216
11244
  {
10217
11245
  "slug": "contextSummary",
@@ -10481,6 +11509,18 @@ var DATA = {
10481
11509
  "at_risk",
10482
11510
  "critical"
10483
11511
  ]
11512
+ },
11513
+ {
11514
+ "slug": "driveLink",
11515
+ "label": "Drive folder",
11516
+ "valueType": "string",
11517
+ "inputType": "url"
11518
+ },
11519
+ {
11520
+ "slug": "driveFolderId",
11521
+ "label": "Drive folder ID",
11522
+ "valueType": "string",
11523
+ "inputType": "text"
10484
11524
  }
10485
11525
  ]
10486
11526
  },
@@ -10648,14 +11688,38 @@ var DATA = {
10648
11688
  ],
10649
11689
  "entityLinks": [
10650
11690
  {
10651
- "sourceProfileSlug": "person",
11691
+ "sourceProfileSlug": "person",
11692
+ "targetProfileSlug": "deal",
11693
+ "type": "linked_to_deal"
11694
+ },
11695
+ {
11696
+ "sourceProfileSlug": "company",
10652
11697
  "targetProfileSlug": "deal",
10653
11698
  "type": "linked_to_deal"
10654
11699
  },
11700
+ {
11701
+ "sourceProfileSlug": "person",
11702
+ "targetProfileSlug": "deal",
11703
+ "type": "client_on_deal",
11704
+ "label": "Client side"
11705
+ },
10655
11706
  {
10656
11707
  "sourceProfileSlug": "company",
10657
11708
  "targetProfileSlug": "deal",
10658
- "type": "linked_to_deal"
11709
+ "type": "client_on_deal",
11710
+ "label": "Client side"
11711
+ },
11712
+ {
11713
+ "sourceProfileSlug": "person",
11714
+ "targetProfileSlug": "deal",
11715
+ "type": "partner_on_deal",
11716
+ "label": "Partner side"
11717
+ },
11718
+ {
11719
+ "sourceProfileSlug": "company",
11720
+ "targetProfileSlug": "deal",
11721
+ "type": "partner_on_deal",
11722
+ "label": "Partner side"
10659
11723
  }
10660
11724
  ],
10661
11725
  "onboarding": {
@@ -12524,6 +13588,7 @@ var DATA = {
12524
13588
  "icon": "user",
12525
13589
  "color": "amber",
12526
13590
  "description": "A person in the ecosystem \u2014 a founder, operator, or contact.",
13591
+ "entityScope": "pod",
12527
13592
  "properties": [
12528
13593
  {
12529
13594
  "slug": "email",
@@ -12545,6 +13610,7 @@ var DATA = {
12545
13610
  "icon": "building-2",
12546
13611
  "color": "#10B981",
12547
13612
  "description": "A company in the ecosystem \u2014 a player you operate around.",
13613
+ "entityScope": "pod",
12548
13614
  "properties": [
12549
13615
  {
12550
13616
  "slug": "website",
@@ -13273,7 +14339,7 @@ var DATA = {
13273
14339
  "slug": "business-developer",
13274
14340
  "kind": "workspace",
13275
14341
  "relation": "require",
13276
- "reason": "The lead-first sales motion (crm-lead/crm-client roles, commercial proposals, commission handoff) that the CRM app runs on. Composes onto the CRM workspace above \u2014 without it the CRM lens is provisioned with pod-wide lead/client only and the app's pipeline is empty."
14342
+ "reason": "The lead-first sales motion (the business-development overlay on the pod-wide lead/client roles, commercial proposals, commission handoff) that the CRM app runs on. Composes onto the CRM workspace above \u2014 without it the CRM lens has the bare lead/client roles with no lifecycle or handoff fields and the app's pipeline is empty."
13277
14343
  },
13278
14344
  {
13279
14345
  "slug": "content-os",
@@ -14757,6 +15823,18 @@ var DATA = {
14757
15823
  "at_risk",
14758
15824
  "critical"
14759
15825
  ]
15826
+ },
15827
+ {
15828
+ "slug": "driveLink",
15829
+ "label": "Drive folder",
15830
+ "valueType": "string",
15831
+ "inputType": "url"
15832
+ },
15833
+ {
15834
+ "slug": "driveFolderId",
15835
+ "label": "Drive folder ID",
15836
+ "valueType": "string",
15837
+ "inputType": "text"
14760
15838
  }
14761
15839
  ]
14762
15840
  },
@@ -15248,6 +16326,12 @@ var DATA = {
15248
16326
  "kind": "workspace",
15249
16327
  "relation": "require",
15250
16328
  "reason": "Provides the pod-wide client / partner / sponsor role vocabulary this workspace reuses."
16329
+ },
16330
+ {
16331
+ "slug": "blockchain-ecosystem",
16332
+ "kind": "workspace",
16333
+ "relation": "require",
16334
+ "reason": "Provides the pod-wide grant-program kind (per-chain funding programs) this workspace's rounds belong to."
15251
16335
  }
15252
16336
  ],
15253
16337
  "workspace": {
@@ -16209,6 +17293,18 @@ var DATA = {
16209
17293
  "at_risk",
16210
17294
  "critical"
16211
17295
  ]
17296
+ },
17297
+ {
17298
+ "slug": "driveLink",
17299
+ "label": "Drive folder",
17300
+ "valueType": "string",
17301
+ "inputType": "url"
17302
+ },
17303
+ {
17304
+ "slug": "driveFolderId",
17305
+ "label": "Drive folder ID",
17306
+ "valueType": "string",
17307
+ "inputType": "text"
16212
17308
  }
16213
17309
  ]
16214
17310
  },
@@ -16391,82 +17487,329 @@ var DATA = {
16391
17487
  "awardedSubmissionsCount",
16392
17488
  "averageAwardAmount"
16393
17489
  ],
16394
- "sortBy": "roundNumber",
16395
- "sortDir": "desc"
16396
- }
16397
- },
16398
- {
16399
- "name": "Awarded Projects",
16400
- "type": "gallery",
16401
- "scopeProfileSlug": "grant-project",
16402
- "config": {
16403
- "coverField": "summary",
16404
- "showFields": [
16405
- "currentStatus",
16406
- "awardedRoundCount",
16407
- "submissionCount"
17490
+ "sortBy": "roundNumber",
17491
+ "sortDir": "desc"
17492
+ }
17493
+ },
17494
+ {
17495
+ "name": "Deadlines",
17496
+ "type": "calendar",
17497
+ "scopeProfileSlug": "round",
17498
+ "description": "Upcoming round submission deadlines, on one time axis.",
17499
+ "config": {
17500
+ "dateField": "properties.submissionDeadline",
17501
+ "colorField": "status"
17502
+ }
17503
+ },
17504
+ {
17505
+ "name": "Awarded Projects",
17506
+ "type": "gallery",
17507
+ "scopeProfileSlug": "grant-project",
17508
+ "config": {
17509
+ "showFields": [
17510
+ "currentStatus",
17511
+ "awardedRoundCount",
17512
+ "submissionCount"
17513
+ ]
17514
+ }
17515
+ },
17516
+ {
17517
+ "name": "Analyses",
17518
+ "type": "table",
17519
+ "scopeProfileSlug": "analysis",
17520
+ "config": {
17521
+ "columns": [
17522
+ "analysisType",
17523
+ "subjectType",
17524
+ "fitBand",
17525
+ "confidenceBand",
17526
+ "positionedCompetitivenessScore"
17527
+ ],
17528
+ "sortBy": "positionedCompetitivenessScore",
17529
+ "sortDir": "desc"
17530
+ }
17531
+ }
17532
+ ],
17533
+ "entityLinks": [
17534
+ {
17535
+ "sourceProfileSlug": "grant_submission",
17536
+ "targetProfileSlug": "grant-project",
17537
+ "type": "belongs_to",
17538
+ "label": "Project"
17539
+ },
17540
+ {
17541
+ "sourceProfileSlug": "grant_submission",
17542
+ "targetProfileSlug": "round",
17543
+ "type": "submitted_to",
17544
+ "label": "Round"
17545
+ },
17546
+ {
17547
+ "sourceProfileSlug": "grant_submission",
17548
+ "targetProfileSlug": "company",
17549
+ "type": "submitted_by",
17550
+ "label": "Submitted By"
17551
+ },
17552
+ {
17553
+ "sourceProfileSlug": "grant_submission",
17554
+ "targetProfileSlug": "contract",
17555
+ "type": "for_contract",
17556
+ "label": "Engagement"
17557
+ },
17558
+ {
17559
+ "sourceProfileSlug": "grant_submission",
17560
+ "targetProfileSlug": "deal",
17561
+ "type": "billed_as",
17562
+ "label": "Billed as"
17563
+ },
17564
+ {
17565
+ "sourceProfileSlug": "round",
17566
+ "targetProfileSlug": "grant-program",
17567
+ "type": "belongs_to",
17568
+ "label": "Program"
17569
+ },
17570
+ {
17571
+ "sourceProfileSlug": "contract",
17572
+ "targetProfileSlug": "grant-program",
17573
+ "type": "pursues",
17574
+ "label": "Pursues"
17575
+ },
17576
+ {
17577
+ "sourceProfileSlug": "grant-project",
17578
+ "targetProfileSlug": "round",
17579
+ "type": "appears_in",
17580
+ "label": "Appears in Round"
17581
+ },
17582
+ {
17583
+ "sourceProfileSlug": "grant-project",
17584
+ "targetProfileSlug": "company",
17585
+ "type": "owned_by",
17586
+ "label": "Owning Company"
17587
+ },
17588
+ {
17589
+ "sourceProfileSlug": "analysis",
17590
+ "targetProfileSlug": "grant_submission",
17591
+ "type": "analyzes",
17592
+ "label": "Analyzes"
17593
+ },
17594
+ {
17595
+ "sourceProfileSlug": "signal",
17596
+ "targetProfileSlug": "grant_submission",
17597
+ "type": "evidenced_by",
17598
+ "label": "Evidenced By"
17599
+ }
17600
+ ],
17601
+ "playbooks": [
17602
+ {
17603
+ "name": "Grant Process",
17604
+ "description": "Run an engagement through a grant program end to end \u2014 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.",
17605
+ "goalTemplate": "Advance this engagement ({contract}) through the grant program ({grantProgram}) it is pursuing. FIRST read the program entity's own data \u2014 its cadence, submissionTracks, trancheModel, and nextDeadline \u2014 plus the client context on the contract, and let THOSE drive the plan: do not assume a chain's mechanics, read them from the program. Then work the stages in order and keep the client's `grant_submission` card as the durable state of record:\n1. Identify \u2014 understand the project and which of the program's\n submissionTracks fit; set the submission `stage` to `scope`.\n2. Draft \u2014 write the abstract, then the full proposal (products & services,\n success criteria, GTM, traction); move `stage` `abstract` \u2192 `build` and\n fill the submission's body fields.\n3. Submit \u2014 finalize against the program's nextDeadline and submit; once\n submitted, set `stage` to `awaiting-results`.\n4. Awaiting \u2014 track the decision and keep `status` current\n (in-review / waiting-client / needs-document / blocked).\n5. Awarded \u2014 on a decision, set `outcome` (awarded / rejected / partial)\n and record the awarded amount.\n6. Delivering \u2014 for an award, work the program's trancheModel: fill the\n tranche deliverables and update trancheCompletion as each milestone ships.\n\nNever fabricate \u2014 leave a field blank rather than guess. Every write is proposal-gated for the user to review.",
17606
+ "subjectProfile": {
17607
+ "profileSlug": "contract"
17608
+ },
17609
+ "params": [
17610
+ {
17611
+ "name": "contract",
17612
+ "type": "entity",
17613
+ "profileSlug": "contract",
17614
+ "label": "Engagement",
17615
+ "required": true
17616
+ },
17617
+ {
17618
+ "name": "grantProgram",
17619
+ "type": "entity",
17620
+ "profileSlug": "grant-program",
17621
+ "label": "Grant Program",
17622
+ "required": true
17623
+ }
17624
+ ],
17625
+ "stages": [
17626
+ {
17627
+ "key": "identify",
17628
+ "name": "Identify",
17629
+ "description": "Understand the project and which of the program's submission tracks fit \u2014 before any writing.",
17630
+ "goal": "A clear project understanding and the fitting submission track; submission stage = scope."
17631
+ },
17632
+ {
17633
+ "key": "draft",
17634
+ "name": "Draft",
17635
+ "description": "Write the abstract, then the full proposal, from the client's real material.",
17636
+ "goal": "Abstract then full proposal drafted; submission stage abstract \u2192 build with body fields filled."
17637
+ },
17638
+ {
17639
+ "key": "submit",
17640
+ "name": "Submit",
17641
+ "description": "Finalize against the program deadline and submit the application.",
17642
+ "goal": "Application submitted on time; submission stage = awaiting-results."
17643
+ },
17644
+ {
17645
+ "key": "awaiting",
17646
+ "name": "Awaiting",
17647
+ "description": "Track the decision and keep the client status current.",
17648
+ "goal": "Submission status reflects where the decision stands (in-review / waiting-client / needs-document / blocked)."
17649
+ },
17650
+ {
17651
+ "key": "awarded",
17652
+ "name": "Awarded",
17653
+ "description": "Record the funder's decision and the awarded amount.",
17654
+ "goal": "Submission outcome set (awarded / rejected / partial) with the awarded amount."
17655
+ },
17656
+ {
17657
+ "key": "delivering",
17658
+ "name": "Delivering",
17659
+ "description": "Work the program's tranche model \u2014 ship and record each tranche.",
17660
+ "goal": "Tranche deliverables filled and trancheCompletion updated as milestones ship."
17661
+ }
17662
+ ],
17663
+ "executor": "is-agent",
17664
+ "grants": [
17665
+ "entity.create"
17666
+ ],
17667
+ "status": "active"
17668
+ }
17669
+ ],
17670
+ "automations": [
17671
+ {
17672
+ "key": "grant-submission-sync",
17673
+ "name": "Grant Submission Sync",
17674
+ "description": "Once a day, keep each grant application's grant_submission card current \u2014 read the submission's recent activity, assess where the pursuit stands, and advance its stage + status so the pipeline stays the durable, kanban-visible state of record.",
17675
+ "triggerType": "cron",
17676
+ "triggerConfig": {
17677
+ "expression": "0 8 * * *"
17678
+ },
17679
+ "status": "draft",
17680
+ "metadata": {
17681
+ "templateKey": "grant-submission-sync"
17682
+ },
17683
+ "flow": {
17684
+ "nodes": [
17685
+ {
17686
+ "id": "trigger",
17687
+ "type": "trigger",
17688
+ "position": {
17689
+ "x": 0,
17690
+ "y": 0
17691
+ },
17692
+ "data": {
17693
+ "label": "Daily at 08:00",
17694
+ "triggerType": "cron",
17695
+ "config": {
17696
+ "expression": "0 8 * * *"
17697
+ }
17698
+ }
17699
+ },
17700
+ {
17701
+ "id": "list-submissions",
17702
+ "type": "capability",
17703
+ "position": {
17704
+ "x": 0,
17705
+ "y": 120
17706
+ },
17707
+ "data": {
17708
+ "label": "List grant submissions to maintain",
17709
+ "verbId": "entity.query",
17710
+ "inputMapping": {
17711
+ "profileSlug": "grant_submission",
17712
+ "limit": "100"
17713
+ }
17714
+ }
17715
+ },
17716
+ {
17717
+ "id": "submissions-loop",
17718
+ "type": "loop",
17719
+ "position": {
17720
+ "x": 0,
17721
+ "y": 240
17722
+ },
17723
+ "data": {
17724
+ "label": "For each grant submission",
17725
+ "iteratorExpression": "steps.list-submissions.output.entities",
17726
+ "itemVariable": "submission"
17727
+ }
17728
+ },
17729
+ {
17730
+ "id": "read-activity",
17731
+ "type": "messages_query",
17732
+ "position": {
17733
+ "x": 0,
17734
+ "y": 360
17735
+ },
17736
+ "data": {
17737
+ "label": "Read the submission's recent activity",
17738
+ "subjectEntityId": "{{loop.item.id}}",
17739
+ "limit": 40
17740
+ }
17741
+ },
17742
+ {
17743
+ "id": "assess",
17744
+ "type": "capability",
17745
+ "position": {
17746
+ "x": 0,
17747
+ "y": 480
17748
+ },
17749
+ "data": {
17750
+ "label": "Assess the submission's stage + status \u2192 JSON",
17751
+ "verbId": "ai.generate",
17752
+ "inputMapping": {
17753
+ "json": "true",
17754
+ "maxTokens": "500",
17755
+ "system": "You are a strict data-extraction function for a grants pipeline. Read a grant application's recent activity and report where the application currently stands. Respond with STRICT JSON and NOTHING else. Never fabricate \u2014 if the activity does not clearly indicate a value, keep the submission's current one.",
17756
+ "prompt": `The grant submission: {{loop.item.title}} Current stage: {{loop.item.properties.stage}} Current status: {{loop.item.properties.status}}
17757
+ Recent activity (oldest first): {{steps.read-activity.output.messages}}
17758
+ Report the submission's current state as STRICT JSON, using ONLY these vocabularies and NOTHING else: {
17759
+ "stage": "scope|abstract|build|awaiting-results",
17760
+ "status": "answered|waiting-client|needs-document|in-review|blocked"
17761
+ }`
17762
+ }
17763
+ }
17764
+ },
17765
+ {
17766
+ "id": "advance-submission",
17767
+ "type": "output",
17768
+ "position": {
17769
+ "x": 0,
17770
+ "y": 600
17771
+ },
17772
+ "data": {
17773
+ "label": "Advance the submission stage + status",
17774
+ "outputType": "entity_update",
17775
+ "config": {
17776
+ "entityId": "{{loop.item.id}}",
17777
+ "properties": {
17778
+ "stage": "{{steps.assess.output.stage}}",
17779
+ "status": "{{steps.assess.output.status}}"
17780
+ }
17781
+ }
17782
+ }
17783
+ }
17784
+ ],
17785
+ "edges": [
17786
+ {
17787
+ "id": "gs-trigger-list",
17788
+ "source": "trigger",
17789
+ "target": "list-submissions"
17790
+ },
17791
+ {
17792
+ "id": "gs-list-loop",
17793
+ "source": "list-submissions",
17794
+ "target": "submissions-loop"
17795
+ },
17796
+ {
17797
+ "id": "gs-loop-read",
17798
+ "source": "submissions-loop",
17799
+ "target": "read-activity"
17800
+ },
17801
+ {
17802
+ "id": "gs-read-assess",
17803
+ "source": "read-activity",
17804
+ "target": "assess"
17805
+ },
17806
+ {
17807
+ "id": "gs-assess-advance",
17808
+ "source": "assess",
17809
+ "target": "advance-submission"
17810
+ }
16408
17811
  ]
16409
17812
  }
16410
- },
16411
- {
16412
- "name": "Analyses",
16413
- "type": "table",
16414
- "scopeProfileSlug": "analysis",
16415
- "config": {
16416
- "columns": [
16417
- "analysisType",
16418
- "subjectType",
16419
- "fitBand",
16420
- "confidenceBand",
16421
- "positionedCompetitivenessScore"
16422
- ],
16423
- "sortBy": "positionedCompetitivenessScore",
16424
- "sortDir": "desc"
16425
- }
16426
- }
16427
- ],
16428
- "entityLinks": [
16429
- {
16430
- "sourceProfileSlug": "grant_submission",
16431
- "targetProfileSlug": "grant-project",
16432
- "type": "belongs_to",
16433
- "label": "Project"
16434
- },
16435
- {
16436
- "sourceProfileSlug": "grant_submission",
16437
- "targetProfileSlug": "round",
16438
- "type": "submitted_to",
16439
- "label": "Round"
16440
- },
16441
- {
16442
- "sourceProfileSlug": "grant_submission",
16443
- "targetProfileSlug": "company",
16444
- "type": "submitted_by",
16445
- "label": "Submitted By"
16446
- },
16447
- {
16448
- "sourceProfileSlug": "grant-project",
16449
- "targetProfileSlug": "round",
16450
- "type": "appears_in",
16451
- "label": "Appears in Round"
16452
- },
16453
- {
16454
- "sourceProfileSlug": "grant-project",
16455
- "targetProfileSlug": "company",
16456
- "type": "owned_by",
16457
- "label": "Owning Company"
16458
- },
16459
- {
16460
- "sourceProfileSlug": "analysis",
16461
- "targetProfileSlug": "grant_submission",
16462
- "type": "analyzes",
16463
- "label": "Analyzes"
16464
- },
16465
- {
16466
- "sourceProfileSlug": "signal",
16467
- "targetProfileSlug": "grant_submission",
16468
- "type": "evidenced_by",
16469
- "label": "Evidenced By"
16470
17813
  }
16471
17814
  ],
16472
17815
  "app": {
@@ -19537,7 +20880,7 @@ var DATA = {
19537
20880
  "icon": "User",
19538
20881
  "color": "#06B6D4",
19539
20882
  "description": "People in your intellectual and professional world.",
19540
- "scope": "WORKSPACE",
20883
+ "entityScope": "pod",
19541
20884
  "properties": [
19542
20885
  {
19543
20886
  "slug": "name",
@@ -23150,6 +24493,7 @@ var DATA = {
23150
24493
  "description": "A company or person that provides a capability The Arch can broker a client need against.",
23151
24494
  "scope": "shared",
23152
24495
  "profileKind": "role",
24496
+ "roleCategory": "provider",
23153
24497
  "applicableKinds": [
23154
24498
  "company",
23155
24499
  "person"
@@ -23329,10 +24673,10 @@ var DATA = {
23329
24673
  "y": 240
23330
24674
  },
23331
24675
  "data": {
23332
- "label": "List the solution-provider roster",
24676
+ "label": "List the provider roster (any roleCategory=provider)",
23333
24677
  "verbId": "entity.query",
23334
24678
  "inputMapping": {
23335
- "profileSlug": "solution-provider",
24679
+ "roleCategory": "provider",
23336
24680
  "limit": "100"
23337
24681
  }
23338
24682
  }
@@ -23888,6 +25232,24 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
23888
25232
  "targetProfileSlug": "company",
23889
25233
  "type": "briefing_about_client",
23890
25234
  "label": "Client"
25235
+ },
25236
+ {
25237
+ "sourceProfileSlug": "engagement",
25238
+ "targetProfileSlug": "company",
25239
+ "type": "engagement_for_client",
25240
+ "label": "Client"
25241
+ },
25242
+ {
25243
+ "sourceProfileSlug": "engagement",
25244
+ "targetProfileSlug": "person",
25245
+ "type": "engagement_for_client",
25246
+ "label": "Client"
25247
+ },
25248
+ {
25249
+ "sourceProfileSlug": "contract",
25250
+ "targetProfileSlug": "engagement",
25251
+ "type": "contract_for_engagement",
25252
+ "label": "Engagement"
23891
25253
  }
23892
25254
  ],
23893
25255
  "views": [
@@ -23946,83 +25308,327 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
23946
25308
  "due-date"
23947
25309
  ]
23948
25310
  }
23949
- ],
23950
- "openingQuestions": [
23951
- "Which client engagements are active right now, and who's the client on each?",
23952
- "What's due next \u2014 which deliverables are in flight, and when are they owed?",
23953
- "Which engagements have a contract behind them, and which are still on a handshake?"
23954
- ],
23955
- "doneWhen": "at least one active engagement with a deliverable placed inside it."
23956
- },
23957
- "app": {
23958
- "kind": "stored",
23959
- "cellKey": "entity-detail"
23960
- },
23961
- "layout": {
23962
- "pinnedApps": [
23963
- "dashboard",
23964
- "data"
23965
- ],
23966
- "defaultView": "Engagements",
23967
- "defaultApp": null,
23968
- "sidebarItems": [
23969
- {
23970
- "kind": "view",
23971
- "viewName": "Engagements",
23972
- "label": "Engagements",
23973
- "icon": "briefcase"
23974
- },
23975
- {
23976
- "kind": "view",
23977
- "viewName": "Deliverables",
23978
- "label": "Deliverables",
23979
- "icon": "package-check"
23980
- },
23981
- {
23982
- "kind": "view",
23983
- "viewName": "Contracts",
23984
- "label": "Contracts",
23985
- "icon": "file-signature"
23986
- }
23987
- ]
23988
- },
23989
- "bento": {
23990
- "viewName": "Engagements",
23991
- "blocks": [
23992
- {
23993
- "kind": "view",
23994
- "viewName": "Deliverables",
23995
- "pos": {
23996
- "x": 0,
23997
- "y": 0,
23998
- "w": 6,
23999
- "h": 5
24000
- }
24001
- },
24002
- {
24003
- "kind": "view",
24004
- "viewName": "Contracts",
24005
- "pos": {
24006
- "x": 6,
24007
- "y": 0,
24008
- "w": 6,
24009
- "h": 5
24010
- }
24011
- }
24012
- ]
24013
- },
24014
- "automations": [
25311
+ ],
25312
+ "openingQuestions": [
25313
+ "Which client engagements are active right now, and who's the client on each?",
25314
+ "What's due next \u2014 which deliverables are in flight, and when are they owed?",
25315
+ "Which engagements have a contract behind them, and which are still on a handshake?"
25316
+ ],
25317
+ "doneWhen": "at least one active engagement with a deliverable placed inside it."
25318
+ },
25319
+ "app": {
25320
+ "kind": "stored",
25321
+ "cellKey": "entity-detail"
25322
+ },
25323
+ "layout": {
25324
+ "pinnedApps": [
25325
+ "dashboard",
25326
+ "data"
25327
+ ],
25328
+ "defaultView": "Engagements",
25329
+ "defaultApp": null,
25330
+ "sidebarItems": [
25331
+ {
25332
+ "kind": "view",
25333
+ "viewName": "Engagements",
25334
+ "label": "Engagements",
25335
+ "icon": "briefcase"
25336
+ },
25337
+ {
25338
+ "kind": "view",
25339
+ "viewName": "Deliverables",
25340
+ "label": "Deliverables",
25341
+ "icon": "package-check"
25342
+ },
25343
+ {
25344
+ "kind": "view",
25345
+ "viewName": "Contracts",
25346
+ "label": "Contracts",
25347
+ "icon": "file-signature"
25348
+ }
25349
+ ]
25350
+ },
25351
+ "bento": {
25352
+ "viewName": "Engagements",
25353
+ "blocks": [
25354
+ {
25355
+ "kind": "view",
25356
+ "viewName": "Deliverables",
25357
+ "pos": {
25358
+ "x": 0,
25359
+ "y": 0,
25360
+ "w": 6,
25361
+ "h": 5
25362
+ }
25363
+ },
25364
+ {
25365
+ "kind": "view",
25366
+ "viewName": "Contracts",
25367
+ "pos": {
25368
+ "x": 6,
25369
+ "y": 0,
25370
+ "w": 6,
25371
+ "h": 5
25372
+ }
25373
+ }
25374
+ ]
25375
+ },
25376
+ "automations": [
25377
+ {
25378
+ "key": "operations-daily-client-briefing",
25379
+ "name": "Daily client briefing",
25380
+ "description": "Operations-native daily client briefing. Writes a durable, dated client-briefing ENTITY per client (queryable history, linked to the client) from their recent comms, and posts the recap to the client's internal TEAM channel (never the client-facing channel). Knowledge-building and entity-first \u2014 the delivery OS's structured memory of each client. For a lighter, portable message-only digest installable on any pod, use the client-comms-report capability.",
25381
+ "triggerType": "cron",
25382
+ "triggerConfig": {
25383
+ "expression": "0 8 * * *"
25384
+ },
25385
+ "status": "active",
25386
+ "metadata": {
25387
+ "templateKey": "operations-daily-client-briefing",
25388
+ "requiresPodWideEntityQueryLens": true
25389
+ },
25390
+ "flow": {
25391
+ "nodes": [
25392
+ {
25393
+ "id": "trigger",
25394
+ "type": "trigger",
25395
+ "position": {
25396
+ "x": 0,
25397
+ "y": 0
25398
+ },
25399
+ "data": {
25400
+ "label": "Daily at 08:00",
25401
+ "triggerType": "cron",
25402
+ "config": {
25403
+ "expression": "0 8 * * *"
25404
+ }
25405
+ }
25406
+ },
25407
+ {
25408
+ "id": "list-clients",
25409
+ "type": "capability",
25410
+ "position": {
25411
+ "x": 0,
25412
+ "y": 120
25413
+ },
25414
+ "data": {
25415
+ "label": "List clients (pod-wide `client` role)",
25416
+ "verbId": "entity.query",
25417
+ "inputMapping": {
25418
+ "profileSlug": "client",
25419
+ "scope": "pod",
25420
+ "limit": "100"
25421
+ }
25422
+ }
25423
+ },
25424
+ {
25425
+ "id": "clients-loop",
25426
+ "type": "loop",
25427
+ "position": {
25428
+ "x": 0,
25429
+ "y": 240
25430
+ },
25431
+ "data": {
25432
+ "label": "For each client",
25433
+ "iteratorExpression": "steps.list-clients.output.entities",
25434
+ "itemVariable": "client"
25435
+ }
25436
+ },
25437
+ {
25438
+ "id": "read-comms",
25439
+ "type": "messages_query",
25440
+ "position": {
25441
+ "x": 0,
25442
+ "y": 360
25443
+ },
25444
+ "data": {
25445
+ "label": "Read this client's recent comms",
25446
+ "subjectEntityId": "{{loop.item.id}}",
25447
+ "limit": 40
25448
+ }
25449
+ },
25450
+ {
25451
+ "id": "digest",
25452
+ "type": "capability",
25453
+ "position": {
25454
+ "x": 0,
25455
+ "y": 480
25456
+ },
25457
+ "data": {
25458
+ "label": "Distil the real signal \u2192 JSON",
25459
+ "verbId": "ai.generate",
25460
+ "inputMapping": {
25461
+ "json": "true",
25462
+ "maxTokens": "1200",
25463
+ "system": "You advise a client-delivery team. You read one client's recent inbound messages and surface only what genuinely matters. You are firewall-aware: your output is for the INTERNAL team, never the client.",
25464
+ "prompt": `Digest the recent communications for the client "{{loop.item.title}}" into a concise internal briefing for OUR delivery team.
25465
+ PEOPLE GUARD (do not break): distinguish the CLIENT's people from OUR team members who also appear in the thread. Never treat a teammate as a client contact, lead, or new person. Only real, client-side signal counts.
25466
+ Surface ONLY real signal: status changes, decisions, deals/opportunities, competitor mentions, new client-side leads/contacts, risks/blockers, and deadlines/commitments. Skip small talk and noise. Lead with what matters; keep it tight.
25467
+ Respond with STRICT JSON and NOTHING else: {
25468
+ "hasSignal": "true" or "false" (a STRING),
25469
+ "headline": "<one-line summary; empty string when hasSignal is 'false'>",
25470
+ "summary": "<a short markdown digest for the team; empty string when 'false'>",
25471
+ "risks": "<risks/blockers as short markdown, or empty string>",
25472
+ "nextActions": "<recommended next actions as short markdown, or empty string>"
25473
+ } Set hasSignal to "false" when there is no notable activity (no messages, or only chatter).
25474
+ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
25475
+ }
25476
+ }
25477
+ },
25478
+ {
25479
+ "id": "signal-gate",
25480
+ "type": "condition",
25481
+ "position": {
25482
+ "x": 0,
25483
+ "y": 600
25484
+ },
25485
+ "data": {
25486
+ "label": "Only continue when there's real signal",
25487
+ "expression": "steps.digest.output.hasSignal === true"
25488
+ }
25489
+ },
25490
+ {
25491
+ "id": "write-briefing",
25492
+ "type": "output",
25493
+ "position": {
25494
+ "x": 0,
25495
+ "y": 720
25496
+ },
25497
+ "data": {
25498
+ "label": "Save the client briefing",
25499
+ "outputType": "entity_create",
25500
+ "config": {
25501
+ "profileSlug": "client-briefing",
25502
+ "title": "Briefing \u2014 {{loop.item.title}}",
25503
+ "dedupeBy": "briefing-key",
25504
+ "properties": {
25505
+ "briefing-key": "{{loop.item.id}}::{{trigger.payload.scheduledAt}}",
25506
+ "briefing-client": "{{loop.item.id}}",
25507
+ "briefing-date": "{{trigger.payload.scheduledAt}}",
25508
+ "briefing-headline": "{{steps.digest.output.headline}}",
25509
+ "briefing-summary": "{{steps.digest.output.summary}}",
25510
+ "briefing-risks": "{{steps.digest.output.risks}}",
25511
+ "briefing-next-actions": "{{steps.digest.output.nextActions}}",
25512
+ "briefing-message-count": "{{steps.read-comms.output.count}}"
25513
+ }
25514
+ }
25515
+ }
25516
+ },
25517
+ {
25518
+ "id": "link-briefing",
25519
+ "type": "output",
25520
+ "position": {
25521
+ "x": 0,
25522
+ "y": 840
25523
+ },
25524
+ "data": {
25525
+ "label": "Link briefing \u2192 client",
25526
+ "outputType": "relation_create",
25527
+ "config": {
25528
+ "fromEntityId": "{{steps.write-briefing.output.entityId}}",
25529
+ "toEntityId": "{{loop.item.id}}",
25530
+ "relationType": "briefing_about_client"
25531
+ }
25532
+ }
25533
+ },
25534
+ {
25535
+ "id": "resolve-team",
25536
+ "type": "capability",
25537
+ "position": {
25538
+ "x": 0,
25539
+ "y": 960
25540
+ },
25541
+ "data": {
25542
+ "label": "Resolve the client's TEAM channel (FIREWALL)",
25543
+ "verbId": "channel.resolve",
25544
+ "inputMapping": {
25545
+ "contextObjectType": "entity",
25546
+ "contextObjectId": "{{loop.item.id}}",
25547
+ "branchPurpose": "team"
25548
+ }
25549
+ }
25550
+ },
25551
+ {
25552
+ "id": "post-recap",
25553
+ "type": "output",
25554
+ "position": {
25555
+ "x": 0,
25556
+ "y": 1080
25557
+ },
25558
+ "data": {
25559
+ "label": "Post the recap to the TEAM channel (FIREWALL)",
25560
+ "outputType": "channel_message",
25561
+ "config": {
25562
+ "channelId": "{{steps.resolve-team.output.channelId}}",
25563
+ "content": "**{{loop.item.title}} \u2014 daily briefing**\n{{steps.digest.output.headline}}\n{{steps.digest.output.summary}}\n**Risks:** {{steps.digest.output.risks}}\n**Next:** {{steps.digest.output.nextActions}}",
25564
+ "metadata": {
25565
+ "automationMessage": true,
25566
+ "kind": "client-briefing"
25567
+ }
25568
+ }
25569
+ }
25570
+ }
25571
+ ],
25572
+ "edges": [
25573
+ {
25574
+ "id": "e-trigger-list",
25575
+ "source": "trigger",
25576
+ "target": "list-clients"
25577
+ },
25578
+ {
25579
+ "id": "e-list-loop",
25580
+ "source": "list-clients",
25581
+ "target": "clients-loop"
25582
+ },
25583
+ {
25584
+ "id": "e-loop-read",
25585
+ "source": "clients-loop",
25586
+ "target": "read-comms"
25587
+ },
25588
+ {
25589
+ "id": "e-read-digest",
25590
+ "source": "read-comms",
25591
+ "target": "digest"
25592
+ },
25593
+ {
25594
+ "id": "e-digest-gate",
25595
+ "source": "digest",
25596
+ "target": "signal-gate"
25597
+ },
25598
+ {
25599
+ "id": "e-gate-write",
25600
+ "source": "signal-gate",
25601
+ "target": "write-briefing"
25602
+ },
25603
+ {
25604
+ "id": "e-write-link",
25605
+ "source": "write-briefing",
25606
+ "target": "link-briefing"
25607
+ },
25608
+ {
25609
+ "id": "e-link-resolve",
25610
+ "source": "link-briefing",
25611
+ "target": "resolve-team"
25612
+ },
25613
+ {
25614
+ "id": "e-resolve-post",
25615
+ "source": "resolve-team",
25616
+ "target": "post-recap"
25617
+ }
25618
+ ]
25619
+ }
25620
+ },
24015
25621
  {
24016
- "key": "operations-daily-client-briefing",
24017
- "name": "Daily client briefing",
24018
- "description": "Operations-native daily client briefing. Writes a durable, dated client-briefing ENTITY per client (queryable history, linked to the client) from their recent comms, and posts the recap to the client's internal TEAM channel (never the client-facing channel). Knowledge-building and entity-first \u2014 the delivery OS's structured memory of each client. For a lighter, portable message-only digest installable on any pod, use the client-comms-report capability.",
25622
+ "key": "operations-crm-handoff-pickup",
25623
+ "name": "Pick up handed-off clients",
25624
+ "description": "Daily. Finds pod-wide clients the CRM marked ready or handed off that have no engagement yet, and opens the delivery shell for them \u2014 a proposed engagement plus a draft contract, both linked to the client. Deduped per client, so it is safe to re-run.",
24019
25625
  "triggerType": "cron",
24020
25626
  "triggerConfig": {
24021
- "expression": "0 8 * * *"
25627
+ "expression": "0 7 * * *"
24022
25628
  },
24023
25629
  "status": "active",
24024
25630
  "metadata": {
24025
- "templateKey": "operations-daily-client-briefing",
25631
+ "templateKey": "operations-crm-handoff-pickup",
24026
25632
  "requiresPodWideEntityQueryLens": true
24027
25633
  },
24028
25634
  "flow": {
@@ -24035,10 +25641,10 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
24035
25641
  "y": 0
24036
25642
  },
24037
25643
  "data": {
24038
- "label": "Daily at 08:00",
25644
+ "label": "Daily at 07:00",
24039
25645
  "triggerType": "cron",
24040
25646
  "config": {
24041
- "expression": "0 8 * * *"
25647
+ "expression": "0 7 * * *"
24042
25648
  }
24043
25649
  }
24044
25650
  },
@@ -24060,7 +25666,7 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
24060
25666
  }
24061
25667
  },
24062
25668
  {
24063
- "id": "clients-loop",
25669
+ "id": "handoff-loop",
24064
25670
  "type": "loop",
24065
25671
  "position": {
24066
25672
  "x": 0,
@@ -24073,185 +25679,223 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
24073
25679
  }
24074
25680
  },
24075
25681
  {
24076
- "id": "read-comms",
24077
- "type": "messages_query",
25682
+ "id": "read-client-facets",
25683
+ "type": "capability",
24078
25684
  "position": {
24079
25685
  "x": 0,
24080
25686
  "y": 360
24081
25687
  },
24082
25688
  "data": {
24083
- "label": "Read this client's recent comms",
24084
- "subjectEntityId": "{{loop.item.id}}",
24085
- "limit": 40
25689
+ "label": "Read this party's role facets",
25690
+ "verbId": "entity_facet.list",
25691
+ "inputMapping": {
25692
+ "entityId": "{{loop.item.id}}"
25693
+ }
25694
+ }
25695
+ },
25696
+ {
25697
+ "id": "client-handoff-status",
25698
+ "type": "transform",
25699
+ "position": {
25700
+ "x": 0,
25701
+ "y": 480
25702
+ },
25703
+ "data": {
25704
+ "label": "Read the client facet's handoff status",
25705
+ "expression": "{{steps.read-client-facets.output.facets}} | filter:item.profileSlug === client | map:{{item.properties.handoffStatus}}"
25706
+ }
25707
+ },
25708
+ {
25709
+ "id": "handoff-gate",
25710
+ "type": "condition",
25711
+ "position": {
25712
+ "x": 0,
25713
+ "y": 600
25714
+ },
25715
+ "data": {
25716
+ "label": "Only clients the CRM marked ready or handed off",
25717
+ "expression": "steps.client-handoff-status.output.result contains-any ready,handed_off"
25718
+ }
25719
+ },
25720
+ {
25721
+ "id": "existing-engagements",
25722
+ "type": "query",
25723
+ "position": {
25724
+ "x": 0,
25725
+ "y": 720
25726
+ },
25727
+ "data": {
25728
+ "label": "Does this client already have an engagement?",
25729
+ "profileSlug": "engagement",
25730
+ "filter": '{"engagement-client": "{{loop.item.id}}"}',
25731
+ "limit": 1
24086
25732
  }
24087
25733
  },
24088
25734
  {
24089
- "id": "digest",
24090
- "type": "capability",
25735
+ "id": "engagement-gate",
25736
+ "type": "condition",
24091
25737
  "position": {
24092
25738
  "x": 0,
24093
- "y": 480
25739
+ "y": 840
24094
25740
  },
24095
25741
  "data": {
24096
- "label": "Distil the real signal \u2192 JSON",
24097
- "verbId": "ai.generate",
24098
- "inputMapping": {
24099
- "json": "true",
24100
- "maxTokens": "1200",
24101
- "system": "You advise a client-delivery team. You read one client's recent inbound messages and surface only what genuinely matters. You are firewall-aware: your output is for the INTERNAL team, never the client.",
24102
- "prompt": `Digest the recent communications for the client "{{loop.item.title}}" into a concise internal briefing for OUR delivery team.
24103
- PEOPLE GUARD (do not break): distinguish the CLIENT's people from OUR team members who also appear in the thread. Never treat a teammate as a client contact, lead, or new person. Only real, client-side signal counts.
24104
- Surface ONLY real signal: status changes, decisions, deals/opportunities, competitor mentions, new client-side leads/contacts, risks/blockers, and deadlines/commitments. Skip small talk and noise. Lead with what matters; keep it tight.
24105
- Respond with STRICT JSON and NOTHING else: {
24106
- "hasSignal": "true" or "false" (a STRING),
24107
- "headline": "<one-line summary; empty string when hasSignal is 'false'>",
24108
- "summary": "<a short markdown digest for the team; empty string when 'false'>",
24109
- "risks": "<risks/blockers as short markdown, or empty string>",
24110
- "nextActions": "<recommended next actions as short markdown, or empty string>"
24111
- } Set hasSignal to "false" when there is no notable activity (no messages, or only chatter).
24112
- Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
24113
- }
25742
+ "label": "Only clients with no engagement yet",
25743
+ "expression": "steps.existing-engagements.output.count === 0"
24114
25744
  }
24115
25745
  },
24116
25746
  {
24117
- "id": "signal-gate",
24118
- "type": "condition",
25747
+ "id": "handoff-time",
25748
+ "type": "compute",
24119
25749
  "position": {
24120
25750
  "x": 0,
24121
- "y": 600
25751
+ "y": 900
24122
25752
  },
24123
25753
  "data": {
24124
- "label": "Only continue when there's real signal",
24125
- "expression": "steps.digest.output.hasSignal === true"
25754
+ "label": "Record handoff time",
25755
+ "operation": "now"
24126
25756
  }
24127
25757
  },
24128
25758
  {
24129
- "id": "write-briefing",
25759
+ "id": "create-engagement",
24130
25760
  "type": "output",
24131
25761
  "position": {
24132
25762
  "x": 0,
24133
- "y": 720
25763
+ "y": 960
24134
25764
  },
24135
25765
  "data": {
24136
- "label": "Save the client briefing",
25766
+ "label": "Open a proposed engagement",
24137
25767
  "outputType": "entity_create",
24138
25768
  "config": {
24139
- "profileSlug": "client-briefing",
24140
- "title": "Briefing \u2014 {{loop.item.title}}",
24141
- "dedupeBy": "briefing-key",
25769
+ "profileSlug": "engagement",
25770
+ "title": "Engagement \u2014 {{loop.item.title}}",
25771
+ "dedupeBy": "engagement-client",
24142
25772
  "properties": {
24143
- "briefing-key": "{{loop.item.id}}::{{trigger.payload.scheduledAt}}",
24144
- "briefing-client": "{{loop.item.id}}",
24145
- "briefing-date": "{{trigger.payload.scheduledAt}}",
24146
- "briefing-headline": "{{steps.digest.output.headline}}",
24147
- "briefing-summary": "{{steps.digest.output.summary}}",
24148
- "briefing-risks": "{{steps.digest.output.risks}}",
24149
- "briefing-next-actions": "{{steps.digest.output.nextActions}}",
24150
- "briefing-message-count": "{{steps.read-comms.output.count}}"
25773
+ "engagement-status": "proposed",
25774
+ "engagement-client": "{{loop.item.id}}",
25775
+ "engagement-start": "{{steps.handoff-time.output.result}}",
25776
+ "engagement-notes": "Opened automatically from the CRM handoff. Confirm scope, dates, and owner before activating."
24151
25777
  }
24152
25778
  }
24153
25779
  }
24154
25780
  },
24155
25781
  {
24156
- "id": "link-briefing",
25782
+ "id": "link-engagement",
24157
25783
  "type": "output",
24158
25784
  "position": {
24159
25785
  "x": 0,
24160
- "y": 840
25786
+ "y": 1080
24161
25787
  },
24162
25788
  "data": {
24163
- "label": "Link briefing \u2192 client",
25789
+ "label": "Link engagement \u2192 client",
24164
25790
  "outputType": "relation_create",
24165
25791
  "config": {
24166
- "fromEntityId": "{{steps.write-briefing.output.entityId}}",
25792
+ "fromEntityId": "{{steps.create-engagement.output.entityId}}",
24167
25793
  "toEntityId": "{{loop.item.id}}",
24168
- "relationType": "briefing_about_client"
25794
+ "relationType": "engagement_for_client"
24169
25795
  }
24170
25796
  }
24171
25797
  },
24172
25798
  {
24173
- "id": "resolve-team",
24174
- "type": "capability",
25799
+ "id": "create-contract",
25800
+ "type": "output",
24175
25801
  "position": {
24176
25802
  "x": 0,
24177
- "y": 960
25803
+ "y": 1200
24178
25804
  },
24179
25805
  "data": {
24180
- "label": "Resolve the client's TEAM channel (FIREWALL)",
24181
- "verbId": "channel.resolve",
24182
- "inputMapping": {
24183
- "contextObjectType": "entity",
24184
- "contextObjectId": "{{loop.item.id}}",
24185
- "branchPurpose": "team"
25806
+ "label": "Open a draft contract",
25807
+ "outputType": "entity_create",
25808
+ "config": {
25809
+ "profileSlug": "contract",
25810
+ "title": "Contract \u2014 {{loop.item.title}}",
25811
+ "dedupeBy": "contract-engagement",
25812
+ "properties": {
25813
+ "contract-status": "draft",
25814
+ "contract-engagement": "{{steps.create-engagement.output.entityId}}",
25815
+ "start-date": "{{steps.handoff-time.output.result}}",
25816
+ "contract-notes": "Drafted automatically from the CRM handoff. Attach the signed document and set the term."
25817
+ }
24186
25818
  }
24187
25819
  }
24188
25820
  },
24189
25821
  {
24190
- "id": "post-recap",
25822
+ "id": "link-contract",
24191
25823
  "type": "output",
24192
25824
  "position": {
24193
25825
  "x": 0,
24194
- "y": 1080
25826
+ "y": 1320
24195
25827
  },
24196
25828
  "data": {
24197
- "label": "Post the recap to the TEAM channel (FIREWALL)",
24198
- "outputType": "channel_message",
25829
+ "label": "Link contract \u2192 engagement",
25830
+ "outputType": "relation_create",
24199
25831
  "config": {
24200
- "channelId": "{{steps.resolve-team.output.channelId}}",
24201
- "content": "**{{loop.item.title}} \u2014 daily briefing**\n{{steps.digest.output.headline}}\n{{steps.digest.output.summary}}\n**Risks:** {{steps.digest.output.risks}}\n**Next:** {{steps.digest.output.nextActions}}",
24202
- "metadata": {
24203
- "automationMessage": true,
24204
- "kind": "client-briefing"
24205
- }
25832
+ "fromEntityId": "{{steps.create-contract.output.entityId}}",
25833
+ "toEntityId": "{{steps.create-engagement.output.entityId}}",
25834
+ "relationType": "contract_for_engagement"
24206
25835
  }
24207
25836
  }
24208
25837
  }
24209
25838
  ],
24210
25839
  "edges": [
24211
25840
  {
24212
- "id": "e-trigger-list",
25841
+ "id": "h-trigger-list",
24213
25842
  "source": "trigger",
24214
25843
  "target": "list-clients"
24215
25844
  },
24216
25845
  {
24217
- "id": "e-list-loop",
25846
+ "id": "h-list-time",
24218
25847
  "source": "list-clients",
24219
- "target": "clients-loop"
25848
+ "target": "handoff-time"
24220
25849
  },
24221
25850
  {
24222
- "id": "e-loop-read",
24223
- "source": "clients-loop",
24224
- "target": "read-comms"
25851
+ "id": "h-time-loop",
25852
+ "source": "handoff-time",
25853
+ "target": "handoff-loop"
24225
25854
  },
24226
25855
  {
24227
- "id": "e-read-digest",
24228
- "source": "read-comms",
24229
- "target": "digest"
25856
+ "id": "h-loop-facets",
25857
+ "source": "handoff-loop",
25858
+ "target": "read-client-facets"
24230
25859
  },
24231
25860
  {
24232
- "id": "e-digest-gate",
24233
- "source": "digest",
24234
- "target": "signal-gate"
25861
+ "id": "h-facets-status",
25862
+ "source": "read-client-facets",
25863
+ "target": "client-handoff-status"
24235
25864
  },
24236
25865
  {
24237
- "id": "e-gate-write",
24238
- "source": "signal-gate",
24239
- "target": "write-briefing"
25866
+ "id": "h-status-gate",
25867
+ "source": "client-handoff-status",
25868
+ "target": "handoff-gate"
24240
25869
  },
24241
25870
  {
24242
- "id": "e-write-link",
24243
- "source": "write-briefing",
24244
- "target": "link-briefing"
25871
+ "id": "h-gate-existing",
25872
+ "source": "handoff-gate",
25873
+ "target": "existing-engagements"
24245
25874
  },
24246
25875
  {
24247
- "id": "e-link-resolve",
24248
- "source": "link-briefing",
24249
- "target": "resolve-team"
25876
+ "id": "h-existing-gate",
25877
+ "source": "existing-engagements",
25878
+ "target": "engagement-gate"
24250
25879
  },
24251
25880
  {
24252
- "id": "e-resolve-post",
24253
- "source": "resolve-team",
24254
- "target": "post-recap"
25881
+ "id": "h-gate-engagement",
25882
+ "source": "engagement-gate",
25883
+ "target": "create-engagement"
25884
+ },
25885
+ {
25886
+ "id": "h-engagement-link",
25887
+ "source": "create-engagement",
25888
+ "target": "link-engagement"
25889
+ },
25890
+ {
25891
+ "id": "h-link-contract",
25892
+ "source": "link-engagement",
25893
+ "target": "create-contract"
25894
+ },
25895
+ {
25896
+ "id": "h-contract-link",
25897
+ "source": "create-contract",
25898
+ "target": "link-contract"
24255
25899
  }
24256
25900
  ]
24257
25901
  }
@@ -24565,6 +26209,164 @@ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
24565
26209
  "status": "active"
24566
26210
  }
24567
26211
  ],
26212
+ "automations": [
26213
+ {
26214
+ "key": "linkedin-conversation-ingest",
26215
+ "name": "LinkedIn conversation ingest",
26216
+ "description": "Pull LinkedIn conversations via Unipile and record each thread as an external channel + its messages through channel.ingest (idempotent, so re-runs are safe). Cron = low-frequency reconciliation; manual trigger = on-demand backfill.",
26217
+ "triggerType": "cron",
26218
+ "triggerConfig": {
26219
+ "expression": "0 6 * * *"
26220
+ },
26221
+ "status": "draft",
26222
+ "flow": {
26223
+ "nodes": [
26224
+ {
26225
+ "id": "trigger",
26226
+ "type": "trigger",
26227
+ "position": {
26228
+ "x": 0,
26229
+ "y": 0
26230
+ },
26231
+ "data": {
26232
+ "label": "Daily 06:00 reconciliation (also fired manually for backfill)",
26233
+ "triggerType": "cron",
26234
+ "config": {
26235
+ "expression": "0 6 * * *"
26236
+ }
26237
+ }
26238
+ },
26239
+ {
26240
+ "id": "list-accounts",
26241
+ "type": "capability",
26242
+ "position": {
26243
+ "x": 0,
26244
+ "y": 120
26245
+ },
26246
+ "data": {
26247
+ "label": "Resolve the connected Unipile account",
26248
+ "verbId": "unipile_list_accounts"
26249
+ }
26250
+ },
26251
+ {
26252
+ "id": "filter-linkedin-account",
26253
+ "type": "transform",
26254
+ "position": {
26255
+ "x": 0,
26256
+ "y": 180
26257
+ },
26258
+ "data": {
26259
+ "label": "Keep only LinkedIn Unipile accounts",
26260
+ "expression": "{{steps.list-accounts.output.accounts}} | filter:item.type == 'LINKEDIN'"
26261
+ }
26262
+ },
26263
+ {
26264
+ "id": "list-conversations",
26265
+ "type": "capability",
26266
+ "position": {
26267
+ "x": 0,
26268
+ "y": 240
26269
+ },
26270
+ "data": {
26271
+ "label": "List LinkedIn conversations for the account",
26272
+ "verbId": "unipile_list_conversations",
26273
+ "inputMapping": {
26274
+ "accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
26275
+ }
26276
+ }
26277
+ },
26278
+ {
26279
+ "id": "conversations-loop",
26280
+ "type": "loop",
26281
+ "position": {
26282
+ "x": 0,
26283
+ "y": 360
26284
+ },
26285
+ "data": {
26286
+ "label": "For each conversation",
26287
+ "iteratorExpression": "steps.list-conversations.output.conversations",
26288
+ "itemVariable": "conversation"
26289
+ }
26290
+ },
26291
+ {
26292
+ "id": "list-messages",
26293
+ "type": "capability",
26294
+ "position": {
26295
+ "x": 0,
26296
+ "y": 480
26297
+ },
26298
+ "data": {
26299
+ "label": "List this thread's messages",
26300
+ "verbId": "unipile_list_messages",
26301
+ "inputMapping": {
26302
+ "threadId": "{{loop.item.id}}",
26303
+ "accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
26304
+ }
26305
+ }
26306
+ },
26307
+ {
26308
+ "id": "ingest",
26309
+ "type": "capability",
26310
+ "position": {
26311
+ "x": 0,
26312
+ "y": 600
26313
+ },
26314
+ "data": {
26315
+ "label": "Ingest thread + messages (idempotent, generic)",
26316
+ "verbId": "channel.ingest",
26317
+ "inputMapping": {
26318
+ "provider": "linkedin",
26319
+ "externalId": "{{loop.item.id}}",
26320
+ "title": "{{loop.item.name}}",
26321
+ "accountExternalId": "{{steps.filter-linkedin-account.output.result.0.id}}",
26322
+ "workspaceId": "499328b7-fd35-4143-b1bc-67b9e45d9b4b",
26323
+ "suppressSideEffects": true,
26324
+ "messages": "{{steps.list-messages.output.messages}}",
26325
+ "messageMap": {
26326
+ "text": "text",
26327
+ "id": "id",
26328
+ "sentAt": "timestamp",
26329
+ "isOutbound": "is_sender"
26330
+ }
26331
+ }
26332
+ }
26333
+ }
26334
+ ],
26335
+ "edges": [
26336
+ {
26337
+ "id": "e-trigger-accounts",
26338
+ "source": "trigger",
26339
+ "target": "list-accounts"
26340
+ },
26341
+ {
26342
+ "id": "e-accounts-filter",
26343
+ "source": "list-accounts",
26344
+ "target": "filter-linkedin-account"
26345
+ },
26346
+ {
26347
+ "id": "e-filter-convos",
26348
+ "source": "filter-linkedin-account",
26349
+ "target": "list-conversations"
26350
+ },
26351
+ {
26352
+ "id": "e-convos-loop",
26353
+ "source": "list-conversations",
26354
+ "target": "conversations-loop"
26355
+ },
26356
+ {
26357
+ "id": "e-loop-messages",
26358
+ "source": "conversations-loop",
26359
+ "target": "list-messages"
26360
+ },
26361
+ {
26362
+ "id": "e-messages-ingest",
26363
+ "source": "list-messages",
26364
+ "target": "ingest"
26365
+ }
26366
+ ]
26367
+ }
26368
+ }
26369
+ ],
24568
26370
  "integrations": [
24569
26371
  {
24570
26372
  "templateKey": "unipile-linkedin"
@@ -25130,7 +26932,7 @@ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
25130
26932
  "icon": "user",
25131
26933
  "color": "#0891B2",
25132
26934
  "description": "Team member or stakeholder",
25133
- "scope": "WORKSPACE",
26935
+ "entityScope": "pod",
25134
26936
  "properties": [
25135
26937
  {
25136
26938
  "slug": "person-role",
@@ -28416,6 +30218,12 @@ counter-evidence: research that only kept what agreed with it isn't research.
28416
30218
  "kind": "workspace",
28417
30219
  "relation": "require",
28418
30220
  "reason": "The engineering / ops dashboard \u2014 the build-side view of the company."
30221
+ },
30222
+ {
30223
+ "slug": "networking",
30224
+ "kind": "workspace",
30225
+ "relation": "require",
30226
+ "reason": "The partner-intro layer \u2014 need \u2192 intro-matcher \u2192 intro \u2192 billed_as \u2192 deal, with the A\u2194B confidentiality firewall; the Stellar Client-Sync emits needs that depend on it."
28419
30227
  }
28420
30228
  ],
28421
30229
  "workspace": {
@@ -28633,7 +30441,8 @@ function rowToRemoteEntry(row, isPrivate) {
28633
30441
  domain: row.domain ?? void 0,
28634
30442
  tags: row.tags ?? [],
28635
30443
  icon: row.icon ?? null,
28636
- isPrivate
30444
+ isPrivate,
30445
+ items: row.items
28637
30446
  // Both list routes strip `definition`; absence ⇒ bundle wins a slug conflict,
28638
30447
  // and the install path fetches the full definition per-slug when needed.
28639
30448
  };
@@ -28669,6 +30478,8 @@ function bundledToEntry(tpl) {
28669
30478
  tags: tpl.meta.tags ?? [],
28670
30479
  icon: tpl.meta.icon,
28671
30480
  isPrivate: tpl.meta.isPublic === false,
30481
+ // Bundled workspace templates carry no cells — be honest, not `undefined`.
30482
+ items: [],
28672
30483
  template: tpl
28673
30484
  };
28674
30485
  }
@@ -28683,6 +30494,7 @@ function remoteToEntry(row) {
28683
30494
  tags: row.tags ?? [],
28684
30495
  icon: row.icon ?? null,
28685
30496
  isPrivate: row.isPrivate ?? false,
30497
+ items: row.items ?? [],
28686
30498
  remote: row
28687
30499
  };
28688
30500
  }
@@ -28889,17 +30701,7 @@ function layerTemplateGraph(input) {
28889
30701
  }
28890
30702
 
28891
30703
  // src/concept-guard.ts
28892
- var DECLARED_DIVERGENCES = {
28893
- // Two-tier lead vocabulary, documented at business-developer.yaml:1-16.
28894
- // Foundation's `lead` is a SHARED pod-wide role: being a lead is true
28895
- // everywhere. `crm-lead` is a WORKSPACE-scoped role so a person can be a lead
28896
- // inside one CRM workspace without becoming a lead across the whole pod. The
28897
- // tiers are intentionally separate rows; `semanticSlug: crm.lead` records the
28898
- // kinship so the two are discoverable as one concept via getBySemanticSlug.
28899
- "crm-lead|lead": "business-developer: workspace-tier lead, deliberately NOT pod-wide foundation `lead` (two-tier CRM design)",
28900
- // Same reasoning for the post-sale side of the same lifecycle.
28901
- "client|crm-client": "business-developer: workspace-tier client, deliberately NOT pod-wide foundation `client` (two-tier CRM design)"
28902
- };
30704
+ var DECLARED_DIVERGENCES = {};
28903
30705
  function effectiveSemanticSlug(profile) {
28904
30706
  return profile.semanticSlug ?? profile.slug;
28905
30707
  }
@@ -28918,7 +30720,7 @@ function declaredValue(profile, field) {
28918
30720
  function pairKey(a, b) {
28919
30721
  return [a, b].sort().join("|");
28920
30722
  }
28921
- function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
30723
+ function findConceptViolations(templates = WORKSPACE_TEMPLATES, declaredDivergences = DECLARED_DIVERGENCES) {
28922
30724
  const violations = [];
28923
30725
  const all = [];
28924
30726
  for (const [templateSlug, template] of Object.entries(templates)) {
@@ -28938,7 +30740,7 @@ function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
28938
30740
  const term = conceptTerm(effectiveSemanticSlug(a.profile));
28939
30741
  if (term !== conceptTerm(effectiveSemanticSlug(b.profile))) continue;
28940
30742
  const key = pairKey(a.profile.slug, b.profile.slug);
28941
- if (key in DECLARED_DIVERGENCES) {
30743
+ if (key in declaredDivergences) {
28942
30744
  usedAllowlistKeys.add(key);
28943
30745
  continue;
28944
30746
  }
@@ -28977,7 +30779,7 @@ function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
28977
30779
  }
28978
30780
  }
28979
30781
  }
28980
- for (const key of Object.keys(DECLARED_DIVERGENCES)) {
30782
+ for (const key of Object.keys(declaredDivergences)) {
28981
30783
  if (usedAllowlistKeys.has(key)) continue;
28982
30784
  violations.push({
28983
30785
  rule: "stale-allowlist",
@@ -29057,6 +30859,8 @@ var FLOW_NODE_TYPES = [
29057
30859
  "select",
29058
30860
  "claim",
29059
30861
  "messages_query",
30862
+ "runs_query",
30863
+ "proposals_query",
29060
30864
  "switch",
29061
30865
  "skill",
29062
30866
  "capability",
@@ -29081,7 +30885,24 @@ var CHANNEL_MESSAGE_CHANNEL_TYPES = [
29081
30885
  "proactive",
29082
30886
  "subjectEntity"
29083
30887
  ];
30888
+ var LOOP_BODY_NODE_TYPES = [
30889
+ "command",
30890
+ "output",
30891
+ "playbook_run",
30892
+ "messages_query",
30893
+ "runs_query",
30894
+ "proposals_query",
30895
+ "query",
30896
+ "fetch",
30897
+ "transform",
30898
+ "condition",
30899
+ "skill",
30900
+ "capability"
30901
+ ];
30902
+ var LOOP_PROVIDED_BINDINGS = ["item", "index"];
29084
30903
  var NODE_TYPE_SET = new Set(FLOW_NODE_TYPES);
30904
+ var LOOP_BODY_TYPE_SET = new Set(LOOP_BODY_NODE_TYPES);
30905
+ var LOOP_BINDING_SET = new Set(LOOP_PROVIDED_BINDINGS);
29085
30906
  var OUTPUT_TYPE_SET = new Set(FLOW_OUTPUT_TYPES);
29086
30907
  var CHANNEL_TYPE_SET = new Set(CHANNEL_MESSAGE_CHANNEL_TYPES);
29087
30908
  function isRecord(v) {
@@ -29323,6 +31144,40 @@ function validateFlowDefinition(flow, resolvers) {
29323
31144
  }
29324
31145
  }
29325
31146
  }
31147
+ const loopBodyNodeIds = /* @__PURE__ */ new Set();
31148
+ for (const node of nodes) {
31149
+ if (!nonEmptyString(node.id) || node.type !== "loop") continue;
31150
+ for (const id of collectLoopBody(node.id, nodesById, edges)) {
31151
+ loopBodyNodeIds.add(id);
31152
+ }
31153
+ }
31154
+ for (const node of nodes) {
31155
+ if (!nonEmptyString(node.id)) continue;
31156
+ const refs = collectLoopBindingRefs(node.data);
31157
+ if (refs.length === 0) continue;
31158
+ if (!loopBodyNodeIds.has(node.id)) {
31159
+ errors.push({
31160
+ nodeId: node.id,
31161
+ code: "loop_ref_outside_loop_body",
31162
+ message: `Node "${node.id}" references ${refs.map((r) => `\`{{loop.${r}...}}\``).join(
31163
+ ", "
31164
+ )} but is not inside a loop body, so there is no item to bind \u2014 it would run once with an empty value. Connect it downstream of a loop node with no switch/delay/nested-loop/sub_automation node in between.`
31165
+ });
31166
+ continue;
31167
+ }
31168
+ const unknown = refs.filter((r) => !LOOP_BINDING_SET.has(r));
31169
+ if (unknown.length > 0) {
31170
+ errors.push({
31171
+ nodeId: node.id,
31172
+ code: "loop_ref_unknown_path",
31173
+ message: `Node "${node.id}" references ${unknown.map((r) => `\`{{loop.${r}}}\``).join(
31174
+ ", "
31175
+ )}, which a loop does not provide. A loop binds only: ${LOOP_PROVIDED_BINDINGS.map(
31176
+ (b) => `\`loop.${b}\``
31177
+ ).join(", ")}.`
31178
+ });
31179
+ }
31180
+ }
29326
31181
  const cyclic = detectCycle(nodeIds, edges);
29327
31182
  if (cyclic.length > 0) {
29328
31183
  errors.push({
@@ -29334,6 +31189,53 @@ function validateFlowDefinition(flow, resolvers) {
29334
31189
  }
29335
31190
  return { valid: errors.length === 0, errors };
29336
31191
  }
31192
+ function collectLoopBody(loopNodeId, nodesById, edges) {
31193
+ const body = /* @__PURE__ */ new Set();
31194
+ const stack = [];
31195
+ for (const edge of edges) {
31196
+ if (edge.source === loopNodeId && nonEmptyString(edge.target))
31197
+ stack.push(edge.target);
31198
+ }
31199
+ while (stack.length > 0) {
31200
+ const id = stack.pop();
31201
+ if (body.has(id)) continue;
31202
+ const node = nodesById.get(id);
31203
+ if (!node || !nonEmptyString(node.type) || !LOOP_BODY_TYPE_SET.has(node.type))
31204
+ continue;
31205
+ body.add(id);
31206
+ for (const edge of edges) {
31207
+ if (edge.source === id && nonEmptyString(edge.target))
31208
+ stack.push(edge.target);
31209
+ }
31210
+ }
31211
+ return body;
31212
+ }
31213
+ function collectLoopBindingRefs(data) {
31214
+ const found = /* @__PURE__ */ new Set();
31215
+ const walk = (v) => {
31216
+ if (typeof v === "string") {
31217
+ const head = v.split("|")[0];
31218
+ const re = /\{\{\s*loop\.([A-Za-z0-9_$]+)/g;
31219
+ let m;
31220
+ let matched = false;
31221
+ while ((m = re.exec(head)) !== null) {
31222
+ found.add(m[1]);
31223
+ matched = true;
31224
+ }
31225
+ if (!matched && /\{\{\s*loop\./.test(head)) found.add("");
31226
+ return;
31227
+ }
31228
+ if (Array.isArray(v)) {
31229
+ for (const x of v) walk(x);
31230
+ return;
31231
+ }
31232
+ if (isRecord(v)) {
31233
+ for (const x of Object.values(v)) walk(x);
31234
+ }
31235
+ };
31236
+ walk(data);
31237
+ return [...found];
31238
+ }
29337
31239
  function detectCycle(nodeIds, edges) {
29338
31240
  const inDegree = /* @__PURE__ */ new Map();
29339
31241
  const adjacency = /* @__PURE__ */ new Map();
@@ -29404,7 +31306,12 @@ var SYSTEM_PROFILES = /* @__PURE__ */ new Set([
29404
31306
  "question",
29405
31307
  "research",
29406
31308
  "knowledge",
29407
- "user_observation"
31309
+ "user_observation",
31310
+ // Generated artifact — a workspace-scoped pod system profile
31311
+ // (`ensure-system-profiles.ts`) written by the `base` report automation. It
31312
+ // was simply missing from this hand-maintained list; the flow's
31313
+ // `create-report` node references it, so referential integrity needs it here.
31314
+ "report"
29408
31315
  ]);
29409
31316
  function isPodWideProfile(p) {
29410
31317
  return String(p.scope || "").toLowerCase() === "shared";
@@ -29474,8 +31381,13 @@ function validateTemplate(tpl, opts = {}) {
29474
31381
  );
29475
31382
  }
29476
31383
  }
29477
- if (!tpl.profiles || tpl.profiles.length === 0) {
29478
- add("profiles-required", "profiles", "At least one profile is required");
31384
+ const carriesOperationalDefaults = tpl.relationDefs && tpl.relationDefs.length > 0 || tpl.commands && tpl.commands.length > 0 || tpl.automations && tpl.automations.length > 0;
31385
+ if ((!tpl.profiles || tpl.profiles.length === 0) && !carriesOperationalDefaults) {
31386
+ add(
31387
+ "profiles-required",
31388
+ "profiles",
31389
+ "At least one profile is required (or operational defaults: relationDefs / commands / automations)"
31390
+ );
29479
31391
  }
29480
31392
  if (tpl.app && tpl.layout && Object.prototype.hasOwnProperty.call(tpl.layout, "primarySurface")) {
29481
31393
  add(
@@ -30038,6 +31950,11 @@ function toWorkspaceDefinition(slugOrYaml, nameOverride) {
30038
31950
  seedEntities: tpl.seedEntities ?? [],
30039
31951
  suggestedRelations: tpl.suggestedRelations,
30040
31952
  displayTemplates: tpl.displayTemplates,
31953
+ // Relation-type vocabulary + intelligence commands — carried verbatim so the
31954
+ // one reconcile door can seed the universal defaults (the `base` template)
31955
+ // that three hardcoded seeders used to. Authored form IS the wire form.
31956
+ relationDefs: tpl.relationDefs,
31957
+ commands: tpl.commands,
30041
31958
  layoutConfig: normalizeTemplateLayout(tpl.layout, tpl.app),
30042
31959
  bentoViewName: tpl.bento?.viewName,
30043
31960
  bentoLayout: bento.bentoLayout,
@@ -30108,6 +32025,8 @@ function toPackageDefinition(slugOrYaml) {
30108
32025
  suggestedRelations: tpl.suggestedRelations,
30109
32026
  displayTemplates: tpl.displayTemplates,
30110
32027
  entityLinks: tpl.entityLinks,
32028
+ relationDefs: tpl.relationDefs,
32029
+ commands: tpl.commands,
30111
32030
  bentoLayout: bento.bentoLayout,
30112
32031
  bentoViewBlocks: bento.bentoViewBlocks,
30113
32032
  bentoViewName: tpl.bento?.viewName,