@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,29 @@
|
|
|
1
|
+
/** Types for `publish-body.mjs` — the official-template publish body builder. */
|
|
2
|
+
import type { PackageDefinitionOutput } from './define';
|
|
3
|
+
|
|
4
|
+
export interface OfficialPublishBody {
|
|
5
|
+
slug: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
isPublic: boolean;
|
|
9
|
+
tags: string[];
|
|
10
|
+
category: 'workspace';
|
|
11
|
+
requiredTier?: string;
|
|
12
|
+
vendorId: string;
|
|
13
|
+
/** `toPackageDefinition()` minus `_meta`, plus `_meta.icon`/`color`/`domain` mirrored on top. */
|
|
14
|
+
definition: Omit<PackageDefinitionOutput, '_meta'> & {
|
|
15
|
+
icon?: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
domain?: string;
|
|
18
|
+
/** Which build of this package generated the row — the catalog winner signal. */
|
|
19
|
+
sourcePackage: { name: string; version: string };
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** This package's own `{ name, version }` (from its package.json). */
|
|
24
|
+
export declare const OFFICIAL_SOURCE_PACKAGE: Readonly<{ name: string; version: string }>;
|
|
25
|
+
|
|
26
|
+
export declare function toOfficialPublishBody(
|
|
27
|
+
pd: PackageDefinitionOutput,
|
|
28
|
+
vendorId: string,
|
|
29
|
+
): OfficialPublishBody;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provenance every official row carries: WHICH build of this package generated
|
|
5
|
+
* it. Read from this package's own `package.json`, so a caller can never forget
|
|
6
|
+
* or misstate it.
|
|
7
|
+
*
|
|
8
|
+
* WHY: clients (CLI, browser) merge the catalog with their own frozen bundle
|
|
9
|
+
* through `mergeCatalog`, where the bundle wins unless the remote row's
|
|
10
|
+
* `definition.sourcePackage.version` is STRICTLY newer. A row with no
|
|
11
|
+
* `sourcePackage` therefore lost to every stale client bundle — the CLI then
|
|
12
|
+
* installed its old copy of content-os (no `primarySurface`) onto a live space.
|
|
13
|
+
* Bump this package's version whenever template content changes, so a fresh
|
|
14
|
+
* official row outranks every older bundle (`synap-market` skill).
|
|
15
|
+
*
|
|
16
|
+
* The CP excludes `sourcePackage` from the content hash (`templateContentHash`),
|
|
17
|
+
* so a version bump alone never changes a row's `h-` version.
|
|
18
|
+
*/
|
|
19
|
+
export const OFFICIAL_SOURCE_PACKAGE = Object.freeze(
|
|
20
|
+
(() => {
|
|
21
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
22
|
+
if (!pkg.name || !pkg.version) throw new Error('workspace-templates package.json has no name/version');
|
|
23
|
+
return { name: pkg.name, version: pkg.version };
|
|
24
|
+
})(),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The ONE builder of the `POST {CP}/api/packages` body for an official
|
|
29
|
+
* workspace template. `scripts/publish-official-lib.mjs` (the ONE official-publish
|
|
30
|
+
* implementation, behind `synap market publish --official` and the CI wrapper
|
|
31
|
+
* `scripts/publish-official.mjs`) calls it; `publish-body.test.ts` drives it from the REAL `toPackageDefinition`
|
|
32
|
+
* output so the wire shape is tested, not re-typed.
|
|
33
|
+
*
|
|
34
|
+
* WHY icon/color/domain ARE MIRRORED ONTO `definition`
|
|
35
|
+
* ====================================================
|
|
36
|
+
* `toPackageDefinition` carries the template's identity (icon, colour, domain)
|
|
37
|
+
* ONLY in `_meta`, and `_meta` is hoisted to row columns here (slug, name,
|
|
38
|
+
* tags…), so it does not travel inside `definition`. The CP door reads the
|
|
39
|
+
* row's `icon`/`domain` columns and the stored `definition.icon`/`.color` from
|
|
40
|
+
* the definition's TOP-LEVEL fields (`publishPackageCore` +
|
|
41
|
+
* `normalizePublishedDefinition`); with nothing there it fell back to
|
|
42
|
+
* `icon: "layers"` / `color: "#6366F1"` and a NULL icon column — which is how
|
|
43
|
+
* 26/28 official catalog rows lost their own icon and colour. The CLI
|
|
44
|
+
* (`synap-cli/src/lib/cp-packages.ts`) already mirrors them; this is the same
|
|
45
|
+
* shape.
|
|
46
|
+
*
|
|
47
|
+
* @param {import('./define').PackageDefinitionOutput} pd `toPackageDefinition()` output
|
|
48
|
+
* @param {string} vendorId the `synap-official` vendor id
|
|
49
|
+
*/
|
|
50
|
+
export function toOfficialPublishBody(pd, vendorId) {
|
|
51
|
+
const { _meta, ...rest } = pd;
|
|
52
|
+
const definition = {
|
|
53
|
+
...rest,
|
|
54
|
+
...(_meta.icon !== undefined ? { icon: _meta.icon } : {}),
|
|
55
|
+
...(_meta.color !== undefined ? { color: _meta.color } : {}),
|
|
56
|
+
...(_meta.domain !== undefined ? { domain: _meta.domain } : {}),
|
|
57
|
+
sourcePackage: { ...OFFICIAL_SOURCE_PACKAGE },
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
slug: _meta.slug,
|
|
61
|
+
// The package's own name — an overlay's workspaceName is its TARGET's name
|
|
62
|
+
// (business-model composes onto Foundation/Strategy and was listed as "Foundation").
|
|
63
|
+
displayName: _meta.name ?? pd.workspaceName,
|
|
64
|
+
// Same reason: the catalog lists the PACKAGE's description (meta), not the
|
|
65
|
+
// overlay target's workspace description ("Adds … to Foundation").
|
|
66
|
+
description: _meta.description ?? pd.description,
|
|
67
|
+
isPublic: _meta.isPublic ?? true,
|
|
68
|
+
tags: _meta.tags ?? [],
|
|
69
|
+
category: 'workspace',
|
|
70
|
+
...(_meta.requiredTier ? { requiredTier: _meta.requiredTier } : {}),
|
|
71
|
+
vendorId,
|
|
72
|
+
definition,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The official publish body carries each template's OWN identity.
|
|
3
|
+
*
|
|
4
|
+
* Defect this guards: `publish-official.mjs` posted `toPackageDefinition()`
|
|
5
|
+
* minus `_meta` with nothing mirrored, so the CP door found no top-level
|
|
6
|
+
* `icon`/`color` and stored `icon: "layers"` / `color: "#6366F1"` with a NULL
|
|
7
|
+
* icon column — 26/28 official catalog rows lost their icon and colour.
|
|
8
|
+
*
|
|
9
|
+
* Driven from the REAL corpus (every `src/*.yaml`) through the REAL
|
|
10
|
+
* `toPackageDefinition` into the REAL body builder the script calls — nothing
|
|
11
|
+
* hand-built between them. The CP half of the seam (body → publishPackageCore →
|
|
12
|
+
* stored row) is `synap-control-plane-api/src/seeds/publish-package-core.icon.test.ts`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, it } from 'vitest';
|
|
16
|
+
import { readYamlTemplates } from './generate.mjs';
|
|
17
|
+
import { toOfficialPublishBody } from './publish-body.mjs';
|
|
18
|
+
import { toPackageDefinition } from './define';
|
|
19
|
+
import type { WorkspaceYaml } from './types';
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { mergeCatalog } from './catalog';
|
|
22
|
+
|
|
23
|
+
const corpus = readYamlTemplates();
|
|
24
|
+
|
|
25
|
+
describe('toOfficialPublishBody', () => {
|
|
26
|
+
it('reads a plausible corpus (non-vacuity)', () => {
|
|
27
|
+
expect(corpus.length).toBeGreaterThanOrEqual(25);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("mirrors every template's own icon / colour / domain onto the definition", () => {
|
|
31
|
+
const wrong: string[] = [];
|
|
32
|
+
let withIcon = 0;
|
|
33
|
+
for (const t of corpus) {
|
|
34
|
+
const tpl = t.data as WorkspaceYaml;
|
|
35
|
+
const body = toOfficialPublishBody(toPackageDefinition(tpl), 'vendor-x');
|
|
36
|
+
const meta = tpl.meta as { icon?: string; color?: string; domain?: string };
|
|
37
|
+
if (meta.icon) withIcon++;
|
|
38
|
+
if (body.definition.icon !== meta.icon) wrong.push(`${t.slug}.icon`);
|
|
39
|
+
if (body.definition.color !== meta.color) wrong.push(`${t.slug}.color`);
|
|
40
|
+
if (body.definition.domain !== meta.domain) wrong.push(`${t.slug}.domain`);
|
|
41
|
+
// `_meta` is hoisted to columns, never sent inside the definition.
|
|
42
|
+
expect('_meta' in body.definition).toBe(false);
|
|
43
|
+
expect(body.slug).toBe(tpl.meta.slug);
|
|
44
|
+
expect(body.vendorId).toBe('vendor-x');
|
|
45
|
+
}
|
|
46
|
+
expect(wrong).toEqual([]);
|
|
47
|
+
// Non-vacuity: the corpus actually declares icons, so the equality above
|
|
48
|
+
// cannot pass by comparing undefined to undefined everywhere.
|
|
49
|
+
expect(withIcon).toBe(corpus.length);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Incident 2026-10-06: official rows carried NO `definition.sourcePackage`,
|
|
53
|
+
// so `mergeCatalog` let every stale client bundle win (the CLI installed its
|
|
54
|
+
// old content-os — no primarySurface — onto a live space).
|
|
55
|
+
it("stamps this package's own name + version as definition.sourcePackage", () => {
|
|
56
|
+
const own = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
57
|
+
expect(own.version).toMatch(/^\d+\.\d+\.\d+/);
|
|
58
|
+
for (const t of corpus) {
|
|
59
|
+
const body = toOfficialPublishBody(toPackageDefinition(t.data as WorkspaceYaml), 'vendor-x');
|
|
60
|
+
expect(body.definition.sourcePackage).toEqual({ name: '@synap-core/workspace-templates', version: own.version });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('a published row outranks an OLDER client bundle through the real merge door', () => {
|
|
65
|
+
const tpl = corpus.find((t) => t.slug === 'content-os')!.data as WorkspaceYaml;
|
|
66
|
+
const body = toOfficialPublishBody(toPackageDefinition(tpl), 'vendor-x');
|
|
67
|
+
const merged = mergeCatalog({
|
|
68
|
+
bundled: [tpl],
|
|
69
|
+
remote: [{ slug: body.slug, name: body.displayName, definition: body.definition }],
|
|
70
|
+
bundleVersion: '0.0.1',
|
|
71
|
+
});
|
|
72
|
+
expect(merged.entries.find((e) => e.slug === 'content-os')?.source).toBe('remote');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE official-publish implementation (`scripts/publish-official-lib.mjs`),
|
|
3
|
+
* driven from the REAL corpus + REAL `toOfficialPublishBody` through an injected
|
|
4
|
+
* fetch. Nothing between the YAML and the wire is hand-built.
|
|
5
|
+
*
|
|
6
|
+
* Pins: the preflight refuses a non-official account BEFORE any write; the plan
|
|
7
|
+
* is read-only; a failed live read is a refusal, never "new"; the POST carries
|
|
8
|
+
* the official vendorId and `sourcePackage`; the diff names added/removed
|
|
9
|
+
* profiles and fields; a stamp the CP did not store is reported.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from "vitest";
|
|
13
|
+
import {
|
|
14
|
+
OfficialPublishError,
|
|
15
|
+
diffDefinitions,
|
|
16
|
+
executeOfficialPublish,
|
|
17
|
+
planOfficialPublish,
|
|
18
|
+
} from "../scripts/publish-official-lib.mjs";
|
|
19
|
+
import { OFFICIAL_SOURCE_PACKAGE } from "./publish-body.mjs";
|
|
20
|
+
import { cpFetch } from "../scripts/catalog-lib.mjs";
|
|
21
|
+
import { OFFICIAL, fakeCp, isWrite } from "../scripts/__fixtures__/fake-cp.mjs";
|
|
22
|
+
import { readYamlTemplates } from "./generate.mjs";
|
|
23
|
+
|
|
24
|
+
const CP = "https://cp.test";
|
|
25
|
+
|
|
26
|
+
describe("planOfficialPublish — preflight", () => {
|
|
27
|
+
it("refuses an account whose vendor is not synap-official, and writes nothing", async () => {
|
|
28
|
+
const { calls, fetchImpl } = fakeCp({ vendor: { id: "v-1", slug: "antoine" } });
|
|
29
|
+
await expect(
|
|
30
|
+
planOfficialPublish({ cpUrl: CP, token: "t", slugs: ["content-os"], fetchImpl }),
|
|
31
|
+
).rejects.toMatchObject({ code: "not-official-publisher" });
|
|
32
|
+
expect(calls.filter((c) => c.method !== "GET")).toHaveLength(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("a failed live read is a refusal — never guessed as a new template", async () => {
|
|
36
|
+
const { fetchImpl } = fakeCp({ liveStatus: 500 });
|
|
37
|
+
const err: unknown = await planOfficialPublish({ cpUrl: CP, token: "t", slugs: ["content-os"], fetchImpl }).catch((e: unknown) => e);
|
|
38
|
+
expect(err).toBeInstanceOf(OfficialPublishError);
|
|
39
|
+
expect((err as OfficialPublishError).code).toBe("live-read-failed");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("an unknown slug is refused before any network call", async () => {
|
|
43
|
+
const { calls, fetchImpl } = fakeCp();
|
|
44
|
+
await expect(
|
|
45
|
+
planOfficialPublish({ cpUrl: CP, token: "t", slugs: ["no-such-template"], fetchImpl }),
|
|
46
|
+
).rejects.toMatchObject({ code: "unknown-slug" });
|
|
47
|
+
expect(calls).toHaveLength(0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("plan → execute — the wire", () => {
|
|
52
|
+
it("plans read-only, then POSTs the official vendorId + sourcePackage", async () => {
|
|
53
|
+
const { calls, fetchImpl } = fakeCp({ postStatus: 200 });
|
|
54
|
+
const plan = await planOfficialPublish({ cpUrl: CP, token: "t", slugs: ["content-os"], fetchImpl });
|
|
55
|
+
// Read-only: the only non-GET a plan makes is the CP's read-only preview.
|
|
56
|
+
expect(calls.filter(isWrite)).toHaveLength(0);
|
|
57
|
+
expect(plan.items).toHaveLength(1);
|
|
58
|
+
expect(plan.items[0].status).toBe("new");
|
|
59
|
+
|
|
60
|
+
const results = await executeOfficialPublish(plan, { token: "t", fetchImpl });
|
|
61
|
+
const posts = calls.filter(isWrite);
|
|
62
|
+
expect(posts).toHaveLength(1);
|
|
63
|
+
expect(posts[0].body.vendorId).toBe(OFFICIAL.id);
|
|
64
|
+
expect(posts[0].body.definition.sourcePackage).toEqual(OFFICIAL_SOURCE_PACKAGE);
|
|
65
|
+
// The CP refuses an official publish that does not come through the catalog door.
|
|
66
|
+
expect(posts[0].headers["x-synap-publish-door"]).toBe("catalog");
|
|
67
|
+
expect(results[0]).toMatchObject({ slug: "content-os", outcome: "updated", stampStored: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// The process contract (statusProperty, subjectStatus, onFail, gate, criteria,
|
|
71
|
+
// activators, relationToSubject, humanOnlyStatuses) is authored in YAML and
|
|
72
|
+
// must arrive in the POSTed definition UNCHANGED — through the real dist
|
|
73
|
+
// `toPackageDefinition` and the real body builder. Compared to the YAML parse,
|
|
74
|
+
// so a converter that drops or rewrites any playbook field fails here.
|
|
75
|
+
it("POSTs every authored playbook field — the process contract — unchanged", async () => {
|
|
76
|
+
const yamlPlaybooks = (readYamlTemplates().find((t) => t.slug === "content-os")!.data as any)
|
|
77
|
+
.playbooks as any[];
|
|
78
|
+
const produce = yamlPlaybooks.find((p) => p.name === "Produce Content");
|
|
79
|
+
// Non-vacuity: the YAML really declares the contract fields compared below.
|
|
80
|
+
expect(produce.subjectProfile.statusProperty).toBe("post-status");
|
|
81
|
+
expect(produce.activators).toEqual([{ on: "enters_status", status: "Briefed", mode: "propose" }]);
|
|
82
|
+
expect(produce.stages.find((s: any) => s.key === "review").onFail).toEqual({ toStage: "draft" });
|
|
83
|
+
expect(
|
|
84
|
+
produce.stages.find((s: any) => s.key === "draft").expectedOutputs[0].relationToSubject,
|
|
85
|
+
).toBe("made_for");
|
|
86
|
+
|
|
87
|
+
const { calls, fetchImpl } = fakeCp();
|
|
88
|
+
const plan = await planOfficialPublish({ cpUrl: CP, token: "t", slugs: ["content-os"], fetchImpl });
|
|
89
|
+
await executeOfficialPublish(plan, { token: "t", fetchImpl });
|
|
90
|
+
const posted = calls.find(isWrite)!.body.definition.playbooks;
|
|
91
|
+
expect(posted).toEqual(yamlPlaybooks);
|
|
92
|
+
const postedProduce = posted.find((p: any) => p.name === "Produce Content");
|
|
93
|
+
expect(postedProduce.stages.map((s: any) => s.subjectStatus)).toEqual([
|
|
94
|
+
"Briefed",
|
|
95
|
+
"Scripted",
|
|
96
|
+
"In Draft",
|
|
97
|
+
"Review",
|
|
98
|
+
"Scheduled",
|
|
99
|
+
"Published",
|
|
100
|
+
]);
|
|
101
|
+
expect(postedProduce.humanOnlyStatuses).toEqual(["Idea", "Archived"]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("reports a stamp the CP did not store", async () => {
|
|
105
|
+
const { fetchImpl } = fakeCp({ storeStamp: false });
|
|
106
|
+
const plan = await planOfficialPublish({ cpUrl: CP, token: "t", slugs: ["content-os"], fetchImpl });
|
|
107
|
+
const [r] = await executeOfficialPublish(plan, { token: "t", fetchImpl });
|
|
108
|
+
expect(r.stampStored).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("diffDefinitions", () => {
|
|
113
|
+
it("names added/removed profiles, fields, views, links and dependencies", () => {
|
|
114
|
+
const live = {
|
|
115
|
+
profiles: [
|
|
116
|
+
{ slug: "post", properties: [{ slug: "title" }, { slug: "old-field" }] },
|
|
117
|
+
{ slug: "gone" },
|
|
118
|
+
],
|
|
119
|
+
views: [{ slug: "v-old" }],
|
|
120
|
+
entityLinks: [{ sourceProfileSlug: "post", type: "x", targetProfileSlug: "gone" }],
|
|
121
|
+
dependencies: [{ slug: "foundation" }],
|
|
122
|
+
onboarding: { a: 1, b: 2 },
|
|
123
|
+
};
|
|
124
|
+
const next = {
|
|
125
|
+
profiles: [{ slug: "post", properties: [{ slug: "title" }, { slug: "new-field" }] }, { slug: "fresh" }],
|
|
126
|
+
views: [{ slug: "v-new" }],
|
|
127
|
+
entityLinks: [],
|
|
128
|
+
dependencies: [{ slug: "foundation" }, { slug: "brand-library" }],
|
|
129
|
+
onboarding: { b: 2, a: 1 }, // same content, different key order → NOT a change
|
|
130
|
+
};
|
|
131
|
+
const d = diffDefinitions(live, next);
|
|
132
|
+
expect(d.profiles).toEqual({ added: ["fresh"], removed: ["gone"] });
|
|
133
|
+
expect(d.fields).toEqual({ added: ["post.new-field"], removed: ["post.old-field"] });
|
|
134
|
+
expect(d.views).toEqual({ added: ["v-new"], removed: ["v-old"] });
|
|
135
|
+
expect(d.entityLinks.removed).toEqual(["post —x→ gone"]);
|
|
136
|
+
expect(d.dependencies).toEqual({ added: ["brand-library"], removed: [] });
|
|
137
|
+
expect(d.otherKeysChanged).toEqual([]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("matches a live view (CP-derived slug) to the catalog view that names it", () => {
|
|
141
|
+
const live = { views: [{ name: "Task Board", slug: "task-board", displayName: "Task Board" }] };
|
|
142
|
+
const next = { views: [{ name: "Task Board" }] };
|
|
143
|
+
expect(diffDefinitions(live, next).views).toEqual({ added: [], removed: [] });
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("cpFetch — a dropped read is retried once, a write never", () => {
|
|
148
|
+
const flaky = (failures: number) => {
|
|
149
|
+
let calls = 0;
|
|
150
|
+
const impl = (async () => {
|
|
151
|
+
calls++;
|
|
152
|
+
if (calls <= failures) throw new TypeError("fetch failed");
|
|
153
|
+
return new Response("{}", { status: 200 });
|
|
154
|
+
}) as unknown as typeof fetch;
|
|
155
|
+
return { impl, calls: () => calls };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
it("a GET that drops once succeeds on the retry", async () => {
|
|
159
|
+
const f = flaky(1);
|
|
160
|
+
const res = await cpFetch(f.impl, "https://cp.test/api/packages", {}, { retryDelayMs: 0 });
|
|
161
|
+
expect(res.status).toBe(200);
|
|
162
|
+
expect(f.calls()).toBe(2);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("a GET that drops twice is cp-unreachable", async () => {
|
|
166
|
+
const f = flaky(2);
|
|
167
|
+
await expect(cpFetch(f.impl, "https://cp.test/x", {}, { retryDelayMs: 0 })).rejects.toMatchObject({ code: "cp-unreachable" });
|
|
168
|
+
expect(f.calls()).toBe(2);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("a POST that drops is never re-sent (it may have landed)", async () => {
|
|
172
|
+
const f = flaky(1);
|
|
173
|
+
await expect(cpFetch(f.impl, "https://cp.test/api/packages", { method: "POST" }, { retryDelayMs: 0 })).rejects.toMatchObject({ code: "cp-unreachable" });
|
|
174
|
+
expect(f.calls()).toBe(1);
|
|
175
|
+
});
|
|
176
|
+
});
|