@synap-core/workspace-templates 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -751,6 +751,177 @@ declare function isHashVersion(version: string | undefined): boolean;
751
751
  */
752
752
  declare function computeTemplateUpdates(installed: InstalledTemplate[], catalog: CatalogEntry[], bundleVersion?: string): TemplateUpdateCheck[];
753
753
 
754
+ /**
755
+ * layerTemplateGraph — the KEYSTONE composition-graph engine.
756
+ * ============================================================
757
+ *
758
+ * Today the CLI (`synap-cli/src/commands/market.ts` `buildCompositionTrees`)
759
+ * renders template composition as a nested TREE: `buildDependencyNode`
760
+ * rebuilds a fresh `TreeNode` for every OCCURRENCE of a slug on every path, so
761
+ * a widely-required base (e.g. `foundation`) prints once per branch that
762
+ * reaches it (6× under a suite like `enterprise-os`). Its "shared" tag is only
763
+ * top-level-package granularity — there is no true fan-in count.
764
+ *
765
+ * This module replaces that with a LAYERED DAG: every node appears EXACTLY
766
+ * ONCE, positioned at its longest-path depth, with edges converging on it —
767
+ * so `foundation` sinks to the bedrock row with N inbound edges instead of
768
+ * being redrawn N times. It is the single source of truth for template
769
+ * composition; the CLI, browser dialogs, backend boot-ordering, and (later)
770
+ * the Atlas all consume it.
771
+ *
772
+ * PURE + TRANSPORT-AGNOSTIC (same discipline as `catalog.ts`): this is the
773
+ * ONLY module `synap-cli`, `browser`, `synap-control-plane-api`, and
774
+ * `synap-backend` can all resolve. No React, no dagre/d3, no fetch, no
775
+ * Node-only APIs. Pixel layout lives elsewhere (browser-only) — this module
776
+ * only computes topology (layers + edges + memoized nodes), never coordinates.
777
+ *
778
+ * CONSUMES drift, never recomputes it: `installed` / `updates` are read
779
+ * straight off `catalog.ts`'s `InstalledTemplate`-shaped rows and
780
+ * `computeTemplateUpdates()` output — this module does no semver comparison
781
+ * of its own.
782
+ */
783
+
784
+ /**
785
+ * An installed template row, keyed by slug — the shape this engine needs to
786
+ * mark a node `installed` and populate `workspaceIds`.
787
+ *
788
+ * NOTE ON NAMING: `catalog.ts`'s `InstalledTemplate` is `{ slug, version }`
789
+ * only — it was built for `computeTemplateUpdates`'s narrower need (a version
790
+ * to diff) and carries no `workspaceId`. `CompositionNode.workspaceIds` is
791
+ * part of the ratified API and cannot be populated from that shape, so this
792
+ * is a REAL conflict, not a preference: this module declares its own,
793
+ * slightly richer row instead of importing `catalog.ts`'s `InstalledTemplate`
794
+ * under that name. Every field of `catalog.ts`'s `InstalledTemplate` is a
795
+ * subset of this one, so a caller building `InstalledTemplate[]` for
796
+ * `computeTemplateUpdates` can build ONE array of `CompositionInstalledEntry`
797
+ * and pass it to both doors.
798
+ */
799
+ interface CompositionInstalledEntry {
800
+ slug: string;
801
+ workspaceId: string;
802
+ workspaceName?: string;
803
+ /** `settings.packageVersion`, when the pod stamped one. Omit when unknown. */
804
+ version?: string;
805
+ }
806
+ /** One compose-relation dependency, attached to the BASE node it overlays onto. */
807
+ interface CompositionOverlay {
808
+ slug: string;
809
+ name: string;
810
+ installed: boolean;
811
+ }
812
+ /** One edge in the raw dependency graph, exactly as declared (kept even when broken by a cycle). */
813
+ interface CompositionEdge {
814
+ from: string;
815
+ to: string;
816
+ relation: "compose" | "require";
817
+ depKind?: string;
818
+ }
819
+ interface CompositionNode {
820
+ slug: string;
821
+ name: string;
822
+ /** Longest-path depth: `1 + max(layer of every node that depends on this one)`. Roots = 0, bedrock = max. */
823
+ layer: number;
824
+ /** TRUE fan-in — every slug (node or compose-only overlay source) that declares a dependency on this one. */
825
+ dependents: string[];
826
+ /** Which of the input `roots` transitively reach this node (self-inclusive for a root). */
827
+ roots: string[];
828
+ installed: boolean;
829
+ workspaceIds: string[];
830
+ installedVersion?: string;
831
+ latestVersion?: string | null;
832
+ updateAvailable: boolean;
833
+ noVersionInfo: boolean;
834
+ /** `compose`-relation deps that overlay onto THIS node (see rule 2 — the overlay source itself is never a layer node). */
835
+ overlays: CompositionOverlay[];
836
+ }
837
+ interface CompositionGraph {
838
+ nodes: Map<string, CompositionNode>;
839
+ edges: CompositionEdge[];
840
+ /** `layers[0]` = roots … `layers[n]` = bedrock. Compose-overlay SOURCES never appear here (rule 2). */
841
+ layers: CompositionNode[][];
842
+ /** Broken back-edges `[from, to]` — surfaced, never silently swallowed (rule 4). */
843
+ cycles: string[][];
844
+ }
845
+ interface LayerTemplateGraphInput {
846
+ /** Top-level slugs to walk from. Always land at `layer: 0`. */
847
+ roots: string[];
848
+ /** INJECTED edge source (rule 5) — never hardcoded to the bundled map. See `bundledEdgesOf`. */
849
+ edgesOf: (slug: string) => readonly TemplateDependency[];
850
+ nameOf?: (slug: string) => string | undefined;
851
+ installed?: CompositionInstalledEntry[];
852
+ updates?: TemplateUpdateCheck[];
853
+ }
854
+ /**
855
+ * Default `edgesOf` — reads `dependencies[]` off the BUNDLED template map
856
+ * only. This is what the current CLI walker is hardcoded to, which is
857
+ * precisely the live bug rule 5 fixes: a remote/private CP-only package's
858
+ * `definition.dependencies` are invisible to it, so its subtree silently
859
+ * renders empty. Callers with a remote catalog entry available should pass
860
+ * their OWN `edgesOf` that falls back to `entry.remote.definition.dependencies`
861
+ * (or similar) for slugs this default can't see.
862
+ */
863
+ declare function bundledEdgesOf(slug: string): readonly TemplateDependency[];
864
+ declare function layerTemplateGraph(input: LayerTemplateGraphInput): CompositionGraph;
865
+
866
+ /**
867
+ * Cross-template CONCEPT guard — catches vocabulary forks between templates.
868
+ *
869
+ * WHY THIS EXISTS (read before weakening or deleting it):
870
+ * `business-developer.yaml` introduced workspace-scoped `crm-lead`/`crm-client`
871
+ * for the same real-world concept that `foundation.yaml`/`crm.yaml` declare as
872
+ * pod-wide shared `lead`/`client`. Nothing failed. Three reasons:
873
+ *
874
+ * 1. `build.mjs`'s `validate()` runs PER FILE, inside the read loop, so it
875
+ * structurally cannot see that another template already owns `lead`.
876
+ * 2. The only vocabulary rule that exists (`template-shared-role-ssot.test.ts`
877
+ * in synap-backend) polices a HARDCODED five-slug set — and it FORBIDS
878
+ * declaring one of those slugs workspace-scoped, which is exactly what the
879
+ * two-tier design legitimately needed. So RENAMING was the path of least
880
+ * resistance: `crm-lead` is outside the set and passes silently. That rule
881
+ * polices SPELLING, not MEANING.
882
+ * 3. That test also cannot run in CI — it resolves this package across a
883
+ * sibling-repo relative path that CI never checks out. It is green on a dev
884
+ * machine and mute everywhere else.
885
+ *
886
+ * This guard lives HERE, in the repo that owns the template source, and runs in
887
+ * this repo's CI (`pnpm test`) and in `pnpm build` / `prepublishOnly` via
888
+ * `scripts/check-concepts.mjs`. It is deliberately NOT a hardcoded slug list —
889
+ * that is the pattern that failed.
890
+ *
891
+ * WHAT IT DOES NOT DO: it does not guess. There is no fuzzy name matching, no
892
+ * edit distance, no synonym table. A guesser nobody trusts is worse than no
893
+ * guard. It uses two exact, falsifiable signals only — see the rules below.
894
+ */
895
+
896
+ interface ConceptViolation {
897
+ rule: "concept-collision" | "alias-shape-divergence" | "stale-allowlist";
898
+ /** The concept term or semantic slug the finding is about. */
899
+ concept: string;
900
+ message: string;
901
+ }
902
+ /**
903
+ * DECLARED, INTENTIONAL divergences — the escape hatch, and deliberately the
904
+ * SECONDARY mechanism. An entry here is an author saying "yes, two spellings of
905
+ * one concept, on purpose, and here is why". Keep it tiny; every entry must
906
+ * carry its WHY. A key that stops matching anything FAILS the guard, so this
907
+ * list cannot rot into a graveyard.
908
+ *
909
+ * Key = the two colliding profile slugs, sorted, joined by "|".
910
+ */
911
+ declare const DECLARED_DIVERGENCES: Record<string, string>;
912
+ /**
913
+ * The bare noun a concept identifier ends in: `crm.lead` → `lead`,
914
+ * `crm-lead` → `lead`, `lead` → `lead`.
915
+ *
916
+ * This is EXACT terminal-token equality on the author's own qualified name —
917
+ * not a similarity heuristic. `crm-lead` and `lead` collide because the author
918
+ * literally wrote the same noun; `prospect` and `lead` do not collide, and that
919
+ * is honest: no guard can know they mean the same thing.
920
+ */
921
+ declare function conceptTerm(semanticSlug: string): string;
922
+ declare function findConceptViolations(templates?: Record<string, WorkspaceYaml>): ConceptViolation[];
923
+ declare function formatConceptViolations(violations: ConceptViolation[]): string;
924
+
754
925
  /**
755
926
  * @synap-core/workspace-templates — Definition adapter
756
927
  *
@@ -955,4 +1126,4 @@ interface PackageDefinitionOutput {
955
1126
  */
956
1127
  declare function toPackageDefinition(slugOrYaml: string | WorkspaceYaml): PackageDefinitionOutput;
957
1128
 
958
- export { type AssembleCatalogInput, type BentoViewBlockWire, type BentoWidgetBlockWire, type BundledCatalogEntry, type BundledEntry, type CatalogEntry, type CpPackageRow, type InstalledTemplate, type MergeCatalogInput, type MergeCatalogResult, PACKAGE_DEPENDENCY_KINDS, type PackageDefinitionOutput, type PackageDependencyKind, type RemoteCatalogEntry, type RemoteEntry, type RemoteSourceStatus, type SplitBentoBlocks, type TemplateActionPlacement, type TemplateAppKind, type TemplateAppRef, type TemplateAutomation, type TemplateBentoBlock, type TemplateDefaultSource, type TemplateDependency, type TemplateDisplayTemplate, type TemplateEntityLink, type TemplateExtendsRef, type TemplateIntegration, type TemplateLayoutConfig, type TemplateMeta, type TemplateOnboarding, type TemplateOnboardingCollectTarget, type TemplatePlaybook, type TemplatePlaybookParam, type TemplateProfile, type TemplateProfileEntityBentoTemplates, type TemplateProperty, type TemplateSeedEntity, type TemplateSidebarItem, type TemplateSidebarSurface, type TemplateSourceRole, type TemplateSuggestedRelation, type TemplateUpdateCheck, type TemplateView, type ToDefinitionResult, WORKSPACE_TEMPLATES, type WorkspaceDefinitionInput, type WorkspaceTemplateSlug, type WorkspaceYaml, assembleCatalog, computeTemplateUpdates, getWorkspaceTemplate, isHashVersion, listPublicTemplates, listWorkspaceTemplates, mergeCatalog, rowToRemoteEntry, splitBentoBlocks, toPackageDefinition, toWorkspaceDefinition };
1129
+ export { type AssembleCatalogInput, type BentoViewBlockWire, type BentoWidgetBlockWire, type BundledCatalogEntry, type BundledEntry, type CatalogEntry, type CompositionEdge, type CompositionGraph, type CompositionInstalledEntry, type CompositionNode, type CompositionOverlay, type ConceptViolation, type CpPackageRow, DECLARED_DIVERGENCES, type InstalledTemplate, type LayerTemplateGraphInput, type MergeCatalogInput, type MergeCatalogResult, PACKAGE_DEPENDENCY_KINDS, type PackageDefinitionOutput, type PackageDependencyKind, type RemoteCatalogEntry, type RemoteEntry, type RemoteSourceStatus, type SplitBentoBlocks, type TemplateActionPlacement, type TemplateAppKind, type TemplateAppRef, type TemplateAutomation, type TemplateBentoBlock, type TemplateDefaultSource, type TemplateDependency, type TemplateDisplayTemplate, type TemplateEntityLink, type TemplateExtendsRef, type TemplateIntegration, type TemplateLayoutConfig, type TemplateMeta, type TemplateOnboarding, type TemplateOnboardingCollectTarget, type TemplatePlaybook, type TemplatePlaybookParam, type TemplateProfile, type TemplateProfileEntityBentoTemplates, type TemplateProperty, type TemplateSeedEntity, type TemplateSidebarItem, type TemplateSidebarSurface, type TemplateSourceRole, type TemplateSuggestedRelation, type TemplateUpdateCheck, type TemplateView, type ToDefinitionResult, WORKSPACE_TEMPLATES, type WorkspaceDefinitionInput, type WorkspaceTemplateSlug, type WorkspaceYaml, assembleCatalog, bundledEdgesOf, computeTemplateUpdates, conceptTerm, findConceptViolations, formatConceptViolations, getWorkspaceTemplate, isHashVersion, layerTemplateGraph, listPublicTemplates, listWorkspaceTemplates, mergeCatalog, rowToRemoteEntry, splitBentoBlocks, toPackageDefinition, toWorkspaceDefinition };