@fuaran-ui/conformance 0.1.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 (301) hide show
  1. package/LICENSE +215 -0
  2. package/README.md +85 -0
  3. package/corpus/elicitation/elc-ans-accept-full.json +1 -0
  4. package/corpus/elicitation/elc-ans-accept-int-for-float.json +1 -0
  5. package/corpus/elicitation/elc-ans-accept-minimal.json +1 -0
  6. package/corpus/elicitation/elc-ans-reject-enum.json +1 -0
  7. package/corpus/elicitation/elc-ans-reject-float-range.json +1 -0
  8. package/corpus/elicitation/elc-ans-reject-int-range.json +1 -0
  9. package/corpus/elicitation/elc-ans-reject-missing-required.json +1 -0
  10. package/corpus/elicitation/elc-ans-reject-string-len.json +1 -0
  11. package/corpus/elicitation/elc-ans-reject-type-mismatch.json +1 -0
  12. package/corpus/elicitation/elc-ans-reject-undeclared.json +1 -0
  13. package/corpus/elicitation/elc-full.json +1 -0
  14. package/corpus/elicitation/elc-minimal.json +1 -0
  15. package/corpus/elicitation/elc-out-answered.json +1 -0
  16. package/corpus/elicitation/elc-out-declined.json +1 -0
  17. package/corpus/elicitation/elc-out-reject-empty-id.json +1 -0
  18. package/corpus/elicitation/elc-out-reject-missing-answer.json +1 -0
  19. package/corpus/elicitation/elc-out-reject-stray-key.json +1 -0
  20. package/corpus/elicitation/elc-out-reject-unknown-type.json +1 -0
  21. package/corpus/elicitation/elc-out-superseded-anon.json +1 -0
  22. package/corpus/elicitation/elc-out-superseded-by.json +1 -0
  23. package/corpus/elicitation/elc-out-timedout.json +1 -0
  24. package/corpus/elicitation/elc-reject-bad-space.json +1 -0
  25. package/corpus/elicitation/elc-reject-bad-timeout.json +1 -0
  26. package/corpus/elicitation/elc-reject-bad-version.json +1 -0
  27. package/corpus/elicitation/elc-reject-default-nonconformant.json +1 -0
  28. package/corpus/elicitation/elc-reject-duplicate-field.json +1 -0
  29. package/corpus/elicitation/elc-reject-empty-contract.json +1 -0
  30. package/corpus/elicitation/elc-reject-empty-id.json +1 -0
  31. package/corpus/elicitation/elc-reject-missing-version.json +1 -0
  32. package/corpus/elicitation/elc-reject-stray-key.json +1 -0
  33. package/corpus/elicitation/elc-reject-tree-decode.json +1 -0
  34. package/corpus/elicitation/elc-reject-unknown-node.json +1 -0
  35. package/corpus/envelope/envelope-foreign-major.json +1 -0
  36. package/corpus/envelope/envelope-foreign-namespace.json +1 -0
  37. package/corpus/envelope/envelope-known-current.json +1 -0
  38. package/corpus/envelope/envelope-unknown-behind.json +1 -0
  39. package/corpus/envelope/envelope-unknown-malformed-required-profile.json +1 -0
  40. package/corpus/envelope/envelope-unknown-required-profile.json +1 -0
  41. package/corpus/lenient/legacy-upgrade-card.expected.json +1 -0
  42. package/corpus/lenient/legacy-upgrade-card.json +1 -0
  43. package/corpus/lenient/legacy-upgrade-dashboard.expected.json +1 -0
  44. package/corpus/lenient/legacy-upgrade-dashboard.json +1 -0
  45. package/corpus/lenient/legacy-upgrade-gridlayout.expected.json +1 -0
  46. package/corpus/lenient/legacy-upgrade-gridlayout.json +1 -0
  47. package/corpus/lenient/legacy-upgrade-stack.expected.json +1 -0
  48. package/corpus/lenient/legacy-upgrade-stack.json +1 -0
  49. package/corpus/lenient/legacy-upgrade-table.expected.json +1 -0
  50. package/corpus/lenient/legacy-upgrade-table.json +1 -0
  51. package/corpus/lenient/lenient-022-lvr-emphasis-loud.expected.json +1 -0
  52. package/corpus/lenient/lenient-022-lvr-emphasis-loud.json +1 -0
  53. package/corpus/lenient/lenient-022-lvr-emphasis-normal.expected.json +1 -0
  54. package/corpus/lenient/lenient-022-lvr-emphasis-normal.json +1 -0
  55. package/corpus/lenient/lenient-460-alias-emphasis-muted.expected.json +1 -0
  56. package/corpus/lenient/lenient-460-alias-emphasis-muted.json +1 -0
  57. package/corpus/lenient/lenient-460-alias-emphasis-strong.expected.json +1 -0
  58. package/corpus/lenient/lenient-460-alias-emphasis-strong.json +1 -0
  59. package/corpus/lenient/lenient-460-alias-tone-danger.expected.json +1 -0
  60. package/corpus/lenient/lenient-460-alias-tone-danger.json +1 -0
  61. package/corpus/lenient/lenient-460-alias-tone-positive.expected.json +1 -0
  62. package/corpus/lenient/lenient-460-alias-tone-positive.json +1 -0
  63. package/corpus/lenient/lenient-460-explicit-default-column.expected.json +1 -0
  64. package/corpus/lenient/lenient-460-explicit-default-column.json +1 -0
  65. package/corpus/lenient/lenient-460-explicit-default-metric.expected.json +1 -0
  66. package/corpus/lenient/lenient-460-explicit-default-metric.json +1 -0
  67. package/corpus/lenient/lenient-460-explicit-default-style.expected.json +1 -0
  68. package/corpus/lenient/lenient-460-explicit-default-style.json +1 -0
  69. package/corpus/lenient/lenient-596-form-explicit-auto-state.expected.json +1 -0
  70. package/corpus/lenient/lenient-596-form-explicit-auto-state.json +1 -0
  71. package/corpus/lenient/lenient-alias-call-url.expected.json +1 -0
  72. package/corpus/lenient/lenient-alias-call-url.json +1 -0
  73. package/corpus/lenient/lenient-alias-card-title-metric-value.expected.json +1 -0
  74. package/corpus/lenient/lenient-alias-card-title-metric-value.json +1 -0
  75. package/corpus/lenient/lenient-alias-datagrid-data-column-type.expected.json +1 -0
  76. package/corpus/lenient/lenient-alias-datagrid-data-column-type.json +1 -0
  77. package/corpus/lenient/lenient-alias-form-field-name.expected.json +1 -0
  78. package/corpus/lenient/lenient-alias-form-field-name.json +1 -0
  79. package/corpus/lenient/lenient-alias-grid-columns-row.expected.json +1 -0
  80. package/corpus/lenient/lenient-alias-grid-columns-row.json +1 -0
  81. package/corpus/lenient/lenient-alias-navigate-href.expected.json +1 -0
  82. package/corpus/lenient/lenient-alias-navigate-href.json +1 -0
  83. package/corpus/lenient/lenient-alias-select-options-query-deps.expected.json +1 -0
  84. package/corpus/lenient/lenient-alias-select-options-query-deps.json +1 -0
  85. package/corpus/lenient/lenient-bare-text-button-label.expected.json +1 -0
  86. package/corpus/lenient/lenient-bare-text-button-label.json +1 -0
  87. package/corpus/lenient/lenient-bare-text-callout.expected.json +1 -0
  88. package/corpus/lenient/lenient-bare-text-callout.json +1 -0
  89. package/corpus/lenient/lenient-bare-text-heading.expected.json +1 -0
  90. package/corpus/lenient/lenient-bare-text-heading.json +1 -0
  91. package/corpus/lenient/lenient-bare-text-markdown.expected.json +1 -0
  92. package/corpus/lenient/lenient-bare-text-markdown.json +1 -0
  93. package/corpus/lenient/lenient-binding-bound-wrapper.expected.json +1 -0
  94. package/corpus/lenient/lenient-binding-bound-wrapper.json +1 -0
  95. package/corpus/lenient/lenient-emphasis-cross-vocab.expected.json +1 -0
  96. package/corpus/lenient/lenient-emphasis-cross-vocab.json +1 -0
  97. package/corpus/lenient/lenient-fact-explicit-defaults.expected.json +1 -0
  98. package/corpus/lenient/lenient-fact-explicit-defaults.json +1 -0
  99. package/corpus/lenient/lenient-null-static-options.expected.json +1 -0
  100. package/corpus/lenient/lenient-null-static-options.json +1 -0
  101. package/corpus/lenient/lenient-opaque-static-markers.expected.json +1 -0
  102. package/corpus/lenient/lenient-opaque-static-markers.json +1 -0
  103. package/corpus/lenient/lenient-opaque-static-options.expected.json +1 -0
  104. package/corpus/lenient/lenient-opaque-static-options.json +1 -0
  105. package/corpus/lenient/lenient-opaque-static-series.expected.json +1 -0
  106. package/corpus/lenient/lenient-opaque-static-series.json +1 -0
  107. package/corpus/lenient/lenient-opaque-static-values.expected.json +1 -0
  108. package/corpus/lenient/lenient-opaque-static-values.json +1 -0
  109. package/corpus/lenient/lenient-shape-binding-scalar-fraction.expected.json +1 -0
  110. package/corpus/lenient/lenient-shape-binding-scalar-fraction.json +1 -0
  111. package/corpus/lenient/lenient-shape-grid-no-cols.expected.json +1 -0
  112. package/corpus/lenient/lenient-shape-grid-no-cols.json +1 -0
  113. package/corpus/lenient/lenient-shape-grid-template-no-cols.expected.json +1 -0
  114. package/corpus/lenient/lenient-shape-grid-template-no-cols.json +1 -0
  115. package/corpus/lenient/lenient-shape-options-bare-strings.expected.json +1 -0
  116. package/corpus/lenient/lenient-shape-options-bare-strings.json +1 -0
  117. package/corpus/lenient/lenient-shape-params-map.expected.json +1 -0
  118. package/corpus/lenient/lenient-shape-params-map.json +1 -0
  119. package/corpus/lenient/lenient-shape-segmented-orientation-omitted.expected.json +1 -0
  120. package/corpus/lenient/lenient-shape-segmented-orientation-omitted.json +1 -0
  121. package/corpus/lenient/lenient-shape-static-envelope-plain-scalars.expected.json +1 -0
  122. package/corpus/lenient/lenient-shape-static-envelope-plain-scalars.json +1 -0
  123. package/corpus/lenient/lenient-transform-bare-columns.expected.json +1 -0
  124. package/corpus/lenient/lenient-transform-bare-columns.json +1 -0
  125. package/corpus/lenient/lenient-transform-epoch-timestamps.expected.json +1 -0
  126. package/corpus/lenient/lenient-transform-epoch-timestamps.json +1 -0
  127. package/corpus/lenient/lenient-transform-expr-spellings.expected.json +1 -0
  128. package/corpus/lenient/lenient-transform-expr-spellings.json +1 -0
  129. package/corpus/lenient/lenient-transform-flat-contains.expected.json +1 -0
  130. package/corpus/lenient/lenient-transform-flat-contains.json +1 -0
  131. package/corpus/lenient/lenient-transform-flat-filter.expected.json +1 -0
  132. package/corpus/lenient/lenient-transform-flat-filter.json +1 -0
  133. package/corpus/lenient/lenient-transform-flat-or.expected.json +1 -0
  134. package/corpus/lenient/lenient-transform-flat-or.json +1 -0
  135. package/corpus/lenient/lenient-transform-flat-scalar-fn.expected.json +1 -0
  136. package/corpus/lenient/lenient-transform-flat-scalar-fn.json +1 -0
  137. package/corpus/lenient/lenient-transform-schemaless.expected.json +1 -0
  138. package/corpus/lenient/lenient-transform-schemaless.json +1 -0
  139. package/corpus/lenient/lenient-transform-step-aliases.expected.json +1 -0
  140. package/corpus/lenient/lenient-transform-step-aliases.json +1 -0
  141. package/corpus/lenient/lenient-transform-values-only-columns.expected.json +1 -0
  142. package/corpus/lenient/lenient-transform-values-only-columns.json +1 -0
  143. package/corpus/lenient/lenient-window-cumsum-legacy.expected.json +1 -0
  144. package/corpus/lenient/lenient-window-cumsum-legacy.json +1 -0
  145. package/corpus/manifest.json +2000 -0
  146. package/corpus/nodes/badge-1.json +1 -0
  147. package/corpus/nodes/boundary-1.json +1 -0
  148. package/corpus/nodes/btn-1.json +1 -0
  149. package/corpus/nodes/btn-copy-link.json +1 -0
  150. package/corpus/nodes/btn-invoke.json +1 -0
  151. package/corpus/nodes/btn-read-workbook.json +1 -0
  152. package/corpus/nodes/call-into.json +1 -0
  153. package/corpus/nodes/callout-1.json +1 -0
  154. package/corpus/nodes/card-1.json +1 -0
  155. package/corpus/nodes/chart-1.json +1 -0
  156. package/corpus/nodes/code-1.json +1 -0
  157. package/corpus/nodes/composite-root.json +1 -0
  158. package/corpus/nodes/controls-closure.json +1 -0
  159. package/corpus/nodes/controls-declarative.json +1 -0
  160. package/corpus/nodes/custom-1.json +1 -0
  161. package/corpus/nodes/custom-bounded-1.json +1 -0
  162. package/corpus/nodes/custom-bounded-advisory.json +1 -0
  163. package/corpus/nodes/dash-empty.json +1 -0
  164. package/corpus/nodes/discl-1.json +1 -0
  165. package/corpus/nodes/drawing-1.json +1 -0
  166. package/corpus/nodes/drawing-empty.json +1 -0
  167. package/corpus/nodes/fact-1.json +1 -0
  168. package/corpus/nodes/filterable-static-dashboard.json +1 -0
  169. package/corpus/nodes/filters-1.json +1 -0
  170. package/corpus/nodes/filters-declarative.json +1 -0
  171. package/corpus/nodes/filters-segmented.json +1 -0
  172. package/corpus/nodes/form-1.json +1 -0
  173. package/corpus/nodes/form-date.json +1 -0
  174. package/corpus/nodes/form-declarative-minimal.json +1 -0
  175. package/corpus/nodes/form-declarative.json +1 -0
  176. package/corpus/nodes/form-local-1.json +1 -0
  177. package/corpus/nodes/form-local-debounce.json +1 -0
  178. package/corpus/nodes/form-ranged.json +1 -0
  179. package/corpus/nodes/form-segmented.json +1 -0
  180. package/corpus/nodes/format-bindings.json +1 -0
  181. package/corpus/nodes/frag-decl-1.json +1 -0
  182. package/corpus/nodes/frag-decl-param.json +1 -0
  183. package/corpus/nodes/frag-ref-1.json +1 -0
  184. package/corpus/nodes/frag-ref-args.json +1 -0
  185. package/corpus/nodes/glayout-1.json +1 -0
  186. package/corpus/nodes/glayout-tpl-autofit.json +1 -0
  187. package/corpus/nodes/glayout-tpl-fixed.json +1 -0
  188. package/corpus/nodes/glayout-tpl-ratio.json +1 -0
  189. package/corpus/nodes/grid-1.json +1 -0
  190. package/corpus/nodes/grid-field-named.json +1 -0
  191. package/corpus/nodes/grid-transform-param.json +1 -0
  192. package/corpus/nodes/grid-transform.json +1 -0
  193. package/corpus/nodes/heading-1.json +1 -0
  194. package/corpus/nodes/image-1.json +1 -0
  195. package/corpus/nodes/link-1.json +1 -0
  196. package/corpus/nodes/list-1.json +1 -0
  197. package/corpus/nodes/lvr-1.json +1 -0
  198. package/corpus/nodes/map-1.json +1 -0
  199. package/corpus/nodes/markdown-1.json +1 -0
  200. package/corpus/nodes/master-detail-preselected.json +1 -0
  201. package/corpus/nodes/math-1.json +1 -0
  202. package/corpus/nodes/metric-1.json +1 -0
  203. package/corpus/nodes/metric-float-17sig.json +1 -0
  204. package/corpus/nodes/metric-float-bigint.json +1 -0
  205. package/corpus/nodes/metric-float-exp-neg.json +1 -0
  206. package/corpus/nodes/metric-float-exp-pos.json +1 -0
  207. package/corpus/nodes/metric-invoke.json +1 -0
  208. package/corpus/nodes/modal-1.json +1 -0
  209. package/corpus/nodes/mount-1.json +1 -0
  210. package/corpus/nodes/mount-2.json +1 -0
  211. package/corpus/nodes/multiselect-1.json +1 -0
  212. package/corpus/nodes/progress-1.json +1 -0
  213. package/corpus/nodes/query-dependson.json +1 -0
  214. package/corpus/nodes/scalar-transform-composition.json +1 -0
  215. package/corpus/nodes/scroll-1.json +1 -0
  216. package/corpus/nodes/select-1.json +1 -0
  217. package/corpus/nodes/skel-1.json +1 -0
  218. package/corpus/nodes/spark-1.json +1 -0
  219. package/corpus/nodes/split-1.json +1 -0
  220. package/corpus/nodes/stack-1.json +1 -0
  221. package/corpus/nodes/step-1.json +1 -0
  222. package/corpus/nodes/style-role-voice-1.json +1 -0
  223. package/corpus/nodes/summary-1.json +1 -0
  224. package/corpus/nodes/switch-1.json +1 -0
  225. package/corpus/nodes/table-1.json +1 -0
  226. package/corpus/nodes/tabs-1.json +1 -0
  227. package/corpus/nodes/tabs-explicit-1.json +1 -0
  228. package/corpus/nodes/toast-1.json +1 -0
  229. package/corpus/nodes/upload-1.json +1 -0
  230. package/corpus/ops/op-batch.json +1 -0
  231. package/corpus/ops/op-editnode.json +1 -0
  232. package/corpus/ops/op-insertchild.json +1 -0
  233. package/corpus/ops/op-movenode.json +1 -0
  234. package/corpus/ops/op-removenode.json +1 -0
  235. package/corpus/ops/op-reorderchildren.json +1 -0
  236. package/corpus/ops/op-replacebinding.json +1 -0
  237. package/corpus/ops/op-replaceroot.json +1 -0
  238. package/corpus/ops/op-updateprop-nested-badfield.json +1 -0
  239. package/corpus/ops/op-updateprop-nested-badindex.json +1 -0
  240. package/corpus/ops/op-updateprop-nested-column0-label.json +1 -0
  241. package/corpus/ops/op-updateprop-nested-column1-label.json +1 -0
  242. package/corpus/ops/op-updateprop-nested-field0-required.json +1 -0
  243. package/corpus/ops/op-updateprop-nested-field1-required.json +1 -0
  244. package/corpus/ops/op-updateprop-nested-malformed.json +1 -0
  245. package/corpus/ops/op-updateprop-nested-object-value.json +1 -0
  246. package/corpus/ops/op-updateprop-nested-yfield0.json +1 -0
  247. package/corpus/ops/op-updateprop-nested-yfield1.json +1 -0
  248. package/corpus/ops/op-updateprop.json +1 -0
  249. package/corpus/ops/op-updatestate.json +1 -0
  250. package/corpus/ops/op-updatestyle (phase 147 role=eyebrow + voice=structural).json +1 -0
  251. package/corpus/ops/op-updatestyle.json +1 -0
  252. package/corpus/reject/reject-emptynodeid.json +1 -0
  253. package/corpus/reject/reject-invalid-empty.json +0 -0
  254. package/corpus/reject/reject-invalid-garbage.json +1 -0
  255. package/corpus/reject/reject-invalid-truncated.json +1 -0
  256. package/corpus/reject/reject-missing-binding-type.json +1 -0
  257. package/corpus/reject/reject-missing-custom-moduleid.json +1 -0
  258. package/corpus/reject/reject-missing-markdown-text.json +1 -0
  259. package/corpus/reject/reject-missing-metric-value.json +1 -0
  260. package/corpus/reject/reject-missing-mount-scopeid.json +1 -0
  261. package/corpus/reject/reject-missing-node-id.json +1 -0
  262. package/corpus/reject/reject-missing-node-kind.json +1 -0
  263. package/corpus/reject/reject-missing-switch-cases.json +1 -0
  264. package/corpus/reject/reject-missing-switch-default.json +1 -0
  265. package/corpus/reject/reject-missing-switch-statekey.json +1 -0
  266. package/corpus/reject/reject-null-action-aitool-args.json +1 -0
  267. package/corpus/reject/reject-null-action-notify-payload.json +1 -0
  268. package/corpus/reject/reject-null-action-setstate-value.json +1 -0
  269. package/corpus/reject/reject-null-custom-prop.json +1 -0
  270. package/corpus/reject/reject-null-i18n-arg.json +1 -0
  271. package/corpus/reject/reject-null-updateprop-value.json +1 -0
  272. package/corpus/reject/reject-op-insertchild-empty-childid.json +1 -0
  273. package/corpus/reject/reject-op-insertchild-missing-parentid.json +1 -0
  274. package/corpus/reject/reject-op-missing-type.json +1 -0
  275. package/corpus/reject/reject-op-reorderchildren-wrongtype.json +1 -0
  276. package/corpus/reject/reject-op-unknown-yeet.json +1 -0
  277. package/corpus/reject/reject-op-updateprop-missing-path.json +1 -0
  278. package/corpus/reject/reject-unknown-binding.json +1 -0
  279. package/corpus/reject/reject-unknown-drawing-curve-command.json +1 -0
  280. package/corpus/reject/reject-unknown-drawing-shape.json +1 -0
  281. package/corpus/reject/reject-unknown-mount-direction.json +1 -0
  282. package/corpus/reject/reject-unknown-textsource.json +1 -0
  283. package/corpus/reject/reject-unknown-tone.json +1 -0
  284. package/corpus/reject/reject-wrongnodekind-sparkler.json +1 -0
  285. package/corpus/reject/reject-wrongnodekind-widget.json +1 -0
  286. package/corpus/reject/reject-wrongtype-dashboard-children.json +1 -0
  287. package/corpus/reject/reject-wrongtype-discriminator.json +1 -0
  288. package/corpus/reject/reject-wrongtype-heading-level.json +1 -0
  289. package/corpus/reject/reject-wrongtype-id.json +1 -0
  290. package/corpus/reject/reject-wrongtype-kind.json +1 -0
  291. package/corpus/reject/reject-wrongtype-style-tone.json +1 -0
  292. package/corpus/schema.json +4396 -0
  293. package/dist/cli.js +612 -0
  294. package/dist/cli.js.map +1 -0
  295. package/dist/index.cjs +579 -0
  296. package/dist/index.cjs.map +1 -0
  297. package/dist/index.d.cts +159 -0
  298. package/dist/index.d.ts +159 -0
  299. package/dist/index.js +570 -0
  300. package/dist/index.js.map +1 -0
  301. package/package.json +59 -0
package/dist/index.js ADDED
@@ -0,0 +1,570 @@
1
+ import { createHash } from 'crypto';
2
+ import { readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { Ajv2020 } from 'ajv/dist/2020.js';
6
+
7
+ // src/corpus.ts
8
+ var bundledRoot = () => (
9
+ // dist/index.js → ../corpus (tsup `shims: true` provides import.meta.url under CJS).
10
+ fileURLToPath(new URL("../corpus/", import.meta.url))
11
+ );
12
+ var loadCorpus = (options) => {
13
+ const external = options?.corpusRoot;
14
+ const root = external ?? bundledRoot();
15
+ const read = (relPath) => readFileSync(join(root, relPath), "utf8");
16
+ const manifestText = read("manifest.json");
17
+ const manifest = JSON.parse(manifestText);
18
+ const hash = createHash("sha256");
19
+ hash.update(manifestText);
20
+ hash.update(read(manifest.schema));
21
+ for (const f of manifest.fixtures) {
22
+ hash.update(read(f.inputFile));
23
+ if (f.expectedFile !== void 0 && f.expectedFile !== f.inputFile)
24
+ hash.update(read(f.expectedFile));
25
+ }
26
+ return {
27
+ root,
28
+ source: external === void 0 ? "bundled" : "external",
29
+ manifestVersion: manifest.version,
30
+ fixtures: manifest.fixtures,
31
+ digest: hash.digest("hex"),
32
+ schema: JSON.parse(read(manifest.schema)),
33
+ read
34
+ };
35
+ };
36
+
37
+ // src/report.ts
38
+ var computeVerdict = (legs) => {
39
+ if (legs.some((l) => l.status === "fail")) return "non-conformant";
40
+ if (legs.some((l) => l.mandatory && l.status === "skipped")) return "partially-conformant";
41
+ return "conformant";
42
+ };
43
+ var statusGlyph = { pass: "\u2713", fail: "\u2717", skipped: "\u2013" };
44
+ var formatReport = (report) => {
45
+ const lines = [];
46
+ const impl = report.implementation.version === void 0 ? report.implementation.name : `${report.implementation.name} ${report.implementation.version}`;
47
+ lines.push(`Fuaran wire-format conformance report`);
48
+ lines.push(` kit ${report.kit.name}@${report.kit.version}`);
49
+ lines.push(` implementation ${impl}`);
50
+ lines.push(
51
+ ` corpus v${report.corpus.manifestVersion} (${report.corpus.fixtureCount} fixtures, ${report.corpus.source})`
52
+ );
53
+ lines.push(` corpus digest sha256:${report.corpus.digest}`);
54
+ lines.push(` generated ${report.generatedAt}`);
55
+ lines.push("");
56
+ for (const leg of report.legs) {
57
+ const tier = leg.mandatory ? "mandatory" : "optional ";
58
+ const counts = leg.status === "skipped" ? leg.skipReason ?? "skipped" : `${leg.fixturesPassed}/${leg.fixturesTotal}`;
59
+ lines.push(` ${statusGlyph[leg.status]} ${leg.leg.padEnd(20)} ${tier} ${counts}`);
60
+ for (const f of leg.failures) {
61
+ lines.push(` ${f.fixtureId}: ${f.summary}`);
62
+ if (f.detail !== void 0)
63
+ for (const detailLine of f.detail.split("\n")) lines.push(` ${detailLine}`);
64
+ }
65
+ }
66
+ lines.push("");
67
+ lines.push(` verdict: ${report.verdict.toUpperCase()}`);
68
+ if (report.verdict === "partially-conformant") {
69
+ const skipped = report.legs.filter((l) => l.mandatory && l.status === "skipped").map((l) => l.leg);
70
+ lines.push(` (mandatory legs not attempted: ${skipped.join(", ")})`);
71
+ }
72
+ lines.push(
73
+ ` Certification is per corpus version \u2014 re-certify when the corpus advances (WIRE_FORMAT.md \xA711).`
74
+ );
75
+ return lines.join("\n");
76
+ };
77
+ var KIT_NAME = "@fuaran-ui/conformance";
78
+ var KIT_VERSION = "0.1.0";
79
+ var canon = (s) => s.replace(/\r?\n$/, "");
80
+ var firstDiff = (expected, actual) => {
81
+ const n = Math.min(expected.length, actual.length);
82
+ let i = 0;
83
+ while (i < n && expected[i] === actual[i]) i++;
84
+ const from = Math.max(0, i - 30);
85
+ const win = (s) => JSON.stringify(s.slice(from, i + 30));
86
+ return `byte offset ${i} (length canonical=${expected.length}, host=${actual.length})
87
+ canonical \u2026${win(expected)}\u2026
88
+ host \u2026${win(actual)}\u2026`;
89
+ };
90
+ var messageOf = (err) => err instanceof Error ? err.message : String(err);
91
+ var newAcc = () => ({ total: 0, passed: 0, failures: [] });
92
+ var toLeg = (leg, mandatory, acc, skipReason) => acc === void 0 ? {
93
+ leg,
94
+ mandatory,
95
+ status: "skipped",
96
+ ...skipReason === void 0 ? {} : { skipReason },
97
+ fixturesTotal: 0,
98
+ fixturesPassed: 0,
99
+ failures: []
100
+ } : {
101
+ leg,
102
+ mandatory,
103
+ status: acc.failures.length === 0 ? "pass" : "fail",
104
+ fixturesTotal: acc.total,
105
+ fixturesPassed: acc.passed,
106
+ failures: acc.failures
107
+ };
108
+ var runConformance = (adapter, options) => {
109
+ const corpus = loadCorpus(
110
+ options.corpusRoot === void 0 ? void 0 : { corpusRoot: options.corpusRoot }
111
+ );
112
+ const ajv = new Ajv2020({ strict: false, allErrors: true });
113
+ const schemaValid = ajv.compile(corpus.schema);
114
+ const accepts = (decoder) => corpus.fixtures.filter(
115
+ (f) => f.decoder === decoder && (f.kind === "node-round-trip" || f.kind === "op-round-trip")
116
+ );
117
+ const rejects = (decoder) => corpus.fixtures.filter((f) => f.decoder === decoder && f.kind === "reject");
118
+ const lenients = (decoder) => corpus.fixtures.filter((f) => f.decoder === decoder && f.kind === "lenient-accept");
119
+ const accs = /* @__PURE__ */ new Map();
120
+ const acc = (leg) => {
121
+ let a = accs.get(leg);
122
+ if (a === void 0) {
123
+ a = newAcc();
124
+ accs.set(leg, a);
125
+ }
126
+ return a;
127
+ };
128
+ const runSide = (decoder, decode, encode) => {
129
+ const decodeLeg = decoder === "node" ? "node-decode" : "op-decode";
130
+ const byteLeg = decoder === "node" ? "node-byte-identity" : "op-byte-identity";
131
+ const rejectLeg = decoder === "node" ? "node-reject" : "op-reject";
132
+ if (decode === void 0) return;
133
+ for (const f of accepts(decoder)) {
134
+ const input = corpus.read(f.inputFile);
135
+ const d = acc(decodeLeg);
136
+ d.total++;
137
+ let decoded;
138
+ try {
139
+ decoded = decode(input);
140
+ } catch (err) {
141
+ d.failures.push({
142
+ fixtureId: f.id,
143
+ summary: "decoder THREW on an accept fixture (a conformant decoder never throws)",
144
+ detail: messageOf(err)
145
+ });
146
+ continue;
147
+ }
148
+ if (!decoded.ok) {
149
+ d.failures.push({
150
+ fixtureId: f.id,
151
+ summary: "decoder rejected an accept fixture",
152
+ detail: `${decoded.error.code} at ${decoded.error.path}${decoded.error.message === void 0 ? "" : ` \u2014 ${decoded.error.message}`}`
153
+ });
154
+ continue;
155
+ }
156
+ d.passed++;
157
+ if (encode === void 0) continue;
158
+ const b = acc(byteLeg);
159
+ b.total++;
160
+ const expected = canon(corpus.read(f.expectedFile ?? f.inputFile));
161
+ let actual;
162
+ try {
163
+ actual = encode(decoded.value);
164
+ } catch (err) {
165
+ b.failures.push({
166
+ fixtureId: f.id,
167
+ summary: "encoder THREW re-encoding a decoded accept fixture",
168
+ detail: messageOf(err)
169
+ });
170
+ continue;
171
+ }
172
+ if (actual !== expected) {
173
+ b.failures.push({
174
+ fixtureId: f.id,
175
+ summary: "encoder output diverges from the canonical form",
176
+ detail: firstDiff(expected, actual)
177
+ });
178
+ continue;
179
+ }
180
+ b.passed++;
181
+ const s = acc("schema-validation");
182
+ s.total++;
183
+ if (schemaValid(JSON.parse(actual))) {
184
+ s.passed++;
185
+ } else {
186
+ s.failures.push({
187
+ fixtureId: f.id,
188
+ summary: "encoder output fails the canonical JSON Schema (schema.json)",
189
+ detail: (schemaValid.errors ?? []).map((e) => `${e.instancePath === "" ? "$" : e.instancePath} ${e.message ?? ""}`).join("\n")
190
+ });
191
+ }
192
+ }
193
+ if (encode !== void 0) {
194
+ for (const f of lenients(decoder)) {
195
+ const input = corpus.read(f.inputFile);
196
+ const l = acc("lenient-accept");
197
+ l.total++;
198
+ let decoded;
199
+ try {
200
+ decoded = decode(input);
201
+ } catch (err) {
202
+ l.failures.push({
203
+ fixtureId: f.id,
204
+ summary: "decoder THREW on a \xA716 shorthand (a conformant decoder accepts it)",
205
+ detail: messageOf(err)
206
+ });
207
+ continue;
208
+ }
209
+ if (!decoded.ok) {
210
+ l.failures.push({
211
+ fixtureId: f.id,
212
+ summary: "decoder rejected a \xA716 shorthand (MUST accept \u2014 WIRE_FORMAT \xA716)",
213
+ detail: `${decoded.error.code} at ${decoded.error.path}${decoded.error.message === void 0 ? "" : ` \u2014 ${decoded.error.message}`}`
214
+ });
215
+ continue;
216
+ }
217
+ let actual;
218
+ try {
219
+ actual = encode(decoded.value);
220
+ } catch (err) {
221
+ l.failures.push({
222
+ fixtureId: f.id,
223
+ summary: "encoder THREW re-encoding a decoded \xA716 shorthand",
224
+ detail: messageOf(err)
225
+ });
226
+ continue;
227
+ }
228
+ const expected = canon(corpus.read(f.expectedFile ?? f.inputFile));
229
+ if (actual !== expected) {
230
+ l.failures.push({
231
+ fixtureId: f.id,
232
+ summary: "shorthand normalisation diverges from the verbose canonical form",
233
+ detail: firstDiff(expected, actual)
234
+ });
235
+ continue;
236
+ }
237
+ l.passed++;
238
+ }
239
+ }
240
+ for (const f of rejects(decoder)) {
241
+ const input = corpus.read(f.inputFile);
242
+ const r = acc(rejectLeg);
243
+ r.total++;
244
+ let decoded;
245
+ try {
246
+ decoded = decode(input);
247
+ } catch (err) {
248
+ r.failures.push({
249
+ fixtureId: f.id,
250
+ summary: "decoder THREW on a reject fixture (must return a structured error)",
251
+ detail: messageOf(err)
252
+ });
253
+ continue;
254
+ }
255
+ if (decoded.ok) {
256
+ r.failures.push({
257
+ fixtureId: f.id,
258
+ summary: "decoder ACCEPTED a reject fixture",
259
+ detail: `expected ${f.expectedErrorCode} at ${f.expectedPath}`
260
+ });
261
+ continue;
262
+ }
263
+ if (decoded.error.code !== f.expectedErrorCode) {
264
+ r.failures.push({
265
+ fixtureId: f.id,
266
+ summary: "decode-error CODE diverges from the canonical error",
267
+ detail: `expected ${f.expectedErrorCode}, got ${decoded.error.code} (path ${decoded.error.path})`
268
+ });
269
+ continue;
270
+ }
271
+ if (!decoded.error.path.startsWith(f.expectedPath ?? "$")) {
272
+ r.failures.push({
273
+ fixtureId: f.id,
274
+ summary: "decode-error PATH diverges from the canonical error",
275
+ detail: `expected prefix ${f.expectedPath}, got ${decoded.error.path} (code ${decoded.error.code})`
276
+ });
277
+ continue;
278
+ }
279
+ r.passed++;
280
+ }
281
+ };
282
+ runSide("node", adapter.decodeNode, adapter.encodeNode);
283
+ runSide("op", adapter.decodeOp, adapter.encodeOp);
284
+ const negotiateEnvelope = adapter.negotiateEnvelope;
285
+ if (negotiateEnvelope !== void 0) {
286
+ const byKind = (kind) => corpus.fixtures.filter((f) => f.kind === kind);
287
+ for (const f of byKind("envelope-round-trip")) {
288
+ const a = acc("envelope-round-trip");
289
+ a.total++;
290
+ const input = corpus.read(f.inputFile);
291
+ let r;
292
+ try {
293
+ r = negotiateEnvelope(input);
294
+ } catch (err) {
295
+ a.failures.push({
296
+ fixtureId: f.id,
297
+ summary: "negotiateEnvelope THREW on a round-trip fixture (must return a structured result)",
298
+ detail: messageOf(err)
299
+ });
300
+ continue;
301
+ }
302
+ if (!r.ok) {
303
+ a.failures.push({
304
+ fixtureId: f.id,
305
+ summary: "negotiateEnvelope refused a round-trip fixture (Current/Behind must be tolerated)",
306
+ detail: `${r.error.code} at ${r.error.path}`
307
+ });
308
+ continue;
309
+ }
310
+ const expected = canon(corpus.read(f.expectedFile ?? f.inputFile));
311
+ if (typeof r.value !== "string" || r.value !== expected) {
312
+ a.failures.push({
313
+ fixtureId: f.id,
314
+ summary: "envelope re-encode diverges from the canonical form (must-ignore-but-preserve)",
315
+ detail: typeof r.value === "string" ? firstDiff(expected, r.value) : "value was not a string"
316
+ });
317
+ continue;
318
+ }
319
+ a.passed++;
320
+ }
321
+ for (const f of byKind("envelope-reject")) {
322
+ const a = acc("envelope-reject");
323
+ a.total++;
324
+ const input = corpus.read(f.inputFile);
325
+ let r;
326
+ try {
327
+ r = negotiateEnvelope(input);
328
+ } catch (err) {
329
+ a.failures.push({
330
+ fixtureId: f.id,
331
+ summary: "negotiateEnvelope THREW on a reject fixture (must return a structured error)",
332
+ detail: messageOf(err)
333
+ });
334
+ continue;
335
+ }
336
+ if (r.ok) {
337
+ a.failures.push({
338
+ fixtureId: f.id,
339
+ summary: "negotiateEnvelope ACCEPTED a Foreign artifact (must hard-refuse)",
340
+ detail: `expected ${f.expectedErrorCode} at ${f.expectedPath}`
341
+ });
342
+ continue;
343
+ }
344
+ if (r.error.code !== f.expectedErrorCode) {
345
+ a.failures.push({
346
+ fixtureId: f.id,
347
+ summary: "refusal CODE diverges from the canonical error",
348
+ detail: `expected ${f.expectedErrorCode}, got ${r.error.code} (path ${r.error.path})`
349
+ });
350
+ continue;
351
+ }
352
+ if (!r.error.path.startsWith(f.expectedPath ?? "$")) {
353
+ a.failures.push({
354
+ fixtureId: f.id,
355
+ summary: "refusal PATH diverges from the canonical error",
356
+ detail: `expected prefix ${f.expectedPath}, got ${r.error.path}`
357
+ });
358
+ continue;
359
+ }
360
+ a.passed++;
361
+ }
362
+ }
363
+ const roundTripElicitation = adapter.roundTripElicitation;
364
+ if (roundTripElicitation !== void 0) {
365
+ const elDecoder = (f) => f.decoder === "elicitation-outcome" ? "elicitation-outcome" : "elicitation";
366
+ for (const f of corpus.fixtures.filter((x) => x.kind === "elicitation-round-trip")) {
367
+ const a = acc("elicitation-round-trip");
368
+ a.total++;
369
+ const input = corpus.read(f.inputFile);
370
+ let r;
371
+ try {
372
+ r = roundTripElicitation(elDecoder(f), input);
373
+ } catch (err) {
374
+ a.failures.push({
375
+ fixtureId: f.id,
376
+ summary: "roundTripElicitation THREW on a round-trip fixture (must return a structured result)",
377
+ detail: messageOf(err)
378
+ });
379
+ continue;
380
+ }
381
+ if (!r.ok) {
382
+ a.failures.push({
383
+ fixtureId: f.id,
384
+ summary: "roundTripElicitation refused a round-trip fixture",
385
+ detail: `${r.error.code} at ${r.error.path}`
386
+ });
387
+ continue;
388
+ }
389
+ const expected = canon(corpus.read(f.expectedFile ?? f.inputFile));
390
+ if (typeof r.value !== "string" || r.value !== expected) {
391
+ a.failures.push({
392
+ fixtureId: f.id,
393
+ summary: "elicitation re-encode diverges from the canonical form",
394
+ detail: typeof r.value === "string" ? firstDiff(expected, r.value) : "value was not a string"
395
+ });
396
+ continue;
397
+ }
398
+ a.passed++;
399
+ }
400
+ for (const f of corpus.fixtures.filter((x) => x.kind === "elicitation-reject")) {
401
+ const a = acc("elicitation-reject");
402
+ a.total++;
403
+ const input = corpus.read(f.inputFile);
404
+ let r;
405
+ try {
406
+ r = roundTripElicitation(elDecoder(f), input);
407
+ } catch (err) {
408
+ a.failures.push({
409
+ fixtureId: f.id,
410
+ summary: "roundTripElicitation THREW on a reject fixture (must return a structured error)",
411
+ detail: messageOf(err)
412
+ });
413
+ continue;
414
+ }
415
+ if (r.ok) {
416
+ a.failures.push({
417
+ fixtureId: f.id,
418
+ summary: "roundTripElicitation ACCEPTED a reject fixture",
419
+ detail: `expected ${f.expectedErrorCode} at ${f.expectedPath}`
420
+ });
421
+ continue;
422
+ }
423
+ if (r.error.code !== f.expectedErrorCode) {
424
+ a.failures.push({
425
+ fixtureId: f.id,
426
+ summary: "refusal CODE diverges from the canonical error",
427
+ detail: `expected ${f.expectedErrorCode}, got ${r.error.code} (path ${r.error.path})`
428
+ });
429
+ continue;
430
+ }
431
+ if (!r.error.path.startsWith(f.expectedPath ?? "$")) {
432
+ a.failures.push({
433
+ fixtureId: f.id,
434
+ summary: "refusal PATH diverges from the canonical error",
435
+ detail: `expected prefix ${f.expectedPath}, got ${r.error.path}`
436
+ });
437
+ continue;
438
+ }
439
+ a.passed++;
440
+ }
441
+ }
442
+ const validateAnswerDocument = adapter.validateAnswerDocument;
443
+ if (validateAnswerDocument !== void 0) {
444
+ for (const f of corpus.fixtures.filter(
445
+ (x) => x.kind === "elicitation-answer-accept" || x.kind === "elicitation-answer-reject"
446
+ )) {
447
+ const a = acc("elicitation-answer");
448
+ a.total++;
449
+ const input = corpus.read(f.inputFile);
450
+ let r;
451
+ try {
452
+ r = validateAnswerDocument(input);
453
+ } catch (err) {
454
+ a.failures.push({
455
+ fixtureId: f.id,
456
+ summary: "validateAnswerDocument THREW (must return a structured result)",
457
+ detail: messageOf(err)
458
+ });
459
+ continue;
460
+ }
461
+ if (f.kind === "elicitation-answer-accept") {
462
+ if (!r.ok) {
463
+ a.failures.push({
464
+ fixtureId: f.id,
465
+ summary: "validateAnswerDocument refused a conforming answer",
466
+ detail: `${r.error.code} at ${r.error.path}`
467
+ });
468
+ continue;
469
+ }
470
+ a.passed++;
471
+ continue;
472
+ }
473
+ if (r.ok) {
474
+ a.failures.push({
475
+ fixtureId: f.id,
476
+ summary: "validateAnswerDocument ACCEPTED a nonconforming answer",
477
+ detail: `expected ${f.expectedErrorCode} at ${f.expectedPath}`
478
+ });
479
+ continue;
480
+ }
481
+ if (r.error.code !== f.expectedErrorCode) {
482
+ a.failures.push({
483
+ fixtureId: f.id,
484
+ summary: "refusal CODE diverges from the canonical error",
485
+ detail: `expected ${f.expectedErrorCode}, got ${r.error.code} (path ${r.error.path})`
486
+ });
487
+ continue;
488
+ }
489
+ if (!r.error.path.startsWith(f.expectedPath ?? "$")) {
490
+ a.failures.push({
491
+ fixtureId: f.id,
492
+ summary: "refusal PATH diverges from the canonical error",
493
+ detail: `expected prefix ${f.expectedPath}, got ${r.error.path}`
494
+ });
495
+ continue;
496
+ }
497
+ a.passed++;
498
+ }
499
+ }
500
+ const need = (hooks) => `adapter does not provide ${hooks.join(" + ")}`;
501
+ const hasSchemaInput = adapter.encodeNode !== void 0 || adapter.encodeOp !== void 0;
502
+ const legs = [
503
+ toLeg("node-decode", true, accs.get("node-decode"), need(["decodeNode"])),
504
+ toLeg(
505
+ "node-byte-identity",
506
+ true,
507
+ accs.get("node-byte-identity"),
508
+ need(["decodeNode", "encodeNode"])
509
+ ),
510
+ toLeg("node-reject", true, accs.get("node-reject"), need(["decodeNode"])),
511
+ toLeg("op-decode", true, accs.get("op-decode"), need(["decodeOp"])),
512
+ toLeg("op-byte-identity", true, accs.get("op-byte-identity"), need(["decodeOp", "encodeOp"])),
513
+ toLeg("op-reject", true, accs.get("op-reject"), need(["decodeOp"])),
514
+ toLeg(
515
+ "lenient-accept",
516
+ true,
517
+ accs.get("lenient-accept"),
518
+ need(["a decode + encode hook pair (node and/or op)"])
519
+ ),
520
+ toLeg(
521
+ "envelope-round-trip",
522
+ true,
523
+ accs.get("envelope-round-trip"),
524
+ need(["negotiateEnvelope"])
525
+ ),
526
+ toLeg("envelope-reject", true, accs.get("envelope-reject"), need(["negotiateEnvelope"])),
527
+ toLeg(
528
+ "elicitation-round-trip",
529
+ true,
530
+ accs.get("elicitation-round-trip"),
531
+ need(["roundTripElicitation"])
532
+ ),
533
+ toLeg(
534
+ "elicitation-reject",
535
+ true,
536
+ accs.get("elicitation-reject"),
537
+ need(["roundTripElicitation"])
538
+ ),
539
+ toLeg(
540
+ "elicitation-answer",
541
+ true,
542
+ accs.get("elicitation-answer"),
543
+ need(["validateAnswerDocument"])
544
+ ),
545
+ toLeg(
546
+ "schema-validation",
547
+ true,
548
+ accs.get("schema-validation"),
549
+ hasSchemaInput ? need(["a decode hook for the encode hooks provided"]) : need(["encodeNode and/or encodeOp"])
550
+ ),
551
+ toLeg("apply", false, void 0, "corpus v1 ships no apply fixtures \u2014 leg reserved")
552
+ ];
553
+ return {
554
+ kit: { name: KIT_NAME, version: KIT_VERSION },
555
+ implementation: options.implementation,
556
+ corpus: {
557
+ manifestVersion: corpus.manifestVersion,
558
+ digest: corpus.digest,
559
+ fixtureCount: corpus.fixtures.length,
560
+ source: corpus.source
561
+ },
562
+ legs,
563
+ verdict: computeVerdict(legs),
564
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
565
+ };
566
+ };
567
+
568
+ export { KIT_NAME, KIT_VERSION, computeVerdict, formatReport, loadCorpus, runConformance };
569
+ //# sourceMappingURL=index.js.map
570
+ //# sourceMappingURL=index.js.map