@synap-core/workspace-templates 0.12.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +483 -0
- package/automations/bookmark-enrichment.automation.json +321 -0
- package/automations/github-pr-events.automation.json +505 -0
- package/automations/mail-feed.automation.json +86 -0
- package/automations/ship-a-change-checks-green.automation.json +106 -0
- package/automations/ship-a-change-checks-red.automation.json +106 -0
- package/automations/ship-a-change-deployed.automation.json +56 -0
- package/automations/ship-a-change-pr-merged.automation.json +110 -0
- package/automations/ship-a-change-pr-opened.automation.json +104 -0
- package/automations/url-bookmark-capture.automation.json +132 -0
- package/capabilities/agency-skills.capability.json +157 -0
- package/capabilities/apify.capability.json +265 -0
- package/capabilities/apollo.capability.json +120 -0
- package/capabilities/arch-backend.capability.json +97 -0
- package/capabilities/arch-client-intelligence.capability.json +401 -0
- package/capabilities/backblaze-b2.capability.json +205 -0
- package/capabilities/cal-com.capability.json +266 -0
- package/capabilities/canva.capability.json +274 -0
- package/capabilities/claude-code-routines.capability.json +172 -0
- package/capabilities/claude-managed-agents.capability.json +215 -0
- package/capabilities/client-comms-report.capability.json +246 -0
- package/capabilities/content-idea-to-production.capability.json +29 -0
- package/capabilities/datagouv.capability.json +315 -0
- package/capabilities/discord-bot.capability.json +157 -0
- package/capabilities/dropbox.capability.json +281 -0
- package/capabilities/exa.capability.json +109 -0
- package/capabilities/fal-ai.capability.json +83 -0
- package/capabilities/fal-media-lab.capability.json +158 -0
- package/capabilities/fireflies.capability.json +191 -0
- package/capabilities/freellmapi.capability.json +114 -0
- package/capabilities/generic-apikey.capability.json +92 -0
- package/capabilities/github-merge.capability.json +99 -0
- package/capabilities/github-read.capability.json +325 -0
- package/capabilities/governance-tighten-recommender.capability.json +81 -0
- package/capabilities/jev.capability.json +226 -0
- package/capabilities/lead-conversion-enrollment.capability.json +246 -0
- package/capabilities/mail-feed.capability.json +277 -0
- package/capabilities/mailgun.capability.json +52 -0
- package/capabilities/nango-google.capability.json +639 -0
- package/capabilities/notion.capability.json +417 -0
- package/capabilities/omniroute.capability.json +137 -0
- package/capabilities/posthog-analytics.capability.json +258 -0
- package/capabilities/postiz.capability.json +467 -0
- package/capabilities/proactive-digest.capability.json +190 -0
- package/capabilities/proton.capability.json +288 -0
- package/capabilities/replicate-rvm.capability.json +97 -0
- package/capabilities/research-methods.capability.json +57 -0
- package/capabilities/shopify.capability.json +395 -0
- package/capabilities/slack.capability.json +369 -0
- package/capabilities/stripe.capability.json +420 -0
- package/capabilities/telegram-bridge.capability.json +56 -0
- package/capabilities/treg.capability.json +102 -0
- package/capabilities/unipile-linkedin.capability.json +471 -0
- package/capabilities/vercel.capability.json +424 -0
- package/dist/define-CPS0n8BV.d.ts +1331 -0
- package/dist/index.d.ts +174 -900
- package/dist/index.js +26148 -23931
- package/dist/index.js.map +1 -1
- package/dist/wire.d.ts +222 -0
- package/dist/wire.js +77 -0
- package/dist/wire.js.map +1 -0
- package/package.json +27 -4
- package/packs/bookmark-views-pack.json +33 -0
- package/packs/crm-pack.json +76 -0
- package/packs/customer-profile.json +28 -0
- package/packs/deep-research.playbook.json +30 -0
- package/packs/event-views-pack.json +33 -0
- package/packs/habit-profile.json +27 -0
- package/packs/hr-pack.json +54 -0
- package/packs/invoice-profile.json +28 -0
- package/packs/knowledge-base-pack.json +56 -0
- package/packs/meeting-profile.json +29 -0
- package/packs/product-profile.json +30 -0
- package/packs/project-management-pack.json +49 -0
- package/packs/recipe-profile.json +29 -0
- package/packs/task-views-pack.json +39 -0
- package/retired.yaml +136 -0
- package/skills/app-factory-build-slice/SKILL.md +70 -0
- package/skills/app-factory-build-slice.catalog.json +17 -0
- package/skills/app-factory-design-canvas/SKILL.md +96 -0
- package/skills/app-factory-design-canvas.catalog.json +17 -0
- package/skills/app-factory-design-review/SKILL.md +99 -0
- package/skills/app-factory-design-review.catalog.json +18 -0
- package/skills/app-factory-research/SKILL.md +123 -0
- package/skills/app-factory-research.catalog.json +17 -0
- package/skills/bookmark-enrichment/SKILL.md +40 -0
- package/skills/carousel-format/carousel-format.skill.json +18 -0
- package/skills/collage-broll/collage-broll.skill.json +20 -0
- package/skills/content-taste/content-taste.skill.json +18 -0
- package/skills/creative-director/creative-director.skill.json +18 -0
- package/skills/design-critique/SKILL.md +135 -0
- package/skills/design-critique.catalog.json +12 -0
- package/skills/designing-with-real-components/SKILL.md +140 -0
- package/skills/designing-with-real-components.catalog.json +12 -0
- package/skills/edit-loop/edit-loop.skill.json +25 -0
- package/skills/facecam-edit/facecam-edit.skill.json +24 -0
- package/skills/mapping-the-server/SKILL.md +54 -0
- package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
- package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
- package/skills/open-design-edit/open-design-edit.skill.json +24 -0
- package/skills/reading-client-progress/SKILL.md +65 -0
- package/skills/routing-to-channels/SKILL.md +54 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/skills/video-direction/video-direction.skill.json +20 -0
- package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
- package/skills/video-toolbox/video-toolbox.skill.json +20 -0
- package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
- package/src/agency-os.yaml +122 -0
- package/src/agent-os-skill-slugs.test.ts +65 -0
- package/src/apify-social-reader.test.ts +317 -0
- package/src/base.yaml +85 -39
- package/src/blockchain-ecosystem.yaml +1 -0
- package/src/brand-library.yaml +525 -14
- package/src/builder-dev-fold.template.test.ts +113 -0
- package/src/builder.yaml +779 -91
- package/src/business-model-catalog-description.test.ts +47 -0
- package/src/business-model-grp.template.test.ts +214 -0
- package/src/business-model.yaml +296 -49
- package/src/catalog-safety.test.ts +198 -0
- package/src/catalog.template-items.test.ts +75 -0
- package/src/catalog.ts +66 -2
- package/src/concept-guard.test.ts +28 -8
- package/src/concept-guard.ts +2 -2
- package/src/content-creation.template.test.ts +52 -0
- package/src/content-creation.yaml +192 -0
- package/src/content-os.edit-loop.template.test.ts +210 -0
- package/src/content-os.publish-due-posts.template.test.ts +261 -0
- package/src/content-os.template.test.ts +322 -0
- package/src/content-os.video-takes.template.test.ts +338 -0
- package/src/content-os.work-a-take.template.test.ts +125 -0
- package/src/content-os.yaml +1830 -39
- package/src/content-studio-retired.template.test.ts +82 -0
- package/src/corpus-flow-validity.test.ts +4 -5
- package/src/crm-absorbs-outreach.template.test.ts +117 -0
- package/src/crm-build-plan.template.test.ts +128 -0
- package/src/crm-lists.template.test.ts +96 -0
- package/src/crm-messages-cadence.template.test.ts +164 -0
- package/src/crm-one-send-door.template.test.ts +65 -0
- package/src/crm.yaml +1456 -181
- package/src/decisions-wave.template.test.ts +186 -0
- package/src/define.ts +130 -17
- package/src/design-to-implementation.template.test.ts +94 -0
- package/src/domain-template-type.test.ts +75 -0
- package/src/ecosystem.yaml +27 -66
- package/src/enterprise-os.yaml +10 -134
- package/src/facecam-edit.audio-studio.test.ts +188 -0
- package/src/fal-generate-image.test.ts +200 -0
- package/src/fal-media-lab-pod-files.test.ts +180 -0
- package/src/finance.yaml +254 -1
- package/src/foundation.yaml +78 -38
- package/src/frame-host-modules.d.mts +6 -0
- package/src/frame-host-modules.mjs +58 -0
- package/src/generate.mjs +9 -3
- package/src/github-merge.test.ts +210 -0
- package/src/grants.yaml +46 -17
- package/src/hr.yaml +39 -6
- package/src/index.ts +36 -1
- package/src/intent.test.ts +161 -0
- package/src/internal-runbook.yaml +19 -1
- package/src/jev.test.ts +356 -0
- package/src/legal.yaml +33 -1
- package/src/life-os.yaml +51 -240
- package/src/lifecycle-coverage.test.ts +124 -0
- package/src/lifecycle-declarations.test.ts +252 -0
- package/src/lifecycle-presets.test.ts +468 -0
- package/src/lifecycle-presets.ts +494 -0
- package/src/lifecycle-stuck.test.ts +46 -0
- package/src/lifecycle-waits.parity.test.ts +319 -0
- package/src/marketing.yaml +108 -4
- package/src/meetings-knowledge.yaml +18 -0
- package/src/networking.yaml +23 -2
- package/src/operations.yaml +304 -4
- package/src/pipeline-views.template.test.ts +88 -0
- package/src/playbook-goal-placeholders.test.ts +57 -0
- package/src/postiz-contract.test.ts +134 -0
- package/src/presentation-declarations.test.ts +174 -0
- package/src/process-contract.validate.test.ts +352 -0
- package/src/project-management.yaml +39 -740
- package/src/publish-body.d.mts +29 -0
- package/src/publish-body.mjs +74 -0
- package/src/publish-body.test.ts +74 -0
- package/src/publish-official-lib.test.ts +176 -0
- package/src/replicate-rvm.test.ts +504 -0
- package/src/research-base.yaml +119 -20
- package/src/research-decision.yaml +60 -3
- package/src/reserved-profile-slugs.test.ts +83 -0
- package/src/retired-goal-kinds.test.ts +58 -0
- package/src/role-reference-and-step-domain.test.ts +192 -0
- package/src/seed-obligation.test.ts +1 -6
- package/src/send-message-maps.test.ts +227 -0
- package/src/shared-role-ssot.test.ts +40 -2
- package/src/ship-a-change.template.test.ts +177 -0
- package/src/skills.test.ts +170 -0
- package/src/social.yaml +85 -27
- package/src/space-packages.freshness.test.ts +97 -0
- package/src/space-packages.generated.ts +40 -0
- package/src/space-packages.test.ts +125 -0
- package/src/space-packages.ts +73 -0
- package/src/start.test.ts +188 -0
- package/src/task-intents.test.ts +293 -0
- package/src/templates.ts +20851 -21496
- package/src/types.ts +443 -60
- package/src/unipile-publish-post.test.ts +162 -0
- package/src/validate-flow.drift.test.ts +2 -3
- package/src/validate-flow.ts +22 -2
- package/src/validate-onboarding.test.ts +59 -0
- package/src/validate.test.ts +71 -4
- package/src/validate.ts +1335 -7
- package/src/wire-core.ts +231 -0
- package/src/wire.test.ts +159 -0
- package/src/wire.ts +107 -0
- package/src/agent-fleet.yaml +0 -1645
- package/src/business-developer.template.test.ts +0 -351
- package/src/business-developer.yaml +0 -860
- package/src/content-studio.yaml +0 -361
- package/src/dev-dashboard.yaml +0 -990
- package/src/outreach-comms.yaml +0 -486
- package/src/the-arch.yaml +0 -201
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PARITY — a template's lifecycle option `dateBinding` and step `waits` are
|
|
3
|
+
* accepted by the catalog validator EXACTLY when the pod accepts them.
|
|
4
|
+
*
|
|
5
|
+
* Three bindings, each against the source of the repo that owns the rule:
|
|
6
|
+
* 1. VOCABULARY: the validator's mirrors (`LIFECYCLE_WAITS_ON`,
|
|
7
|
+
* `LIFECYCLE_WAIT_KINDS`) equal `@synap-core/types` (`HOLD_ON` without
|
|
8
|
+
* `unspecified`, `WAIT_KINDS`), imported at run time.
|
|
9
|
+
* 2. FIELD SET: `LIFECYCLE_OPTION_FIELDS` / `LIFECYCLE_OPTION_WAITS_FIELDS`
|
|
10
|
+
* equal the keys of `interface LifecycleOption` / `LifecycleOptionWaits` in
|
|
11
|
+
* synap-backend `packages/types/src/lifecycle/option.ts`, and the pod's
|
|
12
|
+
* parser carries every one of them through.
|
|
13
|
+
* 3. BEHAVIOUR: synap-backend's OWN parser (`parseLifecycleOptions`,
|
|
14
|
+
* `packages/database/src/utils/lifecycle-model.ts`, compiled here with
|
|
15
|
+
* esbuild and run against the types this package resolves) and
|
|
16
|
+
* `validateTemplate` give the same verdict on every row of a fixture table.
|
|
17
|
+
* A convergence check proves sameness, not correctness, so every row also
|
|
18
|
+
* names the verdict the rule should give (`ok`), read from the pod's rules,
|
|
19
|
+
* and both sides are held to it.
|
|
20
|
+
*
|
|
21
|
+
* The rows are chosen where a plausible wrong rule DISAGREES: `unspecified`
|
|
22
|
+
* (a validator that takes all of `HOLD_ON`), the 64/65-character slug (an
|
|
23
|
+
* off-by-one length), a done or lost step (a validator that forgets the
|
|
24
|
+
* category), an unknown `waits` field (a validator that ignores it), `null`
|
|
25
|
+
* (a validator that treats null as absent).
|
|
26
|
+
*
|
|
27
|
+
* NOT covered (stated, not implied): unknown TOP-LEVEL option fields (the pod
|
|
28
|
+
* ignores them and so does the validator; no row asserts it), `humanOnly`
|
|
29
|
+
* and `stuckAfterDays` (pinned by `lifecycle-stuck.test.ts`), and whether the
|
|
30
|
+
* named property exists on the kind (the pod does not check it either). The
|
|
31
|
+
* behaviour half skips, with the reason logged, when synap-backend is not a
|
|
32
|
+
* sibling checkout.
|
|
33
|
+
*/
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
35
|
+
import { dirname, resolve } from "node:path";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
import { transformSync } from "esbuild";
|
|
38
|
+
import { describe, expect, it } from "vitest";
|
|
39
|
+
import * as typesLifecycle from "@synap-core/types/lifecycle";
|
|
40
|
+
import * as typesUnits from "@synap-core/types/units";
|
|
41
|
+
import { stripComments } from "../../design-tokens/scripts/strip-comments.mjs";
|
|
42
|
+
import {
|
|
43
|
+
LIFECYCLE_OPTION_FIELDS,
|
|
44
|
+
LIFECYCLE_OPTION_WAITS_FIELDS,
|
|
45
|
+
LIFECYCLE_WAIT_KINDS,
|
|
46
|
+
LIFECYCLE_WAITS_ON,
|
|
47
|
+
validateTemplate,
|
|
48
|
+
} from "./validate.js";
|
|
49
|
+
import { resolveLifecyclePresets } from "./lifecycle-presets.js";
|
|
50
|
+
import type { WorkspaceYaml } from "./types.js";
|
|
51
|
+
import { readYamlTemplates } from "./generate.mjs";
|
|
52
|
+
|
|
53
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
const BE = resolve(HERE, "../../../../synap-backend/packages");
|
|
55
|
+
const OPTION_TS = resolve(BE, "types/src/lifecycle/option.ts");
|
|
56
|
+
const MODEL_TS = resolve(BE, "database/src/utils/lifecycle-model.ts");
|
|
57
|
+
const DID_YOU_MEAN_TS = resolve(BE, "database/src/services/did-you-mean.ts");
|
|
58
|
+
const hasBackend = existsSync(OPTION_TS) && existsSync(MODEL_TS) && existsSync(DID_YOU_MEAN_TS);
|
|
59
|
+
if (!hasBackend) console.warn(`[lifecycle-waits parity] SKIP the backend half: ${MODEL_TS} or ${OPTION_TS} is absent`);
|
|
60
|
+
|
|
61
|
+
describe("lifecycle waits parity — the vocabulary", () => {
|
|
62
|
+
it("waits.on is HOLD_ON without unspecified", () => {
|
|
63
|
+
expect([...LIFECYCLE_WAITS_ON]).toEqual(typesLifecycle.HOLD_ON.filter((on) => on !== "unspecified"));
|
|
64
|
+
});
|
|
65
|
+
it("waits.waitKind is WAIT_KINDS", () => {
|
|
66
|
+
expect([...LIFECYCLE_WAIT_KINDS]).toEqual([...typesUnits.WAIT_KINDS]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/** The keys of `export interface <name> {…}` (no nested braces in these two). */
|
|
71
|
+
function interfaceKeys(src: string, name: string): string[] {
|
|
72
|
+
const code = stripComments(src, "ts");
|
|
73
|
+
const at = code.indexOf(`export interface ${name} {`);
|
|
74
|
+
if (at < 0) throw new Error(`interface ${name} not found`);
|
|
75
|
+
const body = code.slice(code.indexOf("{", at) + 1, code.indexOf("}", at));
|
|
76
|
+
return [...body.matchAll(/^\s*([A-Za-z_]\w*)\??\s*:/gm)].map((m) => m[1]!);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type ParseLifecycleOptions = (raw: unknown) => { options: Array<Record<string, unknown>>; errors: string[] };
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The pod's parser, compiled from its source and run against the types this
|
|
83
|
+
* package resolves. Every import it makes must be provided here: a new one
|
|
84
|
+
* throws, naming it, instead of being stubbed silently.
|
|
85
|
+
*/
|
|
86
|
+
function loadBackendParser(): ParseLifecycleOptions {
|
|
87
|
+
const load = (file: string, deps: Record<string, unknown>): Record<string, unknown> => {
|
|
88
|
+
const { code } = transformSync(readFileSync(file, "utf8"), { loader: "ts", format: "cjs", target: "es2022" });
|
|
89
|
+
const mod = { exports: {} as Record<string, unknown> };
|
|
90
|
+
const req = (id: string) => {
|
|
91
|
+
if (Object.hasOwn(deps, id)) return deps[id];
|
|
92
|
+
throw new Error(`[parity] ${file} imports "${id}", which this harness does not provide`);
|
|
93
|
+
};
|
|
94
|
+
new Function("require", "module", "exports", code)(req, mod, mod.exports);
|
|
95
|
+
return mod.exports;
|
|
96
|
+
};
|
|
97
|
+
const didYouMean = load(DID_YOU_MEAN_TS, {});
|
|
98
|
+
const model = load(MODEL_TS, {
|
|
99
|
+
"@synap-core/types/lifecycle": typesLifecycle,
|
|
100
|
+
"@synap-core/types/units": typesUnits,
|
|
101
|
+
"../services/did-you-mean.js": didYouMean,
|
|
102
|
+
});
|
|
103
|
+
if (typeof model.parseLifecycleOptions !== "function")
|
|
104
|
+
throw new Error("[parity] lifecycle-model.ts no longer exports parseLifecycleOptions");
|
|
105
|
+
return model.parseLifecycleOptions as ParseLifecycleOptions;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
describe.skipIf(!hasBackend)("lifecycle waits parity — the field set", () => {
|
|
109
|
+
it("the option and waits fields are the types' interfaces", () => {
|
|
110
|
+
const src = readFileSync(OPTION_TS, "utf8");
|
|
111
|
+
const option = interfaceKeys(src, "LifecycleOption");
|
|
112
|
+
const waits = interfaceKeys(src, "LifecycleOptionWaits");
|
|
113
|
+
// non-vacuity: the extraction still sees fields it must
|
|
114
|
+
expect(option).toContain("stuckAfterDays");
|
|
115
|
+
expect(waits).toContain("on");
|
|
116
|
+
expect([...option].sort()).toEqual([...LIFECYCLE_OPTION_FIELDS].sort());
|
|
117
|
+
expect([...waits].sort()).toEqual([...LIFECYCLE_OPTION_WAITS_FIELDS].sort());
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("the pod's parser carries every field the validator names", () => {
|
|
121
|
+
const parse = loadBackendParser();
|
|
122
|
+
const full = {
|
|
123
|
+
key: "sent",
|
|
124
|
+
label: "Sent",
|
|
125
|
+
category: "active",
|
|
126
|
+
tone: "info",
|
|
127
|
+
humanOnly: true,
|
|
128
|
+
stuckAfterDays: 7,
|
|
129
|
+
dateBinding: "dueDate",
|
|
130
|
+
waits: { on: "external", waitKind: "payment", refFrom: "clientId", dateBinding: "dueDate" },
|
|
131
|
+
};
|
|
132
|
+
const { options, errors } = parse([full]);
|
|
133
|
+
expect(errors).toEqual([]);
|
|
134
|
+
expect(Object.keys(options[0]!).sort()).toEqual([...LIFECYCLE_OPTION_FIELDS].sort());
|
|
135
|
+
expect(Object.keys(options[0]!.waits as object).sort()).toEqual([...LIFECYCLE_OPTION_WAITS_FIELDS].sort());
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ── the behaviour table ─────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
const crm = readYamlTemplates()
|
|
142
|
+
.map((t) => t.data as WorkspaceYaml)
|
|
143
|
+
.find((t) => t.meta.slug === "crm")!;
|
|
144
|
+
|
|
145
|
+
/** crm's deal lifecycle: draft (open) · proposed, negotiating (active) · won (done) · lost (lost). */
|
|
146
|
+
function dealWith(key: string, extra: Record<string, unknown>): WorkspaceYaml {
|
|
147
|
+
const clone = structuredClone(crm);
|
|
148
|
+
const deal = clone.profiles.find((p) => p.slug === "deal")!;
|
|
149
|
+
deal.lifecycle!.options = deal.lifecycle!.options.map((o) => (o.key === key ? { ...o, ...extra } : o));
|
|
150
|
+
return clone;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const waitsErrors = (t: WorkspaceYaml) =>
|
|
154
|
+
validateTemplate(t).errors.filter((e) => /\.options\[\d+\]\.(dateBinding|waits)$/.test(e.path));
|
|
155
|
+
|
|
156
|
+
const SLUG_64 = `a${"b".repeat(63)}`;
|
|
157
|
+
const SLUG_65 = `a${"b".repeat(64)}`;
|
|
158
|
+
|
|
159
|
+
type Row = { name: string; key: string; extra: Record<string, unknown>; ok: boolean };
|
|
160
|
+
const ROWS: Row[] = [
|
|
161
|
+
{ name: "dateBinding on an active step", key: "proposed", extra: { dateBinding: "dueDate" }, ok: true },
|
|
162
|
+
{ name: "dateBinding on an open step, - and _", key: "draft", extra: { dateBinding: "next_action-at" }, ok: true },
|
|
163
|
+
{ name: "dateBinding of 64 characters", key: "proposed", extra: { dateBinding: SLUG_64 }, ok: true },
|
|
164
|
+
{ name: "dateBinding of 65 characters", key: "proposed", extra: { dateBinding: SLUG_65 }, ok: false },
|
|
165
|
+
{ name: "dateBinding with a space", key: "proposed", extra: { dateBinding: "due date" }, ok: false },
|
|
166
|
+
{ name: "dateBinding starting with a digit", key: "proposed", extra: { dateBinding: "1dueDate" }, ok: false },
|
|
167
|
+
{ name: "dateBinding that is a number", key: "proposed", extra: { dateBinding: 42 }, ok: false },
|
|
168
|
+
{ name: "dateBinding null", key: "proposed", extra: { dateBinding: null }, ok: false },
|
|
169
|
+
{ name: "dateBinding on a done step", key: "won", extra: { dateBinding: "dueDate" }, ok: false },
|
|
170
|
+
{ name: "waits on an active step", key: "proposed", extra: { waits: { on: "external" } }, ok: true },
|
|
171
|
+
{
|
|
172
|
+
name: "waits with every field",
|
|
173
|
+
key: "negotiating",
|
|
174
|
+
extra: { waits: { on: "nobody", waitKind: "payment", refFrom: "contactId", dateBinding: "nextActionAt" } },
|
|
175
|
+
ok: true,
|
|
176
|
+
},
|
|
177
|
+
{ name: "waits on an open step", key: "draft", extra: { waits: { on: "date", dateBinding: "nextActionAt" } }, ok: true },
|
|
178
|
+
{ name: "waits.on unspecified", key: "proposed", extra: { waits: { on: "unspecified" } }, ok: false },
|
|
179
|
+
{ name: "waits.on unknown", key: "proposed", extra: { waits: { on: "client" } }, ok: false },
|
|
180
|
+
{ name: "waits without on", key: "proposed", extra: { waits: { waitKind: "payment" } }, ok: false },
|
|
181
|
+
{ name: "waits.waitKind unknown", key: "proposed", extra: { waits: { on: "person", waitKind: "bogus" } }, ok: false },
|
|
182
|
+
{ name: "waits.refFrom with a space", key: "proposed", extra: { waits: { on: "person", refFrom: "contact id" } }, ok: false },
|
|
183
|
+
{ name: "waits.dateBinding a number", key: "proposed", extra: { waits: { on: "date", dateBinding: 3 } }, ok: false },
|
|
184
|
+
{ name: "waits with an unknown field", key: "proposed", extra: { waits: { on: "person", who: "x" } }, ok: false },
|
|
185
|
+
{ name: "waits on a lost step", key: "lost", extra: { waits: { on: "person" } }, ok: false },
|
|
186
|
+
{ name: "waits as a list", key: "proposed", extra: { waits: [] }, ok: false },
|
|
187
|
+
{ name: "waits as a word", key: "proposed", extra: { waits: "external" }, ok: false },
|
|
188
|
+
{ name: "waits null", key: "proposed", extra: { waits: null }, ok: false },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
describe("lifecycle waits parity — the table is discriminating", () => {
|
|
192
|
+
it("non-vacuity: the table holds accepted and refused rows of both fields, on every category", () => {
|
|
193
|
+
for (const field of ["dateBinding", "waits"]) {
|
|
194
|
+
const rows = ROWS.filter((r) => field in r.extra);
|
|
195
|
+
expect(rows.some((r) => r.ok), field).toBe(true);
|
|
196
|
+
expect(rows.some((r) => !r.ok), field).toBe(true);
|
|
197
|
+
}
|
|
198
|
+
expect(new Set(ROWS.map((r) => r.key))).toEqual(new Set(["draft", "proposed", "negotiating", "won", "lost"]));
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("the unmodified crm template has no waits errors (the clone is a clean base)", () => {
|
|
202
|
+
expect(waitsErrors(structuredClone(crm))).toEqual([]);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("lifecycle waits parity — the validator gives each row's verdict", () => {
|
|
207
|
+
it.each(ROWS)("$name → ok=$ok", ({ key, extra, ok }) => {
|
|
208
|
+
const errs = waitsErrors(dealWith(key, extra));
|
|
209
|
+
expect(errs.length === 0, errs.map((e) => e.message).join("; ")).toBe(ok);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe.skipIf(!hasBackend)("lifecycle waits parity — the pod's parser gives the same verdict", () => {
|
|
214
|
+
const parse = hasBackend ? loadBackendParser() : null;
|
|
215
|
+
const dealOption = (key: string) => crm.profiles.find((p) => p.slug === "deal")!.lifecycle!.options.find((o) => o.key === key)!;
|
|
216
|
+
|
|
217
|
+
it.each(ROWS)("$name → ok=$ok", ({ key, extra, ok }) => {
|
|
218
|
+
const option = { ...dealOption(key), ...extra };
|
|
219
|
+
const { options, errors } = parse!([option]);
|
|
220
|
+
expect(errors.length === 0, errors.join("; ")).toBe(ok);
|
|
221
|
+
if (ok) {
|
|
222
|
+
// accepted means CARRIED, never silently dropped
|
|
223
|
+
for (const [field, value] of Object.entries(extra)) expect(options[0]![field]).toEqual(value);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("the same words: a refused row says what the pod would say", () => {
|
|
228
|
+
for (const row of ROWS.filter((r) => !r.ok)) {
|
|
229
|
+
const option = { ...dealOption(row.key), ...row.extra };
|
|
230
|
+
const pod = parse!([option]).errors;
|
|
231
|
+
const ours = waitsErrors(dealWith(row.key, row.extra)).map((e) => e.message);
|
|
232
|
+
expect(ours, row.name).toEqual(pod);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// ── presets carry them ──────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
describe("lifecycle presets carry dateBinding and waits", () => {
|
|
240
|
+
/** crm, plus a kind `invoice` declaring `lifecycle` (a real template, so the validator runs whole). */
|
|
241
|
+
const withLifecycle = (lifecycle: Record<string, unknown>): WorkspaceYaml => {
|
|
242
|
+
const clone = structuredClone(crm);
|
|
243
|
+
clone.profiles.push({
|
|
244
|
+
slug: "invoice",
|
|
245
|
+
displayName: "Invoice",
|
|
246
|
+
properties: [
|
|
247
|
+
{ slug: "invoice-status", label: "Status", valueType: "string", inputType: "select" },
|
|
248
|
+
{ slug: "dueDate", label: "Due date", valueType: "date", inputType: "date" },
|
|
249
|
+
],
|
|
250
|
+
lifecycle,
|
|
251
|
+
} as unknown as WorkspaceYaml["profiles"][number]);
|
|
252
|
+
return clone;
|
|
253
|
+
};
|
|
254
|
+
const resolved = (lifecycle: Record<string, unknown>) => {
|
|
255
|
+
const { template, problems } = resolveLifecyclePresets(withLifecycle(lifecycle));
|
|
256
|
+
const invoice = template.profiles.find((p) => p.slug === "invoice")!;
|
|
257
|
+
return { options: invoice.lifecycle!.options, problems };
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
it("per resulting key, and on an added step, as given", () => {
|
|
261
|
+
const waits = { on: "external", waitKind: "payment" };
|
|
262
|
+
const { options, problems } = resolved({
|
|
263
|
+
property: "invoice-status",
|
|
264
|
+
adopts: "agreement",
|
|
265
|
+
dateBinding: { sent: "dueDate" },
|
|
266
|
+
waits: { sent: waits },
|
|
267
|
+
add: [{ key: "reminded", category: "active", after: "sent", waits: { on: "date", dateBinding: "dueDate" } }],
|
|
268
|
+
});
|
|
269
|
+
expect(problems).toEqual([]);
|
|
270
|
+
const sent = options.find((o) => o.key === "sent")!;
|
|
271
|
+
expect(sent.dateBinding).toBe("dueDate");
|
|
272
|
+
expect(sent.waits).toEqual(waits);
|
|
273
|
+
expect(sent.stuckAfterDays).toBe(7);
|
|
274
|
+
expect(options.find((o) => o.key === "reminded")!.waits).toEqual({ on: "date", dateBinding: "dueDate" });
|
|
275
|
+
// a step the overlay did not name carries neither
|
|
276
|
+
expect("waits" in options.find((o) => o.key === "draft")!).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("a flat lifecycle keeps them untouched", () => {
|
|
280
|
+
const flat = {
|
|
281
|
+
property: "invoice-status",
|
|
282
|
+
options: [
|
|
283
|
+
{ key: "draft", category: "open" },
|
|
284
|
+
{ key: "sent", category: "active", dateBinding: "dueDate", waits: { on: "external" } },
|
|
285
|
+
{ key: "paid", category: "done" },
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
expect(resolved(flat).options[1]).toEqual(flat.options[1]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("refuses an override of the wrong type, and an unknown step", () => {
|
|
292
|
+
const { problems } = resolved({
|
|
293
|
+
property: "invoice-status",
|
|
294
|
+
adopts: "agreement",
|
|
295
|
+
dateBinding: { sent: 3, nope: "dueDate" },
|
|
296
|
+
waits: { sent: "external" },
|
|
297
|
+
});
|
|
298
|
+
expect(problems.map((p) => p.path)).toEqual([
|
|
299
|
+
"profiles.invoice.lifecycle.dateBinding.sent",
|
|
300
|
+
"profiles.invoice.lifecycle.dateBinding.nope",
|
|
301
|
+
"profiles.invoice.lifecycle.waits.sent",
|
|
302
|
+
]);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("the validator judges what the presets carried", () => {
|
|
306
|
+
const tpl = withLifecycle({
|
|
307
|
+
property: "invoice-status",
|
|
308
|
+
adopts: "agreement",
|
|
309
|
+
waits: { sent: { on: "unspecified" }, fulfilled: { on: "person" } },
|
|
310
|
+
});
|
|
311
|
+
const errs = validateTemplate(tpl).errors.filter(
|
|
312
|
+
(e) => e.path.startsWith("profiles.invoice.") && e.path.endsWith(".waits")
|
|
313
|
+
);
|
|
314
|
+
expect(errs.map((e) => e.message)).toEqual([
|
|
315
|
+
'option "sent" waits.on must be one of person | agent | external | thing | decision | date | nobody',
|
|
316
|
+
'option "fulfilled" is done: a finished thing waits on nothing (waits is for open, active or paused options)',
|
|
317
|
+
]);
|
|
318
|
+
});
|
|
319
|
+
});
|
package/src/marketing.yaml
CHANGED
|
@@ -10,6 +10,11 @@ meta:
|
|
|
10
10
|
color: "#E91E8C"
|
|
11
11
|
tags: [marketing, crm, outreach, leads, campaigns]
|
|
12
12
|
isPublic: true
|
|
13
|
+
# What this space PRODUCES (homes): the purpose word, then its kinds/roles.
|
|
14
|
+
intent:
|
|
15
|
+
label: Marketing
|
|
16
|
+
produces:
|
|
17
|
+
- profile: campaign
|
|
13
18
|
|
|
14
19
|
# SSOT ordering guarantee — this template REFERENCES foundation's pod-wide
|
|
15
20
|
# shared `lead` role. The apply engine seeds a shared profile's pod-wide BASE
|
|
@@ -47,6 +52,15 @@ profiles:
|
|
|
47
52
|
color: pink
|
|
48
53
|
description: "A marketing campaign — email, social, event, content, or partnership"
|
|
49
54
|
semanticSlug: marketing.campaign
|
|
55
|
+
# The ONE lifecycle (work model L1): every option's category.
|
|
56
|
+
lifecycle:
|
|
57
|
+
property: campaign-status
|
|
58
|
+
options:
|
|
59
|
+
- { key: planning, category: open }
|
|
60
|
+
- { key: active, category: active }
|
|
61
|
+
- { key: paused, category: paused, humanOnly: true }
|
|
62
|
+
- { key: completed, category: done }
|
|
63
|
+
- { key: cancelled, category: lost, humanOnly: true }
|
|
50
64
|
properties:
|
|
51
65
|
- slug: campaign-status
|
|
52
66
|
label: Status
|
|
@@ -96,7 +110,7 @@ profiles:
|
|
|
96
110
|
# `campaign-channel` (the delivery medium). scoping/outreach/live have no
|
|
97
111
|
# channel equivalent, so folding them into `campaign-channel` would lose
|
|
98
112
|
# them — they get their own def. Kept verbatim across the three campaign
|
|
99
|
-
# declarations (
|
|
113
|
+
# declarations (crm / content-os / here) so there is no drift.
|
|
100
114
|
- slug: campaign-type
|
|
101
115
|
label: Type
|
|
102
116
|
valueType: string
|
|
@@ -256,10 +270,13 @@ views:
|
|
|
256
270
|
description: "Marketing contacts in a table."
|
|
257
271
|
- name: Lead Pipeline
|
|
258
272
|
slug: lead-pipeline
|
|
259
|
-
|
|
273
|
+
# The ONE board (work model L3, D3): columns ARE the lead role's declared
|
|
274
|
+
# lifecycle (foundation's leadStage), moves go through the one mover.
|
|
275
|
+
type: pipeline
|
|
260
276
|
scopeProfileSlug: lead
|
|
261
|
-
|
|
262
|
-
|
|
277
|
+
config:
|
|
278
|
+
profileSlug: lead
|
|
279
|
+
description: "Leads in the columns of their stage."
|
|
263
280
|
|
|
264
281
|
onboarding:
|
|
265
282
|
goal: >
|
|
@@ -311,10 +328,12 @@ onboarding:
|
|
|
311
328
|
leaking, and where the next dollar should go.
|
|
312
329
|
collect:
|
|
313
330
|
- profileSlug: campaign
|
|
331
|
+
min: 1
|
|
314
332
|
what: The campaigns live or planned right now — one per real initiative, with its channel and goal.
|
|
315
333
|
cardinality: several
|
|
316
334
|
keyFields: [campaign-channel, campaign-goal, campaign-status]
|
|
317
335
|
- profileSlug: lead
|
|
336
|
+
min: 1
|
|
318
337
|
what: A few representative leads already in motion, placed at their true pipeline stage.
|
|
319
338
|
cardinality: several
|
|
320
339
|
keyFields: [leadStage, lead-source, lead-company]
|
|
@@ -362,3 +381,88 @@ bento:
|
|
|
362
381
|
- kind: view
|
|
363
382
|
viewName: Lead Pipeline
|
|
364
383
|
pos: { x: 6, y: 0, w: 6, h: 5 }
|
|
384
|
+
|
|
385
|
+
playbooks:
|
|
386
|
+
- name: Run a campaign
|
|
387
|
+
description: Take a campaign from brief to results — goal, audience, plan, content, launch with a person's approval, and a report on the leads it brought.
|
|
388
|
+
goalTemplate: >-
|
|
389
|
+
Run this campaign ({campaign}); each stage moves its status. BRIEF: write
|
|
390
|
+
the campaign's goal as a number, its target audience, channel, budget,
|
|
391
|
+
start and end dates, from the brand kit and the agency's offers. PLAN:
|
|
392
|
+
list the pieces to make and the outreach to send, each with a date. MAKE:
|
|
393
|
+
create each piece as a post at the Idea status in Content OS and each
|
|
394
|
+
outreach sequence as an outreach campaign, so their own processes
|
|
395
|
+
produce them. LAUNCH: a person approves the launch; nothing is published,
|
|
396
|
+
sent or paid for before that. REPORT: after the end date, record the lead
|
|
397
|
+
count and conversion rate, link every new lead the campaign brought, and
|
|
398
|
+
write what worked and what to change. Pausing or cancelling is a
|
|
399
|
+
person's call.
|
|
400
|
+
subjectProfile:
|
|
401
|
+
profileSlug: campaign
|
|
402
|
+
statusProperty: campaign-status
|
|
403
|
+
humanOnlyStatuses: [paused, cancelled]
|
|
404
|
+
activators:
|
|
405
|
+
- { on: created, mode: propose }
|
|
406
|
+
params:
|
|
407
|
+
- { name: campaign, type: entity, label: Campaign, required: true }
|
|
408
|
+
executor: is-agent
|
|
409
|
+
status: active
|
|
410
|
+
requiredIntents: [publish_post, send_message, list_records]
|
|
411
|
+
expectedOutputs:
|
|
412
|
+
- { kind: post, label: Campaign pieces, relationToSubject: relates_to }
|
|
413
|
+
- { kind: person, label: Leads from the campaign, relationToSubject: relates_to }
|
|
414
|
+
- { kind: document, label: Campaign report }
|
|
415
|
+
stages:
|
|
416
|
+
- key: brief
|
|
417
|
+
name: Brief
|
|
418
|
+
category: planned
|
|
419
|
+
subjectStatus: planning
|
|
420
|
+
goal: Write the goal as a number, the audience, channel, budget and dates.
|
|
421
|
+
criteria:
|
|
422
|
+
- key: campaign-goal-number
|
|
423
|
+
statement: The campaign's goal is a number with a unit (leads, sign-ups, revenue).
|
|
424
|
+
check: { kind: judge }
|
|
425
|
+
- key: campaign-audience
|
|
426
|
+
statement: The target audience is written and matches an audience in Foundation.
|
|
427
|
+
check: { kind: judge }
|
|
428
|
+
- key: campaign-dates-budget
|
|
429
|
+
statement: The start date, end date and budget are set.
|
|
430
|
+
check: { kind: judge }
|
|
431
|
+
- key: plan
|
|
432
|
+
name: Plan
|
|
433
|
+
category: planned
|
|
434
|
+
goal: List every piece and outreach to send, each with a date.
|
|
435
|
+
gate: { kind: check }
|
|
436
|
+
onFail: { toStage: brief }
|
|
437
|
+
criteria:
|
|
438
|
+
- key: campaign-plan-dated
|
|
439
|
+
statement: Every planned piece and outreach has a date inside the campaign window.
|
|
440
|
+
check: { kind: judge }
|
|
441
|
+
- key: make
|
|
442
|
+
name: Make
|
|
443
|
+
category: started
|
|
444
|
+
goal: Create each piece as a post at Idea and each outreach sequence as an outreach campaign.
|
|
445
|
+
criteria:
|
|
446
|
+
- key: campaign-pieces-created
|
|
447
|
+
statement: Every planned piece exists as a post or an outreach campaign linked to this campaign.
|
|
448
|
+
check: { kind: judge }
|
|
449
|
+
- key: launch
|
|
450
|
+
name: Launch
|
|
451
|
+
category: started
|
|
452
|
+
subjectStatus: active
|
|
453
|
+
goal: A person approves the launch; then the pieces go out on their dates.
|
|
454
|
+
gate: { kind: human }
|
|
455
|
+
onFail: { toStage: make }
|
|
456
|
+
indefinite: true
|
|
457
|
+
- key: report
|
|
458
|
+
name: Report
|
|
459
|
+
category: completed
|
|
460
|
+
subjectStatus: completed
|
|
461
|
+
goal: Record lead count and conversion rate, link the leads, and write what to change.
|
|
462
|
+
criteria:
|
|
463
|
+
- key: campaign-results
|
|
464
|
+
statement: The lead count and conversion rate are set.
|
|
465
|
+
check: { kind: judge }
|
|
466
|
+
- key: campaign-learnings
|
|
467
|
+
statement: The report says what worked and what to change next time.
|
|
468
|
+
check: { kind: judge }
|
|
@@ -13,6 +13,9 @@ meta:
|
|
|
13
13
|
color: "#8B5CF6"
|
|
14
14
|
tags: [meetings, notes, tasks, transcripts, calendar]
|
|
15
15
|
isPublic: true
|
|
16
|
+
# A LIBRARY: it produces nothing a home counts (homes).
|
|
17
|
+
intent:
|
|
18
|
+
label: Meetings
|
|
16
19
|
|
|
17
20
|
workspace:
|
|
18
21
|
name: Meetings & Knowledge
|
|
@@ -226,6 +229,21 @@ integrations:
|
|
|
226
229
|
- templateKey: fireflies
|
|
227
230
|
- templateKey: nango-google
|
|
228
231
|
|
|
232
|
+
# ── taskIntents — WHAT THIS WORKSPACE NEEDS TO FUNCTION ───────────────────
|
|
233
|
+
# Closed vocabulary: `@synap-core/types/capability-intents` (pod migrations
|
|
234
|
+
# 0283/0284). DERIVED from the declared intents of this template's own
|
|
235
|
+
# `integrations` capability packs — not hand-guessed. Checked at authoring
|
|
236
|
+
# time by `validateTemplate` (`task-intent-unknown`) and by
|
|
237
|
+
# `src/task-intents.test.ts`.
|
|
238
|
+
taskIntents:
|
|
239
|
+
- capture_into_pod
|
|
240
|
+
- search_external
|
|
241
|
+
- fetch_record
|
|
242
|
+
- list_records
|
|
243
|
+
- send_message
|
|
244
|
+
- schedule_event
|
|
245
|
+
- manage_file
|
|
246
|
+
|
|
229
247
|
onboarding:
|
|
230
248
|
goal: >
|
|
231
249
|
Get recent meetings into the pod so summaries, action items, and attendees
|
package/src/networking.yaml
CHANGED
|
@@ -77,6 +77,15 @@ profiles:
|
|
|
77
77
|
color: amber
|
|
78
78
|
description: A client's stated ask — the demand The Arch brokers a provider against.
|
|
79
79
|
scope: WORKSPACE
|
|
80
|
+
# The ONE lifecycle (work model L1): every option's category.
|
|
81
|
+
lifecycle:
|
|
82
|
+
property: needStatus
|
|
83
|
+
options:
|
|
84
|
+
- { key: open, category: open }
|
|
85
|
+
- { key: matching, category: active }
|
|
86
|
+
- { key: intro-made, category: active }
|
|
87
|
+
- { key: satisfied, category: done }
|
|
88
|
+
- { key: dropped, category: lost }
|
|
80
89
|
properties:
|
|
81
90
|
- slug: needStatus
|
|
82
91
|
label: Status
|
|
@@ -133,6 +142,17 @@ profiles:
|
|
|
133
142
|
color: violet
|
|
134
143
|
description: The middleman brokerage artifact — connects a requester to a provider for a need, and bills the match as a deal.
|
|
135
144
|
scope: WORKSPACE
|
|
145
|
+
# The ONE lifecycle (work model L1): every option's category.
|
|
146
|
+
lifecycle:
|
|
147
|
+
property: introStage
|
|
148
|
+
options:
|
|
149
|
+
- { key: identified, category: open }
|
|
150
|
+
- { key: proposed, category: active }
|
|
151
|
+
- { key: approved, category: active }
|
|
152
|
+
- { key: introduced, category: active }
|
|
153
|
+
- { key: in-discussion, category: active }
|
|
154
|
+
- { key: signed, category: done }
|
|
155
|
+
- { key: dead, category: lost }
|
|
136
156
|
properties:
|
|
137
157
|
- slug: introStage
|
|
138
158
|
label: Stage
|
|
@@ -155,8 +175,9 @@ profiles:
|
|
|
155
175
|
valueType: entity_id
|
|
156
176
|
inputType: entity_select
|
|
157
177
|
targetProfileSlug: need
|
|
158
|
-
# ── Fee vocabulary — REUSED VERBATIM from
|
|
159
|
-
#
|
|
178
|
+
# ── Fee vocabulary — REUSED VERBATIM from the `deal` overlay of the retired
|
|
179
|
+
# arch-sales (retired-sources/arch-sales.yaml), so an intro bills exactly
|
|
180
|
+
# like a CRM deal did. ──
|
|
160
181
|
- slug: synapFeeModel
|
|
161
182
|
label: Synap fee model
|
|
162
183
|
valueType: string
|