@warpgogol/werkstatt-shared 0.13.0 → 0.14.1

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.
Files changed (178) hide show
  1. package/README.md +26 -16
  2. package/README.uk.md +24 -14
  3. package/package.json +27 -69
  4. package/src/checks/result-helpers.ts +21 -4
  5. package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
  6. package/src/integration/funnel.ts +78 -1
  7. package/src/integration/index.ts +19 -2
  8. package/src/integration/lagebild-ingress.ts +169 -0
  9. package/src/integration/orchestration.ts +7 -13
  10. package/src/integration/port-barrel.ts +9 -28
  11. package/src/integration/tests/funnel-bridge.test.ts +7 -7
  12. package/src/integration/tests/lagebild-ingress.test.ts +118 -0
  13. package/src/integration/vitest.config.ts +7 -0
  14. package/src/observability/typed-refs.ts +1 -1
  15. package/src/ontology/analytics/matomo-fleet.registry.yaml +1 -1
  16. package/src/ontology/archetypes/index.json +12 -9
  17. package/src/ontology/archetypes/index.yaml +26 -10
  18. package/src/ontology/blueprints/website-local.yaml +11 -11
  19. package/src/ontology/blueprints/website-service.yaml +20 -20
  20. package/src/ontology/capabilities/lead.submit.yaml +2 -2
  21. package/src/ontology/enums.ts +2 -2
  22. package/src/ontology/external-surfaces/jsonld-types.yaml +14 -14
  23. package/src/ontology/external-surfaces/url-schema.yaml +1 -1
  24. package/src/ontology/index.ts +2 -0
  25. package/src/ontology/manifest.ts +19 -0
  26. package/src/ontology/schemas/index.ts +7 -2
  27. package/src/ontology/schemas/system/integrations.ts +1 -1
  28. package/src/ontology/schemas/system/manifest.ts +38 -1
  29. package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
  30. package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
  31. package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
  32. package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
  33. package/src/ontology/schemas/system/tests/release.test.ts +22 -0
  34. package/src/ontology/schemas/system/tests/text.test.ts +18 -0
  35. package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
  36. package/src/ontology/schemas/system.ts +7 -2
  37. package/src/ontology/schemas/tests/biome.test.ts +19 -0
  38. package/src/ontology/schemas/tests/capability.test.ts +22 -0
  39. package/src/ontology/schemas/tests/constellation.test.ts +22 -0
  40. package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
  41. package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
  42. package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
  43. package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
  44. package/src/ontology/schemas/tests/site-family.test.ts +18 -0
  45. package/src/ontology/schemas/tests/system.test.ts +20 -0
  46. package/src/ontology/shared-section-props/common.ts +11 -0
  47. package/src/ontology/shared-section-props/visual-header.ts +1 -1
  48. package/src/share/agent/api-catalog.ts +5 -0
  49. package/src/share/agent/ard-catalog.ts +14 -0
  50. package/src/share/agent/index.ts +2 -0
  51. package/src/share/agent/manifest.ts +25 -2
  52. package/src/share/agent/mcp-card.ts +4 -0
  53. package/src/share/agent/openapi.ts +78 -0
  54. package/src/share/agent/search.test.ts +188 -0
  55. package/src/share/agent/search.ts +164 -0
  56. package/src/share/agent/tests/api-catalog.test.ts +8 -0
  57. package/src/share/agent/tests/ard-catalog.test.ts +8 -0
  58. package/src/share/agent/tests/capability.test.ts +14 -0
  59. package/src/share/agent/tests/fleet-catalog.test.ts +15 -0
  60. package/src/share/agent/tests/knowledge.test.ts +20 -0
  61. package/src/share/agent/tests/manifest.test.ts +9 -0
  62. package/src/share/agent/tests/mcp-card.test.ts +8 -0
  63. package/src/share/agent/tests/omit-empty.test.ts +33 -0
  64. package/src/share/agent/tests/openapi.test.ts +8 -0
  65. package/src/share/agent/tests/proof.test.ts +15 -0
  66. package/src/share/env.d.ts +0 -4
  67. package/src/share/knowledge/tests/claim.test.ts +11 -0
  68. package/src/share/knowledge/tests/derivation.test.ts +30 -0
  69. package/src/share/knowledge/tests/freshness.test.ts +18 -0
  70. package/src/share/knowledge/tests/ledger.test.ts +35 -0
  71. package/src/share/knowledge/tests/plan.test.ts +19 -0
  72. package/src/share/knowledge/tests/source.test.ts +19 -0
  73. package/src/share/remediation/remediation-catalog.ts +196 -0
  74. package/src/share/schemas/effects.ts +16 -0
  75. package/src/share/schemas/media.ts +4 -0
  76. package/src/share/schemas/section-shell.ts +2 -1
  77. package/src/share/schemas/tests/claim-records.test.ts +52 -0
  78. package/src/share/schemas/tests/claims.test.ts +23 -0
  79. package/src/share/schemas/tests/effects.test.ts +116 -0
  80. package/src/share/schemas/tests/features.test.ts +61 -0
  81. package/src/share/schemas/tests/horizontal-align.test.ts +16 -0
  82. package/src/share/schemas/tests/icon-color.test.ts +16 -0
  83. package/src/share/schemas/tests/live-photo.test.ts +40 -0
  84. package/src/share/schemas/tests/material-credit.test.ts +51 -0
  85. package/src/share/schemas/tests/media.test.ts +26 -0
  86. package/src/share/schemas/tests/navigation.test.ts +58 -0
  87. package/src/share/schemas/tests/page-base.test.ts +19 -0
  88. package/src/share/schemas/tests/participant.test.ts +26 -0
  89. package/src/share/schemas/tests/person.test.ts +12 -0
  90. package/src/share/schemas/tests/print.test.ts +30 -0
  91. package/src/share/schemas/tests/section-background.test.ts +31 -0
  92. package/src/share/schemas/tests/section-body.test.ts +16 -0
  93. package/src/share/schemas/tests/section-cards.test.ts +17 -0
  94. package/src/share/schemas/tests/section-cta.test.ts +41 -0
  95. package/src/share/schemas/tests/section-header.test.ts +31 -0
  96. package/src/share/schemas/tests/section-image.test.ts +24 -0
  97. package/src/share/schemas/tests/section-motion.test.ts +29 -0
  98. package/src/share/schemas/tests/section-shell.test.ts +42 -0
  99. package/src/share/schemas/tests/section-stats.test.ts +17 -0
  100. package/src/share/schemas/tests/site-background.test.ts +33 -0
  101. package/src/share/schemas/tests/standard-list-item.test.ts +16 -0
  102. package/src/share/scripts/external-link-qr.ts +9 -1
  103. package/src/share/scripts/gsap-counter.ts +2 -0
  104. package/src/share/scripts/inline-number-animation.ts +2 -0
  105. package/src/share/scripts/live-photos.ts +2 -0
  106. package/src/share/scripts/tests/external-link-qr.test.ts +8 -0
  107. package/src/share/scripts/tests/external-links.test.ts +25 -0
  108. package/src/share/scripts/tests/gsap-counter.test.ts +8 -0
  109. package/src/share/scripts/tests/gsap-mountain-journey.test.ts +8 -0
  110. package/src/share/scripts/tests/gsap-parallax.test.ts +8 -0
  111. package/src/share/scripts/tests/gsap-reveal.test.ts +8 -0
  112. package/src/share/scripts/tests/gsap-stagger.test.ts +8 -0
  113. package/src/share/scripts/tests/inline-number-animation.test.ts +8 -0
  114. package/src/share/scripts/tests/lenis.test.ts +8 -0
  115. package/src/share/scripts/tests/live-photos.test.ts +8 -0
  116. package/src/share/scripts/tests/lordicon.test.ts +8 -0
  117. package/src/share/scripts/tests/orchestrator.test.ts +8 -0
  118. package/src/share/scripts/tests/scheduler.test.ts +23 -0
  119. package/src/share/scripts/video-player.test.ts +210 -0
  120. package/src/share/scripts/video-player.ts +73 -13
  121. package/src/share/semantic/extract.ts +25 -1
  122. package/src/share/semantic/jsonld/tests/article.test.ts +89 -0
  123. package/src/share/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
  124. package/src/share/semantic/jsonld/tests/collection-list.test.ts +39 -0
  125. package/src/share/semantic/jsonld/tests/faq.test.ts +60 -0
  126. package/src/share/semantic/jsonld/tests/initiative.test.ts +56 -0
  127. package/src/share/semantic/jsonld/tests/organization.test.ts +123 -0
  128. package/src/share/semantic/jsonld/tests/person.test.ts +89 -0
  129. package/src/share/semantic/jsonld/tests/service.test.ts +65 -0
  130. package/src/share/semantic/jsonld/tests/shared.test.ts +41 -0
  131. package/src/share/semantic/jsonld/tests/video.test.ts +84 -0
  132. package/src/share/semantic/jsonld/tests/webpage.test.ts +93 -0
  133. package/src/share/semantic/jsonld/tests/website.test.ts +27 -0
  134. package/src/share/semantic/jsonld/types.ts +1 -0
  135. package/src/share/semantic/models.ts +1 -0
  136. package/src/share/semantic/tests/ai.test.ts +55 -0
  137. package/src/share/semantic/tests/block-extraction.test.ts +34 -0
  138. package/src/share/semantic/tests/breadcrumbs.test.ts +127 -0
  139. package/src/share/semantic/tests/build-page.test.ts +122 -0
  140. package/src/share/semantic/tests/business-projection.test.ts +188 -0
  141. package/src/share/semantic/tests/derived-prices-loader.test.ts +46 -0
  142. package/src/share/semantic/tests/extract.test.ts +126 -0
  143. package/src/share/semantic/tests/feed.test.ts +113 -0
  144. package/src/share/semantic/tests/helpers.ts +32 -0
  145. package/src/share/semantic/tests/ids.test.ts +118 -0
  146. package/src/share/semantic/tests/image-sitemap.test.ts +109 -0
  147. package/src/share/semantic/tests/jsonld.test.ts +61 -0
  148. package/src/share/semantic/tests/llms-policy.test.ts +60 -0
  149. package/src/share/semantic/tests/llms.test.ts +118 -0
  150. package/src/share/semantic/tests/markdown-hygiene.test.ts +72 -0
  151. package/src/share/semantic/tests/markdown-page.test.ts +97 -0
  152. package/src/share/semantic/tests/markdown-twin-provenance.test.ts +152 -0
  153. package/src/share/semantic/tests/organization-profile.test.ts +107 -0
  154. package/src/share/semantic/tests/output-projection.test.ts +77 -0
  155. package/src/share/semantic/tests/page-markdown.test.ts +98 -0
  156. package/src/share/semantic/tests/page-utils.test.ts +88 -0
  157. package/src/share/semantic/tests/price-marker-resolver.test.ts +88 -0
  158. package/src/share/semantic/tests/robots.test.ts +70 -0
  159. package/src/share/semantic/tests/split-sentences.test.ts +57 -0
  160. package/src/share/semantic/tests/update-stamp.test.ts +102 -0
  161. package/src/share/tests/manifest.test.ts +51 -0
  162. package/src/share/tests/openapi.test.ts +30 -0
  163. package/src/share/types/qrcode-browser.d.ts +18 -0
  164. package/src/share/utility-registry.yaml +16 -2
  165. package/src/integration/crm-buffer.ts +0 -491
  166. package/src/integration-adapter-supabase-crm/adapter.ts +0 -437
  167. package/src/integration-adapter-supabase-crm/client.ts +0 -605
  168. package/src/integration-adapter-supabase-crm/index.ts +0 -28
  169. package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +0 -482
  170. package/src/integration-adapter-supabase-crm/supabase-rest.ts +0 -89
  171. package/src/integration-adapter-supabase-crm/tenant-registry.ts +0 -334
  172. package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +0 -83
  173. package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +0 -409
  174. package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +0 -288
  175. package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +0 -80
  176. package/src/integration-adapter-supabase-crm/vitest.config.ts +0 -8
  177. package/src/integration-adapter-supabase-crm/worker.ts +0 -223
  178. package/src/ontology/archetypes/components/currency-selector.yaml +0 -19
@@ -95,8 +95,8 @@ levels:
95
95
  de: "Welche Angaben helfen, den Betrieb einzuschätzen: Qualifikationen, Meisterbetrieb, Fotos, Bewertungen, Öffnungszeiten."
96
96
  uk: "Які відомості допомагають оцінити бізнес: кваліфікації, майстерність, фото, відгуки, години роботи."
97
97
  catalogHeading:
98
- de: "Gewerbe auswählen"
99
- uk: "Обрати галузь"
98
+ de: Gewerbe auswählen
99
+ uk: Обрати галузь
100
100
  productPrice:
101
101
  heading:
102
102
  de: "Gleiche Grundlage, angepasste Struktur"
@@ -104,7 +104,7 @@ levels:
104
104
  body:
105
105
  de: "Die Branche verändert nicht automatisch den Basispreis. Zusätzliche Seiten, Sprachen, Buchungsfunktionen oder Integrationen werden gesondert vereinbart."
106
106
  uk: "Галузь не змінює автоматично базову ціну. Додаткові сторінки, мови, функції бронювання чи інтеграції узгоджуються окремо."
107
- priceRef: "business-profile.offerings/digital-foundation.presentation.price"
107
+ priceRef: business-profile.offerings/digital-foundation.presentation.price
108
108
  finalCta:
109
109
  heading:
110
110
  de: Ihr Gewerbe ist noch nicht aufgeführt?
@@ -141,14 +141,14 @@ levels:
141
141
  minModuleMappings: 3
142
142
  minUniqueFaq: 5
143
143
  claimRestrictions:
144
- - "mehr Anfragen"
145
- - "mehr Termin-Anfragen"
146
- - "echte Aufträge"
147
- - "steigt die Wahrscheinlichkeit"
148
- - "weniger Streuverluste"
149
- - "höhere Conversion"
150
- - "besser gefunden"
151
- - "stärkstes Conversion-Signal"
144
+ - mehr Anfragen
145
+ - mehr Termin-Anfragen
146
+ - echte Aufträge
147
+ - steigt die Wahrscheinlichkeit
148
+ - weniger Streuverluste
149
+ - höhere Conversion
150
+ - besser gefunden
151
+ - stärkstes Conversion-Signal
152
152
  doorwayMaxFlaggedShare: 0.30
153
153
  duplicateMaxSimilarity: 0.70
154
154
  mode: warn
@@ -22,7 +22,7 @@ axes:
22
22
 
23
23
  levels:
24
24
  - depth: 0
25
- slug: { de: "leistungen/website-service", uk: "posluhy/sait-service" }
25
+ slug: { de: leistungen/website-service, uk: posluhy/sait-service }
26
26
  constellation: website-service-root
27
27
  geo: twin-only
28
28
  semanticType: collection
@@ -51,25 +51,25 @@ levels:
51
51
  minFaq: 5
52
52
  minPageStructure: 1
53
53
  claimRestrictions:
54
- - "mehr Anfragen"
55
- - "mehr Termin-Anfragen"
56
- - "mehr Buchungen"
57
- - "echte Aufträge"
58
- - "steigt die Wahrscheinlichkeit"
59
- - "weniger Streuverluste"
60
- - "höhere Conversion"
61
- - "besser gefunden"
62
- - "stärkstes Conversion-Signal"
63
- - "garantierte Ergebnisse"
64
- - "guaranteed results"
65
- - "no. 1"
66
- - "number one"
67
- - "leading provider"
68
- - "top rated"
69
- - "beste Preis"
70
- - "best price"
71
- - "cheapest"
72
- - "günstigste"
54
+ - mehr Anfragen
55
+ - mehr Termin-Anfragen
56
+ - mehr Buchungen
57
+ - echte Aufträge
58
+ - steigt die Wahrscheinlichkeit
59
+ - weniger Streuverluste
60
+ - höhere Conversion
61
+ - besser gefunden
62
+ - stärkstes Conversion-Signal
63
+ - garantierte Ergebnisse
64
+ - guaranteed results
65
+ - no. 1
66
+ - number one
67
+ - leading provider
68
+ - top rated
69
+ - beste Preis
70
+ - best price
71
+ - cheapest
72
+ - günstigste
73
73
  mode: warn
74
74
 
75
75
  policy:
@@ -2,8 +2,8 @@ id: lead.submit
2
2
  version: 1
3
3
  kind: action
4
4
  title:
5
- de: "Anfrage senden"
6
- en: "Submit a lead"
5
+ de: Anfrage senden
6
+ en: Submit a lead
7
7
  description:
8
8
  de: "Reicht eine Kontaktanfrage (Name, E-Mail, Nachricht) beim Gewerbe ein. Die Anfrage wird asynchron zugestellt — die Bestätigung signalisiert Annahme, nicht Zustellung."
9
9
  en: "Submit a contact request (name, email, message) to the business. The request is delivered asynchronously — the confirmation signals acceptance, not delivery."
@@ -87,7 +87,7 @@ export type SemanticRole = string;
87
87
  * section-cta | [RFC-0104] Single CTA / CTA group primitive
88
88
  * section-image | [RFC-0104] Authored image primitive with fade masks
89
89
  * site-background | [RFC-0105] Full-viewport background shell-layer component
90
- * currency-selector | [RFC-0743] Currency selector dropdown for multi-currency sites
90
+ * cycle-toggle | [RFC-0946] Generic cycle toggle button with localStorage persistence
91
91
  * price-display | [RFC-0743] Currency-aware price display with pre-rendered variants
92
92
  * scroll-to-top | [RFC-0768] Floating scroll-to-top button with Lenis smooth scrolling
93
93
  */
@@ -107,7 +107,7 @@ export const ComponentRoleValues = [
107
107
  "section-cta",
108
108
  "section-image",
109
109
  "site-background",
110
- "currency-selector",
110
+ "cycle-toggle",
111
111
  "price-display",
112
112
  "scroll-to-top",
113
113
  ] as const;
@@ -105,31 +105,31 @@ surfacePolicy:
105
105
  # Enforced by surface.media-leakage.validate and surface.contract.validate.
106
106
  mediaLeakagePolicy:
107
107
  prohibitedStrings:
108
- - pattern: "Gemini"
108
+ - pattern: Gemini
109
109
  matchingStrategy: context-aware
110
110
  contextSelector: "[data-credit-context], figcaption, details, dl"
111
111
  reason: "AI model name is internal metadata, not visitor-facing"
112
- - pattern: "AIPlatform"
112
+ - pattern: AIPlatform
113
113
  matchingStrategy: whole-word
114
- reason: "Internal enum value (CreditPartyKind)"
115
- - pattern: "Organization"
114
+ reason: Internal enum value (CreditPartyKind)
115
+ - pattern: Organization
116
116
  matchingStrategy: context-aware
117
117
  contextSelector: "[data-credit-context], figcaption, details, dl"
118
- reason: "Internal enum value (CreditPartyKind) — only as media author label"
119
- - pattern: "commissioned-warpgogol-material"
118
+ reason: Internal enum value (CreditPartyKind) — only as media author label
119
+ - pattern: commissioned-warpgogol-material
120
120
  matchingStrategy: whole-word
121
- reason: "Internal enum value (MaterialSourceType)"
122
- - pattern: "linked-public-source"
121
+ reason: Internal enum value (MaterialSourceType)
122
+ - pattern: linked-public-source
123
123
  matchingStrategy: whole-word
124
- reason: "Internal enum value"
125
- - pattern: "Copyright © 2026 Warpgogol"
124
+ reason: Internal enum value
125
+ - pattern: Copyright © 2026 Warpgogol
126
126
  matchingStrategy: exact
127
127
  contextSelector: ":not(footer):not([data-footer])"
128
- reason: "Repetitive copyright boilerplate — per-material usage status is on /bildnachweise/"
128
+ reason: Repetitive copyright boilerplate — per-material usage status is on /bildnachweise/
129
129
  requiredLabels:
130
- - label: "Konzeptillustration"
130
+ - label: Konzeptillustration
131
131
  lang: de
132
- - label: "Концептуальна ілюстрація"
132
+ - label: Концептуальна ілюстрація
133
133
  lang: uk
134
134
  requiredLinkPattern: "/bildnachweise/#"
135
- aiImageAttribute: "data-ai-generated"
135
+ aiImageAttribute: data-ai-generated
@@ -77,7 +77,7 @@ routePatterns:
77
77
  type: string
78
78
  generated: true
79
79
  # RFC-0512: Team JSON endpoints (machine-readable profiles)
80
- - pattern: "/team/profiles.json"
80
+ - pattern: /team/profiles.json
81
81
  params: {}
82
82
  generated: true
83
83
  - pattern: "/team/:slug/profile.json"
@@ -81,6 +81,7 @@ export {
81
81
  SiteFamilyContract,
82
82
  sectionArchetypeSchema,
83
83
  systemManifestSchema,
84
+ systemCollectionSchema,
84
85
  } from "./schemas/index.ts";
85
86
  export type {
86
87
  Constellation,
@@ -106,6 +107,7 @@ export type {
106
107
  SystemManifest,
107
108
  SystemPagePin,
108
109
  SystemPlanetPin,
110
+ SystemCollectionData,
109
111
  } from "./schemas/index.ts";
110
112
 
111
113
  // ---------------------------------------------------------------------------
@@ -305,6 +305,25 @@ export const componentManifestSchema = manifestBaseSchema.extend({
305
305
  * Do not use these five names for any other component manifest.
306
306
  */
307
307
  cosmicName: moonNameSchema,
308
+ /**
309
+ * RFC-1023: JSON Schema (draft-07 compatible) describing the shape of
310
+ * resolved content returned by getResolvedComponentContent. This is the
311
+ * flat, merged content (after pageOverride merge), NOT the prose .md
312
+ * frontmatter schema. Used by props.types.generate to emit
313
+ * XxxComponentContent interfaces. Required for content-driven components
314
+ * (those that call getResolvedComponentContent). Optional for structural
315
+ * components that consume no content.
316
+ */
317
+ contentSchema: z.record(z.string(), z.unknown()).optional(),
318
+ /**
319
+ * RFC-1023: JSON Schema (draft-07 compatible) describing the shape of
320
+ * Astro.props accepted by the component — caller-supplied props such as
321
+ * lang, defaultLang, pageOverride, etc. Used by props.types.generate to
322
+ * emit XxxComponentProps interfaces. Distinct from contentSchema: propsSchema
323
+ * describes what the caller passes, contentSchema describes what the
324
+ * component resolves from content files.
325
+ */
326
+ propsSchema: z.record(z.string(), z.unknown()).optional(),
308
327
  });
309
328
 
310
329
  /**
@@ -63,8 +63,13 @@ export type {
63
63
  SectionArchetypeLayoutHint,
64
64
  } from "./section-archetype.ts";
65
65
 
66
- export { systemManifestSchema } from "./system.ts";
67
- export type { SystemManifest, SystemPagePin, SystemPlanetPin } from "./system.ts";
66
+ export { systemManifestSchema, systemCollectionSchema } from "./system.ts";
67
+ export type {
68
+ SystemManifest,
69
+ SystemPagePin,
70
+ SystemPlanetPin,
71
+ SystemCollectionData,
72
+ } from "./system.ts";
68
73
 
69
74
  // Architecture review 2026-07-10: Re-export growthVendorSchema for @warpgogol/werkstatt-site/growth/config.ts.
70
75
  export { growthVendorSchema } from "./system.ts";
@@ -128,7 +128,7 @@ export const systemIntegrationsSchema = z.object({
128
128
  * (e.g. uchat, stripe, operator), and whether the pilot is `enabled`. UChat is the
129
129
  * conversation runtime — it renders the funnel and requests transitions; it never
130
130
  * owns the graph, pricing, or canonical state. Make.com is never a source. Validated
131
- * by `funnel.contract.validate` / `funnel.stage.validate` / `funnel.lagebild.validate`.
131
+ * by `funnel.contract.validate` / `funnel.stage.validate` / `funnel.crm.validate`.
132
132
  * Absent ⇒ the funnel validators are a no-op pass (funnel not yet enabled for the app).
133
133
  */
134
134
  funnel: z
@@ -8,6 +8,7 @@
8
8
  <CHANGE_SUMMARY>
9
9
  <item>RFC-0303 Phase 3: extracted from schemas/system.ts as part of the domain split.</item>
10
10
  <item>RFC-0377: added optional `audience` field to the per-page pin schema.</item>
11
+ <item>ADR-0062: added `ctaTarget` to identity, `sectionNav` + `ctaTarget` to page pins, created `systemCollectionSchema` for content collection typing.</item>
11
12
  </CHANGE_SUMMARY>
12
13
  */
13
14
 
@@ -79,6 +80,13 @@ export const systemManifestSchema = z.object({
79
80
  * brief.validate can cross-check the brief against the running app identity.
80
81
  */
81
82
  domain: z.string().min(1).optional(),
83
+
84
+ /**
85
+ * ADR-0062: App-level default CTA target — the pageId of the contact/CTA page.
86
+ * Per-page `ctaTarget` in pages[] overrides this app-level default.
87
+ * Used by resolve-route.ts to populate `ctaTarget` in PageRouteData.
88
+ */
89
+ ctaTarget: z.string().min(1).optional(),
82
90
  }),
83
91
 
84
92
  /**
@@ -222,7 +230,7 @@ export const systemManifestSchema = z.object({
222
230
  enabled: z.boolean(),
223
231
  cosmicMoon: z.string().min(1),
224
232
  pin: z.string().min(1),
225
- props: z.record(z.string(), z.any()).optional(),
233
+ props: z.record(z.string(), z.unknown()).optional(),
226
234
  })
227
235
  .optional(),
228
236
  })
@@ -247,6 +255,19 @@ export const systemManifestSchema = z.object({
247
255
  )
248
256
  .optional()
249
257
  .default([]),
258
+
259
+ /**
260
+ * RFC-0922 / ADR-0062: per-page section nav toggle. When false, the
261
+ * page template skips extractSectionNavItems and the Header secondary
262
+ * nav strip is omitted. Defaults to true when absent.
263
+ */
264
+ sectionNav: z.boolean().optional(),
265
+
266
+ /**
267
+ * ADR-0062: per-page CTA target override. When present, overrides the
268
+ * app-level `identity.ctaTarget` for this page.
269
+ */
270
+ ctaTarget: z.string().min(1).optional(),
250
271
  }),
251
272
  )
252
273
  .optional()
@@ -498,3 +519,19 @@ export type SystemManifest = z.infer<typeof systemManifestSchema>;
498
519
  export type SystemPagePin = NonNullable<SystemManifest["pages"]>[number];
499
520
  /** A single planet pin within a page-route entry. */
500
521
  export type SystemPlanetPin = NonNullable<SystemManifest["pages"][number]["planets"]>[number];
522
+
523
+ /**
524
+ * ADR-0062: Lenient schema for the `system` Astro content collection.
525
+ *
526
+ * `systemManifestSchema` is the strict validation schema used by `system.manifest.validate`.
527
+ * The content collection schema needs to be more lenient:
528
+ * - `.partial()` — all top-level fields are optional (system.md content varies per site)
529
+ * - `.loose()` — unknown top-level keys pass through (e.g. `policy`, `seo`)
530
+ *
531
+ * This schema is used in `src/content/config.ts` via `defineCollection({ schema: systemCollectionSchema })`.
532
+ * It provides compile-time typing for `getCollection("system")` consumers, eliminating `as any` casts.
533
+ */
534
+ export const systemCollectionSchema = systemManifestSchema.partial().loose();
535
+
536
+ /** Inferred type for the `system` content collection data. */
537
+ export type SystemCollectionData = z.infer<typeof systemCollectionSchema>;
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { systemGrowthSchema, growthVendorSchema } from "../growth.ts";
3
+
4
+ describe("growthVendorSchema", () => {
5
+ it("is defined", () => {
6
+ expect(growthVendorSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => growthVendorSchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("systemGrowthSchema", () => {
15
+ it("is defined", () => {
16
+ expect(systemGrowthSchema).toBeDefined();
17
+ });
18
+
19
+ it("rejects null", () => {
20
+ expect(() => systemGrowthSchema.parse(null)).toThrow();
21
+ });
22
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { systemIntegrationsSchema } from "../integrations.ts";
3
+
4
+ describe("systemIntegrationsSchema", () => {
5
+ it("is defined", () => {
6
+ expect(systemIntegrationsSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => systemIntegrationsSchema.parse(null)).toThrow();
11
+ });
12
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { systemManifestSchema, systemCollectionSchema } from "../manifest.ts";
3
+
4
+ describe("systemManifestSchema", () => {
5
+ it("is defined", () => {
6
+ expect(systemManifestSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => systemManifestSchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("systemCollectionSchema", () => {
15
+ it("is defined", () => {
16
+ expect(systemCollectionSchema).toBeDefined();
17
+ });
18
+
19
+ it("rejects null", () => {
20
+ expect(() => systemCollectionSchema.parse(null)).toThrow();
21
+ });
22
+ });
@@ -0,0 +1,13 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { semanticPageTypeSchema } from "../page-output.ts";
3
+
4
+ describe("semanticPageTypeSchema", () => {
5
+ it("accepts valid page types", () => {
6
+ const result = semanticPageTypeSchema.safeParse("home");
7
+ expect(result.success).toBe(true);
8
+ });
9
+
10
+ it("rejects invalid page type", () => {
11
+ expect(() => semanticPageTypeSchema.parse("invalid-type")).toThrow();
12
+ });
13
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { systemPassportSchema, systemReleaseSchema } from "../release.ts";
3
+
4
+ describe("systemPassportSchema", () => {
5
+ it("is defined", () => {
6
+ expect(systemPassportSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => systemPassportSchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("systemReleaseSchema", () => {
15
+ it("is defined", () => {
16
+ expect(systemReleaseSchema).toBeDefined();
17
+ });
18
+
19
+ it("rejects null", () => {
20
+ expect(() => systemReleaseSchema.parse(null)).toThrow();
21
+ });
22
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { systemTextSchema, systemTextNormalizeSchema } from "../text.ts";
3
+
4
+ describe("systemTextSchema", () => {
5
+ it("is defined", () => {
6
+ expect(systemTextSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => systemTextSchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("systemTextNormalizeSchema", () => {
15
+ it("is defined", () => {
16
+ expect(systemTextNormalizeSchema).toBeDefined();
17
+ });
18
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { systemVerificationSchema } from "../verification.ts";
3
+
4
+ describe("systemVerificationSchema", () => {
5
+ it("is defined", () => {
6
+ expect(systemVerificationSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => systemVerificationSchema.parse(null)).toThrow();
11
+ });
12
+ });
@@ -49,5 +49,10 @@ export {
49
49
  pageOutputSchema,
50
50
  } from "./system/page-output.ts";
51
51
 
52
- export { systemManifestSchema } from "./system/manifest.ts";
53
- export type { SystemManifest, SystemPagePin, SystemPlanetPin } from "./system/manifest.ts";
52
+ export { systemManifestSchema, systemCollectionSchema } from "./system/manifest.ts";
53
+ export type {
54
+ SystemManifest,
55
+ SystemPagePin,
56
+ SystemPlanetPin,
57
+ SystemCollectionData,
58
+ } from "./system/manifest.ts";
@@ -0,0 +1,19 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { biomeProvenanceSchema, biomeAxesSchema } from "../biome.ts";
3
+
4
+ describe("biomeProvenanceSchema", () => {
5
+ it("is defined", () => {
6
+ expect(biomeProvenanceSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => biomeProvenanceSchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("biomeAxesSchema", () => {
15
+ it("is defined and parseable", () => {
16
+ expect(biomeAxesSchema).toBeDefined();
17
+ expect(() => biomeAxesSchema.parse(null)).toThrow();
18
+ });
19
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { capabilityInputOutputSchema, capabilityRecordSchema } from "../capability.ts";
3
+
4
+ describe("capabilityInputOutputSchema", () => {
5
+ it("is defined", () => {
6
+ expect(capabilityInputOutputSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => capabilityInputOutputSchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("capabilityRecordSchema", () => {
15
+ it("is defined", () => {
16
+ expect(capabilityRecordSchema).toBeDefined();
17
+ });
18
+
19
+ it("rejects null", () => {
20
+ expect(() => capabilityRecordSchema.parse(null)).toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { constellationSlotSchema, constellationSchema } from "../constellation.ts";
3
+
4
+ describe("constellationSlotSchema", () => {
5
+ it("is defined", () => {
6
+ expect(constellationSlotSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => constellationSlotSchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("constellationSchema", () => {
15
+ it("is defined", () => {
16
+ expect(constellationSchema).toBeDefined();
17
+ });
18
+
19
+ it("rejects null", () => {
20
+ expect(() => constellationSchema.parse(null)).toThrow();
21
+ });
22
+ });
@@ -0,0 +1,13 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { dnsRecordTypeSchema } from "../dns-records.ts";
3
+
4
+ describe("dnsRecordTypeSchema", () => {
5
+ it("accepts valid DNS record types", () => {
6
+ const result = dnsRecordTypeSchema.safeParse("A");
7
+ expect(result.success).toBe(true);
8
+ });
9
+
10
+ it("rejects invalid type", () => {
11
+ expect(() => dnsRecordTypeSchema.parse("INVALID")).toThrow();
12
+ });
13
+ });
@@ -0,0 +1,13 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { getSectionPropsSchema } from "../manifest-resolver.ts";
3
+
4
+ describe("getSectionPropsSchema", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof getSectionPropsSchema).toBe("function");
7
+ });
8
+
9
+ it("returns null for unknown archetype", async () => {
10
+ const result = await getSectionPropsSchema("nonexistent-archetype", "/nonexistent");
11
+ expect(result).toBeNull();
12
+ });
13
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { BlockEntrySchema, PageEntrySchema } from "../page-entry.ts";
3
+
4
+ describe("BlockEntrySchema", () => {
5
+ it("is defined", () => {
6
+ expect(BlockEntrySchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => BlockEntrySchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("PageEntrySchema", () => {
15
+ it("is defined", () => {
16
+ expect(PageEntrySchema).toBeDefined();
17
+ });
18
+
19
+ it("rejects null", () => {
20
+ expect(() => PageEntrySchema.parse(null)).toThrow();
21
+ });
22
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { sectionArchetypeSchema } from "../section-archetype.ts";
3
+
4
+ describe("sectionArchetypeSchema", () => {
5
+ it("is defined", () => {
6
+ expect(sectionArchetypeSchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => sectionArchetypeSchema.parse(null)).toThrow();
11
+ });
12
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { siteFamilySchema, SiteFamilyContract } from "../site-family.ts";
3
+
4
+ describe("siteFamilySchema", () => {
5
+ it("is defined", () => {
6
+ expect(siteFamilySchema).toBeDefined();
7
+ });
8
+
9
+ it("rejects null", () => {
10
+ expect(() => siteFamilySchema.parse(null)).toThrow();
11
+ });
12
+ });
13
+
14
+ describe("SiteFamilyContract", () => {
15
+ it("is the same schema as siteFamilySchema", () => {
16
+ expect(SiteFamilyContract).toBe(siteFamilySchema);
17
+ });
18
+ });
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as systemModule from "../system.ts";
3
+
4
+ describe("system.ts barrel re-exports", () => {
5
+ it("re-exports systemManifestSchema", () => {
6
+ expect(systemModule.systemManifestSchema).toBeDefined();
7
+ });
8
+
9
+ it("re-exports systemIntegrationsSchema", () => {
10
+ expect(systemModule.systemIntegrationsSchema).toBeDefined();
11
+ });
12
+
13
+ it("re-exports systemPassportSchema", () => {
14
+ expect(systemModule.systemPassportSchema).toBeDefined();
15
+ });
16
+
17
+ it("re-exports systemReleaseSchema", () => {
18
+ expect(systemModule.systemReleaseSchema).toBeDefined();
19
+ });
20
+ });