@warpgogol/werkstatt-shared 0.14.1 → 0.16.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.
Files changed (451) hide show
  1. package/README.md +23 -15
  2. package/README.uk.md +1 -1
  3. package/package.json +534 -766
  4. package/src/{share/agent → agent}/api-catalog.ts +14 -1
  5. package/src/{share/agent → agent}/ard-catalog.ts +6 -1
  6. package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
  7. package/src/agent/guidance.ts +52 -0
  8. package/src/agent/health.ts +68 -0
  9. package/src/{share/agent → agent}/index.ts +5 -1
  10. package/src/{share/agent → agent}/manifest.ts +83 -6
  11. package/src/{share/agent → agent}/openapi.ts +97 -4
  12. package/src/agent/receipt.ts +123 -0
  13. package/src/{share/agent → agent}/search.test.ts +3 -0
  14. package/src/{share/agent → agent}/search.ts +31 -5
  15. package/src/{share → agent}/tests/api-catalog.test.ts +8 -2
  16. package/src/{share → agent}/tests/ard-catalog.test.ts +11 -2
  17. package/src/{share → agent}/tests/capability.test.ts +1 -1
  18. package/src/{share → agent}/tests/fleet-catalog.test.ts +1 -1
  19. package/src/{share → agent}/tests/knowledge.test.ts +2 -2
  20. package/src/{share → agent}/tests/manifest.test.ts +72 -1
  21. package/src/{share → agent}/tests/mcp-card.test.ts +6 -2
  22. package/src/{share → agent}/tests/openapi.test.ts +119 -2
  23. package/src/{share → agent}/tests/proof.test.ts +1 -1
  24. package/src/{share → attribution}/material-credits.ts +4 -4
  25. package/src/{share → checks}/check-hints.ts +6 -1
  26. package/src/checks/index.ts +4 -1
  27. package/src/checks/lib/astro-site-url.ts +6 -1
  28. package/src/checks/lib/i18n.ts +14 -0
  29. package/src/checks/result-helpers.ts +2 -2
  30. package/src/checks/tests/remediation-auto-populate.test.ts +1 -1
  31. package/src/{share → client-scripts}/counter-utils.ts +6 -2
  32. package/src/{share/scripts → client-scripts}/external-link-qr.ts +11 -16
  33. package/src/{share/scripts → client-scripts}/external-links.ts +12 -1
  34. package/src/{share/scripts → client-scripts}/gsap-counter.ts +8 -3
  35. package/src/client-scripts/index.ts +35 -0
  36. package/src/{share/scripts → client-scripts}/inline-number-animation.ts +1 -1
  37. package/src/{share/scripts → client-scripts}/lenis.ts +6 -1
  38. package/src/{share/scripts → client-scripts}/lordicon.ts +6 -1
  39. package/src/{share/scripts → client-scripts}/orchestrator.ts +10 -1
  40. package/src/{share/scripts → client-scripts}/scheduler.ts +6 -1
  41. package/src/client-scripts/scroll-spy.ts +106 -0
  42. package/src/client-scripts/tests/orchestrator.test.ts +47 -0
  43. package/src/client-scripts/tests/scroll-spy.test.ts +204 -0
  44. package/src/{share/scripts → client-scripts/tests}/video-player.test.ts +51 -31
  45. package/src/component/contracts.ts +206 -0
  46. package/src/component/index.ts +13 -0
  47. package/src/{share → content}/content-reference.ts +3 -3
  48. package/src/{share/content → content}/entity-id.ts +6 -3
  49. package/src/content/index.ts +3 -0
  50. package/src/{share/content → content}/merge.ts +6 -1
  51. package/src/content/system-manifest.test.ts +107 -0
  52. package/src/content/system-manifest.ts +12 -94
  53. package/src/{share → content}/tests/resolve-field-path.test.ts +1 -1
  54. package/src/{share → content}/tests/substitute-deep.test.ts +1 -1
  55. package/src/content-discipline/index.ts +19 -0
  56. package/src/{share/content-discipline → content-discipline}/parsers.ts +2 -2
  57. package/src/{share/env.d.ts → env.d.ts} +6 -3
  58. package/src/fingerprint/canonical-json.ts +731 -0
  59. package/src/fingerprint/index.ts +14 -0
  60. package/src/fingerprint/primitives.ts +72 -0
  61. package/src/{share/i18n → i18n}/localization.ts +6 -2
  62. package/src/integration/adapters.ts +10 -71
  63. package/src/integration/dispatch.ts +1 -1
  64. package/src/integration/funnel.ts +6 -1
  65. package/src/integration/idempotency.ts +88 -0
  66. package/src/integration/index.ts +105 -4
  67. package/src/integration/lagebild-destination-adapter.ts +108 -0
  68. package/src/integration/lagebild-ingress.ts +7 -12
  69. package/src/integration/orchestration.ts +11 -10
  70. package/src/integration/tests/deliver-event.test.ts +1 -1
  71. package/src/integration/tests/dispatch.test.ts +4 -4
  72. package/src/integration/tests/hub-runtime.test.ts +22 -14
  73. package/src/integration/tests/idempotency.test.ts +81 -0
  74. package/src/integration/vitest.config.ts +1 -0
  75. package/src/kernel/desired-state.ts +227 -0
  76. package/src/kernel/diagnostic.ts +289 -0
  77. package/src/kernel/drift-guard.ts +166 -0
  78. package/src/kernel/fs-atomic.ts +131 -0
  79. package/src/kernel/index.ts +22 -0
  80. package/src/kernel/tests/drift-guard.test.ts +112 -0
  81. package/src/kernel/tests/fs-atomic.test.ts +150 -0
  82. package/src/kernel/tests/workpiece-env.test.ts +44 -0
  83. package/src/kernel/tests/workspace-io.test.ts +187 -0
  84. package/src/kernel/types.ts +518 -0
  85. package/src/kernel/workpiece-env.ts +48 -0
  86. package/src/kernel/workspace-io.ts +248 -0
  87. package/src/{share → knowledge}/tests/claim.test.ts +1 -1
  88. package/src/{share → knowledge}/tests/derivation.test.ts +1 -1
  89. package/src/{share → knowledge}/tests/freshness.test.ts +1 -1
  90. package/src/{share → knowledge}/tests/ledger.test.ts +1 -1
  91. package/src/{share → knowledge}/tests/plan.test.ts +1 -1
  92. package/src/{share → knowledge}/tests/source.test.ts +1 -1
  93. package/src/legal/index.ts +15 -0
  94. package/src/{share/middleware → middleware}/access-protection.ts +11 -8
  95. package/src/{share/middleware → middleware}/language-redirect.ts +3 -0
  96. package/src/{share → node}/dev-props-validator.ts +6 -1
  97. package/src/{share → node}/import-scan.ts +23 -3
  98. package/src/{share → node}/semantic/derived-prices-loader.ts +4 -2
  99. package/src/{share → node}/semantic/markdown-twin-provenance.ts +1 -1
  100. package/src/{share/semantic → node}/tests/derived-prices-loader.test.ts +14 -1
  101. package/src/{share/semantic → node}/tests/markdown-twin-provenance.test.ts +1 -1
  102. package/src/observability/env-resolver.ts +6 -1
  103. package/src/observability/index.ts +14 -1
  104. package/src/observability/otlp-converter.ts +6 -1
  105. package/src/observability/otlp-transport.ts +6 -1
  106. package/src/observability/pipeline-log.ts +81 -0
  107. package/src/observability/pusher.ts +6 -1
  108. package/src/observability/redact.ts +6 -1
  109. package/src/observability/typed-refs.ts +6 -2
  110. package/src/observability/vitest.config.ts +1 -0
  111. package/src/{share → offers}/formula-eval.ts +3 -3
  112. package/src/{share → offers}/tests/formula-eval.test.ts +2 -2
  113. package/src/onboarding/index.ts +1 -0
  114. package/src/{share → onboarding}/tests/onboarding-yaml.test.ts +1 -1
  115. package/src/ontology/archetype-registry.ts +6 -1
  116. package/src/ontology/archetypes/index.json +33 -1
  117. package/src/ontology/archetypes/index.yaml +27 -1
  118. package/src/ontology/archetypes/sections/entity-grid.yaml +46 -0
  119. package/src/ontology/archetypes/sections/exit-diagram.yaml +43 -0
  120. package/src/ontology/biome-token-projection.ts +6 -1
  121. package/src/ontology/capabilities/lead.prepare.yaml +44 -0
  122. package/src/ontology/capabilities/lead.submit.yaml +20 -3
  123. package/src/ontology/cosmic/index.ts +6 -1
  124. package/src/ontology/cosmic/moon-catalog.ts +6 -1
  125. package/src/ontology/cosmic/planet-catalog.ts +6 -1
  126. package/src/ontology/cosmic/star-catalog.ts +6 -1
  127. package/src/ontology/enums.ts +6 -1
  128. package/src/ontology/external-surfaces/index.ts +1 -1
  129. package/src/ontology/index.ts +6 -2
  130. package/src/ontology/manifest.ts +7 -4
  131. package/src/ontology/operations/artifact-store.ts +45 -0
  132. package/src/ontology/operations/dht.ts +98 -0
  133. package/src/ontology/operations/handoff.ts +92 -0
  134. package/src/ontology/operations/index.ts +193 -1
  135. package/src/ontology/operations/leitstand.ts +99 -0
  136. package/src/ontology/operations/materialization.ts +90 -0
  137. package/src/ontology/operations/mission.ts +111 -0
  138. package/src/ontology/operations/naming-policy.ts +132 -0
  139. package/src/ontology/operations/notausgang.ts +108 -0
  140. package/src/ontology/operations/release.ts +121 -0
  141. package/src/ontology/operations/sternsystem.ts +192 -0
  142. package/src/ontology/operations/werkstatt.ts +48 -0
  143. package/src/ontology/schemas/biome.ts +7 -2
  144. package/src/ontology/schemas/capability.ts +56 -4
  145. package/src/ontology/schemas/constellation.ts +6 -1
  146. package/src/ontology/schemas/index.ts +6 -2
  147. package/src/ontology/schemas/manifest-resolver.ts +6 -1
  148. package/src/ontology/schemas/page-entry.ts +9 -5
  149. package/src/ontology/schemas/system/growth.ts +6 -1
  150. package/src/ontology/schemas/system/manifest.ts +40 -1
  151. package/src/ontology/schemas/system/page-output.ts +7 -6
  152. package/src/ontology/schemas/system/text.ts +15 -0
  153. package/src/ontology/schemas/system.ts +6 -2
  154. package/src/ontology/shared-section-props/body-fragments.ts +2 -0
  155. package/src/ontology/tests/sternsystem-owner.test.ts +1 -1
  156. package/src/ontology/vitest.config.ts +1 -0
  157. package/src/{share → page}/page.ts +13 -9
  158. package/src/{share → page}/tests/dev-props-validator.test.ts +1 -1
  159. package/src/{share → page}/tests/hero-section-numbering-parity.test.ts +1 -1
  160. package/src/passport/claim-sign.ts +120 -0
  161. package/src/passport/index.ts +9 -1
  162. package/src/passport/key-rotate.ts +10 -2
  163. package/src/passport/schema.ts +6 -1
  164. package/src/passport/sign.ts +8 -3
  165. package/src/passport/vitest.config.ts +1 -0
  166. package/src/pbt/index.test.ts +95 -0
  167. package/src/pbt/index.ts +251 -0
  168. package/src/plugin/plugin-contract.ts +2 -2
  169. package/src/{share → policy}/entitlement.ts +24 -4
  170. package/src/{share → policy}/feature-policy.ts +8 -3
  171. package/src/{share → policy}/visibility.ts +7 -2
  172. package/src/redirects/redirects.ts +117 -0
  173. package/src/{share → redirects/tests}/redirects.test.ts +1 -1
  174. package/src/remediation/remediation-catalog.ts +316 -0
  175. package/src/{share/routes → routes}/template-filter.ts +1 -1
  176. package/src/{share → runtime}/runtime-context.ts +7 -2
  177. package/src/{share → runtime}/shared-context.ts +1 -1
  178. package/src/{share/schemas → schemas}/claims.ts +2 -2
  179. package/src/{share/schemas → schemas}/effects.ts +6 -1
  180. package/src/{share/schemas → schemas}/features.ts +1 -1
  181. package/src/{share/schemas → schemas}/index.ts +3 -3
  182. package/src/{share/schemas → schemas}/navigation.ts +6 -1
  183. package/src/{share/schemas → schemas}/page-base.ts +6 -2
  184. package/src/{share/schemas → schemas}/person.ts +1 -1
  185. package/src/{share/schemas → schemas}/standard-list-item.ts +1 -1
  186. package/src/{share → sections}/rfc0042-utils.ts +1 -1
  187. package/src/{share/semantic → semantic}/block-extractors/index.ts +20 -1
  188. package/src/{share/semantic → semantic}/business-projection.ts +95 -1
  189. package/src/semantic/canonical-uri.ts +74 -0
  190. package/src/{share/semantic → semantic}/extract.ts +100 -1
  191. package/src/semantic/fact-extraction.test.ts +47 -0
  192. package/src/semantic/fact-extraction.ts +46 -0
  193. package/src/semantic/freshness.ts +67 -0
  194. package/src/{share/semantic → semantic}/ids.ts +6 -1
  195. package/src/{share/semantic → semantic}/index.ts +9 -2
  196. package/src/{share/semantic → semantic}/jsonld/breadcrumb.ts +6 -2
  197. package/src/{share/semantic → semantic}/jsonld/context.ts +6 -2
  198. package/src/{share/semantic → semantic}/jsonld/faq.ts +6 -2
  199. package/src/{share/semantic → semantic}/jsonld/initiative.ts +6 -1
  200. package/src/{share/semantic → semantic}/jsonld/organization.ts +6 -1
  201. package/src/{share/semantic → semantic}/jsonld/person.ts +6 -2
  202. package/src/{share/semantic → semantic}/jsonld/service.ts +6 -2
  203. package/src/semantic/jsonld/shared.ts +36 -0
  204. package/src/semantic/jsonld/types.ts +24 -0
  205. package/src/{share/semantic → semantic}/jsonld/webpage.ts +6 -1
  206. package/src/semantic/jsonld/website.ts +32 -0
  207. package/src/{share/semantic → semantic}/jsonld.ts +6 -1
  208. package/src/{share/semantic → semantic}/llms.ts +51 -5
  209. package/src/{share/semantic → semantic}/models.ts +26 -38
  210. package/src/{share/semantic → semantic}/organization-profile.ts +3 -0
  211. package/src/{share/semantic → semantic}/page-builders/markdown-page.ts +6 -3
  212. package/src/{share/semantic → semantic}/page-utils.ts +6 -3
  213. package/src/{share/tests/breadcrumbs.test.ts → semantic/tests/breadcrumbs-0229.test.ts} +1 -1
  214. package/src/{share → semantic}/tests/build-page-price-markers.test.ts +2 -2
  215. package/src/{share/semantic → semantic}/tests/business-projection.test.ts +179 -0
  216. package/src/semantic/tests/canonical-uri.test.ts +68 -0
  217. package/src/{share/semantic → semantic}/tests/extract.test.ts +83 -0
  218. package/src/semantic/tests/freshness.pbt.test.ts +108 -0
  219. package/src/{share/tests/image-sitemap.test.ts → semantic/tests/image-sitemap-0150.test.ts} +1 -1
  220. package/src/{share → semantic}/tests/jsonld-service.test.ts +2 -2
  221. package/src/{share → semantic}/tests/jsonld-video.test.ts +3 -3
  222. package/src/{share → semantic}/tests/jsonld-webpage.test.ts +3 -3
  223. package/src/{share → semantic}/tests/llms-0789.test.ts +2 -2
  224. package/src/{share/semantic → semantic}/tests/llms.test.ts +46 -0
  225. package/src/{share → semantic}/tests/organization-jsonld.test.ts +3 -3
  226. package/src/{share/tests/output-projection.test.ts → semantic/tests/output-projection-0143.test.ts} +1 -1
  227. package/src/{share/tests/price-marker-resolver.test.ts → semantic/tests/price-marker-resolver-0767.test.ts} +2 -2
  228. package/src/{share/semantic → semantic}/tests/price-marker-resolver.test.ts +4 -2
  229. package/src/semantic/tests/project-claims.pbt.test.ts +60 -0
  230. package/src/{share/semantic → semantic}/tests/split-sentences.test.ts +1 -1
  231. package/src/{share → semantic}/tests/twin-path.test.ts +1 -1
  232. package/src/signing/index.ts +15 -0
  233. package/src/signing/key.ts +139 -0
  234. package/src/signing/sign.ts +66 -0
  235. package/src/signing/types.ts +29 -0
  236. package/src/{share/slug → slug}/slug-url.ts +4 -1
  237. package/src/stack/run-tool.ts +106 -0
  238. package/src/stack/stack-checks.ts +124 -0
  239. package/src/stack/tests/run-tool.test.ts +138 -0
  240. package/src/stack/tests/stack-checks.test.ts +141 -0
  241. package/src/stack/tests/walk-files.test.ts +131 -0
  242. package/src/stack/walk-files.ts +114 -0
  243. package/src/surface/blueprint.ts +342 -66
  244. package/src/surface/decision-composer.ts +6 -1
  245. package/src/surface/geo.ts +6 -1
  246. package/src/surface/index.ts +11 -7
  247. package/src/surface/types.ts +2 -2
  248. package/src/surface/vitest.config.ts +1 -0
  249. package/src/tests/signing-core.test.ts +129 -0
  250. package/src/{share → text}/css-value-normalize.ts +6 -1
  251. package/src/text/string-utils.ts +24 -0
  252. package/src/{share → text}/tests/text-normalize.test.ts +5 -0
  253. package/src/{share → text}/text-normalize.ts +4 -3
  254. package/src/{share → text}/wrap-inline-numbers.ts +1 -1
  255. package/src/types/axiom-study.d.ts +36 -0
  256. package/src/types/hls-js-light.d.ts +21 -0
  257. package/src/types/qrcode-browser.d.ts +23 -0
  258. package/src/typography/fix.pbt.test.ts +208 -0
  259. package/src/typography/fix.ts +148 -0
  260. package/src/typography/index.ts +54 -0
  261. package/src/typography/locale-defaults.ts +97 -0
  262. package/src/typography/rules-tier1.test.ts +459 -0
  263. package/src/typography/rules-tier1.ts +925 -0
  264. package/src/typography/rules-tier2-locale.pbt.test.ts +190 -0
  265. package/src/typography/rules-tier2-locale.test.ts +286 -0
  266. package/src/typography/rules-tier2-locale.ts +399 -0
  267. package/src/typography/rules-tier2-structure.test.ts +330 -0
  268. package/src/typography/rules-tier2-structure.ts +378 -0
  269. package/src/typography/rules-tier3-advisory.test.ts +290 -0
  270. package/src/typography/rules-tier3-advisory.ts +239 -0
  271. package/src/typography/text-surface.test.ts +244 -0
  272. package/src/typography/text-surface.ts +448 -0
  273. package/src/index.ts +0 -14
  274. package/src/integration/port-barrel.ts +0 -105
  275. package/src/share/agent/tests/api-catalog.test.ts +0 -8
  276. package/src/share/agent/tests/ard-catalog.test.ts +0 -8
  277. package/src/share/agent/tests/capability.test.ts +0 -14
  278. package/src/share/agent/tests/fleet-catalog.test.ts +0 -15
  279. package/src/share/agent/tests/knowledge.test.ts +0 -20
  280. package/src/share/agent/tests/manifest.test.ts +0 -9
  281. package/src/share/agent/tests/mcp-card.test.ts +0 -8
  282. package/src/share/agent/tests/openapi.test.ts +0 -8
  283. package/src/share/agent/tests/proof.test.ts +0 -15
  284. package/src/share/content/index.ts +0 -16
  285. package/src/share/content-discipline/index.ts +0 -14
  286. package/src/share/index.ts +0 -86
  287. package/src/share/knowledge/tests/claim.test.ts +0 -11
  288. package/src/share/knowledge/tests/derivation.test.ts +0 -30
  289. package/src/share/knowledge/tests/freshness.test.ts +0 -18
  290. package/src/share/knowledge/tests/ledger.test.ts +0 -35
  291. package/src/share/knowledge/tests/plan.test.ts +0 -19
  292. package/src/share/knowledge/tests/source.test.ts +0 -19
  293. package/src/share/legal/index.ts +0 -1
  294. package/src/share/redirects.ts +0 -54
  295. package/src/share/remediation/remediation-catalog.ts +0 -196
  296. package/src/share/scripts/index.ts +0 -26
  297. package/src/share/scripts/tests/orchestrator.test.ts +0 -8
  298. package/src/share/semantic/jsonld/shared.ts +0 -31
  299. package/src/share/semantic/jsonld/types.ts +0 -19
  300. package/src/share/semantic/jsonld/website.ts +0 -27
  301. package/src/share/string-utils.ts +0 -19
  302. package/src/share/types/axiom-study.d.ts +0 -22
  303. package/src/share/types/hls-js-light.d.ts +0 -16
  304. package/src/share/types/qrcode-browser.d.ts +0 -18
  305. package/src/share/utility-registry.yaml +0 -50
  306. package/src/share/vitest.config.ts +0 -8
  307. package/src/surface/blueprint-schema.ts +0 -314
  308. package/src/surface/blueprint-types.ts +0 -256
  309. /package/src/{share/agent → agent}/capability.ts +0 -0
  310. /package/src/{share/agent → agent}/dns-aid.ts +0 -0
  311. /package/src/{share/agent → agent}/fleet-catalog.ts +0 -0
  312. /package/src/{share/agent → agent}/knowledge.ts +0 -0
  313. /package/src/{share/agent → agent}/mcp-card.ts +0 -0
  314. /package/src/{share/agent → agent}/omit-empty.ts +0 -0
  315. /package/src/{share/agent → agent}/proof.ts +0 -0
  316. /package/src/{share/agent → agent}/tests/omit-empty.test.ts +0 -0
  317. /package/src/{share → attribution}/attribution-display.ts +0 -0
  318. /package/src/{share/scripts → client-scripts}/gsap-mountain-journey.ts +0 -0
  319. /package/src/{share/scripts → client-scripts}/gsap-parallax.ts +0 -0
  320. /package/src/{share/scripts → client-scripts}/gsap-reveal.ts +0 -0
  321. /package/src/{share/scripts → client-scripts}/gsap-stagger.ts +0 -0
  322. /package/src/{share/scripts → client-scripts}/live-photos.ts +0 -0
  323. /package/src/{share/scripts → client-scripts}/tests/external-link-qr.test.ts +0 -0
  324. /package/src/{share/scripts → client-scripts}/tests/external-links.test.ts +0 -0
  325. /package/src/{share/scripts → client-scripts}/tests/gsap-counter.test.ts +0 -0
  326. /package/src/{share/scripts → client-scripts}/tests/gsap-mountain-journey.test.ts +0 -0
  327. /package/src/{share/scripts → client-scripts}/tests/gsap-parallax.test.ts +0 -0
  328. /package/src/{share/scripts → client-scripts}/tests/gsap-reveal.test.ts +0 -0
  329. /package/src/{share/scripts → client-scripts}/tests/gsap-stagger.test.ts +0 -0
  330. /package/src/{share/scripts → client-scripts}/tests/inline-number-animation.test.ts +0 -0
  331. /package/src/{share/scripts → client-scripts}/tests/lenis.test.ts +0 -0
  332. /package/src/{share/scripts → client-scripts}/tests/live-photos.test.ts +0 -0
  333. /package/src/{share/scripts → client-scripts}/tests/lordicon.test.ts +0 -0
  334. /package/src/{share/scripts → client-scripts}/tests/scheduler.test.ts +0 -0
  335. /package/src/{share/scripts → client-scripts}/video-player.ts +0 -0
  336. /package/src/{share/content → content}/resolve-field-path.ts +0 -0
  337. /package/src/{share/content → content}/substitute-deep.ts +0 -0
  338. /package/src/{share → content}/tests/content-ref-index.test.ts +0 -0
  339. /package/src/{share/content-discipline → content-discipline}/types.ts +0 -0
  340. /package/src/{share → image-provider}/image-provider.ts +0 -0
  341. /package/src/{share/knowledge → knowledge}/claim.ts +0 -0
  342. /package/src/{share/knowledge → knowledge}/derivation.ts +0 -0
  343. /package/src/{share/knowledge → knowledge}/freshness.ts +0 -0
  344. /package/src/{share/knowledge → knowledge}/index.ts +0 -0
  345. /package/src/{share/knowledge → knowledge}/ledger.ts +0 -0
  346. /package/src/{share/knowledge → knowledge}/plan.ts +0 -0
  347. /package/src/{share/knowledge → knowledge}/source.ts +0 -0
  348. /package/src/{share/legal → legal}/translation-policy.ts +0 -0
  349. /package/src/{share/middleware → middleware}/tests/access-protection.test.ts +0 -0
  350. /package/src/{share → node}/fs/index.ts +0 -0
  351. /package/src/{share/tests/markdown-twin-provenance.test.ts → node/tests/markdown-twin-provenance-0613.test.ts} +0 -0
  352. /package/src/{share → offers}/offer-capacity.ts +0 -0
  353. /package/src/{share/onboarding-yaml/index.ts → onboarding/yaml.ts} +0 -0
  354. /package/src/{share → ontology}/tests/ontology-role-by-cosmic-name.test.ts +0 -0
  355. /package/src/{share/routes → routes}/tests/template-filter.test.ts +0 -0
  356. /package/src/{share/schemas → schemas}/claim-records.ts +0 -0
  357. /package/src/{share/schemas → schemas}/horizontal-align.ts +0 -0
  358. /package/src/{share/schemas → schemas}/icon-color.ts +0 -0
  359. /package/src/{share/schemas → schemas}/live-photo.ts +0 -0
  360. /package/src/{share/schemas → schemas}/material-credit.ts +0 -0
  361. /package/src/{share/schemas → schemas}/media.ts +0 -0
  362. /package/src/{share/schemas → schemas}/participant.ts +0 -0
  363. /package/src/{share/schemas → schemas}/print.ts +0 -0
  364. /package/src/{share/schemas → schemas}/section-background.ts +0 -0
  365. /package/src/{share/schemas → schemas}/section-body.ts +0 -0
  366. /package/src/{share/schemas → schemas}/section-cards.ts +0 -0
  367. /package/src/{share/schemas → schemas}/section-cta.ts +0 -0
  368. /package/src/{share/schemas → schemas}/section-header.ts +0 -0
  369. /package/src/{share/schemas → schemas}/section-image.ts +0 -0
  370. /package/src/{share/schemas → schemas}/section-motion.ts +0 -0
  371. /package/src/{share/schemas → schemas}/section-shell.ts +0 -0
  372. /package/src/{share/schemas → schemas}/section-stats.ts +0 -0
  373. /package/src/{share/schemas → schemas}/site-background.ts +0 -0
  374. /package/src/{share/schemas → schemas}/tests/claim-records.test.ts +0 -0
  375. /package/src/{share/schemas → schemas}/tests/claims.test.ts +0 -0
  376. /package/src/{share/schemas → schemas}/tests/effects.test.ts +0 -0
  377. /package/src/{share/schemas → schemas}/tests/features.test.ts +0 -0
  378. /package/src/{share/schemas → schemas}/tests/horizontal-align.test.ts +0 -0
  379. /package/src/{share/schemas → schemas}/tests/icon-color.test.ts +0 -0
  380. /package/src/{share/schemas → schemas}/tests/live-photo.test.ts +0 -0
  381. /package/src/{share/schemas → schemas}/tests/material-credit.test.ts +0 -0
  382. /package/src/{share/schemas → schemas}/tests/media.test.ts +0 -0
  383. /package/src/{share/schemas → schemas}/tests/navigation.test.ts +0 -0
  384. /package/src/{share/schemas → schemas}/tests/page-base.test.ts +0 -0
  385. /package/src/{share/schemas → schemas}/tests/participant.test.ts +0 -0
  386. /package/src/{share/schemas → schemas}/tests/person.test.ts +0 -0
  387. /package/src/{share/schemas → schemas}/tests/print.test.ts +0 -0
  388. /package/src/{share/schemas → schemas}/tests/section-background.test.ts +0 -0
  389. /package/src/{share/schemas → schemas}/tests/section-body.test.ts +0 -0
  390. /package/src/{share/schemas → schemas}/tests/section-cards.test.ts +0 -0
  391. /package/src/{share/schemas → schemas}/tests/section-cta.test.ts +0 -0
  392. /package/src/{share/schemas → schemas}/tests/section-header.test.ts +0 -0
  393. /package/src/{share/schemas → schemas}/tests/section-image.test.ts +0 -0
  394. /package/src/{share/schemas → schemas}/tests/section-motion.test.ts +0 -0
  395. /package/src/{share/schemas → schemas}/tests/section-shell.test.ts +0 -0
  396. /package/src/{share/schemas → schemas}/tests/section-stats.test.ts +0 -0
  397. /package/src/{share/schemas → schemas}/tests/site-background.test.ts +0 -0
  398. /package/src/{share/schemas → schemas}/tests/standard-list-item.test.ts +0 -0
  399. /package/src/{share/semantic → semantic}/ai.ts +0 -0
  400. /package/src/{share/semantic → semantic}/block-extraction.ts +0 -0
  401. /package/src/{share/semantic → semantic}/breadcrumbs.ts +0 -0
  402. /package/src/{share/semantic → semantic}/build-page.ts +0 -0
  403. /package/src/{share/semantic → semantic}/feed.ts +0 -0
  404. /package/src/{share/semantic → semantic}/image-sitemap.ts +0 -0
  405. /package/src/{share/semantic → semantic}/jsonld/article.ts +0 -0
  406. /package/src/{share/semantic → semantic}/jsonld/collection-list.ts +0 -0
  407. /package/src/{share/semantic → semantic}/jsonld/tests/article.test.ts +0 -0
  408. /package/src/{share/semantic → semantic}/jsonld/tests/breadcrumb.test.ts +0 -0
  409. /package/src/{share/semantic → semantic}/jsonld/tests/collection-list.test.ts +0 -0
  410. /package/src/{share/semantic → semantic}/jsonld/tests/faq.test.ts +0 -0
  411. /package/src/{share/semantic → semantic}/jsonld/tests/initiative.test.ts +0 -0
  412. /package/src/{share/semantic → semantic}/jsonld/tests/organization.test.ts +0 -0
  413. /package/src/{share/semantic → semantic}/jsonld/tests/person.test.ts +0 -0
  414. /package/src/{share/semantic → semantic}/jsonld/tests/service.test.ts +0 -0
  415. /package/src/{share/semantic → semantic}/jsonld/tests/shared.test.ts +0 -0
  416. /package/src/{share/semantic → semantic}/jsonld/tests/video.test.ts +0 -0
  417. /package/src/{share/semantic → semantic}/jsonld/tests/webpage.test.ts +0 -0
  418. /package/src/{share/semantic → semantic}/jsonld/tests/website.test.ts +0 -0
  419. /package/src/{share/semantic → semantic}/jsonld/video.ts +0 -0
  420. /package/src/{share/semantic → semantic}/llms-policy.ts +0 -0
  421. /package/src/{share/semantic → semantic}/markdown-hygiene.ts +0 -0
  422. /package/src/{share/semantic → semantic}/output-projection.ts +0 -0
  423. /package/src/{share/semantic → semantic}/page-markdown.ts +0 -0
  424. /package/src/{share/semantic → semantic}/price-marker-resolver.ts +0 -0
  425. /package/src/{share/semantic → semantic}/robots.ts +0 -0
  426. /package/src/{share/semantic → semantic}/tests/ai.test.ts +0 -0
  427. /package/src/{share/semantic → semantic}/tests/block-extraction.test.ts +0 -0
  428. /package/src/{share/semantic → semantic}/tests/breadcrumbs.test.ts +0 -0
  429. /package/src/{share/semantic → semantic}/tests/build-page.test.ts +0 -0
  430. /package/src/{share/semantic → semantic}/tests/feed.test.ts +0 -0
  431. /package/src/{share/semantic → semantic}/tests/helpers.ts +0 -0
  432. /package/src/{share/semantic → semantic}/tests/ids.test.ts +0 -0
  433. /package/src/{share/semantic → semantic}/tests/image-sitemap.test.ts +0 -0
  434. /package/src/{share/semantic → semantic}/tests/jsonld.test.ts +0 -0
  435. /package/src/{share/semantic → semantic}/tests/llms-policy.test.ts +0 -0
  436. /package/src/{share/semantic → semantic}/tests/markdown-hygiene.test.ts +0 -0
  437. /package/src/{share/semantic → semantic}/tests/markdown-page.test.ts +0 -0
  438. /package/src/{share/semantic → semantic}/tests/organization-profile.test.ts +0 -0
  439. /package/src/{share/semantic → semantic}/tests/output-projection.test.ts +0 -0
  440. /package/src/{share/semantic → semantic}/tests/page-markdown.test.ts +0 -0
  441. /package/src/{share/semantic → semantic}/tests/page-utils.test.ts +0 -0
  442. /package/src/{share/semantic → semantic}/tests/robots.test.ts +0 -0
  443. /package/src/{share/semantic → semantic}/tests/update-stamp.test.ts +0 -0
  444. /package/src/{share/semantic → semantic}/update-stamp.ts +0 -0
  445. /package/src/{share/slug → slug}/heading-slugger.ts +0 -0
  446. /package/src/{share/slug → slug}/index.ts +0 -0
  447. /package/src/{share/slug → slug}/slug-id.ts +0 -0
  448. /package/src/{share/slug → slug}/strategies.ts +0 -0
  449. /package/src/{share/slug → slug}/tests/slug.test.ts +0 -0
  450. /package/src/{share → text}/tests/text-normalize.pbt.test.ts +0 -0
  451. /package/src/{share → text}/text-position.ts +0 -0
@@ -0,0 +1,289 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Sole runtime and type owner of DiagnosticSeverity, DiagnosticEvidence, and
5
+ Diagnostic strict Zod schemas (RFC-0852). Types are inferred from schemas.
6
+ kernel/types.ts re-exports these types; the site plugin imports the schemas.
7
+ </purpose>
8
+ <non-goals>
9
+ <item>Do not define canonical JSON bytes or limits; RFC-0849 owns them.</item>
10
+ <item>Do not define EvidenceEnvelopeV1, certification identities, or dossier admission; RFC-0853 owns those.</item>
11
+ <item>Do not add command status vocabularies or suppression systems.</item>
12
+ </non-goals>
13
+ </MODULE_CONTRACT>
14
+ <CHANGE_SUMMARY>
15
+ <item>RFC-0852: Move canonical Diagnostic ownership from site plugin into the engine.</item>
16
+ <item>RFC-1027: Add structured remediation hints — remediationRefSchema and remediation field on Diagnostic.</item>
17
+ </CHANGE_SUMMARY>
18
+ */
19
+
20
+ import { z } from "zod";
21
+ import {
22
+ isCanonicalJsonObjectV1,
23
+ canonicalJsonBytesV1,
24
+ type CanonicalJsonObjectV1,
25
+ } from "../fingerprint/canonical-json.ts";
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Limits (RFC-0852 §Bounds)
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export const DIAGNOSTIC_LIMITS = {
32
+ ruleIdChars: 128,
33
+ messageBytes: 4 * 1024,
34
+ fixHintBytes: 8 * 1024,
35
+ pathBytes: 1024,
36
+ urlBytes: 4 * 1024,
37
+ snippetBytes: 16 * 1024,
38
+ evidenceItems: 32,
39
+ dataBytes: 64 * 1024,
40
+ diagnosticBytes: 128 * 1024,
41
+ diagnosticsPerResult: 1000,
42
+ remediationActionBytes: 512,
43
+ remediationTargetFiles: 8,
44
+ } as const;
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Helpers
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function utf8ByteLength(value: string): number {
51
+ return Buffer.byteLength(value, "utf8");
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Diagnostic rule ID — [A-Z0-9][A-Z0-9._-]* , max 128 ASCII chars
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const diagnosticRuleIdPattern = /^[A-Z0-9][A-Z0-9._-]*$/;
59
+
60
+ export const diagnosticRuleIdSchema = z
61
+ .string()
62
+ .max(DIAGNOSTIC_LIMITS.ruleIdChars)
63
+ .regex(
64
+ diagnosticRuleIdPattern,
65
+ "CERT-DIAGNOSTIC-SCHEMA-01: ruleId must match [A-Z0-9][A-Z0-9._-]*",
66
+ );
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Safe workspace-relative POSIX path
70
+ // ---------------------------------------------------------------------------
71
+
72
+ const unsafePathPatterns = [
73
+ /[/\\]\.\.([/\\]|$)/, // .. component
74
+ /[/\\]\.([/\\]|$)/, // . component (but allow leading . for dotfiles? No — RFC says reject empty/.)
75
+ /^\//, // absolute path
76
+ /^[/\\]/, // leading separator
77
+ /\\/, // backslash
78
+ /\0/, // NUL
79
+ /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, // URI scheme
80
+ /^~([/\\]|$)/, // home expansion
81
+ /^[a-zA-Z]:[\\/]/, // Windows drive letter
82
+ ];
83
+
84
+ function isSafeWorkspaceRelativePath(value: string): boolean {
85
+ if (value.length === 0) return false;
86
+ if (utf8ByteLength(value) > DIAGNOSTIC_LIMITS.pathBytes) return false;
87
+ for (const pattern of unsafePathPatterns) {
88
+ if (pattern.test(value)) return false;
89
+ }
90
+ // Reject empty components from split
91
+ const segments = value.split("/");
92
+ for (const seg of segments) {
93
+ if (seg.length === 0) return false; // empty component (e.g. "a//b")
94
+ if (seg === "." || seg === "..") return false;
95
+ }
96
+ return true;
97
+ }
98
+
99
+ export const safeWorkspaceRelativePathSchema = z.string().refine(isSafeWorkspaceRelativePath, {
100
+ message:
101
+ "CERT-DIAGNOSTIC-LOCATOR-01: path must be a workspace-relative POSIX path without absolute paths, backslashes, empty/./.. components, NUL, URI schemes, or home expansion",
102
+ });
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Safe diagnostic URL — absolute http/https, no userinfo, no credentials
106
+ // ---------------------------------------------------------------------------
107
+
108
+ function isSafeDiagnosticUrl(value: string): boolean {
109
+ if (utf8ByteLength(value) > DIAGNOSTIC_LIMITS.urlBytes) return false;
110
+ if (/\0/.test(value)) return false;
111
+ if (/[\x00-\x1f\x7f]/.test(value)) return false; // control characters
112
+
113
+ let parsed: URL;
114
+ try {
115
+ parsed = new URL(value);
116
+ } catch {
117
+ return false;
118
+ }
119
+
120
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
121
+
122
+ // Reject userinfo (credentials in authority)
123
+ if (parsed.username || parsed.password) return false;
124
+
125
+ // Reject credential-bearing query parameters (best-effort high-confidence)
126
+ const query = parsed.searchParams;
127
+ const credentialKeys = [
128
+ "token",
129
+ "access_token",
130
+ "secret",
131
+ "api_key",
132
+ "apikey",
133
+ "key",
134
+ "password",
135
+ "passwd",
136
+ "pwd",
137
+ "authorization",
138
+ "auth",
139
+ "signature",
140
+ "sig",
141
+ ];
142
+ for (const key of credentialKeys) {
143
+ if (query.has(key)) return false;
144
+ }
145
+
146
+ return true;
147
+ }
148
+
149
+ export const safeDiagnosticUrlSchema = z.string().refine(isSafeDiagnosticUrl, {
150
+ message:
151
+ "CERT-DIAGNOSTIC-LOCATOR-01: URL must be an absolute http/https URL with no userinfo, control characters, or credential-bearing query parameters",
152
+ });
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Redacted diagnostic text — no known secrets/PII/absolute paths
156
+ // ---------------------------------------------------------------------------
157
+
158
+ const secretPatterns = [
159
+ /sk-[A-Za-z0-9_-]{20,}/, // OpenAI-style API key
160
+ /eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+/, // JWT
161
+ /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/, // private key
162
+ /Bearer\s+[A-Za-z0-9_-]{20,}/, // bearer token
163
+ /Authorization:\s*Bearer\s+[A-Za-z0-9_-]{20,}/i, // auth header
164
+ /(?:mongodb|postgres|postgresql|mysql|redis):\/\/[^\s@"']+:[^\s@"']+@/, // connection string
165
+ /(?:aws_access_key_id|aws_secret_access_key)\s*=\s*[A-Za-z0-9/+=]{16,}/, // AWS creds
166
+ ];
167
+
168
+ const absolutePathPatterns = [
169
+ /^(?:\/|[A-Za-z]:[\\/]|~[\\/])/, // absolute or home path
170
+ ];
171
+
172
+ const piiPatterns = [
173
+ /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/, // email
174
+ /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, // phone (US-like)
175
+ ];
176
+
177
+ function isRedactedDiagnosticText(value: string, maxBytes: number): boolean {
178
+ if (utf8ByteLength(value) > maxBytes) return false;
179
+ if (value.trim().length === 0) return false;
180
+
181
+ for (const pattern of secretPatterns) {
182
+ if (pattern.test(value)) return false;
183
+ }
184
+ for (const pattern of absolutePathPatterns) {
185
+ if (pattern.test(value)) return false;
186
+ }
187
+ for (const pattern of piiPatterns) {
188
+ if (pattern.test(value)) return false;
189
+ }
190
+ return true;
191
+ }
192
+
193
+ function redactedDiagnosticTextSchema(maxBytes: number) {
194
+ return z.string().refine((val) => isRedactedDiagnosticText(val, maxBytes), {
195
+ message: `CERT-DIAGNOSTIC-REDACTION-01: text must not contain known secrets, PII, or absolute paths and must be within ${maxBytes} UTF-8 bytes`,
196
+ });
197
+ }
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Canonical JSON object V1 schema for Diagnostic.data
201
+ // ---------------------------------------------------------------------------
202
+ // RFC-0852: data must be a runtime-branded CanonicalJsonObjectV1 at parse time.
203
+ // The TypeScript type is Record<string, unknown> for structural compatibility
204
+ // with existing producers; the Zod custom check enforces canonical validation
205
+ // before persistence. z.custom is used instead of z.record because Zod 4's
206
+ // z.record structural validation rejects branded frozen objects created by
207
+ // snapshotCanonicalJsonObjectV1.
208
+
209
+ const canonicalJsonObjectV1Schema = z
210
+ .custom<Record<string, unknown>>(
211
+ (val): val is CanonicalJsonObjectV1 => isCanonicalJsonObjectV1(val),
212
+ {
213
+ message: "CERT-DIAGNOSTIC-SCHEMA-01: data must be a runtime-branded CanonicalJsonObjectV1",
214
+ },
215
+ )
216
+ .refine(
217
+ (val) => {
218
+ if (!isCanonicalJsonObjectV1(val)) return false;
219
+ const bytes = canonicalJsonBytesV1(val);
220
+ return bytes.length <= DIAGNOSTIC_LIMITS.dataBytes;
221
+ },
222
+ {
223
+ message: `CERT-DIAGNOSTIC-LIMIT-01: canonical data bytes must not exceed ${DIAGNOSTIC_LIMITS.dataBytes} bytes`,
224
+ },
225
+ );
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Severity
229
+ // ---------------------------------------------------------------------------
230
+
231
+ export const diagnosticSeveritySchema = z.enum(["error", "warning", "info"]);
232
+ export type DiagnosticSeverity = z.infer<typeof diagnosticSeveritySchema>;
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Evidence
236
+ // ---------------------------------------------------------------------------
237
+
238
+ export const diagnosticEvidenceSchema = z
239
+ .object({
240
+ kind: z.enum(["rule", "rendered", "source", "config", "cache", "runtime"]),
241
+ ruleFile: safeWorkspaceRelativePathSchema.optional(),
242
+ ruleId: diagnosticRuleIdSchema.optional(),
243
+ file: safeWorkspaceRelativePathSchema.optional(),
244
+ url: safeDiagnosticUrlSchema.optional(),
245
+ snippet: redactedDiagnosticTextSchema(DIAGNOSTIC_LIMITS.snippetBytes).optional(),
246
+ })
247
+ .strict();
248
+
249
+ export type DiagnosticEvidence = z.infer<typeof diagnosticEvidenceSchema>;
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // Remediation reference (RFC-1027)
253
+ // ---------------------------------------------------------------------------
254
+
255
+ export const remediationRefSchema = z
256
+ .object({
257
+ ruleId: diagnosticRuleIdSchema,
258
+ action: redactedDiagnosticTextSchema(DIAGNOSTIC_LIMITS.remediationActionBytes),
259
+ template: redactedDiagnosticTextSchema(DIAGNOSTIC_LIMITS.fixHintBytes).optional(),
260
+ docRef: safeWorkspaceRelativePathSchema.optional(),
261
+ targetFiles: z
262
+ .array(safeWorkspaceRelativePathSchema)
263
+ .max(DIAGNOSTIC_LIMITS.remediationTargetFiles)
264
+ .optional(),
265
+ })
266
+ .strict();
267
+
268
+ export type RemediationRef = z.infer<typeof remediationRefSchema>;
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Diagnostic
272
+ // ---------------------------------------------------------------------------
273
+
274
+ export const diagnosticSchema = z
275
+ .object({
276
+ ruleId: diagnosticRuleIdSchema,
277
+ severity: diagnosticSeveritySchema,
278
+ message: redactedDiagnosticTextSchema(DIAGNOSTIC_LIMITS.messageBytes),
279
+ file: safeWorkspaceRelativePathSchema.optional(),
280
+ line: z.number().int().positive().optional(),
281
+ column: z.number().int().positive().optional(),
282
+ fixHint: redactedDiagnosticTextSchema(DIAGNOSTIC_LIMITS.fixHintBytes).optional(),
283
+ remediation: remediationRefSchema.optional(),
284
+ evidence: z.array(diagnosticEvidenceSchema).max(DIAGNOSTIC_LIMITS.evidenceItems).optional(),
285
+ data: canonicalJsonObjectV1Schema.optional(),
286
+ })
287
+ .strict();
288
+
289
+ export type Diagnostic = z.infer<typeof diagnosticSchema>;
@@ -0,0 +1,166 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ RFC-1126: pre-overwrite drift guard. Generators that rewrite git-tracked files
5
+ call guardTrackedOverwrite before writing; when the on-disk content carries
6
+ manual edits that the new content would destroy, the guard warns (default) or
7
+ throws (--strict) listing the exact keys/sections that would be lost.
8
+ </purpose>
9
+ <non-goals>
10
+ <item>Does not perform the write itself — callers write via context.io / writeFileIfChanged after the guard returns.</item>
11
+ <item>Does not decide marker-protocol skips — callers run hasGeneratedMarker checks first; the guard only sees files about to be written.</item>
12
+ <item>Does not guard non-tracked files — the caller decides which paths are git-tracked generated outputs.</item>
13
+ </non-goals>
14
+ </MODULE_CONTRACT>
15
+ <KEY_DECISIONS>
16
+ <item>Warn-by-default: an unattended pipeline must not break on pre-existing drift; --strict is the sole fail-hard opt-in (never --force, which keeps DNA-93 cache-clearing semantics).</item>
17
+ <item>The guard is a pre-write side effect, never a cached result — command-result caching (RFC-0390) must not cache it.</item>
18
+ </KEY_DECISIONS>
19
+ <CHANGE_SUMMARY>
20
+ <item>RFC-1126: initial implementation — DriftReport, DriftGuardOptions, guardTrackedOverwrite with json/yaml/text key extraction.</item>
21
+ <item>RFC-1126: step 1 — shared contracts (drift guard + workpiece env)
22
+
23
+ Add guardTrackedOverwrite (json/yaml/text lost-key diff, warn-by-default, --strict fail-hard) and loadWorkpieceEnv (dotenv.parse wrapper) to werkstatt-shared kernel subpath; extend KernelRuntimeContext with optional workpieceEnv field.</item>
24
+ </CHANGE_SUMMARY>
25
+ */
26
+
27
+ import { readFile } from "node:fs/promises";
28
+ import { parse as yamlParse } from "yaml";
29
+
30
+ export interface DriftReport {
31
+ file: string;
32
+ /** Keys/sections present on disk but absent or changed in the new content (dotted paths). */
33
+ wouldLose: string[];
34
+ /** True when on-disk content already equals new content — caller may skip the write. */
35
+ identical: boolean;
36
+ }
37
+
38
+ export interface DriftGuardOptions {
39
+ format: "json" | "yaml" | "text";
40
+ /** Fail-hard instead of warning. */
41
+ strict?: boolean;
42
+ /** Remediation hint appended to the warning/error message. */
43
+ fixHint?: string;
44
+ }
45
+
46
+ export interface DriftGuardLogger {
47
+ warn(msg: string): void;
48
+ }
49
+
50
+ const MAX_REPORTED_KEYS = 20;
51
+
52
+ /** Collect dotted paths present in `oldValue` but absent or changed in `newValue`. */
53
+ function collectLostKeys(
54
+ oldValue: unknown,
55
+ newValue: unknown,
56
+ prefix: string,
57
+ out: string[],
58
+ ): void {
59
+ if (out.length >= MAX_REPORTED_KEYS) return;
60
+ if (oldValue === null || typeof oldValue !== "object") {
61
+ if (oldValue !== newValue) out.push(prefix);
62
+ return;
63
+ }
64
+ if (Array.isArray(oldValue)) {
65
+ if (!Array.isArray(newValue)) {
66
+ out.push(prefix);
67
+ return;
68
+ }
69
+ for (let i = 0; i < oldValue.length; i++) {
70
+ if (i >= newValue.length) {
71
+ out.push(`${prefix}[${i}]`);
72
+ } else {
73
+ collectLostKeys(oldValue[i], newValue[i], `${prefix}[${i}]`, out);
74
+ }
75
+ if (out.length >= MAX_REPORTED_KEYS) return;
76
+ }
77
+ return;
78
+ }
79
+ const oldObj = oldValue as Record<string, unknown>;
80
+ const newObj =
81
+ newValue !== null && typeof newValue === "object" && !Array.isArray(newValue)
82
+ ? (newValue as Record<string, unknown>)
83
+ : {};
84
+ for (const key of Object.keys(oldObj)) {
85
+ const path = prefix ? `${prefix}.${key}` : key;
86
+ if (!(key in newObj)) {
87
+ out.push(path);
88
+ } else {
89
+ collectLostKeys(oldObj[key], newObj[key], path, out);
90
+ }
91
+ if (out.length >= MAX_REPORTED_KEYS) return;
92
+ }
93
+ }
94
+
95
+ function diffStructured(format: "json" | "yaml", existing: string, next: string): string[] {
96
+ try {
97
+ const oldValue: unknown = format === "json" ? JSON.parse(existing) : yamlParse(existing);
98
+ const newValue: unknown = format === "json" ? JSON.parse(next) : yamlParse(next);
99
+ const out: string[] = [];
100
+ collectLostKeys(oldValue, newValue, "", out);
101
+ return out;
102
+ } catch {
103
+ // Unparseable existing content — fall back to line diff so drift is still visible.
104
+ return diffText(existing, next);
105
+ }
106
+ }
107
+
108
+ function diffText(existing: string, next: string): string[] {
109
+ const nextLines = new Set(next.split("\n").map((l) => l.trim()));
110
+ const lost: string[] = [];
111
+ for (const line of existing.split("\n")) {
112
+ const trimmed = line.trim();
113
+ if (trimmed && !nextLines.has(trimmed)) {
114
+ lost.push(trimmed.length > 80 ? `${trimmed.slice(0, 77)}…` : trimmed);
115
+ if (lost.length >= MAX_REPORTED_KEYS) break;
116
+ }
117
+ }
118
+ return lost;
119
+ }
120
+
121
+ /**
122
+ * Compare newContent against the on-disk file before overwriting. Returns a
123
+ * DriftReport; warns via logger (or throws when options.strict) listing the
124
+ * keys that would be destroyed. Never throws for a missing file — nothing to
125
+ * lose. Callers should skip the write entirely when report.identical is true.
126
+ */
127
+ export async function guardTrackedOverwrite(
128
+ filePath: string,
129
+ newContent: string,
130
+ options: DriftGuardOptions,
131
+ logger: DriftGuardLogger,
132
+ ): Promise<DriftReport> {
133
+ let existing: string | null = null;
134
+ try {
135
+ existing = await readFile(filePath, "utf-8");
136
+ } catch {
137
+ // File absent — nothing to lose.
138
+ }
139
+
140
+ if (existing === null) {
141
+ return { file: filePath, wouldLose: [], identical: false };
142
+ }
143
+ if (existing === newContent) {
144
+ return { file: filePath, wouldLose: [], identical: true };
145
+ }
146
+
147
+ const wouldLose =
148
+ options.format === "text"
149
+ ? diffText(existing, newContent)
150
+ : diffStructured(options.format, existing, newContent);
151
+
152
+ if (wouldLose.length === 0) {
153
+ return { file: filePath, wouldLose, identical: false };
154
+ }
155
+
156
+ const hint = options.fixHint ? ` ${options.fixHint}` : "";
157
+ const message =
158
+ `DRIFT-GUARD-01: ${filePath} — manual edits would be destroyed: ` +
159
+ `${wouldLose.join(", ")}.${hint}`;
160
+
161
+ if (options.strict) {
162
+ throw new Error(message);
163
+ }
164
+ logger.warn(message);
165
+ return { file: filePath, wouldLose, identical: false };
166
+ }
@@ -0,0 +1,131 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ RFC-0258: atomic file-write primitive for workspace-shared kernel-command
5
+ outputs. Every kernel command that writes a file outside its target app
6
+ directory (workspace root, docs/, packages/ontology/) MUST write through
7
+ writeFileAtomic instead of node:fs/promises writeFile, so that a reader
8
+ polling the target path never observes a torn/partial file when Turborepo
9
+ runs multiple app builds in parallel.
10
+ </purpose>
11
+ <non-goals>
12
+ <item>Do not provide cross-process mutual exclusion (locks) — outputs are deterministic (RFC-0087), so convergent atomic writes are sufficient.</item>
13
+ <item>Do not fall back to a non-atomic write when retries are exhausted — fail loudly instead.</item>
14
+ </non-goals>
15
+ </MODULE_CONTRACT>
16
+ <KEY_DECISIONS>
17
+ <item>Writes go through temp-file plus rename — readers never observe partial content.</item>
18
+ <item>Canonical implementation lives in @warpgogol/forge — this is a compatibility re-export.</item>
19
+ </KEY_DECISIONS>
20
+ <CHANGE_SUMMARY>
21
+ <item>RFC-0258: initial implementation.</item>
22
+ <item>RFC-1097: step 6 — compass.migrate codemod run
23
+
24
+ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.</item>
25
+ <item>RFC-1097: sweep — werkstatt-engine clean
26
+
27
+ Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
28
+ </CHANGE_SUMMARY>
29
+ */
30
+
31
+ import { writeFile, rename as fsRename, unlink } from "node:fs/promises";
32
+ import { existsSync } from "node:fs";
33
+ import { dirname, join, basename } from "node:path";
34
+ import { randomBytes } from "node:crypto";
35
+
36
+ export interface WriteFileAtomicOptions {
37
+ /** Max rename retries on transient EPERM/EBUSY. Default 10, linear 50ms backoff. */
38
+ retries?: number;
39
+ }
40
+
41
+ // Indirection seam so unit tests can simulate a transient EPERM/EBUSY
42
+ // on the first rename attempt without monkey-patching the frozen node:fs/promises
43
+ // module namespace (ESM named exports are read-only bindings). Production code
44
+ // always goes through fsRename; only tests in this package call the setter.
45
+ let renameImpl: typeof fsRename = fsRename;
46
+
47
+ /** @internal test-only seam — do not call from production code. */
48
+ export function __setRenameImplForTests(impl: typeof fsRename | undefined): void {
49
+ renameImpl = impl ?? fsRename;
50
+ }
51
+
52
+ const DEFAULT_RETRIES = 10;
53
+ const RETRY_BACKOFF_MS = 50;
54
+
55
+ const RETRYABLE_CODES = new Set(["EPERM", "EBUSY"]);
56
+
57
+ function isRetryableError(error: unknown): boolean {
58
+ if (!error || typeof error !== "object") return false;
59
+ const code = (error as NodeJS.ErrnoException).code;
60
+ return typeof code === "string" && RETRYABLE_CODES.has(code);
61
+ }
62
+
63
+ function sleep(ms: number): Promise<void> {
64
+ return new Promise((resolve) => setTimeout(resolve, ms));
65
+ }
66
+
67
+ function makeTempPath(filePath: string): string {
68
+ const dir = dirname(filePath);
69
+ const name = basename(filePath);
70
+ const random = randomBytes(6).toString("hex");
71
+ return join(dir, `${name}.${random}.tmp`);
72
+ }
73
+
74
+ /**
75
+ * Write `content` to `filePath` atomically: write to a temp file in the same
76
+ * directory, then rename over the target. Rename within one directory is
77
+ * atomic on POSIX; a bounded retry loop absorbs transient EPERM/EBUSY
78
+ * (e.g. a reader briefly holding the file handle during overwrite).
79
+ *
80
+ * On failure (write or all rename retries exhausted), the temp file is
81
+ * unlinked in a finally block and the original error is rethrown — this
82
+ * helper never falls back to a non-atomic write.
83
+ */
84
+ export async function writeFileAtomic(
85
+ filePath: string,
86
+ content: string | Uint8Array,
87
+ options?: WriteFileAtomicOptions,
88
+ ): Promise<void> {
89
+ const retries = options?.retries ?? DEFAULT_RETRIES;
90
+ const tempPath = makeTempPath(filePath);
91
+ let tempWritten = false;
92
+
93
+ try {
94
+ await writeFile(tempPath, content);
95
+ tempWritten = true;
96
+
97
+ let attempt = 0;
98
+ // Bounded retry loop: attempt 0 is the first try, then up to `retries` more.
99
+ for (;;) {
100
+ try {
101
+ await renameImpl(tempPath, filePath);
102
+ return;
103
+ } catch (error) {
104
+ if (attempt >= retries || !isRetryableError(error)) {
105
+ throw error;
106
+ }
107
+ attempt += 1;
108
+ // EPERM on rename-over-existing: try unlinking the target
109
+ // first (it may have been briefly locked by a reader that has since
110
+ // released it), then retry the rename on the next iteration.
111
+ if (existsSync(filePath)) {
112
+ try {
113
+ await unlink(filePath);
114
+ } catch {
115
+ // If unlink also fails, the retry loop will try again.
116
+ }
117
+ }
118
+ await sleep(RETRY_BACKOFF_MS * attempt);
119
+ }
120
+ }
121
+ } finally {
122
+ if (tempWritten) {
123
+ try {
124
+ // If rename succeeded, the temp file no longer exists — unlink is a no-op error we swallow.
125
+ await unlink(tempPath);
126
+ } catch {
127
+ // Expected in the success path (temp file already renamed away) — ignore.
128
+ }
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,22 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Barrel export for @warpgogol/werkstatt-shared/kernel sub-path. Re-exports the kernel contract cluster (command/pipeline types, workspace IO, atomic fs, desired state, diagnostic schemas) sunk from @warpgogol/werkstatt-engine per RFC-1104.</purpose>
4
+ <non-goals>
5
+ <item>Does not re-export engine runtime modules (executor, module registry, CLI).</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>RFC-1104: initial barrel — kernel contract cluster sunk from werkstatt-engine.</item>
10
+ <item>RFC-1126: step 1 — shared contracts (drift guard + workpiece env)
11
+
12
+ Add guardTrackedOverwrite (json/yaml/text lost-key diff, warn-by-default, --strict fail-hard) and loadWorkpieceEnv (dotenv.parse wrapper) to werkstatt-shared kernel subpath; extend KernelRuntimeContext with optional workpieceEnv field.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ export * from "./types.ts";
17
+ export * from "./workspace-io.ts";
18
+ export * from "./fs-atomic.ts";
19
+ export * from "./desired-state.ts";
20
+ export * from "./diagnostic.ts";
21
+ export * from "./drift-guard.ts";
22
+ export * from "./workpiece-env.ts";