@warpgogol/werkstatt-shared 0.14.0 → 0.15.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 (437) hide show
  1. package/README.md +31 -13
  2. package/README.uk.md +25 -15
  3. package/package.json +519 -762
  4. package/src/{share/agent → agent}/ard-catalog.ts +6 -1
  5. package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
  6. package/src/{share/agent → agent}/manifest.ts +15 -1
  7. package/src/{share/agent → agent}/search.ts +6 -1
  8. package/src/{share → agent}/tests/api-catalog.test.ts +2 -2
  9. package/src/{share → agent}/tests/ard-catalog.test.ts +2 -2
  10. package/src/{share → agent}/tests/capability.test.ts +1 -1
  11. package/src/{share → agent}/tests/fleet-catalog.test.ts +1 -1
  12. package/src/{share → agent}/tests/knowledge.test.ts +2 -2
  13. package/src/{share → agent}/tests/manifest.test.ts +1 -1
  14. package/src/{share → agent}/tests/mcp-card.test.ts +2 -2
  15. package/src/agent/tests/omit-empty.test.ts +33 -0
  16. package/src/{share → agent}/tests/openapi.test.ts +2 -2
  17. package/src/{share → agent}/tests/proof.test.ts +1 -1
  18. package/src/{share → attribution}/material-credits.ts +4 -4
  19. package/src/{share → checks}/check-hints.ts +6 -1
  20. package/src/checks/index.ts +4 -1
  21. package/src/checks/lib/astro-site-url.ts +6 -1
  22. package/src/checks/lib/i18n.ts +14 -0
  23. package/src/checks/result-helpers.ts +22 -5
  24. package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
  25. package/src/{share → client-scripts}/counter-utils.ts +6 -2
  26. package/src/{share/scripts → client-scripts}/external-link-qr.ts +11 -16
  27. package/src/{share/scripts → client-scripts}/external-links.ts +12 -1
  28. package/src/{share/scripts → client-scripts}/gsap-counter.ts +8 -3
  29. package/src/client-scripts/index.ts +35 -0
  30. package/src/{share/scripts → client-scripts}/inline-number-animation.ts +1 -1
  31. package/src/{share/scripts → client-scripts}/lenis.ts +6 -1
  32. package/src/{share/scripts → client-scripts}/lordicon.ts +6 -1
  33. package/src/{share/scripts → client-scripts}/orchestrator.ts +10 -1
  34. package/src/{share/scripts → client-scripts}/scheduler.ts +6 -1
  35. package/src/client-scripts/scroll-spy.ts +106 -0
  36. package/src/client-scripts/tests/external-link-qr.test.ts +8 -0
  37. package/src/client-scripts/tests/external-links.test.ts +25 -0
  38. package/src/client-scripts/tests/gsap-counter.test.ts +8 -0
  39. package/src/client-scripts/tests/gsap-mountain-journey.test.ts +8 -0
  40. package/src/client-scripts/tests/gsap-parallax.test.ts +8 -0
  41. package/src/client-scripts/tests/gsap-reveal.test.ts +8 -0
  42. package/src/client-scripts/tests/gsap-stagger.test.ts +8 -0
  43. package/src/client-scripts/tests/inline-number-animation.test.ts +8 -0
  44. package/src/client-scripts/tests/lenis.test.ts +8 -0
  45. package/src/client-scripts/tests/live-photos.test.ts +8 -0
  46. package/src/client-scripts/tests/lordicon.test.ts +8 -0
  47. package/src/client-scripts/tests/orchestrator.test.ts +47 -0
  48. package/src/client-scripts/tests/scheduler.test.ts +23 -0
  49. package/src/client-scripts/tests/scroll-spy.test.ts +204 -0
  50. package/src/{share/scripts → client-scripts/tests}/video-player.test.ts +51 -31
  51. package/src/component/contracts.ts +206 -0
  52. package/src/component/index.ts +11 -0
  53. package/src/{share → content}/content-reference.ts +3 -3
  54. package/src/{share/content → content}/entity-id.ts +6 -3
  55. package/src/content/index.ts +3 -0
  56. package/src/{share/content → content}/merge.ts +6 -1
  57. package/src/content/system-manifest.test.ts +107 -0
  58. package/src/content/system-manifest.ts +12 -94
  59. package/src/{share → content}/tests/resolve-field-path.test.ts +1 -1
  60. package/src/{share → content}/tests/substitute-deep.test.ts +1 -1
  61. package/src/content-discipline/index.ts +19 -0
  62. package/src/{share/content-discipline → content-discipline}/parsers.ts +2 -2
  63. package/src/{share/env.d.ts → env.d.ts} +6 -3
  64. package/src/fingerprint/canonical-json.ts +731 -0
  65. package/src/fingerprint/index.ts +12 -0
  66. package/src/fingerprint/primitives.ts +72 -0
  67. package/src/{share/i18n → i18n}/localization.ts +6 -2
  68. package/src/integration/adapters.ts +10 -71
  69. package/src/integration/dispatch.ts +1 -1
  70. package/src/integration/funnel.ts +6 -1
  71. package/src/integration/index.ts +101 -4
  72. package/src/integration/lagebild-destination-adapter.ts +108 -0
  73. package/src/integration/lagebild-ingress.ts +7 -12
  74. package/src/integration/orchestration.ts +11 -10
  75. package/src/integration/tests/deliver-event.test.ts +1 -1
  76. package/src/integration/tests/dispatch.test.ts +4 -4
  77. package/src/integration/tests/hub-runtime.test.ts +22 -14
  78. package/src/integration/vitest.config.ts +8 -0
  79. package/src/kernel/desired-state.ts +227 -0
  80. package/src/kernel/diagnostic.ts +289 -0
  81. package/src/kernel/fs-atomic.ts +131 -0
  82. package/src/kernel/index.ts +15 -0
  83. package/src/kernel/tests/fs-atomic.test.ts +150 -0
  84. package/src/kernel/tests/workspace-io.test.ts +187 -0
  85. package/src/kernel/types.ts +459 -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 +8 -8
  95. package/src/{share → node}/dev-props-validator.ts +6 -1
  96. package/src/{share → node}/import-scan.ts +23 -3
  97. package/src/{share → node}/semantic/derived-prices-loader.ts +4 -2
  98. package/src/{share → node}/semantic/markdown-twin-provenance.ts +1 -1
  99. package/src/node/tests/derived-prices-loader.test.ts +59 -0
  100. package/src/node/tests/markdown-twin-provenance.test.ts +152 -0
  101. package/src/observability/env-resolver.ts +6 -1
  102. package/src/observability/index.ts +6 -1
  103. package/src/observability/otlp-converter.ts +6 -1
  104. package/src/observability/otlp-transport.ts +6 -1
  105. package/src/observability/pusher.ts +6 -1
  106. package/src/observability/redact.ts +6 -1
  107. package/src/observability/typed-refs.ts +6 -2
  108. package/src/observability/vitest.config.ts +1 -0
  109. package/src/{share → offers}/formula-eval.ts +3 -3
  110. package/src/{share → offers}/tests/formula-eval.test.ts +2 -2
  111. package/src/onboarding/index.ts +1 -0
  112. package/src/{share → onboarding}/tests/onboarding-yaml.test.ts +1 -1
  113. package/src/ontology/archetype-registry.ts +6 -1
  114. package/src/ontology/archetypes/index.json +33 -1
  115. package/src/ontology/archetypes/index.yaml +27 -1
  116. package/src/ontology/archetypes/sections/entity-grid.yaml +46 -0
  117. package/src/ontology/archetypes/sections/exit-diagram.yaml +43 -0
  118. package/src/ontology/biome-token-projection.ts +6 -1
  119. package/src/ontology/cosmic/index.ts +6 -1
  120. package/src/ontology/cosmic/moon-catalog.ts +6 -1
  121. package/src/ontology/cosmic/planet-catalog.ts +6 -1
  122. package/src/ontology/cosmic/star-catalog.ts +6 -1
  123. package/src/ontology/enums.ts +6 -1
  124. package/src/ontology/external-surfaces/index.ts +1 -1
  125. package/src/ontology/index.ts +6 -2
  126. package/src/ontology/manifest.ts +26 -4
  127. package/src/ontology/operations/artifact-store.ts +45 -0
  128. package/src/ontology/operations/dht.ts +98 -0
  129. package/src/ontology/operations/handoff.ts +92 -0
  130. package/src/ontology/operations/index.ts +187 -1
  131. package/src/ontology/operations/leitstand.ts +99 -0
  132. package/src/ontology/operations/materialization.ts +90 -0
  133. package/src/ontology/operations/mission.ts +111 -0
  134. package/src/ontology/operations/naming-policy.ts +132 -0
  135. package/src/ontology/operations/notausgang.ts +64 -0
  136. package/src/ontology/operations/release.ts +121 -0
  137. package/src/ontology/operations/sternsystem.ts +192 -0
  138. package/src/ontology/operations/werkstatt.ts +48 -0
  139. package/src/ontology/schemas/biome.ts +7 -2
  140. package/src/ontology/schemas/capability.ts +1 -1
  141. package/src/ontology/schemas/constellation.ts +6 -1
  142. package/src/ontology/schemas/index.ts +6 -2
  143. package/src/ontology/schemas/manifest-resolver.ts +6 -1
  144. package/src/ontology/schemas/page-entry.ts +9 -5
  145. package/src/ontology/schemas/system/growth.ts +6 -1
  146. package/src/ontology/schemas/system/manifest.ts +40 -1
  147. package/src/ontology/schemas/system/page-output.ts +7 -6
  148. package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
  149. package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
  150. package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
  151. package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
  152. package/src/ontology/schemas/system/tests/release.test.ts +22 -0
  153. package/src/ontology/schemas/system/tests/text.test.ts +18 -0
  154. package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
  155. package/src/ontology/schemas/system/text.ts +15 -0
  156. package/src/ontology/schemas/system.ts +6 -2
  157. package/src/ontology/schemas/tests/biome.test.ts +19 -0
  158. package/src/ontology/schemas/tests/capability.test.ts +22 -0
  159. package/src/ontology/schemas/tests/constellation.test.ts +22 -0
  160. package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
  161. package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
  162. package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
  163. package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
  164. package/src/ontology/schemas/tests/site-family.test.ts +18 -0
  165. package/src/ontology/schemas/tests/system.test.ts +20 -0
  166. package/src/ontology/shared-section-props/body-fragments.ts +2 -0
  167. package/src/ontology/tests/sternsystem-owner.test.ts +1 -1
  168. package/src/ontology/vitest.config.ts +1 -0
  169. package/src/{share → page}/page.ts +13 -9
  170. package/src/{share → page}/tests/dev-props-validator.test.ts +1 -1
  171. package/src/{share → page}/tests/hero-section-numbering-parity.test.ts +1 -1
  172. package/src/passport/index.ts +6 -1
  173. package/src/passport/key-rotate.ts +10 -2
  174. package/src/passport/schema.ts +6 -1
  175. package/src/passport/sign.ts +8 -3
  176. package/src/passport/vitest.config.ts +1 -0
  177. package/src/pbt/index.test.ts +95 -0
  178. package/src/pbt/index.ts +251 -0
  179. package/src/plugin/plugin-contract.ts +1 -1
  180. package/src/{share → policy}/entitlement.ts +24 -4
  181. package/src/{share → policy}/feature-policy.ts +8 -3
  182. package/src/{share → policy}/visibility.ts +7 -2
  183. package/src/redirects/redirects.ts +117 -0
  184. package/src/{share → redirects/tests}/redirects.test.ts +1 -1
  185. package/src/remediation/remediation-catalog.ts +316 -0
  186. package/src/{share/routes → routes}/template-filter.ts +1 -1
  187. package/src/{share → runtime}/runtime-context.ts +7 -2
  188. package/src/{share → runtime}/shared-context.ts +1 -1
  189. package/src/{share/schemas → schemas}/claims.ts +2 -2
  190. package/src/{share/schemas → schemas}/effects.ts +6 -1
  191. package/src/{share/schemas → schemas}/features.ts +1 -1
  192. package/src/{share/schemas → schemas}/index.ts +3 -3
  193. package/src/{share/schemas → schemas}/navigation.ts +6 -1
  194. package/src/{share/schemas → schemas}/page-base.ts +6 -2
  195. package/src/{share/schemas → schemas}/person.ts +1 -1
  196. package/src/{share/schemas → schemas}/standard-list-item.ts +1 -1
  197. package/src/schemas/tests/claim-records.test.ts +52 -0
  198. package/src/schemas/tests/claims.test.ts +23 -0
  199. package/src/schemas/tests/effects.test.ts +116 -0
  200. package/src/schemas/tests/features.test.ts +61 -0
  201. package/src/schemas/tests/horizontal-align.test.ts +16 -0
  202. package/src/schemas/tests/icon-color.test.ts +16 -0
  203. package/src/schemas/tests/live-photo.test.ts +40 -0
  204. package/src/schemas/tests/material-credit.test.ts +51 -0
  205. package/src/schemas/tests/media.test.ts +26 -0
  206. package/src/schemas/tests/navigation.test.ts +58 -0
  207. package/src/schemas/tests/page-base.test.ts +19 -0
  208. package/src/schemas/tests/participant.test.ts +26 -0
  209. package/src/schemas/tests/person.test.ts +12 -0
  210. package/src/schemas/tests/print.test.ts +30 -0
  211. package/src/schemas/tests/section-background.test.ts +31 -0
  212. package/src/schemas/tests/section-body.test.ts +16 -0
  213. package/src/schemas/tests/section-cards.test.ts +17 -0
  214. package/src/schemas/tests/section-cta.test.ts +41 -0
  215. package/src/schemas/tests/section-header.test.ts +31 -0
  216. package/src/schemas/tests/section-image.test.ts +24 -0
  217. package/src/schemas/tests/section-motion.test.ts +29 -0
  218. package/src/schemas/tests/section-shell.test.ts +42 -0
  219. package/src/schemas/tests/section-stats.test.ts +17 -0
  220. package/src/schemas/tests/site-background.test.ts +33 -0
  221. package/src/schemas/tests/standard-list-item.test.ts +16 -0
  222. package/src/{share → sections}/rfc0042-utils.ts +1 -1
  223. package/src/{share/semantic → semantic}/block-extractors/index.ts +20 -1
  224. package/src/{share/semantic → semantic}/business-projection.ts +95 -1
  225. package/src/semantic/canonical-uri.ts +54 -0
  226. package/src/{share/semantic → semantic}/extract.ts +100 -1
  227. package/src/semantic/fact-extraction.test.ts +47 -0
  228. package/src/semantic/fact-extraction.ts +46 -0
  229. package/src/semantic/freshness.ts +67 -0
  230. package/src/{share/semantic → semantic}/ids.ts +6 -1
  231. package/src/{share/semantic → semantic}/index.ts +9 -2
  232. package/src/{share/semantic → semantic}/jsonld/breadcrumb.ts +6 -2
  233. package/src/{share/semantic → semantic}/jsonld/context.ts +6 -2
  234. package/src/{share/semantic → semantic}/jsonld/faq.ts +6 -2
  235. package/src/{share/semantic → semantic}/jsonld/initiative.ts +6 -1
  236. package/src/{share/semantic → semantic}/jsonld/organization.ts +6 -1
  237. package/src/{share/semantic → semantic}/jsonld/person.ts +6 -2
  238. package/src/{share/semantic → semantic}/jsonld/service.ts +6 -2
  239. package/src/semantic/jsonld/shared.ts +36 -0
  240. package/src/semantic/jsonld/tests/article.test.ts +89 -0
  241. package/src/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
  242. package/src/semantic/jsonld/tests/collection-list.test.ts +39 -0
  243. package/src/semantic/jsonld/tests/faq.test.ts +60 -0
  244. package/src/semantic/jsonld/tests/initiative.test.ts +56 -0
  245. package/src/semantic/jsonld/tests/organization.test.ts +123 -0
  246. package/src/semantic/jsonld/tests/person.test.ts +89 -0
  247. package/src/semantic/jsonld/tests/service.test.ts +65 -0
  248. package/src/semantic/jsonld/tests/shared.test.ts +41 -0
  249. package/src/semantic/jsonld/tests/video.test.ts +84 -0
  250. package/src/semantic/jsonld/tests/webpage.test.ts +93 -0
  251. package/src/semantic/jsonld/tests/website.test.ts +27 -0
  252. package/src/semantic/jsonld/types.ts +24 -0
  253. package/src/{share/semantic → semantic}/jsonld/webpage.ts +6 -1
  254. package/src/semantic/jsonld/website.ts +32 -0
  255. package/src/{share/semantic → semantic}/jsonld.ts +6 -1
  256. package/src/{share/semantic → semantic}/llms.ts +41 -3
  257. package/src/{share/semantic → semantic}/models.ts +27 -38
  258. package/src/{share/semantic → semantic}/organization-profile.ts +3 -0
  259. package/src/{share/semantic → semantic}/page-builders/markdown-page.ts +6 -3
  260. package/src/{share/semantic → semantic}/page-utils.ts +6 -3
  261. package/src/semantic/tests/ai.test.ts +55 -0
  262. package/src/semantic/tests/block-extraction.test.ts +34 -0
  263. package/src/{share/tests/breadcrumbs.test.ts → semantic/tests/breadcrumbs-0229.test.ts} +1 -1
  264. package/src/semantic/tests/breadcrumbs.test.ts +127 -0
  265. package/src/{share → semantic}/tests/build-page-price-markers.test.ts +2 -2
  266. package/src/semantic/tests/build-page.test.ts +122 -0
  267. package/src/semantic/tests/business-projection.test.ts +367 -0
  268. package/src/semantic/tests/canonical-uri.test.ts +56 -0
  269. package/src/semantic/tests/extract.test.ts +209 -0
  270. package/src/semantic/tests/feed.test.ts +113 -0
  271. package/src/semantic/tests/freshness.pbt.test.ts +108 -0
  272. package/src/semantic/tests/helpers.ts +32 -0
  273. package/src/semantic/tests/ids.test.ts +118 -0
  274. package/src/{share/tests/image-sitemap.test.ts → semantic/tests/image-sitemap-0150.test.ts} +1 -1
  275. package/src/semantic/tests/image-sitemap.test.ts +109 -0
  276. package/src/{share → semantic}/tests/jsonld-service.test.ts +2 -2
  277. package/src/{share → semantic}/tests/jsonld-video.test.ts +3 -3
  278. package/src/{share → semantic}/tests/jsonld-webpage.test.ts +3 -3
  279. package/src/semantic/tests/jsonld.test.ts +61 -0
  280. package/src/{share → semantic}/tests/llms-0789.test.ts +2 -2
  281. package/src/semantic/tests/llms-policy.test.ts +60 -0
  282. package/src/semantic/tests/llms.test.ts +164 -0
  283. package/src/semantic/tests/markdown-hygiene.test.ts +72 -0
  284. package/src/semantic/tests/markdown-page.test.ts +97 -0
  285. package/src/{share → semantic}/tests/organization-jsonld.test.ts +3 -3
  286. package/src/semantic/tests/organization-profile.test.ts +107 -0
  287. package/src/{share/tests/output-projection.test.ts → semantic/tests/output-projection-0143.test.ts} +1 -1
  288. package/src/semantic/tests/output-projection.test.ts +77 -0
  289. package/src/semantic/tests/page-markdown.test.ts +98 -0
  290. package/src/semantic/tests/page-utils.test.ts +88 -0
  291. package/src/{share/tests/price-marker-resolver.test.ts → semantic/tests/price-marker-resolver-0767.test.ts} +2 -2
  292. package/src/semantic/tests/price-marker-resolver.test.ts +90 -0
  293. package/src/semantic/tests/project-claims.pbt.test.ts +60 -0
  294. package/src/semantic/tests/robots.test.ts +70 -0
  295. package/src/{share/semantic → semantic}/tests/split-sentences.test.ts +1 -1
  296. package/src/{share → semantic}/tests/twin-path.test.ts +1 -1
  297. package/src/semantic/tests/update-stamp.test.ts +102 -0
  298. package/src/signing/index.ts +13 -0
  299. package/src/signing/key.ts +139 -0
  300. package/src/signing/sign.ts +66 -0
  301. package/src/signing/types.ts +29 -0
  302. package/src/{share/slug → slug}/slug-url.ts +4 -1
  303. package/src/stack/run-tool.ts +108 -0
  304. package/src/stack/stack-checks.ts +124 -0
  305. package/src/stack/tests/run-tool.test.ts +138 -0
  306. package/src/stack/tests/stack-checks.test.ts +141 -0
  307. package/src/stack/tests/walk-files.test.ts +131 -0
  308. package/src/stack/walk-files.ts +114 -0
  309. package/src/surface/blueprint.ts +342 -66
  310. package/src/surface/decision-composer.ts +6 -1
  311. package/src/surface/geo.ts +6 -1
  312. package/src/surface/index.ts +11 -7
  313. package/src/surface/types.ts +2 -2
  314. package/src/surface/vitest.config.ts +1 -0
  315. package/src/tests/signing-core.test.ts +129 -0
  316. package/src/{share → text}/css-value-normalize.ts +6 -1
  317. package/src/text/string-utils.ts +24 -0
  318. package/src/{share → text}/tests/text-normalize.test.ts +5 -0
  319. package/src/{share → text}/text-normalize.ts +4 -3
  320. package/src/{share → text}/wrap-inline-numbers.ts +1 -1
  321. package/src/types/axiom-study.d.ts +36 -0
  322. package/src/types/hls-js-light.d.ts +21 -0
  323. package/src/types/qrcode-browser.d.ts +23 -0
  324. package/src/typography/fix.pbt.test.ts +208 -0
  325. package/src/typography/fix.ts +148 -0
  326. package/src/typography/index.ts +54 -0
  327. package/src/typography/locale-defaults.ts +97 -0
  328. package/src/typography/rules-tier1.test.ts +459 -0
  329. package/src/typography/rules-tier1.ts +925 -0
  330. package/src/typography/rules-tier2-locale.pbt.test.ts +190 -0
  331. package/src/typography/rules-tier2-locale.test.ts +286 -0
  332. package/src/typography/rules-tier2-locale.ts +399 -0
  333. package/src/typography/rules-tier2-structure.test.ts +330 -0
  334. package/src/typography/rules-tier2-structure.ts +378 -0
  335. package/src/typography/rules-tier3-advisory.test.ts +290 -0
  336. package/src/typography/rules-tier3-advisory.ts +239 -0
  337. package/src/typography/text-surface.test.ts +244 -0
  338. package/src/typography/text-surface.ts +448 -0
  339. package/src/index.ts +0 -14
  340. package/src/integration/port-barrel.ts +0 -105
  341. package/src/share/content/index.ts +0 -16
  342. package/src/share/content-discipline/index.ts +0 -14
  343. package/src/share/index.ts +0 -86
  344. package/src/share/legal/index.ts +0 -1
  345. package/src/share/redirects.ts +0 -54
  346. package/src/share/scripts/index.ts +0 -26
  347. package/src/share/semantic/jsonld/shared.ts +0 -31
  348. package/src/share/semantic/jsonld/types.ts +0 -18
  349. package/src/share/semantic/jsonld/website.ts +0 -27
  350. package/src/share/string-utils.ts +0 -19
  351. package/src/share/types/axiom-study.d.ts +0 -22
  352. package/src/share/types/hls-js-light.d.ts +0 -16
  353. package/src/share/types/qrcode-browser.d.ts +0 -18
  354. package/src/share/utility-registry.yaml +0 -36
  355. package/src/share/vitest.config.ts +0 -8
  356. package/src/surface/blueprint-schema.ts +0 -314
  357. package/src/surface/blueprint-types.ts +0 -256
  358. /package/src/{share/agent → agent}/api-catalog.ts +0 -0
  359. /package/src/{share/agent → agent}/capability.ts +0 -0
  360. /package/src/{share/agent → agent}/dns-aid.ts +0 -0
  361. /package/src/{share/agent → agent}/fleet-catalog.ts +0 -0
  362. /package/src/{share/agent → agent}/index.ts +0 -0
  363. /package/src/{share/agent → agent}/knowledge.ts +0 -0
  364. /package/src/{share/agent → agent}/mcp-card.ts +0 -0
  365. /package/src/{share/agent → agent}/omit-empty.ts +0 -0
  366. /package/src/{share/agent → agent}/openapi.ts +0 -0
  367. /package/src/{share/agent → agent}/proof.ts +0 -0
  368. /package/src/{share/agent → agent}/search.test.ts +0 -0
  369. /package/src/{share → attribution}/attribution-display.ts +0 -0
  370. /package/src/{share/scripts → client-scripts}/gsap-mountain-journey.ts +0 -0
  371. /package/src/{share/scripts → client-scripts}/gsap-parallax.ts +0 -0
  372. /package/src/{share/scripts → client-scripts}/gsap-reveal.ts +0 -0
  373. /package/src/{share/scripts → client-scripts}/gsap-stagger.ts +0 -0
  374. /package/src/{share/scripts → client-scripts}/live-photos.ts +0 -0
  375. /package/src/{share/scripts → client-scripts}/video-player.ts +0 -0
  376. /package/src/{share/content → content}/resolve-field-path.ts +0 -0
  377. /package/src/{share/content → content}/substitute-deep.ts +0 -0
  378. /package/src/{share → content}/tests/content-ref-index.test.ts +0 -0
  379. /package/src/{share/content-discipline → content-discipline}/types.ts +0 -0
  380. /package/src/{share → image-provider}/image-provider.ts +0 -0
  381. /package/src/{share/knowledge → knowledge}/claim.ts +0 -0
  382. /package/src/{share/knowledge → knowledge}/derivation.ts +0 -0
  383. /package/src/{share/knowledge → knowledge}/freshness.ts +0 -0
  384. /package/src/{share/knowledge → knowledge}/index.ts +0 -0
  385. /package/src/{share/knowledge → knowledge}/ledger.ts +0 -0
  386. /package/src/{share/knowledge → knowledge}/plan.ts +0 -0
  387. /package/src/{share/knowledge → knowledge}/source.ts +0 -0
  388. /package/src/{share/legal → legal}/translation-policy.ts +0 -0
  389. /package/src/{share/middleware → middleware}/language-redirect.ts +0 -0
  390. /package/src/{share/middleware → middleware}/tests/access-protection.test.ts +0 -0
  391. /package/src/{share → node}/fs/index.ts +0 -0
  392. /package/src/{share/tests/markdown-twin-provenance.test.ts → node/tests/markdown-twin-provenance-0613.test.ts} +0 -0
  393. /package/src/{share → offers}/offer-capacity.ts +0 -0
  394. /package/src/{share/onboarding-yaml/index.ts → onboarding/yaml.ts} +0 -0
  395. /package/src/{share → ontology}/tests/ontology-role-by-cosmic-name.test.ts +0 -0
  396. /package/src/{share/routes → routes}/tests/template-filter.test.ts +0 -0
  397. /package/src/{share/schemas → schemas}/claim-records.ts +0 -0
  398. /package/src/{share/schemas → schemas}/horizontal-align.ts +0 -0
  399. /package/src/{share/schemas → schemas}/icon-color.ts +0 -0
  400. /package/src/{share/schemas → schemas}/live-photo.ts +0 -0
  401. /package/src/{share/schemas → schemas}/material-credit.ts +0 -0
  402. /package/src/{share/schemas → schemas}/media.ts +0 -0
  403. /package/src/{share/schemas → schemas}/participant.ts +0 -0
  404. /package/src/{share/schemas → schemas}/print.ts +0 -0
  405. /package/src/{share/schemas → schemas}/section-background.ts +0 -0
  406. /package/src/{share/schemas → schemas}/section-body.ts +0 -0
  407. /package/src/{share/schemas → schemas}/section-cards.ts +0 -0
  408. /package/src/{share/schemas → schemas}/section-cta.ts +0 -0
  409. /package/src/{share/schemas → schemas}/section-header.ts +0 -0
  410. /package/src/{share/schemas → schemas}/section-image.ts +0 -0
  411. /package/src/{share/schemas → schemas}/section-motion.ts +0 -0
  412. /package/src/{share/schemas → schemas}/section-shell.ts +0 -0
  413. /package/src/{share/schemas → schemas}/section-stats.ts +0 -0
  414. /package/src/{share/schemas → schemas}/site-background.ts +0 -0
  415. /package/src/{share/semantic → semantic}/ai.ts +0 -0
  416. /package/src/{share/semantic → semantic}/block-extraction.ts +0 -0
  417. /package/src/{share/semantic → semantic}/breadcrumbs.ts +0 -0
  418. /package/src/{share/semantic → semantic}/build-page.ts +0 -0
  419. /package/src/{share/semantic → semantic}/feed.ts +0 -0
  420. /package/src/{share/semantic → semantic}/image-sitemap.ts +0 -0
  421. /package/src/{share/semantic → semantic}/jsonld/article.ts +0 -0
  422. /package/src/{share/semantic → semantic}/jsonld/collection-list.ts +0 -0
  423. /package/src/{share/semantic → semantic}/jsonld/video.ts +0 -0
  424. /package/src/{share/semantic → semantic}/llms-policy.ts +0 -0
  425. /package/src/{share/semantic → semantic}/markdown-hygiene.ts +0 -0
  426. /package/src/{share/semantic → semantic}/output-projection.ts +0 -0
  427. /package/src/{share/semantic → semantic}/page-markdown.ts +0 -0
  428. /package/src/{share/semantic → semantic}/price-marker-resolver.ts +0 -0
  429. /package/src/{share/semantic → semantic}/robots.ts +0 -0
  430. /package/src/{share/semantic → semantic}/update-stamp.ts +0 -0
  431. /package/src/{share/slug → slug}/heading-slugger.ts +0 -0
  432. /package/src/{share/slug → slug}/index.ts +0 -0
  433. /package/src/{share/slug → slug}/slug-id.ts +0 -0
  434. /package/src/{share/slug → slug}/strategies.ts +0 -0
  435. /package/src/{share/slug → slug}/tests/slug.test.ts +0 -0
  436. /package/src/{share → text}/tests/text-normalize.pbt.test.ts +0 -0
  437. /package/src/{share → text}/text-position.ts +0 -0
@@ -0,0 +1,731 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0849: Bounded canonical JSON identity bytes — object-only opaque snapshot, strict RFC 8785 JCS encoder, and SHA-256 hash delegation.</purpose>
4
+ <non-goals>
5
+ <item>Do not import node:crypto, stableJsonHash, or any plugin module — delegate to byteHash only.</item>
6
+ <item>Do not define certification schemas, Diagnostic, or persistence — those belong to later packets.</item>
7
+ <item>Do not change stableJsonHash or its consumers.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0849: initial implementation of werkstatt/canonical-json@1 — bounded object-root profile of RFC 8785 JCS.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import { byteHash, type Sha256Digest } from "./primitives.ts";
16
+
17
+ export const CANONICAL_JSON_V1 = "werkstatt/canonical-json@1" as const;
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Opaque branded type
21
+ // ---------------------------------------------------------------------------
22
+
23
+ declare const canonicalJsonObjectV1Brand: unique symbol;
24
+
25
+ export type CanonicalJsonObjectV1 = {
26
+ readonly [canonicalJsonObjectV1Brand]: true;
27
+ };
28
+
29
+ const brandedRegistry = new WeakSet<object>();
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Path segments
33
+ // ---------------------------------------------------------------------------
34
+
35
+ export type CanonicalJsonPathSegmentV1 =
36
+ | { readonly kind: "array-index"; readonly index: number }
37
+ | { readonly kind: "object-key"; readonly sortedIndex: number };
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Failure types
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export type CanonicalJsonFailureCodeV1 =
44
+ | "CERT-CANONICAL-DOMAIN-01"
45
+ | "CERT-CANONICAL-TRAVERSAL-01"
46
+ | "CERT-CANONICAL-UNICODE-01"
47
+ | "CERT-CANONICAL-LIMIT-01";
48
+
49
+ export interface CanonicalJsonFailureV1 {
50
+ readonly ok: false;
51
+ readonly code: CanonicalJsonFailureCodeV1;
52
+ readonly path: readonly CanonicalJsonPathSegmentV1[];
53
+ readonly omittedPathSegments: number;
54
+ readonly message: string;
55
+ readonly limit?:
56
+ "bytes" | "depth" | "nodes" | "object-keys" | "array-items" | "string-bytes" | "key-bytes";
57
+ readonly actual?: number;
58
+ readonly maximum?: number;
59
+ }
60
+
61
+ export interface CanonicalJsonSuccessV1 {
62
+ readonly ok: true;
63
+ readonly value: CanonicalJsonObjectV1;
64
+ }
65
+
66
+ export type CanonicalJsonObjectSnapshotResultV1 = CanonicalJsonSuccessV1 | CanonicalJsonFailureV1;
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Invariant error
70
+ // ---------------------------------------------------------------------------
71
+
72
+ export class CanonicalJsonInvariantError extends Error {
73
+ readonly code = "CERT-CANONICAL-BRAND-01" as const;
74
+ constructor(message: string) {
75
+ super(message);
76
+ this.name = "CanonicalJsonInvariantError";
77
+ }
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Hard limits
82
+ // ---------------------------------------------------------------------------
83
+
84
+ const MAX_BYTES = 8 * 1024 * 1024;
85
+ const MAX_DEPTH = 64;
86
+ const MAX_NODES = 250_000;
87
+ const MAX_OBJECT_KEYS = 10_000;
88
+ const MAX_ARRAY_ITEMS = 100_000;
89
+ const MAX_STRING_BYTES = 1024 * 1024;
90
+ const MAX_KEY_BYTES = 1024;
91
+ const MAX_MESSAGE_BYTES = 512;
92
+ const MAX_PATH_SEGMENTS = 64;
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // UTF-16 lone surrogate check
96
+ // ---------------------------------------------------------------------------
97
+
98
+ function hasLoneSurrogate(str: string): boolean {
99
+ for (let i = 0; i < str.length; i++) {
100
+ const code = str.charCodeAt(i);
101
+ if (code >= 0xd800 && code <= 0xdbff) {
102
+ if (i + 1 >= str.length) return true;
103
+ const next = str.charCodeAt(i + 1);
104
+ if (next < 0xdc00 || next > 0xdfff) return true;
105
+ i++;
106
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
107
+ return true;
108
+ }
109
+ }
110
+ return false;
111
+ }
112
+
113
+ function utf8ByteLength(str: string): number {
114
+ return Buffer.byteLength(str, "utf8");
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // RFC 8785 key comparator (lexicographic over UTF-16 code units)
119
+ // ---------------------------------------------------------------------------
120
+
121
+ function compareJcsKeys(a: string, b: string): number {
122
+ const len = Math.min(a.length, b.length);
123
+ for (let i = 0; i < len; i++) {
124
+ const ca = a.charCodeAt(i);
125
+ const cb = b.charCodeAt(i);
126
+ if (ca !== cb) return ca - cb;
127
+ }
128
+ return a.length - b.length;
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // RFC 8785 number serialization (§3.2.2.3 / ECMA-262 §7.1.12.1)
133
+ // ---------------------------------------------------------------------------
134
+
135
+ function serializeNumber(value: number): string {
136
+ if (Object.is(value, -0)) {
137
+ throw new CanonicalJsonInvariantError("negative zero rejected by Werkstatt profile");
138
+ }
139
+ if (!Number.isFinite(value)) {
140
+ throw new CanonicalJsonInvariantError("non-finite numbers rejected by Werkstatt profile");
141
+ }
142
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
143
+ throw new CanonicalJsonInvariantError("unsafe integer rejected by Werkstatt profile");
144
+ }
145
+ if (value === 0) return "0";
146
+ if (Number.isInteger(value)) return value.toString();
147
+
148
+ // Non-integer finite numbers: use ECMA-262 §7.1.12.1 Number.prototype.toString
149
+ // which produces the shortest representation that round-trips.
150
+ return value.toString();
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // RFC 8785 string escaping (§3.2.2.2)
155
+ // ---------------------------------------------------------------------------
156
+
157
+ function escapeString(str: string): string {
158
+ let out = '"';
159
+ for (let i = 0; i < str.length; i++) {
160
+ const code = str.charCodeAt(i);
161
+ switch (code) {
162
+ case 0x22:
163
+ out += '\\"';
164
+ break;
165
+ case 0x5c:
166
+ out += "\\\\";
167
+ break;
168
+ case 0x08:
169
+ out += "\\b";
170
+ break;
171
+ case 0x09:
172
+ out += "\\t";
173
+ break;
174
+ case 0x0a:
175
+ out += "\\n";
176
+ break;
177
+ case 0x0c:
178
+ out += "\\f";
179
+ break;
180
+ case 0x0d:
181
+ out += "\\r";
182
+ break;
183
+ default:
184
+ if (code < 0x20) {
185
+ out += "\\u" + code.toString(16).padStart(4, "0");
186
+ } else if (code >= 0xd800 && code <= 0xdbff) {
187
+ // High surrogate — must be followed by low surrogate
188
+ const next = str.charCodeAt(i + 1);
189
+ if (next >= 0xdc00 && next <= 0xdfff) {
190
+ // Valid surrogate pair — emit raw UTF-16 code units
191
+ out += String.fromCharCode(code);
192
+ out += String.fromCharCode(next);
193
+ i++;
194
+ } else {
195
+ // This should have been caught by hasLoneSurrogate, but defensive
196
+ throw new CanonicalJsonInvariantError(
197
+ "lone high surrogate in string — should have been rejected",
198
+ );
199
+ }
200
+ } else {
201
+ out += String.fromCharCode(code);
202
+ }
203
+ break;
204
+ }
205
+ }
206
+ out += '"';
207
+ return out;
208
+ }
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // Traversal state
212
+ // ---------------------------------------------------------------------------
213
+
214
+ interface TraversalState {
215
+ nodes: number;
216
+ bytes: number;
217
+ output: string[];
218
+ }
219
+
220
+ type FailureResult = CanonicalJsonFailureV1;
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Deep freeze
224
+ // ---------------------------------------------------------------------------
225
+
226
+ function deepFreeze(value: unknown): void {
227
+ if (value === null || typeof value !== "object") return;
228
+ if (Array.isArray(value)) {
229
+ for (const item of value) deepFreeze(item);
230
+ Object.freeze(value);
231
+ } else {
232
+ for (const key of Object.keys(value as Record<string, unknown>)) {
233
+ deepFreeze((value as Record<string, unknown>)[key]);
234
+ }
235
+ Object.freeze(value);
236
+ }
237
+ }
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Snapshot builder
241
+ // ---------------------------------------------------------------------------
242
+
243
+ function isPlainObjectRoot(value: unknown): boolean {
244
+ if (value === null || typeof value !== "object") return false;
245
+ const proto = Object.getPrototypeOf(value);
246
+ return proto === Object.prototype || proto === null;
247
+ }
248
+
249
+ function isPlainObject(value: unknown): boolean {
250
+ return isPlainObjectRoot(value);
251
+ }
252
+
253
+ function buildSnapshot(input: unknown): CanonicalJsonObjectSnapshotResultV1 {
254
+ // Root must be a plain object
255
+ if (!isPlainObjectRoot(input)) {
256
+ return fail("CERT-CANONICAL-DOMAIN-01", [], 0, "root must be a plain object");
257
+ }
258
+
259
+ const visited = new Set<object>();
260
+ const state: TraversalState = { nodes: 1, bytes: 0, output: [] };
261
+
262
+ if (state.nodes > MAX_NODES) {
263
+ return failLimit("nodes", [], 0, state.nodes, MAX_NODES);
264
+ }
265
+
266
+ const result = traverseObject(input as Record<string, unknown>, [], 0, state, visited);
267
+ if (result) return result;
268
+
269
+ const outputStr = state.output.join("");
270
+ const outputBytes = Buffer.byteLength(outputStr, "utf8");
271
+ if (outputBytes > MAX_BYTES) {
272
+ return failLimit("bytes", [], 0, outputBytes, MAX_BYTES);
273
+ }
274
+
275
+ // Build the detached frozen copy
276
+ const detached = buildDetached(input);
277
+ deepFreeze(detached);
278
+ const root = detached as unknown as CanonicalJsonObjectV1;
279
+ brandedRegistry.add(detached as object);
280
+
281
+ return { ok: true, value: root };
282
+ }
283
+
284
+ function traverseObject(
285
+ obj: Record<string, unknown>,
286
+ path: CanonicalJsonPathSegmentV1[],
287
+ depth: number,
288
+ state: TraversalState,
289
+ visited: Set<object>,
290
+ ): FailureResult | null {
291
+ if (depth > MAX_DEPTH) {
292
+ return failLimit("depth", path, 0, depth, MAX_DEPTH);
293
+ }
294
+
295
+ // Cycle/alias detection
296
+ if (visited.has(obj)) {
297
+ return fail("CERT-CANONICAL-TRAVERSAL-01", path, 0, "cycle or repeated reference detected");
298
+ }
299
+ visited.add(obj);
300
+
301
+ // Capture own keys
302
+ let ownKeys: string[];
303
+ try {
304
+ ownKeys = Reflect.ownKeys(obj) as string[];
305
+ } catch {
306
+ return fail("CERT-CANONICAL-TRAVERSAL-01", path, 0, "Reflect.ownKeys threw during capture");
307
+ }
308
+
309
+ // Check for symbol keys
310
+ const symbols = Object.getOwnPropertySymbols(obj);
311
+ if (symbols.length > 0) {
312
+ return fail("CERT-CANONICAL-DOMAIN-01", path, 0, "symbol keys are not permitted");
313
+ }
314
+
315
+ // Check all descriptors are enumerable data descriptors
316
+ for (const key of ownKeys) {
317
+ let desc: PropertyDescriptor;
318
+ try {
319
+ desc = Object.getOwnPropertyDescriptor(obj, key)!;
320
+ } catch {
321
+ return fail("CERT-CANONICAL-TRAVERSAL-01", path, 0, "getOwnPropertyDescriptor threw");
322
+ }
323
+ if (!desc) {
324
+ return fail("CERT-CANONICAL-DOMAIN-01", path, 0, `missing descriptor for key`);
325
+ }
326
+ if (!desc.enumerable) {
327
+ return fail("CERT-CANONICAL-DOMAIN-01", path, 0, "non-enumerable property is not permitted");
328
+ }
329
+ if (desc.get !== undefined || desc.set !== undefined) {
330
+ return fail("CERT-CANONICAL-DOMAIN-01", path, 0, "accessor property is not permitted");
331
+ }
332
+ }
333
+
334
+ // Check key count
335
+ if (ownKeys.length > MAX_OBJECT_KEYS) {
336
+ return failLimit("object-keys", path, 0, ownKeys.length, MAX_OBJECT_KEYS);
337
+ }
338
+
339
+ // Check key bytes and lone surrogates
340
+ for (const key of ownKeys) {
341
+ const keyBytes = utf8ByteLength(key);
342
+ if (keyBytes > MAX_KEY_BYTES) {
343
+ return failLimit("key-bytes", path, 0, keyBytes, MAX_KEY_BYTES);
344
+ }
345
+ if (hasLoneSurrogate(key)) {
346
+ return fail("CERT-CANONICAL-UNICODE-01", path, 0, "lone surrogate in object key");
347
+ }
348
+ }
349
+
350
+ // Sort keys by JCS comparator
351
+ const sortedKeys = [...ownKeys].sort(compareJcsKeys);
352
+
353
+ // Verify key list didn't change during capture
354
+ let postKeys: string[];
355
+ try {
356
+ postKeys = Reflect.ownKeys(obj) as string[];
357
+ } catch {
358
+ return fail(
359
+ "CERT-CANONICAL-TRAVERSAL-01",
360
+ path,
361
+ 0,
362
+ "Reflect.ownKeys threw during post-capture",
363
+ );
364
+ }
365
+ if (postKeys.length !== ownKeys.length || !postKeys.every((k, i) => k === ownKeys[i])) {
366
+ return fail("CERT-CANONICAL-TRAVERSAL-01", path, 0, "own-key shape changed during capture");
367
+ }
368
+
369
+ // Emit object
370
+ state.output.push("{");
371
+
372
+ for (let i = 0; i < sortedKeys.length; i++) {
373
+ const key = sortedKeys[i];
374
+ if (i > 0) state.output.push(",");
375
+
376
+ // Emit key
377
+ state.output.push(escapeString(key));
378
+
379
+ state.output.push(":");
380
+
381
+ // Read value once
382
+ let value: unknown;
383
+ try {
384
+ value = (obj as Record<string, unknown>)[key];
385
+ } catch {
386
+ const seg: CanonicalJsonPathSegmentV1 = { kind: "object-key", sortedIndex: i };
387
+ const newPath = pushPath(path, seg);
388
+ return fail(
389
+ "CERT-CANONICAL-TRAVERSAL-01",
390
+ newPath.path,
391
+ newPath.omitted,
392
+ "property read threw",
393
+ );
394
+ }
395
+
396
+ const seg: CanonicalJsonPathSegmentV1 = { kind: "object-key", sortedIndex: i };
397
+ const newPath = pushPath(path, seg);
398
+ const result = traverseValue(value, newPath.path, newPath.omitted, depth + 1, state, visited);
399
+ if (result) return result;
400
+ }
401
+
402
+ state.output.push("}");
403
+ return null;
404
+ }
405
+
406
+ function traverseArray(
407
+ arr: unknown[],
408
+ path: CanonicalJsonPathSegmentV1[],
409
+ omitted: number,
410
+ depth: number,
411
+ state: TraversalState,
412
+ visited: Set<object>,
413
+ ): FailureResult | null {
414
+ if (depth > MAX_DEPTH) {
415
+ return failLimit("depth", path, omitted, depth, MAX_DEPTH);
416
+ }
417
+
418
+ if (visited.has(arr)) {
419
+ return fail(
420
+ "CERT-CANONICAL-TRAVERSAL-01",
421
+ path,
422
+ omitted,
423
+ "cycle or repeated reference detected",
424
+ );
425
+ }
426
+ visited.add(arr);
427
+
428
+ // Check array is dense (no holes)
429
+ const len = arr.length;
430
+ for (let i = 0; i < len; i++) {
431
+ if (!(i in arr)) {
432
+ const seg: CanonicalJsonPathSegmentV1 = { kind: "array-index", index: i };
433
+ const newPath = pushPath(path, seg);
434
+ return fail(
435
+ "CERT-CANONICAL-DOMAIN-01",
436
+ newPath.path,
437
+ newPath.omitted,
438
+ "sparse array hole detected",
439
+ );
440
+ }
441
+ }
442
+
443
+ // Check for extra own keys beyond indices
444
+ const arrKeys = Object.keys(arr);
445
+ for (const k of arrKeys) {
446
+ if (!/^(?:0|[1-9]\d*)$/.test(k) || Number(k) >= len) {
447
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "array has extra own keys");
448
+ }
449
+ }
450
+
451
+ // Check symbol keys
452
+ const symbols = Object.getOwnPropertySymbols(arr);
453
+ if (symbols.length > 0) {
454
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "array has symbol keys");
455
+ }
456
+
457
+ if (len > MAX_ARRAY_ITEMS) {
458
+ return failLimit("array-items", path, omitted, len, MAX_ARRAY_ITEMS);
459
+ }
460
+
461
+ state.output.push("[");
462
+ for (let i = 0; i < len; i++) {
463
+ if (i > 0) state.output.push(",");
464
+ const seg: CanonicalJsonPathSegmentV1 = { kind: "array-index", index: i };
465
+ const newPath = pushPath(path, seg);
466
+ const result = traverseValue(arr[i], newPath.path, newPath.omitted, depth + 1, state, visited);
467
+ if (result) return result;
468
+ }
469
+ state.output.push("]");
470
+ return null;
471
+ }
472
+
473
+ function traverseValue(
474
+ value: unknown,
475
+ path: CanonicalJsonPathSegmentV1[],
476
+ omitted: number,
477
+ depth: number,
478
+ state: TraversalState,
479
+ visited: Set<object>,
480
+ ): FailureResult | null {
481
+ state.nodes++;
482
+ if (state.nodes > MAX_NODES) {
483
+ return failLimit("nodes", path, omitted, state.nodes, MAX_NODES);
484
+ }
485
+
486
+ if (value === null) {
487
+ state.output.push("null");
488
+ return null;
489
+ }
490
+
491
+ if (value === undefined) {
492
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "undefined is not permitted");
493
+ }
494
+
495
+ const type = typeof value;
496
+
497
+ if (type === "boolean") {
498
+ state.output.push(value ? "true" : "false");
499
+ return null;
500
+ }
501
+
502
+ if (type === "number") {
503
+ const num = value as number;
504
+ if (Object.is(num, NaN)) {
505
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "NaN is not permitted");
506
+ }
507
+ if (!Number.isFinite(num)) {
508
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "Infinity is not permitted");
509
+ }
510
+ if (Object.is(num, -0)) {
511
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "negative zero is not permitted");
512
+ }
513
+ if (Number.isInteger(num) && !Number.isSafeInteger(num)) {
514
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "unsafe integer is not permitted");
515
+ }
516
+ try {
517
+ state.output.push(serializeNumber(num));
518
+ } catch (e) {
519
+ if (e instanceof CanonicalJsonInvariantError) {
520
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, e.message);
521
+ }
522
+ throw e;
523
+ }
524
+ return null;
525
+ }
526
+
527
+ if (type === "bigint") {
528
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "bigint is not permitted");
529
+ }
530
+
531
+ if (type === "symbol") {
532
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "symbol is not permitted");
533
+ }
534
+
535
+ if (type === "function") {
536
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, "function is not permitted");
537
+ }
538
+
539
+ if (type === "string") {
540
+ const strBytes = utf8ByteLength(value as string);
541
+ if (strBytes > MAX_STRING_BYTES) {
542
+ return failLimit("string-bytes", path, omitted, strBytes, MAX_STRING_BYTES);
543
+ }
544
+ if (hasLoneSurrogate(value as string)) {
545
+ return fail("CERT-CANONICAL-UNICODE-01", path, omitted, "lone surrogate in string value");
546
+ }
547
+ state.output.push(escapeString(value as string));
548
+ return null;
549
+ }
550
+
551
+ if (type === "object") {
552
+ // Reject host objects and class instances
553
+ if (!isPlainObject(value) && !Array.isArray(value)) {
554
+ // Check for toJSON
555
+ if (typeof (value as { toJSON?: unknown }).toJSON === "function") {
556
+ return fail(
557
+ "CERT-CANONICAL-DOMAIN-01",
558
+ path,
559
+ omitted,
560
+ "toJSON customization is not permitted",
561
+ );
562
+ }
563
+ // Check for Date, Map, Set, RegExp, Error, typed arrays, etc.
564
+ const proto = Object.getPrototypeOf(value);
565
+ if (proto !== Object.prototype && proto !== null) {
566
+ const ctor = proto?.constructor?.name ?? "unknown";
567
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, `non-plain object type: ${ctor}`);
568
+ }
569
+ // null-prototype object — treat as plain
570
+ }
571
+
572
+ if (Array.isArray(value)) {
573
+ return traverseArray(value, path, omitted, depth, state, visited);
574
+ }
575
+
576
+ return traverseObject(value as Record<string, unknown>, path, depth, state, visited);
577
+ }
578
+
579
+ return fail("CERT-CANONICAL-DOMAIN-01", path, omitted, `unsupported value type: ${type}`);
580
+ }
581
+
582
+ // ---------------------------------------------------------------------------
583
+ // Detached copy builder
584
+ // ---------------------------------------------------------------------------
585
+
586
+ function buildDetached(input: unknown): unknown {
587
+ if (input === null || typeof input !== "object") return input;
588
+ if (Array.isArray(input)) {
589
+ const arr: unknown[] = [];
590
+ for (const item of input) arr.push(buildDetached(item));
591
+ return arr;
592
+ }
593
+ const obj: Record<string, unknown> = Object.create(null);
594
+ for (const key of Object.keys(input as Record<string, unknown>)) {
595
+ obj[key] = buildDetached((input as Record<string, unknown>)[key]);
596
+ }
597
+ return obj;
598
+ }
599
+
600
+ // ---------------------------------------------------------------------------
601
+ // Path helpers
602
+ // ---------------------------------------------------------------------------
603
+
604
+ function pushPath(
605
+ path: CanonicalJsonPathSegmentV1[],
606
+ seg: CanonicalJsonPathSegmentV1,
607
+ ): { path: CanonicalJsonPathSegmentV1[]; omitted: number } {
608
+ if (path.length >= MAX_PATH_SEGMENTS) {
609
+ return { path, omitted: 1 };
610
+ }
611
+ return { path: [...path, seg], omitted: 0 };
612
+ }
613
+
614
+ // ---------------------------------------------------------------------------
615
+ // Failure helpers
616
+ // ---------------------------------------------------------------------------
617
+
618
+ function truncateMessage(msg: string): string {
619
+ const buf = Buffer.from(msg, "utf8");
620
+ if (buf.length <= MAX_MESSAGE_BYTES) return msg;
621
+ return buf.subarray(0, MAX_MESSAGE_BYTES).toString("utf8");
622
+ }
623
+
624
+ function fail(
625
+ code: CanonicalJsonFailureCodeV1,
626
+ path: CanonicalJsonPathSegmentV1[],
627
+ omitted: number,
628
+ message: string,
629
+ ): CanonicalJsonFailureV1 {
630
+ return {
631
+ ok: false,
632
+ code,
633
+ path,
634
+ omittedPathSegments: omitted,
635
+ message: truncateMessage(message),
636
+ };
637
+ }
638
+
639
+ function failLimit(
640
+ limit: NonNullable<CanonicalJsonFailureV1["limit"]>,
641
+ path: CanonicalJsonPathSegmentV1[],
642
+ omitted: number,
643
+ actual: number,
644
+ maximum: number,
645
+ ): CanonicalJsonFailureV1 {
646
+ return {
647
+ ok: false,
648
+ code: "CERT-CANONICAL-LIMIT-01",
649
+ path,
650
+ omittedPathSegments: omitted,
651
+ message: truncateMessage(`${limit} limit exceeded: ${actual} > ${maximum}`),
652
+ limit,
653
+ actual,
654
+ maximum,
655
+ };
656
+ }
657
+
658
+ // ---------------------------------------------------------------------------
659
+ // Public API
660
+ // ---------------------------------------------------------------------------
661
+
662
+ export function snapshotCanonicalJsonObjectV1(input: unknown): CanonicalJsonObjectSnapshotResultV1 {
663
+ return buildSnapshot(input);
664
+ }
665
+
666
+ export function isCanonicalJsonObjectV1(value: unknown): value is CanonicalJsonObjectV1 {
667
+ return typeof value === "object" && value !== null && brandedRegistry.has(value as object);
668
+ }
669
+
670
+ export function canonicalJsonBytesV1(value: CanonicalJsonObjectV1): Uint8Array {
671
+ if (!isCanonicalJsonObjectV1(value)) {
672
+ throw new CanonicalJsonInvariantError(
673
+ "CERT-CANONICAL-BRAND-01: value is not a runtime-branded CanonicalJsonObjectV1",
674
+ );
675
+ }
676
+ // Re-encode from the detached frozen copy
677
+ const output: string[] = [];
678
+ encodeValue(value, output);
679
+ const str = output.join("");
680
+ return Buffer.from(str, "utf8");
681
+ }
682
+
683
+ export function canonicalJsonHashV1(value: CanonicalJsonObjectV1): Sha256Digest {
684
+ const bytes = canonicalJsonBytesV1(value);
685
+ return byteHash(bytes);
686
+ }
687
+
688
+ // ---------------------------------------------------------------------------
689
+ // Encoder (operates on detached frozen copy — no caller mutation possible)
690
+ // ---------------------------------------------------------------------------
691
+
692
+ function encodeValue(value: unknown, output: string[]): void {
693
+ if (value === null) {
694
+ output.push("null");
695
+ return;
696
+ }
697
+ if (typeof value === "boolean") {
698
+ output.push(value ? "true" : "false");
699
+ return;
700
+ }
701
+ if (typeof value === "number") {
702
+ output.push(serializeNumber(value));
703
+ return;
704
+ }
705
+ if (typeof value === "string") {
706
+ output.push(escapeString(value));
707
+ return;
708
+ }
709
+ if (Array.isArray(value)) {
710
+ output.push("[");
711
+ for (let i = 0; i < value.length; i++) {
712
+ if (i > 0) output.push(",");
713
+ encodeValue(value[i], output);
714
+ }
715
+ output.push("]");
716
+ return;
717
+ }
718
+ // Plain object (null-prototype after detachment)
719
+ if (typeof value === "object" && value !== null) {
720
+ const keys = Object.keys(value as Record<string, unknown>).sort(compareJcsKeys);
721
+ output.push("{");
722
+ for (let i = 0; i < keys.length; i++) {
723
+ if (i > 0) output.push(",");
724
+ output.push(escapeString(keys[i]));
725
+ output.push(":");
726
+ encodeValue((value as Record<string, unknown>)[keys[i]], output);
727
+ }
728
+ output.push("}");
729
+ return;
730
+ }
731
+ }