ibex 0.2.0 → 0.3.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. checksums.yaml +4 -4
  2. data/README.md +95 -272
  3. data/docs/architecture.md +187 -35
  4. data/docs/bison-import.md +103 -0
  5. data/docs/comparison-policy.md +127 -0
  6. data/docs/configuration-model.md +59 -0
  7. data/docs/configuration-report.md +28 -0
  8. data/docs/conflict-explanation-reviews/v1/records/README.md +28 -0
  9. data/docs/conflict-explanation-study.md +62 -0
  10. data/docs/construction-profiling.md +157 -0
  11. data/docs/cst-migration.md +3 -4
  12. data/docs/cst.md +24 -0
  13. data/docs/decisions/0000-template.md +24 -0
  14. data/docs/decisions/0001-separate-ir-pipeline.md +34 -0
  15. data/docs/decisions/0002-opaque-user-code-boundary.md +32 -0
  16. data/docs/decisions/0003-self-hosted-grammar-frontend.md +32 -0
  17. data/docs/decisions/0004-shared-semantic-and-lossless-source-model.md +33 -0
  18. data/docs/decisions/0005-contained-grammar-composition.md +34 -0
  19. data/docs/decisions/0006-bounded-structural-grammar-lowering.md +32 -0
  20. data/docs/decisions/0007-shared-parser-construction-pipeline.md +33 -0
  21. data/docs/decisions/0008-versioned-runtime-package-boundary.md +36 -0
  22. data/docs/decisions/0009-isolated-parser-sessions.md +34 -0
  23. data/docs/decisions/0010-committed-runtime-observation.md +34 -0
  24. data/docs/decisions/0011-versioned-semantic-action-boundary.md +37 -0
  25. data/docs/decisions/0012-bounded-nonexecuting-analysis.md +37 -0
  26. data/docs/decisions/0013-transactional-generation-publication.md +37 -0
  27. data/docs/decisions/0014-versioned-generated-lexer.md +35 -0
  28. data/docs/decisions/0015-worker-isolated-browser-analysis.md +32 -0
  29. data/docs/decisions/0016-red-green-concrete-syntax.md +34 -0
  30. data/docs/decisions/0017-persistent-syntax-artifacts.md +35 -0
  31. data/docs/decisions/0018-conservative-incremental-syntax-reuse.md +38 -0
  32. data/docs/decisions/0019-runtime-syntax-session-boundary.md +51 -0
  33. data/docs/decisions/0020-grammar-ir-parser-contract.md +65 -0
  34. data/docs/decisions/0021-data-only-parser-table-sidecar.md +50 -0
  35. data/docs/decisions/0022-manifest-bound-verification-report.md +77 -0
  36. data/docs/decisions/0023-syntax-only-repair-results.md +48 -0
  37. data/docs/decisions/0024-direct-ielr-construction.md +42 -0
  38. data/docs/decisions/README.md +74 -0
  39. data/docs/declarative-configuration.md +262 -0
  40. data/docs/development.md +179 -2
  41. data/docs/direct-ielr-decision.md +120 -0
  42. data/docs/direct-multi-entry-decision.md +82 -0
  43. data/docs/editor-setup.md +2 -1
  44. data/docs/error-ux-review-rubric-v1.md +117 -0
  45. data/docs/error-ux-reviews/v1/records/README.md +22 -0
  46. data/docs/error-ux-round2-review-status-v1.json +27 -0
  47. data/docs/error-ux-round2-reviews/v1/records/README.md +30 -0
  48. data/docs/error-ux-round2-v1.json +1581 -0
  49. data/docs/error-ux-round2.md +111 -0
  50. data/docs/error-ux.md +54 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +184 -13
  53. data/docs/ielr-design.md +43 -0
  54. data/docs/ielr.md +92 -0
  55. data/docs/ja/bison-import.md +51 -0
  56. data/docs/ja/stable-api.md +53 -0
  57. data/docs/lexer-construction-profile.md +109 -0
  58. data/docs/lexer-migration.md +5 -0
  59. data/docs/maturity.md +128 -0
  60. data/docs/project-site-strategy.md +54 -0
  61. data/docs/racc-migration-evidence.md +45 -0
  62. data/docs/racc-migration.md +11 -0
  63. data/docs/release-readiness.md +126 -68
  64. data/docs/repair-semantics.md +123 -0
  65. data/docs/runtime-abi-evolution.md +307 -0
  66. data/docs/stability.md +144 -34
  67. data/docs/status.md +43 -0
  68. data/docs/syntax-sessions.md +183 -0
  69. data/docs/table-artifact.md +125 -0
  70. data/docs/test-interactions.md +219 -0
  71. data/docs/verification-report.md +137 -0
  72. data/docs/verifier-trust-boundary.md +199 -0
  73. data/docs/workloads.md +132 -0
  74. data/lib/ibex/analysis/digraph.rb +128 -0
  75. data/lib/ibex/analysis.rb +1 -0
  76. data/lib/ibex/bison_import/importer.rb +516 -0
  77. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  78. data/lib/ibex/bison_import.rb +200 -0
  79. data/lib/ibex/bounded_subprocess.rb +171 -0
  80. data/lib/ibex/cli/ambiguity.rb +14 -5
  81. data/lib/ibex/cli/analysis.rb +134 -0
  82. data/lib/ibex/cli/bison_import.rb +121 -0
  83. data/lib/ibex/cli/config.rb +128 -0
  84. data/lib/ibex/cli/diagnostics.rb +31 -11
  85. data/lib/ibex/cli/documentation.rb +11 -8
  86. data/lib/ibex/cli/equiv.rb +169 -0
  87. data/lib/ibex/cli/error_messages.rb +11 -4
  88. data/lib/ibex/cli/explain.rb +14 -5
  89. data/lib/ibex/cli/fix.rb +203 -0
  90. data/lib/ibex/cli/formatting.rb +11 -10
  91. data/lib/ibex/cli/fuzz.rb +200 -0
  92. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  93. data/lib/ibex/cli/generation_artifacts.rb +53 -2
  94. data/lib/ibex/cli/generation_error_messages.rb +1 -1
  95. data/lib/ibex/cli/grammar_tests.rb +32 -14
  96. data/lib/ibex/cli/ir_tools.rb +5 -61
  97. data/lib/ibex/cli/outputs.rb +60 -33
  98. data/lib/ibex/cli/reduce.rb +202 -0
  99. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  100. data/lib/ibex/cli/samples.rb +49 -23
  101. data/lib/ibex/cli/verify.rb +116 -0
  102. data/lib/ibex/cli/watch.rb +6 -5
  103. data/lib/ibex/cli.rb +357 -56
  104. data/lib/ibex/codegen/action_locations.rb +1 -1
  105. data/lib/ibex/codegen/action_method_source.rb +5 -6
  106. data/lib/ibex/codegen/action_source.rb +3 -2
  107. data/lib/ibex/codegen/ambiguity.rb +5 -4
  108. data/lib/ibex/codegen/explain.rb +123 -55
  109. data/lib/ibex/codegen/generated_action_abi.rb +10 -5
  110. data/lib/ibex/codegen/rbs.rb +15 -7
  111. data/lib/ibex/codegen/report.rb +25 -13
  112. data/lib/ibex/codegen/ruby.rb +28 -64
  113. data/lib/ibex/codegen/ruby_actions.rb +9 -6
  114. data/lib/ibex/codegen/ruby_syntax.rb +15 -9
  115. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  116. data/lib/ibex/configuration/explanation.rb +421 -0
  117. data/lib/ibex/configuration/inspector.rb +232 -0
  118. data/lib/ibex/configuration.rb +580 -0
  119. data/lib/ibex/coverage/collector.rb +21 -11
  120. data/lib/ibex/coverage/event_stream.rb +13 -7
  121. data/lib/ibex/coverage/report.rb +26 -18
  122. data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
  123. data/lib/ibex/delta_reducer.rb +99 -0
  124. data/lib/ibex/diff.rb +140 -0
  125. data/lib/ibex/equiv/machine.rb +135 -0
  126. data/lib/ibex/equiv.rb +373 -0
  127. data/lib/ibex/fix.rb +557 -0
  128. data/lib/ibex/frontend/ast.rb +22 -2
  129. data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
  130. data/lib/ibex/frontend/diagnostic.rb +1 -1
  131. data/lib/ibex/frontend/formatter.rb +72 -26
  132. data/lib/ibex/frontend/generated_parser.rb +147 -117
  133. data/lib/ibex/frontend/generated_parser_base.rb +7 -3
  134. data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
  135. data/lib/ibex/frontend/generation.rb +1 -1
  136. data/lib/ibex/frontend/parser/declarations.rb +17 -2
  137. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  138. data/lib/ibex/frontend/regenerator.rb +1 -0
  139. data/lib/ibex/frontend/resolution.rb +2 -1
  140. data/lib/ibex/frontend/resolver.rb +1 -1
  141. data/lib/ibex/frontend/rule_documentation.rb +2 -1
  142. data/lib/ibex/frontend/source_cursor.rb +2 -2
  143. data/lib/ibex/frontend/source_span.rb +9 -1
  144. data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
  145. data/lib/ibex/frontend/token_adapter.rb +10 -0
  146. data/lib/ibex/frontend.rb +1 -0
  147. data/lib/ibex/fuzz.rb +219 -0
  148. data/lib/ibex/generation_input.rb +1 -1
  149. data/lib/ibex/generation_manifest.rb +36 -25
  150. data/lib/ibex/generation_transaction.rb +1 -1
  151. data/lib/ibex/generation_transaction_recovery.rb +4 -4
  152. data/lib/ibex/generation_transaction_validation.rb +2 -2
  153. data/lib/ibex/grammar_tests.rb +1 -1
  154. data/lib/ibex/ir/automaton_ir.rb +61 -19
  155. data/lib/ibex/ir/grammar_ir.rb +76 -45
  156. data/lib/ibex/ir/lexer_ir.rb +5 -5
  157. data/lib/ibex/ir/parser_contract.rb +135 -0
  158. data/lib/ibex/ir/serialize.rb +203 -76
  159. data/lib/ibex/ir/validator/automaton.rb +64 -34
  160. data/lib/ibex/ir/validator/base.rb +51 -33
  161. data/lib/ibex/ir/validator/grammar.rb +113 -83
  162. data/lib/ibex/ir/validator/lexer.rb +2 -2
  163. data/lib/ibex/ir/validator.rb +2 -1
  164. data/lib/ibex/ir.rb +16 -3
  165. data/lib/ibex/lalr/build_metrics.rb +50 -2
  166. data/lib/ibex/lalr/builder.rb +191 -56
  167. data/lib/ibex/lalr/conflict_search.rb +15 -4
  168. data/lib/ibex/lalr/counterexample.rb +19 -14
  169. data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
  170. data/lib/ibex/lalr/goto_follows.rb +229 -0
  171. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  172. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  173. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  174. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  175. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  176. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  177. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  178. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  179. data/lib/ibex/lalr/ielr_partition.rb +22 -8
  180. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  181. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  182. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  183. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  184. data/lib/ibex/lalr.rb +52 -1
  185. data/lib/ibex/location.rb +3 -3
  186. data/lib/ibex/lsp/document_handlers.rb +9 -7
  187. data/lib/ibex/lsp/initialization_handlers.rb +9 -7
  188. data/lib/ibex/lsp/navigation_handlers.rb +25 -11
  189. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  190. data/lib/ibex/lsp/position_codec.rb +6 -2
  191. data/lib/ibex/lsp/request_handlers.rb +3 -2
  192. data/lib/ibex/lsp/request_support.rb +14 -7
  193. data/lib/ibex/lsp/server.rb +13 -11
  194. data/lib/ibex/lsp/symbol_index.rb +13 -13
  195. data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
  196. data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
  197. data/lib/ibex/lsp/transport.rb +13 -3
  198. data/lib/ibex/lsp/workspace.rb +5 -4
  199. data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
  200. data/lib/ibex/lsp.rb +1 -0
  201. data/lib/ibex/messages/en.yml +28 -0
  202. data/lib/ibex/messages/ja.yml +28 -0
  203. data/lib/ibex/messages.rb +68 -0
  204. data/lib/ibex/metrics.rb +175 -0
  205. data/lib/ibex/normalize/declarations.rb +2 -0
  206. data/lib/ibex/normalize/diagnostics.rb +63 -38
  207. data/lib/ibex/normalize/expander.rb +5 -4
  208. data/lib/ibex/normalize/expression.rb +8 -6
  209. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  210. data/lib/ibex/normalize/inline_expansion.rb +100 -49
  211. data/lib/ibex/normalize/lexer.rb +3 -3
  212. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
  213. data/lib/ibex/normalize/parameter_lowering.rb +40 -23
  214. data/lib/ibex/normalize/parameters.rb +31 -4
  215. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  216. data/lib/ibex/normalize.rb +10 -20
  217. data/lib/ibex/racc_migration/report.rb +6 -3
  218. data/lib/ibex/rake_task.rb +1 -1
  219. data/lib/ibex/samples.rb +43 -13
  220. data/lib/ibex/table_artifact/builder.rb +266 -0
  221. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  222. data/lib/ibex/table_artifact/document.rb +39 -0
  223. data/lib/ibex/table_artifact/executor.rb +187 -0
  224. data/lib/ibex/table_artifact/serializer.rb +57 -0
  225. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  226. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  227. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  228. data/lib/ibex/table_artifact/validator.rb +253 -0
  229. data/lib/ibex/table_artifact.rb +88 -0
  230. data/lib/ibex/table_simulation/result.rb +6 -2
  231. data/lib/ibex/table_simulation/step.rb +3 -1
  232. data/lib/ibex/tables.rb +14 -0
  233. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  234. data/lib/ibex/verification_report/builder.rb +159 -0
  235. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  236. data/lib/ibex/verification_report/logical_path.rb +68 -0
  237. data/lib/ibex/verification_report/validator.rb +329 -0
  238. data/lib/ibex/verification_report.rb +69 -0
  239. data/lib/ibex/verify/action_correspondence.rb +138 -0
  240. data/lib/ibex/verify/language_witness.rb +295 -0
  241. data/lib/ibex/verify/reference_collection.rb +190 -0
  242. data/lib/ibex/verify/result.rb +73 -0
  243. data/lib/ibex/verify/verifier.rb +598 -0
  244. data/lib/ibex/verify.rb +15 -0
  245. data/lib/ibex/version.rb +1 -1
  246. data/lib/ibex/watch/runner.rb +10 -1
  247. data/lib/ibex/watch/source_snapshot.rb +13 -8
  248. data/lib/ibex.rb +10 -0
  249. data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
  250. data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
  251. data/schema/bison-import-v1.schema.json +106 -0
  252. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  253. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  254. data/schema/construction-profile-v1.schema.json +779 -0
  255. data/schema/diff-v1.schema.json +41 -0
  256. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  257. data/schema/equiv-v1.schema.json +64 -0
  258. data/schema/error-ux-review-v1.schema.json +609 -0
  259. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  260. data/schema/error-ux-round2-v1.schema.json +519 -0
  261. data/schema/explain-v1.schema.json +63 -1
  262. data/schema/fix-v1.schema.json +64 -0
  263. data/schema/fix-v2.schema.json +66 -0
  264. data/schema/fix-v3.schema.json +101 -0
  265. data/schema/fuzz-regression-v1.schema.json +79 -0
  266. data/schema/fuzz-v1.schema.json +114 -0
  267. data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
  268. data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
  269. data/schema/grammar-ir.schema.json +271 -0
  270. data/schema/ielr-benchmark-v1.schema.json +85 -0
  271. data/schema/lexer-profile-v1.schema.json +491 -0
  272. data/schema/metrics-v1.schema.json +60 -0
  273. data/schema/reduce-v1.schema.json +22 -0
  274. data/schema/reduce-v2.schema.json +63 -0
  275. data/schema/table-artifact-v1.schema.json +377 -0
  276. data/schema/verification-report-v1.schema.json +222 -0
  277. data/schema/verify-v1.schema.json +42 -0
  278. data/sig/ibex/analysis/digraph.rbs +19 -0
  279. data/sig/ibex/bison_import/importer.rbs +140 -0
  280. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  281. data/sig/ibex/bison_import.rbs +95 -0
  282. data/sig/ibex/bounded_subprocess.rbs +76 -0
  283. data/sig/ibex/cli/ambiguity.rbs +6 -0
  284. data/sig/ibex/cli/analysis.rbs +43 -0
  285. data/sig/ibex/cli/bison_import.rbs +32 -0
  286. data/sig/ibex/cli/config.rbs +35 -0
  287. data/sig/ibex/cli/diagnostics.rbs +16 -3
  288. data/sig/ibex/cli/documentation.rbs +5 -1
  289. data/sig/ibex/cli/equiv.rbs +49 -0
  290. data/sig/ibex/cli/error_messages.rbs +6 -0
  291. data/sig/ibex/cli/explain.rbs +6 -0
  292. data/sig/ibex/cli/fix.rbs +53 -0
  293. data/sig/ibex/cli/formatting.rbs +5 -1
  294. data/sig/ibex/cli/fuzz.rbs +47 -0
  295. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  296. data/sig/ibex/cli/generation_artifacts.rbs +21 -2
  297. data/sig/ibex/cli/generation_error_messages.rbs +2 -2
  298. data/sig/ibex/cli/grammar_tests.rbs +15 -3
  299. data/sig/ibex/cli/ir_tools.rbs +7 -19
  300. data/sig/ibex/cli/outputs.rbs +9 -3
  301. data/sig/ibex/cli/reduce.rbs +60 -0
  302. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  303. data/sig/ibex/cli/samples.rbs +8 -0
  304. data/sig/ibex/cli/verify.rbs +28 -0
  305. data/sig/ibex/cli/watch.rbs +2 -2
  306. data/sig/ibex/cli.rbs +79 -5
  307. data/sig/ibex/codegen/action_method_source.rbs +4 -4
  308. data/sig/ibex/codegen/explain.rbs +45 -25
  309. data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
  310. data/sig/ibex/codegen/rbs.rbs +12 -10
  311. data/sig/ibex/codegen/report.rbs +12 -12
  312. data/sig/ibex/codegen/ruby.rbs +18 -15
  313. data/sig/ibex/codegen/ruby_actions.rbs +6 -6
  314. data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
  315. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  316. data/sig/ibex/configuration/explanation.rbs +195 -0
  317. data/sig/ibex/configuration/inspector.rbs +53 -0
  318. data/sig/ibex/configuration.rbs +242 -0
  319. data/sig/ibex/coverage/collector.rbs +16 -14
  320. data/sig/ibex/coverage/event_stream.rbs +12 -10
  321. data/sig/ibex/coverage/report.rbs +20 -18
  322. data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
  323. data/sig/ibex/delta_reducer.rbs +38 -0
  324. data/sig/ibex/diff.rbs +47 -0
  325. data/sig/ibex/equiv/machine.rbs +54 -0
  326. data/sig/ibex/equiv.rbs +129 -0
  327. data/sig/ibex/fix.rbs +138 -0
  328. data/sig/ibex/frontend/ast.rbs +33 -7
  329. data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
  330. data/sig/ibex/frontend/diagnostic.rbs +2 -2
  331. data/sig/ibex/frontend/formatter.rbs +38 -30
  332. data/sig/ibex/frontend/generated_parser.rbs +87 -77
  333. data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
  334. data/sig/ibex/frontend/parser/declarations.rbs +3 -0
  335. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  336. data/sig/ibex/frontend/resolution.rbs +3 -2
  337. data/sig/ibex/frontend/source_cursor.rbs +2 -2
  338. data/sig/ibex/frontend/source_span.rbs +5 -2
  339. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
  340. data/sig/ibex/frontend/token_adapter.rbs +6 -0
  341. data/sig/ibex/fuzz.rbs +71 -0
  342. data/sig/ibex/generation_input.rbs +2 -2
  343. data/sig/ibex/generation_manifest.rbs +35 -32
  344. data/sig/ibex/generation_transaction_recovery.rbs +2 -2
  345. data/sig/ibex/generation_transaction_validation.rbs +2 -2
  346. data/sig/ibex/grammar_tests.rbs +2 -2
  347. data/sig/ibex/ir/automaton_ir.rbs +30 -11
  348. data/sig/ibex/ir/grammar_ir.rbs +41 -27
  349. data/sig/ibex/ir/lexer_ir.rbs +8 -8
  350. data/sig/ibex/ir/parser_contract.rbs +69 -0
  351. data/sig/ibex/ir/serialize.rbs +63 -28
  352. data/sig/ibex/ir/validator/automaton.rbs +47 -44
  353. data/sig/ibex/ir/validator/base.rbs +35 -31
  354. data/sig/ibex/ir/validator/grammar.rbs +78 -67
  355. data/sig/ibex/ir/validator/lexer.rbs +4 -4
  356. data/sig/ibex/ir.rbs +9 -3
  357. data/sig/ibex/lalr/build_metrics.rbs +42 -2
  358. data/sig/ibex/lalr/builder.rbs +75 -24
  359. data/sig/ibex/lalr/conflict_search.rbs +5 -2
  360. data/sig/ibex/lalr/counterexample.rbs +17 -14
  361. data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
  362. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  363. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  364. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  365. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  366. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  367. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  368. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  369. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  370. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  371. data/sig/ibex/lalr/ielr_partition.rbs +17 -5
  372. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  373. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  374. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  375. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  376. data/sig/ibex/lalr.rbs +11 -1
  377. data/sig/ibex/location.rbs +6 -6
  378. data/sig/ibex/lsp/document_handlers.rbs +8 -8
  379. data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
  380. data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
  381. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  382. data/sig/ibex/lsp/position_codec.rbs +8 -4
  383. data/sig/ibex/lsp/request_handlers.rbs +4 -4
  384. data/sig/ibex/lsp/request_support.rbs +8 -8
  385. data/sig/ibex/lsp/server.rbs +16 -16
  386. data/sig/ibex/lsp/symbol_index.rbs +20 -21
  387. data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
  388. data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
  389. data/sig/ibex/lsp/transport.rbs +18 -6
  390. data/sig/ibex/lsp/workspace.rbs +3 -2
  391. data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
  392. data/sig/ibex/messages.rbs +26 -0
  393. data/sig/ibex/metrics.rbs +50 -0
  394. data/sig/ibex/normalize/diagnostics.rbs +4 -4
  395. data/sig/ibex/normalize/expander.rbs +2 -2
  396. data/sig/ibex/normalize/expression.rbs +10 -8
  397. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  398. data/sig/ibex/normalize/inline_expansion.rbs +59 -50
  399. data/sig/ibex/normalize/lexer.rbs +4 -4
  400. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
  401. data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
  402. data/sig/ibex/normalize/parameters.rbs +13 -8
  403. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  404. data/sig/ibex/normalize.rbs +11 -5
  405. data/sig/ibex/racc_migration/report.rbs +8 -6
  406. data/sig/ibex/rake_task.rbs +1 -1
  407. data/sig/ibex/samples.rbs +13 -4
  408. data/sig/ibex/table_artifact/builder.rbs +93 -0
  409. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  410. data/sig/ibex/table_artifact/document.rbs +27 -0
  411. data/sig/ibex/table_artifact/executor.rbs +66 -0
  412. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  413. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  414. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  415. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  416. data/sig/ibex/table_artifact/validator.rbs +66 -0
  417. data/sig/ibex/table_artifact.rbs +32 -0
  418. data/sig/ibex/table_simulation/result.rbs +8 -4
  419. data/sig/ibex/table_simulation/step.rbs +4 -2
  420. data/sig/ibex/tables.rbs +10 -9
  421. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  422. data/sig/ibex/verification_report/builder.rbs +53 -0
  423. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  424. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  425. data/sig/ibex/verification_report/validator.rbs +114 -0
  426. data/sig/ibex/verification_report.rbs +44 -0
  427. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  428. data/sig/ibex/verify/language_witness.rbs +112 -0
  429. data/sig/ibex/verify/reference_collection.rbs +63 -0
  430. data/sig/ibex/verify/result.rbs +52 -0
  431. data/sig/ibex/verify/verifier.rbs +170 -0
  432. data/sig/ibex/verify.rbs +6 -0
  433. data/sig/ibex/watch/runner.rbs +12 -2
  434. data/sig/ibex/watch/source_snapshot.rbs +13 -9
  435. metadata +232 -12
  436. data/lib/ibex/ir/migration.rb +0 -120
  437. data/sig/ibex/ir/migration.rbs +0 -34
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "digest"
5
+ require "json"
6
+
7
+ module Ibex
8
+ module VerificationReport
9
+ # Rebuilds immutable IR with source locations expressed as report logical identities.
10
+ class CanonicalIR
11
+ # @rbs!
12
+ # type json_value = String | Integer | Float | bool | nil | Array[json_value] | Hash[json_value, json_value]
13
+
14
+ LOGICAL_ROOT = "input" #: String
15
+
16
+ # @rbs (IR::Automaton automaton, source_records: Array[GenerationInput]) -> void
17
+ def initialize(automaton, source_records:)
18
+ raise ArgumentError, "source_records must not be empty" if source_records.empty?
19
+ if source_records.length > LogicalPath::MAX_INPUT_FILES
20
+ raise ArgumentError, "verification reports support at most #{LogicalPath::MAX_INPUT_FILES} input files"
21
+ end
22
+
23
+ @automaton = automaton
24
+ @logical_files = source_records.map.with_index do |record, index|
25
+ [record, LogicalPath.input(record.path, index)]
26
+ end
27
+ end
28
+
29
+ # @rbs () -> IR::Automaton
30
+ def build
31
+ document = JSON.parse(IR::Serialize.dump(@automaton))
32
+ normalize_source_identity!(document)
33
+ grammar = IR::Validator.validate(generate_json(document.fetch("grammar")))
34
+ raise ArgumentError, "canonical bundle grammar did not validate" unless grammar.is_a?(IR::Grammar)
35
+
36
+ document["grammar_digest"] = digest(grammar)
37
+ automaton = IR::Validator.validate(generate_json(document))
38
+ raise ArgumentError, "canonical bundle automaton did not validate" unless automaton.is_a?(IR::Automaton)
39
+
40
+ automaton
41
+ end
42
+
43
+ private
44
+
45
+ # @rbs (json_value value) -> json_value
46
+ def normalize_source_identity!(value)
47
+ case value
48
+ when Array then value.each { |child| normalize_source_identity!(child) }
49
+ when Hash
50
+ value.each do |key, child|
51
+ value[key] = if key == "file" && child.is_a?(String)
52
+ logical_file(child)
53
+ elsif key == "root" && child.is_a?(String)
54
+ LOGICAL_ROOT
55
+ else
56
+ normalize_source_identity!(child)
57
+ end
58
+ end
59
+ end
60
+ value
61
+ end
62
+
63
+ # @rbs (json_value value) -> String
64
+ def generate_json(value)
65
+ JSON.generate(normalize_for_json(value))
66
+ end
67
+
68
+ # @rbs (json_value value) -> json_value
69
+ def normalize_for_json(value)
70
+ case value
71
+ when String
72
+ normalized = value.dup.force_encoding(Encoding::UTF_8)
73
+ normalized.valid_encoding? ? normalized : normalized.scrub
74
+ when Array
75
+ value.map { |child| normalize_for_json(child) }
76
+ when Hash
77
+ normalized = {} #: Hash[json_value, json_value] # steep:ignore UnannotatedEmptyCollection
78
+ value.each_with_object(normalized) do |(key, child), result|
79
+ result[normalize_for_json(key)] = normalize_for_json(child)
80
+ end
81
+ else
82
+ value
83
+ end
84
+ end
85
+
86
+ # @rbs (String file) -> String
87
+ def logical_file(file)
88
+ file_bytes = file.b
89
+ logical = @logical_files.find { |_record, path| path.b == file_bytes }
90
+ return logical.fetch(1) if logical
91
+
92
+ candidates = @logical_files.select { |record, _path| record_matches?(record, file) }
93
+ return candidates.fetch(0).fetch(1) if candidates.one?
94
+
95
+ detail = candidates.empty? ? "is not present in source_records" : "matches multiple source_records"
96
+ raise ArgumentError, "IR source location #{file.inspect} #{detail}"
97
+ end
98
+
99
+ # @rbs (GenerationInput record, String file) -> bool
100
+ def record_matches?(record, file)
101
+ paths = [record.path, *record.access_paths].map(&:b)
102
+ file_bytes = file.b
103
+ return true if paths.include?(file_bytes)
104
+ return true if absolute_file?(file) && paths.include?(File.expand_path(file).b)
105
+
106
+ suffix = "/".b + file_bytes
107
+ !absolute_file?(file) && paths.any? do |path|
108
+ path == file_bytes || path.end_with?(suffix)
109
+ end
110
+ end
111
+
112
+ # @rbs (String file) -> bool
113
+ def absolute_file?(file)
114
+ file.start_with?("/") || file.match?(%r{\A[A-Za-z]:[\\/]})
115
+ end
116
+
117
+ # @rbs (IR::Grammar grammar) -> String
118
+ def digest(grammar)
119
+ "sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(grammar))}"
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module VerificationReport
6
+ # Closed logical identities used by version 1 verification reports.
7
+ module LogicalPath
8
+ MAX_INPUT_FILES = 10_000 #: Integer
9
+ BASENAME = %r{\A(?!\.{1,2}\z)[^/\\\x00-\x1f\x7f]+\z} #: Regexp
10
+ INPUT = %r{\Ainput/(?<index>[0-9]{4})/(?<basename>[^/\\\x00-\x1f\x7f]+)\z} #: Regexp
11
+ TABLE = %r{\Atable/(?<basename>[^/\\\x00-\x1f\x7f]+)\z} #: Regexp
12
+
13
+ module_function
14
+
15
+ # @rbs (String path, Integer index) -> String
16
+ def input(path, index)
17
+ unless index.is_a?(Integer) && index.between?(0, MAX_INPUT_FILES - 1)
18
+ raise ArgumentError, "verification reports support at most #{MAX_INPUT_FILES} input files"
19
+ end
20
+
21
+ "input/#{format('%04d', index)}/#{basename(path)}"
22
+ end
23
+
24
+ # @rbs (String path) -> String
25
+ def table(path)
26
+ "table/#{basename(path)}"
27
+ end
28
+
29
+ # @rbs (Object? value, Integer index) -> bool
30
+ def canonical_input?(value, index)
31
+ return false unless value.is_a?(String)
32
+
33
+ match = INPUT.match(value)
34
+ return false unless match
35
+
36
+ basename = match[:basename]
37
+ !!(basename && match[:index] == format("%04d", index) && usable_basename?(basename))
38
+ end
39
+
40
+ # @rbs (Object? value) -> bool
41
+ def canonical_table?(value)
42
+ return false unless value.is_a?(String)
43
+
44
+ match = TABLE.match(value)
45
+ return false unless match
46
+
47
+ basename = match[:basename]
48
+ !!(basename && usable_basename?(basename))
49
+ end
50
+
51
+ # @rbs (String path) -> String
52
+ def basename(path)
53
+ value = File.basename(path)
54
+ value = value.dup.force_encoding(Encoding::UTF_8) unless value.encoding == Encoding::UTF_8
55
+ return value if usable_basename?(value)
56
+
57
+ raise ArgumentError, "artifact path has no usable logical basename"
58
+ end
59
+ private_class_method :basename
60
+
61
+ # @rbs (String value) -> bool
62
+ def usable_basename?(value)
63
+ value.valid_encoding? && value.match?(BASENAME)
64
+ end
65
+ private_class_method :usable_basename?
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,329 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "digest"
5
+
6
+ module Ibex
7
+ module VerificationReport
8
+ # Validates a report and its cross-artifact manifest/table bindings.
9
+ # rubocop:disable Metrics/ClassLength -- closed report and cross-artifact invariants form one validator contract.
10
+ class Validator
11
+ # @rbs!
12
+ # type json_value = String | Integer | Float | bool | nil | Array[json_value] | Hash[String, json_value]
13
+
14
+ ROOT_KEYS = %w[
15
+ ibex_report schema_version checker profile bounds input ir table outcome excluded_trust evidence_digest
16
+ ].freeze #: Array[String]
17
+ CHECKER_KEYS = %w[name version].freeze #: Array[String]
18
+ BOUNDS_KEYS = %w[max_states max_items].freeze #: Array[String]
19
+ INPUT_KEYS = %w[digest files].freeze #: Array[String]
20
+ INPUT_FILE_KEYS = %w[logical_path sha256 bytesize].freeze #: Array[String]
21
+ IR_KEYS = %w[identity_scope grammar automaton].freeze #: Array[String]
22
+ GRAMMAR_KEYS = %w[schema_version digest].freeze #: Array[String]
23
+ AUTOMATON_KEYS = %w[schema_version algorithm digest].freeze #: Array[String]
24
+ TABLE_KEYS = %w[
25
+ logical_path artifact_type schema_version representation artifact_digest payload_digest
26
+ ].freeze #: Array[String]
27
+ OUTCOME_KEYS = %w[status requested_checks executed_checks violations exhaustion].freeze #: Array[String]
28
+ VIOLATION_KEYS = %w[id location message].freeze #: Array[String]
29
+ EXHAUSTION_KEYS = %w[kind message].freeze #: Array[String]
30
+ DIGEST = /\Asha256:[0-9a-f]{64}\z/ #: Regexp
31
+ ALGORITHMS = %w[slr lalr1 ielr1 lr1].freeze #: Array[String]
32
+
33
+ # @rbs (String source) -> Hash[String, json_value]
34
+ def validate(source)
35
+ document = JSON.parse(source) #: json_value
36
+ validate_document(document)
37
+ document #: Hash[String, json_value]
38
+ rescue JSON::ParserError, KeyError, TypeError, ArgumentError => e
39
+ raise ValidationError, "(verification-report):1:1: invalid report: #{e.message}"
40
+ end
41
+
42
+ # @rbs (manifest_source: String, report_source: String, table_source: String) -> Hash[String, json_value]
43
+ def validate_bundle(manifest_source:, report_source:, table_source:)
44
+ manifest = GenerationManifest.validate(manifest_source, verify_artifacts: false) #: Hash[String, json_value]
45
+ report = validate(report_source)
46
+ table = TableArtifact.load(table_source)
47
+ report_entry = unique_artifact(manifest, "verification_report")
48
+ table_entry = unique_artifact(manifest, "parser_table")
49
+ unique_artifact(manifest, "parser")
50
+
51
+ validate_manifest_bytes!(report_entry, report_source, "verification report")
52
+ validate_manifest_bytes!(table_entry, table_source, "parser table")
53
+ validate_inputs!(manifest, report)
54
+ validate_table_binding!(report, table, table_entry)
55
+ report
56
+ rescue Ibex::Error => e
57
+ raise e if e.is_a?(ValidationError)
58
+
59
+ raise ValidationError, "(verification-bundle):1:1: #{e.message}"
60
+ rescue KeyError, TypeError, ArgumentError => e
61
+ raise ValidationError, "(verification-bundle):1:1: invalid bundle: #{e.message}"
62
+ end
63
+
64
+ # @rbs (String manifest_path) -> Hash[String, json_value]
65
+ def validate_bundle_file(manifest_path)
66
+ manifest_source = File.binread(manifest_path)
67
+ manifest = GenerationManifest.validate(manifest_source, verify_artifacts: false) #: Hash[String, json_value]
68
+ report_entry = unique_artifact(manifest, "verification_report")
69
+ table_entry = unique_artifact(manifest, "parser_table")
70
+ report_path = report_entry.fetch("path") #: String
71
+ table_path = table_entry.fetch("path") #: String
72
+ report_source = File.binread(report_path)
73
+ table_source = File.binread(table_path)
74
+ result = validate_bundle(
75
+ manifest_source: manifest_source, report_source: report_source, table_source: table_source
76
+ )
77
+ GenerationManifest.validate(manifest_source)
78
+ result
79
+ rescue SystemCallError => e
80
+ raise ValidationError, "#{manifest_path}:1:1: cannot read verification bundle: #{e.message}"
81
+ end
82
+
83
+ private
84
+
85
+ # @rbs (json_value document) -> void
86
+ def validate_document(document)
87
+ document = object(document, ROOT_KEYS, "document")
88
+ equal(document.fetch("ibex_report"), IDENTIFIER, "ibex_report")
89
+ equal(document.fetch("schema_version"), SCHEMA_VERSION, "schema_version")
90
+ validate_checker(document.fetch("checker"))
91
+ profile = enum(string(document.fetch("profile"), "profile"), %w[default strict], "profile")
92
+ validate_bounds(document.fetch("bounds"))
93
+ validate_input(document.fetch("input"))
94
+ validate_ir(document.fetch("ir"))
95
+ validate_table(document.fetch("table"))
96
+ validate_outcome(document.fetch("outcome"), profile)
97
+ equal(document.fetch("excluded_trust"), EXCLUDED_TRUST, "excluded_trust")
98
+ digest(document.fetch("evidence_digest"), "evidence_digest")
99
+ unsigned = document.except("evidence_digest")
100
+ equal(document.fetch("evidence_digest"), TableArtifact::Serializer.digest(unsigned), "evidence_digest")
101
+ end
102
+
103
+ # @rbs (json_value value) -> void
104
+ def validate_checker(value)
105
+ checker = object(value, CHECKER_KEYS, "checker")
106
+ equal(checker.fetch("name"), "ibex.verify", "checker.name")
107
+ string(checker.fetch("version"), "checker.version")
108
+ end
109
+
110
+ # @rbs (json_value value) -> void
111
+ def validate_bounds(value)
112
+ bounds = object(value, BOUNDS_KEYS, "bounds")
113
+ BOUNDS_KEYS.each { |key| positive_integer(bounds.fetch(key), "bounds.#{key}") }
114
+ end
115
+
116
+ # @rbs (json_value value) -> void
117
+ def validate_input(value)
118
+ input = object(value, INPUT_KEYS, "input")
119
+ files = array(input.fetch("files"), "input.files")
120
+ raise TypeError, "input.files must not be empty" if files.empty?
121
+
122
+ files.each_with_index do |entry, index|
123
+ file = object(entry, INPUT_FILE_KEYS, "input.files[#{index}]")
124
+ path = file.fetch("logical_path")
125
+ unless LogicalPath.canonical_input?(path, index)
126
+ raise TypeError, "input.files[#{index}].logical_path must be input/NNNN/BASENAME"
127
+ end
128
+
129
+ digest(file.fetch("sha256"), "input.files[#{index}].sha256")
130
+ nonnegative_integer(file.fetch("bytesize"), "input.files[#{index}].bytesize")
131
+ end
132
+ digest(input.fetch("digest"), "input.digest")
133
+ equal(input.fetch("digest"), TableArtifact::Serializer.digest(files), "input.digest")
134
+ end
135
+
136
+ # @rbs (json_value value) -> void
137
+ def validate_ir(value)
138
+ ir = object(value, IR_KEYS, "ir")
139
+ equal(ir.fetch("identity_scope"), IR_IDENTITY_SCOPE, "ir.identity_scope")
140
+ grammar = object(ir.fetch("grammar"), GRAMMAR_KEYS, "ir.grammar")
141
+ automaton = object(ir.fetch("automaton"), AUTOMATON_KEYS, "ir.automaton")
142
+ positive_integer(grammar.fetch("schema_version"), "ir.grammar.schema_version")
143
+ positive_integer(automaton.fetch("schema_version"), "ir.automaton.schema_version")
144
+ digest(grammar.fetch("digest"), "ir.grammar.digest")
145
+ digest(automaton.fetch("digest"), "ir.automaton.digest")
146
+ enum(automaton.fetch("algorithm"), ALGORITHMS, "ir.automaton.algorithm")
147
+ end
148
+
149
+ # @rbs (json_value value) -> void
150
+ def validate_table(value)
151
+ table = object(value, TABLE_KEYS, "table")
152
+ unless LogicalPath.canonical_table?(table.fetch("logical_path"))
153
+ raise TypeError, "table.logical_path must be table/BASENAME"
154
+ end
155
+
156
+ equal(table.fetch("artifact_type"), TableArtifact::ARTIFACT_TYPE, "table.artifact_type")
157
+ equal(table.fetch("schema_version"), TableArtifact::SCHEMA_VERSION, "table.schema_version")
158
+ enum(table.fetch("representation"), %w[plain compact], "table.representation")
159
+ digest(table.fetch("artifact_digest"), "table.artifact_digest")
160
+ digest(table.fetch("payload_digest"), "table.payload_digest")
161
+ end
162
+
163
+ # @rbs (json_value value, String profile) -> void
164
+ def validate_outcome(value, profile)
165
+ outcome = object(value, OUTCOME_KEYS, "outcome")
166
+ status = enum(outcome.fetch("status"), %w[pass violations exhausted], "outcome.status")
167
+ expected_checks = Verify::Verifier::DEFAULT_CHECKS +
168
+ (profile == "strict" ? Verify::Verifier::STRICT_CHECKS : [])
169
+ requested = checks(outcome.fetch("requested_checks"), "outcome.requested_checks")
170
+ executed = checks(outcome.fetch("executed_checks"), "outcome.executed_checks")
171
+ equal(requested, expected_checks, "outcome.requested_checks")
172
+ violations = validate_violations(outcome.fetch("violations"), requested)
173
+
174
+ if status == "exhausted"
175
+ equal(executed, [], "outcome.executed_checks")
176
+ equal(violations, [], "outcome.violations")
177
+ exhaustion = object(outcome.fetch("exhaustion"), EXHAUSTION_KEYS, "outcome.exhaustion")
178
+ equal(exhaustion.fetch("kind"), "reference_collection_budget", "outcome.exhaustion.kind")
179
+ string(exhaustion.fetch("message"), "outcome.exhaustion.message")
180
+ return
181
+ end
182
+
183
+ equal(executed, requested, "outcome.executed_checks")
184
+ equal(outcome.fetch("exhaustion"), nil, "outcome.exhaustion")
185
+ if status == "pass"
186
+ equal(violations, [], "outcome.violations")
187
+ elsif violations.empty?
188
+ raise TypeError, "outcome.violations must not be empty for violations status"
189
+ end
190
+ end
191
+
192
+ # @rbs (json_value value, Array[String] requested) -> Array[json_value]
193
+ def validate_violations(value, requested)
194
+ violations = array(value, "outcome.violations")
195
+ violations.each_with_index do |entry, index|
196
+ violation = object(entry, VIOLATION_KEYS, "outcome.violations[#{index}]")
197
+ id = string(violation.fetch("id"), "outcome.violations[#{index}].id")
198
+ enum(id, requested, "outcome.violations[#{index}].id")
199
+ string(violation.fetch("location"), "outcome.violations[#{index}].location", allow_empty: true)
200
+ string(violation.fetch("message"), "outcome.violations[#{index}].message")
201
+ end
202
+ violations
203
+ end
204
+
205
+ # @rbs (Hash[String, json_value] manifest, String kind) -> Hash[String, json_value]
206
+ def unique_artifact(manifest, kind)
207
+ artifacts = manifest.fetch("artifacts") #: Array[Hash[String, json_value]]
208
+ entries = artifacts.select { |entry| entry.fetch("kind") == kind }
209
+ raise TypeError, "manifest must list exactly one #{kind} artifact" unless entries.one?
210
+
211
+ entries.fetch(0)
212
+ end
213
+
214
+ # @rbs (Hash[String, json_value] entry, String source, String label) -> void
215
+ def validate_manifest_bytes!(entry, source, label)
216
+ expected_digest = Digest::SHA256.hexdigest(source)
217
+ raise TypeError, "#{label} manifest bytesize mismatch" unless entry.fetch("bytesize") == source.bytesize
218
+ raise TypeError, "#{label} manifest digest mismatch" unless entry.fetch("sha256") == expected_digest
219
+ end
220
+
221
+ # @rbs (Hash[String, json_value] manifest, Hash[String, json_value] report) -> void
222
+ def validate_inputs!(manifest, report)
223
+ manifest_files = manifest.dig("input", "files").map.with_index do |entry, index|
224
+ [
225
+ LogicalPath.input(entry.fetch("path"), index),
226
+ "sha256:#{entry.fetch('sha256')}", entry.fetch("bytesize")
227
+ ]
228
+ end
229
+ report_files = report.dig("input", "files").map do |entry|
230
+ [entry.fetch("logical_path"), entry.fetch("sha256"), entry.fetch("bytesize")]
231
+ end
232
+ raise TypeError, "report input identity does not match manifest input" unless report_files == manifest_files
233
+ end
234
+
235
+ # @rbs (Hash[String, json_value] report, TableArtifact::Document table,
236
+ # Hash[String, json_value] table_entry) -> void
237
+ def validate_table_binding!(report, table, table_entry)
238
+ claim = report.fetch("table") #: Hash[String, json_value]
239
+ table_path = table_entry.fetch("path") #: String
240
+ table_sha256 = table_entry.fetch("sha256") #: String
241
+ expected_logical_path = LogicalPath.table(table_path)
242
+ equal(claim.fetch("logical_path"), expected_logical_path, "table.logical_path")
243
+ equal(claim.fetch("artifact_digest"), "sha256:#{table_sha256}", "table.artifact_digest")
244
+ equal(claim.fetch("payload_digest"), table.identity.fetch("payload_digest"), "table.payload_digest")
245
+ equal(claim.fetch("representation"), table.payload.dig("table_format", "representation"),
246
+ "table.representation")
247
+ equal(report.dig("ir", "grammar", "digest"), table.identity.fetch("grammar_digest"),
248
+ "ir.grammar.digest")
249
+ equal(report.dig("ir", "automaton", "digest"), table.identity.fetch("automaton_digest"),
250
+ "ir.automaton.digest")
251
+ end
252
+
253
+ # @rbs (json_value value, String path) -> Array[String]
254
+ def checks(value, path)
255
+ entries = array(value, path).map do |entry|
256
+ check = string(entry, path)
257
+ enum(check, Verify::Verifier::DEFAULT_CHECKS + Verify::Verifier::STRICT_CHECKS, path)
258
+ check
259
+ end
260
+ raise TypeError, "#{path} must contain unique checks" unless entries.uniq == entries
261
+
262
+ entries
263
+ end
264
+
265
+ # @rbs (json_value value, Array[String] keys, String path) -> Hash[String, json_value]
266
+ def object(value, keys, path)
267
+ raise TypeError, "#{path} must be an object" unless value.is_a?(Hash)
268
+ raise TypeError, "#{path} keys must be strings" unless value.keys.all?(String)
269
+
270
+ extras = value.keys - keys
271
+ missing = keys - value.keys
272
+ raise TypeError, "#{path} has unknown property #{extras.fetch(0)}" unless extras.empty?
273
+ raise KeyError, "#{path} is missing #{missing.fetch(0)}" unless missing.empty?
274
+
275
+ value
276
+ end
277
+
278
+ # @rbs (json_value value, String path) -> Array[json_value]
279
+ def array(value, path)
280
+ raise TypeError, "#{path} must be an array" unless value.is_a?(Array)
281
+
282
+ value
283
+ end
284
+
285
+ # @rbs (json_value value, String path, ?allow_empty: bool) -> String
286
+ def string(value, path, allow_empty: false)
287
+ return value if value.is_a?(String) && (allow_empty || !value.empty?)
288
+
289
+ raise TypeError, "#{path} must be #{allow_empty ? 'a string' : 'a non-empty string'}"
290
+ end
291
+
292
+ # @rbs (json_value value, String path) -> String
293
+ def digest(value, path)
294
+ return value if value.is_a?(String) && value.match?(DIGEST)
295
+
296
+ raise TypeError, "#{path} must be a SHA-256 identity"
297
+ end
298
+
299
+ # @rbs (json_value value, String path) -> Integer
300
+ def positive_integer(value, path)
301
+ return value if value.is_a?(Integer) && value.positive?
302
+
303
+ raise TypeError, "#{path} must be a positive integer"
304
+ end
305
+
306
+ # @rbs (json_value value, String path) -> Integer
307
+ def nonnegative_integer(value, path)
308
+ return value if value.is_a?(Integer) && value >= 0
309
+
310
+ raise TypeError, "#{path} must be a non-negative integer"
311
+ end
312
+
313
+ # @rbs [T] (T value, Array[T] values, String path) -> T
314
+ def enum(value, values, path)
315
+ return value if values.include?(value)
316
+
317
+ raise TypeError, "#{path} has an unsupported value"
318
+ end
319
+
320
+ # @rbs (json_value actual, json_value expected, String path) -> void
321
+ def equal(actual, expected, path)
322
+ return if actual == expected
323
+
324
+ raise TypeError, "#{path} mismatch"
325
+ end
326
+ end
327
+ # rubocop:enable Metrics/ClassLength
328
+ end
329
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require_relative "error"
6
+ require_relative "generation_manifest"
7
+ require_relative "table_artifact"
8
+ require_relative "verification_report/logical_path"
9
+ require_relative "verification_report/canonical_ir"
10
+ require_relative "verification_report/builder"
11
+ require_relative "verification_report/validator"
12
+
13
+ module Ibex
14
+ # A versioned, scoped report that binds bounded verification to input and
15
+ # data-only parser table identities without claiming to verify opaque code.
16
+ module VerificationReport
17
+ IDENTIFIER = "scoped_verification" #: String
18
+ SCHEMA_VERSION = 1 #: Integer
19
+ IR_IDENTITY_SCOPE = "source-logical-v1" #: String
20
+ DEFAULT_MAX_STATES = 100_000 #: Integer
21
+ DEFAULT_MAX_ITEMS = 1_000_000 #: Integer
22
+ EXCLUDED_TRUST = %w[
23
+ source_to_ir table_semantic_derivation generated_wrapper semantic_actions lexer_actions
24
+ runtime application_hooks grammar_unambiguity
25
+ ].freeze #: Array[String]
26
+
27
+ class ValidationError < Ibex::Error; end
28
+
29
+ module_function
30
+
31
+ # Rebuild the supplied Automaton IR with path-neutral source locations for one bundle.
32
+ # @rbs (IR::Automaton automaton, source_records: Array[GenerationInput]) -> IR::Automaton
33
+ def canonical_automaton(automaton, source_records:)
34
+ CanonicalIR.new(automaton, source_records: source_records).build
35
+ end
36
+
37
+ # @rbs (IR::Automaton automaton, table: TableArtifact::Document,
38
+ # source_records: Array[GenerationInput], table_path: String, ?strict: bool,
39
+ # ?max_states: Integer, ?max_items: Integer) -> String
40
+ def render(automaton, table:, source_records:, table_path:, strict: false,
41
+ max_states: DEFAULT_MAX_STATES, max_items: DEFAULT_MAX_ITEMS)
42
+ Builder.new(
43
+ automaton, table: table, source_records: source_records, table_path: table_path,
44
+ strict: strict, max_states: max_states, max_items: max_items
45
+ ).render
46
+ end
47
+
48
+ # Validate the closed report shape and its canonical evidence digest.
49
+ # @rbs (String source) -> Hash[String, VerificationReport::Validator::json_value]
50
+ def validate(source)
51
+ Validator.new.validate(source)
52
+ end
53
+
54
+ # Validate report, table, and manifest bytes as one non-cyclic bundle.
55
+ # @rbs (manifest_source: String, report_source: String, table_source: String) -> Hash[String, VerificationReport::Validator::json_value]
56
+ def validate_bundle(manifest_source:, report_source:, table_source:)
57
+ Validator.new.validate_bundle(
58
+ manifest_source: manifest_source, report_source: report_source, table_source: table_source
59
+ )
60
+ end
61
+
62
+ # Resolve the report and table through a manifest and validate every
63
+ # published artifact before checking the cross-artifact identities.
64
+ # @rbs (String manifest_path) -> Hash[String, VerificationReport::Validator::json_value]
65
+ def validate_bundle_file(manifest_path)
66
+ Validator.new.validate_bundle_file(manifest_path)
67
+ end
68
+ end
69
+ end