ibex 0.1.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 (603) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -123
  3. data/docs/architecture.md +477 -31
  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 +106 -0
  12. data/docs/cst.md +237 -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 +306 -0
  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 +26 -0
  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 +114 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +629 -18
  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 +56 -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 +45 -6
  63. data/docs/release-readiness.md +221 -0
  64. data/docs/repair-semantics.md +123 -0
  65. data/docs/runtime-abi-evolution.md +307 -0
  66. data/docs/stability.md +234 -0
  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/examples/README.md +60 -0
  75. data/examples/calculator.y +44 -0
  76. data/examples/csv.y +32 -0
  77. data/examples/ini.y +70 -0
  78. data/examples/json.y +48 -0
  79. data/examples/tiny_language.y +75 -0
  80. data/lib/ibex/analysis/digraph.rb +128 -0
  81. data/lib/ibex/analysis/sets.rb +5 -4
  82. data/lib/ibex/analysis.rb +1 -0
  83. data/lib/ibex/artifact_set.rb +63 -0
  84. data/lib/ibex/bison_import/importer.rb +516 -0
  85. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  86. data/lib/ibex/bison_import.rb +200 -0
  87. data/lib/ibex/bounded_subprocess.rb +171 -0
  88. data/lib/ibex/cli/ambiguity.rb +75 -0
  89. data/lib/ibex/cli/analysis.rb +134 -0
  90. data/lib/ibex/cli/bison_import.rb +121 -0
  91. data/lib/ibex/cli/config.rb +128 -0
  92. data/lib/ibex/cli/counterexample_options.rb +6 -2
  93. data/lib/ibex/cli/coverage.rb +173 -0
  94. data/lib/ibex/cli/debug.rb +106 -0
  95. data/lib/ibex/cli/diagnostics.rb +136 -0
  96. data/lib/ibex/cli/documentation.rb +70 -0
  97. data/lib/ibex/cli/equiv.rb +169 -0
  98. data/lib/ibex/cli/error_messages.rb +184 -0
  99. data/lib/ibex/cli/explain.rb +85 -0
  100. data/lib/ibex/cli/fix.rb +203 -0
  101. data/lib/ibex/cli/formatting.rb +387 -0
  102. data/lib/ibex/cli/fuzz.rb +200 -0
  103. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  104. data/lib/ibex/cli/generation_artifacts.rb +189 -0
  105. data/lib/ibex/cli/generation_error_messages.rb +39 -0
  106. data/lib/ibex/cli/grammar_tests.rb +140 -0
  107. data/lib/ibex/cli/ir_tools.rb +128 -0
  108. data/lib/ibex/cli/lsp.rb +33 -0
  109. data/lib/ibex/cli/outputs.rb +133 -23
  110. data/lib/ibex/cli/racc_migration.rb +87 -0
  111. data/lib/ibex/cli/reduce.rb +202 -0
  112. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  113. data/lib/ibex/cli/samples.rb +118 -0
  114. data/lib/ibex/cli/verify.rb +116 -0
  115. data/lib/ibex/cli/watch.rb +104 -0
  116. data/lib/ibex/cli.rb +825 -55
  117. data/lib/ibex/codegen/action_locations.rb +103 -0
  118. data/lib/ibex/codegen/action_method_source.rb +209 -0
  119. data/lib/ibex/codegen/action_source.rb +119 -0
  120. data/lib/ibex/codegen/ambiguity.rb +174 -0
  121. data/lib/ibex/codegen/cst_metadata.rb +171 -0
  122. data/lib/ibex/codegen/documentation.rb +170 -0
  123. data/lib/ibex/codegen/explain.rb +380 -0
  124. data/lib/ibex/codegen/generated_action_abi.rb +293 -0
  125. data/lib/ibex/codegen/html.rb +94 -10
  126. data/lib/ibex/codegen/mermaid.rb +43 -0
  127. data/lib/ibex/codegen/railroad.rb +197 -0
  128. data/lib/ibex/codegen/railroad_documentation.rb +59 -0
  129. data/lib/ibex/codegen/rbs.rb +331 -2
  130. data/lib/ibex/codegen/report.rb +67 -15
  131. data/lib/ibex/codegen/ruby.rb +305 -66
  132. data/lib/ibex/codegen/ruby_actions.rb +146 -0
  133. data/lib/ibex/codegen/ruby_ast.rb +121 -0
  134. data/lib/ibex/codegen/ruby_error_messages.rb +28 -0
  135. data/lib/ibex/codegen/ruby_lexer.rb +83 -0
  136. data/lib/ibex/codegen/ruby_syntax.rb +96 -0
  137. data/lib/ibex/codegen/ruby_table_metadata.rb +57 -0
  138. data/lib/ibex/codegen/ruby_value_printers.rb +56 -0
  139. data/lib/ibex/codegen/symbol_labels.rb +1 -1
  140. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  141. data/lib/ibex/configuration/explanation.rb +421 -0
  142. data/lib/ibex/configuration/inspector.rb +232 -0
  143. data/lib/ibex/configuration.rb +580 -0
  144. data/lib/ibex/coverage/collector.rb +198 -0
  145. data/lib/ibex/coverage/event_stream.rb +103 -0
  146. data/lib/ibex/coverage/report.rb +267 -0
  147. data/lib/ibex/coverage/runtime_event_validator.rb +169 -0
  148. data/lib/ibex/coverage.rb +13 -0
  149. data/lib/ibex/delta_reducer.rb +99 -0
  150. data/lib/ibex/diff.rb +140 -0
  151. data/lib/ibex/equiv/machine.rb +135 -0
  152. data/lib/ibex/equiv.rb +373 -0
  153. data/lib/ibex/error_messages/parser.rb +159 -0
  154. data/lib/ibex/error_messages/parser_v2.rb +198 -0
  155. data/lib/ibex/error_messages/renderer.rb +65 -0
  156. data/lib/ibex/error_messages/sentence_search.rb +196 -0
  157. data/lib/ibex/error_messages/update.rb +169 -0
  158. data/lib/ibex/error_messages.rb +165 -0
  159. data/lib/ibex/fix.rb +557 -0
  160. data/lib/ibex/frontend/ast.rb +166 -7
  161. data/lib/ibex/frontend/bootstrap_parser.rb +40 -4
  162. data/lib/ibex/frontend/diagnostic.rb +81 -0
  163. data/lib/ibex/frontend/diagnostic_recovery.rb +268 -0
  164. data/lib/ibex/frontend/dsl.rb +66 -7
  165. data/lib/ibex/frontend/formatter.rb +453 -0
  166. data/lib/ibex/frontend/generated_parser.rb +644 -190
  167. data/lib/ibex/frontend/generated_parser_base.rb +170 -32
  168. data/lib/ibex/frontend/generated_parser_includes.rb +51 -0
  169. data/lib/ibex/frontend/generated_parser_metadata.rb +61 -0
  170. data/lib/ibex/frontend/generated_parser_parameters.rb +60 -0
  171. data/lib/ibex/frontend/generation.rb +33 -0
  172. data/lib/ibex/frontend/lexer.rb +162 -10
  173. data/lib/ibex/frontend/lexer_recovery.rb +84 -0
  174. data/lib/ibex/frontend/parser/declarations.rb +262 -11
  175. data/lib/ibex/frontend/parser/parameters.rb +82 -0
  176. data/lib/ibex/frontend/parser/rules.rb +43 -6
  177. data/lib/ibex/frontend/parser.rb +182 -4
  178. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  179. data/lib/ibex/frontend/regenerator.rb +27 -1
  180. data/lib/ibex/frontend/resolution.rb +70 -0
  181. data/lib/ibex/frontend/resolver.rb +217 -0
  182. data/lib/ibex/frontend/rule_documentation.rb +104 -0
  183. data/lib/ibex/frontend/source_cursor.rb +133 -9
  184. data/lib/ibex/frontend/source_document.rb +229 -0
  185. data/lib/ibex/frontend/source_loader.rb +150 -0
  186. data/lib/ibex/frontend/source_span.rb +89 -0
  187. data/lib/ibex/frontend/token_adapter/declaration_document_state.rb +47 -0
  188. data/lib/ibex/frontend/token_adapter/declaration_lexer_state.rb +83 -0
  189. data/lib/ibex/frontend/token_adapter/declaration_state.rb +244 -22
  190. data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +8 -2
  191. data/lib/ibex/frontend/token_adapter/rule_state.rb +60 -2
  192. data/lib/ibex/frontend/token_adapter.rb +18 -3
  193. data/lib/ibex/frontend.rb +14 -2
  194. data/lib/ibex/fuzz.rb +219 -0
  195. data/lib/ibex/generation_input.rb +57 -0
  196. data/lib/ibex/generation_manifest.rb +211 -0
  197. data/lib/ibex/generation_transaction.rb +261 -0
  198. data/lib/ibex/generation_transaction_recovery.rb +109 -0
  199. data/lib/ibex/generation_transaction_validation.rb +196 -0
  200. data/lib/ibex/grammar_tests.rb +206 -0
  201. data/lib/ibex/ir/automaton_ir.rb +86 -11
  202. data/lib/ibex/ir/grammar_ir.rb +170 -26
  203. data/lib/ibex/ir/lexer_ir.rb +76 -0
  204. data/lib/ibex/ir/parser_contract.rb +135 -0
  205. data/lib/ibex/ir/serialize.rb +268 -50
  206. data/lib/ibex/ir/validator/automaton.rb +375 -0
  207. data/lib/ibex/ir/validator/base.rb +147 -0
  208. data/lib/ibex/ir/validator/grammar.rb +634 -0
  209. data/lib/ibex/ir/validator/lexer.rb +113 -0
  210. data/lib/ibex/ir/validator.rb +63 -0
  211. data/lib/ibex/ir.rb +71 -5
  212. data/lib/ibex/lalr/build_metrics.rb +71 -0
  213. data/lib/ibex/lalr/builder.rb +514 -52
  214. data/lib/ibex/lalr/conflict.rb +1 -0
  215. data/lib/ibex/lalr/conflict_search.rb +26 -9
  216. data/lib/ibex/lalr/counterexample.rb +38 -18
  217. data/lib/ibex/lalr/direct_lookaheads.rb +289 -0
  218. data/lib/ibex/lalr/goto_follows.rb +229 -0
  219. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  220. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  221. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  222. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  223. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  224. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  225. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  226. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  227. data/lib/ibex/lalr/ielr_partition.rb +166 -0
  228. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  229. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  230. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  231. data/lib/ibex/lalr/on_error_reductions.rb +74 -0
  232. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  233. data/lib/ibex/lalr.rb +59 -1
  234. data/lib/ibex/location.rb +129 -0
  235. data/lib/ibex/lsp/document_handlers.rb +68 -0
  236. data/lib/ibex/lsp/document_store.rb +264 -0
  237. data/lib/ibex/lsp/document_store_diagnostics.rb +44 -0
  238. data/lib/ibex/lsp/document_store_validation.rb +61 -0
  239. data/lib/ibex/lsp/initialization_handlers.rb +80 -0
  240. data/lib/ibex/lsp/navigation_handlers.rb +70 -0
  241. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  242. data/lib/ibex/lsp/position_codec.rb +113 -0
  243. data/lib/ibex/lsp/protocol_error.rb +33 -0
  244. data/lib/ibex/lsp/request_handlers.rb +46 -0
  245. data/lib/ibex/lsp/request_support.rb +94 -0
  246. data/lib/ibex/lsp/server.rb +148 -0
  247. data/lib/ibex/lsp/symbol_index.rb +241 -0
  248. data/lib/ibex/lsp/symbol_index_builder.rb +277 -0
  249. data/lib/ibex/lsp/symbol_index_precedence_references.rb +44 -0
  250. data/lib/ibex/lsp/symbol_index_source_queries.rb +61 -0
  251. data/lib/ibex/lsp/symbol_occurrence.rb +23 -0
  252. data/lib/ibex/lsp/transport.rb +128 -0
  253. data/lib/ibex/lsp/workspace.rb +128 -0
  254. data/lib/ibex/lsp/workspace_analyzer.rb +199 -0
  255. data/lib/ibex/lsp.rb +33 -0
  256. data/lib/ibex/messages/en.yml +28 -0
  257. data/lib/ibex/messages/ja.yml +28 -0
  258. data/lib/ibex/messages.rb +68 -0
  259. data/lib/ibex/metrics.rb +175 -0
  260. data/lib/ibex/normalize/declarations.rb +142 -7
  261. data/lib/ibex/normalize/diagnostics.rb +94 -24
  262. data/lib/ibex/normalize/expander.rb +64 -59
  263. data/lib/ibex/normalize/expression.rb +63 -40
  264. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  265. data/lib/ibex/normalize/inline_expansion.rb +465 -0
  266. data/lib/ibex/normalize/inline_validation.rb +174 -0
  267. data/lib/ibex/normalize/lexer.rb +131 -0
  268. data/lib/ibex/normalize/named_references.rb +60 -0
  269. data/lib/ibex/normalize/nodes.rb +46 -0
  270. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +70 -0
  271. data/lib/ibex/normalize/parameter_lowering.rb +143 -0
  272. data/lib/ibex/normalize/parameter_substitution.rb +125 -0
  273. data/lib/ibex/normalize/parameter_validation.rb +140 -0
  274. data/lib/ibex/normalize/parameters.rb +226 -0
  275. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  276. data/lib/ibex/normalize/recovery_declarations.rb +84 -0
  277. data/lib/ibex/normalize.rb +172 -17
  278. data/lib/ibex/racc_migration/checker.rb +122 -0
  279. data/lib/ibex/racc_migration/harness.rb +177 -0
  280. data/lib/ibex/racc_migration/report.rb +97 -0
  281. data/lib/ibex/racc_migration.rb +12 -0
  282. data/lib/ibex/rake_task.rb +116 -0
  283. data/lib/ibex/samples.rb +216 -0
  284. data/lib/ibex/table_artifact/builder.rb +266 -0
  285. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  286. data/lib/ibex/table_artifact/document.rb +39 -0
  287. data/lib/ibex/table_artifact/executor.rb +187 -0
  288. data/lib/ibex/table_artifact/serializer.rb +57 -0
  289. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  290. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  291. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  292. data/lib/ibex/table_artifact/validator.rb +253 -0
  293. data/lib/ibex/table_artifact.rb +88 -0
  294. data/lib/ibex/table_simulation/result.rb +55 -0
  295. data/lib/ibex/table_simulation/simulator.rb +253 -0
  296. data/lib/ibex/table_simulation/step.rb +62 -0
  297. data/lib/ibex/table_simulation/text.rb +31 -0
  298. data/lib/ibex/table_simulation.rb +13 -0
  299. data/lib/ibex/tables.rb +22 -69
  300. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  301. data/lib/ibex/verification_report/builder.rb +159 -0
  302. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  303. data/lib/ibex/verification_report/logical_path.rb +68 -0
  304. data/lib/ibex/verification_report/validator.rb +329 -0
  305. data/lib/ibex/verification_report.rb +69 -0
  306. data/lib/ibex/verify/action_correspondence.rb +138 -0
  307. data/lib/ibex/verify/language_witness.rb +295 -0
  308. data/lib/ibex/verify/reference_collection.rb +190 -0
  309. data/lib/ibex/verify/result.rb +73 -0
  310. data/lib/ibex/verify/verifier.rb +598 -0
  311. data/lib/ibex/verify.rb +15 -0
  312. data/lib/ibex/version.rb +1 -1
  313. data/lib/ibex/watch/runner.rb +181 -0
  314. data/lib/ibex/watch/source_snapshot.rb +98 -0
  315. data/lib/ibex/watch.rb +11 -0
  316. data/lib/ibex.rb +35 -1
  317. data/schema/automaton-ir-definitions.schema.json +401 -0
  318. data/schema/automaton-ir.schema.json +65 -0
  319. data/schema/benchmark-v1.schema.json +212 -0
  320. data/schema/benchmark-v2.schema.json +61 -0
  321. data/schema/bison-import-v1.schema.json +106 -0
  322. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  323. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  324. data/schema/construction-profile-v1.schema.json +779 -0
  325. data/schema/cst-v1.json +170 -0
  326. data/schema/diff-v1.schema.json +41 -0
  327. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  328. data/schema/equiv-v1.schema.json +64 -0
  329. data/schema/error-ux-review-v1.schema.json +609 -0
  330. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  331. data/schema/error-ux-round2-v1.schema.json +519 -0
  332. data/schema/error-ux-v1.schema.json +258 -0
  333. data/schema/explain-v1.schema.json +495 -0
  334. data/schema/fix-v1.schema.json +64 -0
  335. data/schema/fix-v2.schema.json +66 -0
  336. data/schema/fix-v3.schema.json +101 -0
  337. data/schema/frontend-diagnostics-v1.schema.json +154 -0
  338. data/schema/fuzz-regression-v1.schema.json +79 -0
  339. data/schema/fuzz-v1.schema.json +114 -0
  340. data/schema/generation-manifest-v1.schema.json +115 -0
  341. data/schema/grammar-ir-extensions.schema.json +734 -0
  342. data/schema/grammar-ir-foundation.schema.json +426 -0
  343. data/schema/grammar-ir.schema.json +271 -0
  344. data/schema/ielr-benchmark-v1.schema.json +85 -0
  345. data/schema/lexer-ir-v1.schema.json +215 -0
  346. data/schema/lexer-profile-v1.schema.json +491 -0
  347. data/schema/metrics-v1.schema.json +60 -0
  348. data/schema/migration-check-v1.schema.json +60 -0
  349. data/schema/performance-comparison-v1.schema.json +395 -0
  350. data/schema/public-performance-comparison-v1.schema.json +506 -0
  351. data/schema/public-performance-profile-v1.schema.json +360 -0
  352. data/schema/reduce-v1.schema.json +22 -0
  353. data/schema/reduce-v2.schema.json +63 -0
  354. data/schema/runtime-coverage-v1.schema.json +86 -0
  355. data/schema/runtime-event-v1.schema.json +308 -0
  356. data/schema/table-artifact-v1.schema.json +377 -0
  357. data/schema/table-simulation-v1.schema.json +70 -0
  358. data/schema/verification-report-v1.schema.json +222 -0
  359. data/schema/verify-v1.schema.json +42 -0
  360. data/sig/ibex/analysis/digraph.rbs +19 -0
  361. data/sig/ibex/artifact_set.rbs +37 -0
  362. data/sig/ibex/bison_import/importer.rbs +140 -0
  363. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  364. data/sig/ibex/bison_import.rbs +95 -0
  365. data/sig/ibex/bounded_subprocess.rbs +76 -0
  366. data/sig/ibex/cli/ambiguity.rbs +28 -0
  367. data/sig/ibex/cli/analysis.rbs +43 -0
  368. data/sig/ibex/cli/bison_import.rbs +32 -0
  369. data/sig/ibex/cli/config.rbs +35 -0
  370. data/sig/ibex/cli/counterexample_options.rbs +2 -0
  371. data/sig/ibex/cli/coverage.rbs +53 -0
  372. data/sig/ibex/cli/debug.rbs +28 -0
  373. data/sig/ibex/cli/diagnostics.rbs +51 -0
  374. data/sig/ibex/cli/documentation.rbs +29 -0
  375. data/sig/ibex/cli/equiv.rbs +49 -0
  376. data/sig/ibex/cli/error_messages.rbs +63 -0
  377. data/sig/ibex/cli/explain.rbs +31 -0
  378. data/sig/ibex/cli/fix.rbs +53 -0
  379. data/sig/ibex/cli/formatting.rbs +107 -0
  380. data/sig/ibex/cli/fuzz.rbs +47 -0
  381. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  382. data/sig/ibex/cli/generation_artifacts.rbs +69 -0
  383. data/sig/ibex/cli/generation_error_messages.rbs +19 -0
  384. data/sig/ibex/cli/grammar_tests.rbs +48 -0
  385. data/sig/ibex/cli/ir_tools.rbs +39 -0
  386. data/sig/ibex/cli/lsp.rbs +14 -0
  387. data/sig/ibex/cli/outputs.rbs +26 -3
  388. data/sig/ibex/cli/racc_migration.rbs +30 -0
  389. data/sig/ibex/cli/reduce.rbs +60 -0
  390. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  391. data/sig/ibex/cli/samples.rbs +38 -0
  392. data/sig/ibex/cli/verify.rbs +28 -0
  393. data/sig/ibex/cli/watch.rbs +43 -0
  394. data/sig/ibex/cli.rbs +182 -9
  395. data/sig/ibex/codegen/action_locations.rbs +45 -0
  396. data/sig/ibex/codegen/action_method_source.rbs +65 -0
  397. data/sig/ibex/codegen/action_source.rbs +50 -0
  398. data/sig/ibex/codegen/ambiguity.rbs +60 -0
  399. data/sig/ibex/codegen/cst_metadata.rbs +59 -0
  400. data/sig/ibex/codegen/documentation.rbs +50 -0
  401. data/sig/ibex/codegen/explain.rbs +105 -0
  402. data/sig/ibex/codegen/generated_action_abi.rbs +105 -0
  403. data/sig/ibex/codegen/html.rbs +18 -2
  404. data/sig/ibex/codegen/mermaid.rbs +16 -0
  405. data/sig/ibex/codegen/railroad.rbs +82 -0
  406. data/sig/ibex/codegen/railroad_documentation.rbs +31 -0
  407. data/sig/ibex/codegen/rbs.rbs +86 -4
  408. data/sig/ibex/codegen/report.rbs +16 -8
  409. data/sig/ibex/codegen/ruby.rbs +105 -28
  410. data/sig/ibex/codegen/ruby_actions.rbs +54 -0
  411. data/sig/ibex/codegen/ruby_ast.rbs +34 -0
  412. data/sig/ibex/codegen/ruby_error_messages.rbs +16 -0
  413. data/sig/ibex/codegen/ruby_lexer.rbs +25 -0
  414. data/sig/ibex/codegen/ruby_syntax.rbs +27 -0
  415. data/sig/ibex/codegen/ruby_table_metadata.rbs +26 -0
  416. data/sig/ibex/codegen/ruby_value_printers.rbs +28 -0
  417. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  418. data/sig/ibex/configuration/explanation.rbs +195 -0
  419. data/sig/ibex/configuration/inspector.rbs +53 -0
  420. data/sig/ibex/configuration.rbs +242 -0
  421. data/sig/ibex/coverage/collector.rbs +78 -0
  422. data/sig/ibex/coverage/event_stream.rbs +44 -0
  423. data/sig/ibex/coverage/report.rbs +102 -0
  424. data/sig/ibex/coverage/runtime_event_validator.rbs +70 -0
  425. data/sig/ibex/coverage.rbs +7 -0
  426. data/sig/ibex/delta_reducer.rbs +38 -0
  427. data/sig/ibex/diff.rbs +47 -0
  428. data/sig/ibex/equiv/machine.rbs +54 -0
  429. data/sig/ibex/equiv.rbs +129 -0
  430. data/sig/ibex/error_messages/parser.rbs +58 -0
  431. data/sig/ibex/error_messages/parser_v2.rbs +67 -0
  432. data/sig/ibex/error_messages/renderer.rbs +23 -0
  433. data/sig/ibex/error_messages/sentence_search.rbs +80 -0
  434. data/sig/ibex/error_messages/update.rbs +43 -0
  435. data/sig/ibex/error_messages.rbs +85 -0
  436. data/sig/ibex/fix.rbs +138 -0
  437. data/sig/ibex/frontend/ast.rbs +237 -22
  438. data/sig/ibex/frontend/bootstrap_parser.rbs +13 -0
  439. data/sig/ibex/frontend/diagnostic.rbs +53 -0
  440. data/sig/ibex/frontend/diagnostic_recovery.rbs +98 -0
  441. data/sig/ibex/frontend/dsl.rbs +33 -4
  442. data/sig/ibex/frontend/formatter.rbs +143 -0
  443. data/sig/ibex/frontend/generated_parser.rbs +228 -68
  444. data/sig/ibex/frontend/generated_parser_base.rbs +65 -12
  445. data/sig/ibex/frontend/generated_parser_includes.rbs +23 -0
  446. data/sig/ibex/frontend/generated_parser_metadata.rbs +23 -0
  447. data/sig/ibex/frontend/generated_parser_parameters.rbs +24 -0
  448. data/sig/ibex/frontend/generation.rbs +6 -0
  449. data/sig/ibex/frontend/lexer.rbs +49 -2
  450. data/sig/ibex/frontend/lexer_recovery.rbs +25 -0
  451. data/sig/ibex/frontend/parser/declarations.rbs +57 -0
  452. data/sig/ibex/frontend/parser/parameters.rbs +28 -0
  453. data/sig/ibex/frontend/parser/rules.rbs +3 -0
  454. data/sig/ibex/frontend/parser.rbs +66 -0
  455. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  456. data/sig/ibex/frontend/regenerator.rbs +11 -0
  457. data/sig/ibex/frontend/resolution.rbs +34 -0
  458. data/sig/ibex/frontend/resolver.rbs +91 -0
  459. data/sig/ibex/frontend/rule_documentation.rbs +42 -0
  460. data/sig/ibex/frontend/source_cursor.rbs +38 -5
  461. data/sig/ibex/frontend/source_document.rbs +119 -0
  462. data/sig/ibex/frontend/source_loader.rbs +66 -0
  463. data/sig/ibex/frontend/source_span.rbs +56 -0
  464. data/sig/ibex/frontend/token_adapter/declaration_document_state.rbs +21 -0
  465. data/sig/ibex/frontend/token_adapter/declaration_lexer_state.rbs +27 -0
  466. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +83 -3
  467. data/sig/ibex/frontend/token_adapter/rule_state.rbs +20 -0
  468. data/sig/ibex/frontend/token_adapter.rbs +11 -2
  469. data/sig/ibex/frontend.rbs +1 -1
  470. data/sig/ibex/fuzz.rbs +71 -0
  471. data/sig/ibex/generation_input.rbs +37 -0
  472. data/sig/ibex/generation_manifest.rbs +70 -0
  473. data/sig/ibex/generation_transaction.rbs +82 -0
  474. data/sig/ibex/generation_transaction_recovery.rbs +36 -0
  475. data/sig/ibex/generation_transaction_validation.rbs +65 -0
  476. data/sig/ibex/grammar_tests.rbs +93 -0
  477. data/sig/ibex/ir/automaton_ir.rbs +35 -10
  478. data/sig/ibex/ir/grammar_ir.rbs +96 -22
  479. data/sig/ibex/ir/lexer_ir.rbs +57 -0
  480. data/sig/ibex/ir/parser_contract.rbs +69 -0
  481. data/sig/ibex/ir/serialize.rbs +75 -23
  482. data/sig/ibex/ir/validator/automaton.rbs +112 -0
  483. data/sig/ibex/ir/validator/base.rbs +69 -0
  484. data/sig/ibex/ir/validator/grammar.rbs +195 -0
  485. data/sig/ibex/ir/validator/lexer.rbs +37 -0
  486. data/sig/ibex/ir/validator.rbs +16 -0
  487. data/sig/ibex/ir.rbs +45 -5
  488. data/sig/ibex/lalr/build_metrics.rbs +60 -0
  489. data/sig/ibex/lalr/builder.rbs +150 -19
  490. data/sig/ibex/lalr/conflict_search.rbs +12 -5
  491. data/sig/ibex/lalr/counterexample.rbs +20 -14
  492. data/sig/ibex/lalr/direct_lookaheads.rbs +93 -0
  493. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  494. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  495. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  496. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  497. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  498. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  499. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  500. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  501. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  502. data/sig/ibex/lalr/ielr_partition.rbs +71 -0
  503. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  504. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  505. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  506. data/sig/ibex/lalr/on_error_reductions.rbs +22 -0
  507. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  508. data/sig/ibex/lalr.rbs +17 -1
  509. data/sig/ibex/location.rbs +67 -0
  510. data/sig/ibex/lsp/document_handlers.rbs +24 -0
  511. data/sig/ibex/lsp/document_store.rbs +94 -0
  512. data/sig/ibex/lsp/document_store_diagnostics.rbs +20 -0
  513. data/sig/ibex/lsp/document_store_validation.rbs +26 -0
  514. data/sig/ibex/lsp/initialization_handlers.rbs +30 -0
  515. data/sig/ibex/lsp/navigation_handlers.rbs +33 -0
  516. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  517. data/sig/ibex/lsp/position_codec.rbs +44 -0
  518. data/sig/ibex/lsp/protocol_error.rbs +32 -0
  519. data/sig/ibex/lsp/request_handlers.rbs +26 -0
  520. data/sig/ibex/lsp/request_support.rbs +40 -0
  521. data/sig/ibex/lsp/server.rbs +55 -0
  522. data/sig/ibex/lsp/symbol_index.rbs +77 -0
  523. data/sig/ibex/lsp/symbol_index_builder.rbs +88 -0
  524. data/sig/ibex/lsp/symbol_index_precedence_references.rbs +18 -0
  525. data/sig/ibex/lsp/symbol_index_source_queries.rbs +26 -0
  526. data/sig/ibex/lsp/symbol_occurrence.rbs +31 -0
  527. data/sig/ibex/lsp/transport.rbs +47 -0
  528. data/sig/ibex/lsp/workspace.rbs +42 -0
  529. data/sig/ibex/lsp/workspace_analyzer.rbs +69 -0
  530. data/sig/ibex/lsp.rbs +7 -0
  531. data/sig/ibex/messages.rbs +26 -0
  532. data/sig/ibex/metrics.rbs +50 -0
  533. data/sig/ibex/normalize/declarations.rbs +31 -0
  534. data/sig/ibex/normalize/diagnostics.rbs +13 -4
  535. data/sig/ibex/normalize/expander.rbs +22 -16
  536. data/sig/ibex/normalize/expression.rbs +11 -9
  537. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  538. data/sig/ibex/normalize/inline_expansion.rbs +129 -0
  539. data/sig/ibex/normalize/inline_validation.rbs +40 -0
  540. data/sig/ibex/normalize/lexer.rbs +37 -0
  541. data/sig/ibex/normalize/named_references.rbs +20 -0
  542. data/sig/ibex/normalize/nodes.rbs +14 -0
  543. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +33 -0
  544. data/sig/ibex/normalize/parameter_lowering.rbs +35 -0
  545. data/sig/ibex/normalize/parameter_substitution.rbs +42 -0
  546. data/sig/ibex/normalize/parameter_validation.rbs +44 -0
  547. data/sig/ibex/normalize/parameters.rbs +51 -0
  548. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  549. data/sig/ibex/normalize/recovery_declarations.rbs +24 -0
  550. data/sig/ibex/normalize.rbs +129 -18
  551. data/sig/ibex/racc_migration/checker.rbs +36 -0
  552. data/sig/ibex/racc_migration/harness.rbs +16 -0
  553. data/sig/ibex/racc_migration/report.rbs +55 -0
  554. data/sig/ibex/racc_migration.rbs +8 -0
  555. data/sig/ibex/rake_task.rbs +51 -0
  556. data/sig/ibex/samples.rbs +60 -0
  557. data/sig/ibex/table_artifact/builder.rbs +93 -0
  558. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  559. data/sig/ibex/table_artifact/document.rbs +27 -0
  560. data/sig/ibex/table_artifact/executor.rbs +66 -0
  561. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  562. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  563. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  564. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  565. data/sig/ibex/table_artifact/validator.rbs +66 -0
  566. data/sig/ibex/table_artifact.rbs +32 -0
  567. data/sig/ibex/table_simulation/result.rbs +36 -0
  568. data/sig/ibex/table_simulation/simulator.rbs +98 -0
  569. data/sig/ibex/table_simulation/step.rbs +42 -0
  570. data/sig/ibex/table_simulation/text.rbs +14 -0
  571. data/sig/ibex/table_simulation.rbs +7 -0
  572. data/sig/ibex/tables.rbs +10 -35
  573. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  574. data/sig/ibex/verification_report/builder.rbs +53 -0
  575. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  576. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  577. data/sig/ibex/verification_report/validator.rbs +114 -0
  578. data/sig/ibex/verification_report.rbs +44 -0
  579. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  580. data/sig/ibex/verify/language_witness.rbs +112 -0
  581. data/sig/ibex/verify/reference_collection.rbs +63 -0
  582. data/sig/ibex/verify/result.rbs +52 -0
  583. data/sig/ibex/verify/verifier.rbs +170 -0
  584. data/sig/ibex/verify.rbs +6 -0
  585. data/sig/ibex/watch/runner.rbs +58 -0
  586. data/sig/ibex/watch/source_snapshot.rbs +46 -0
  587. data/sig/ibex/watch.rbs +7 -0
  588. data/sig/ibex.rbs +2 -0
  589. metadata +524 -19
  590. data/.rubocop.yml +0 -43
  591. data/CHANGELOG.md +0 -30
  592. data/Rakefile +0 -25
  593. data/Steepfile +0 -10
  594. data/docs/compat-notes.md +0 -37
  595. data/docs/lexer-coverage.md +0 -14
  596. data/docs/phase10-extensions.md +0 -27
  597. data/gemfiles/Gemfile +0 -7
  598. data/gemfiles/Gemfile.lock +0 -98
  599. data/lib/ibex/frontend/grammar.y +0 -156
  600. data/lib/ibex/runtime/parser.rb +0 -360
  601. data/lib/ibex/runtime.rb +0 -8
  602. data/sig/ibex/runtime/parser.rbs +0 -167
  603. data/sig/ibex/runtime.rbs +0 -6
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "digest"
5
+ require_relative "error"
6
+ require_relative "generation_transaction_recovery"
7
+ require_relative "generation_transaction_validation"
8
+
9
+ module Ibex
10
+ # Publishes a rendered ArtifactSet with rollback and a manifest-last marker.
11
+ class GenerationTransaction
12
+ include GenerationTransactionRecovery
13
+ include GenerationTransactionValidation
14
+
15
+ class SourceChanged < Ibex::Error; end
16
+
17
+ class Error < Ibex::Error
18
+ attr_reader :rollback_failed #: bool
19
+ attr_reader :recovery_artifacts #: Array[String]
20
+
21
+ # @rbs (String message, ?rollback_failed: bool, ?recovery_artifacts: Array[String]) -> void
22
+ def initialize(message, rollback_failed: false, recovery_artifacts: [])
23
+ super(message)
24
+ @rollback_failed = rollback_failed
25
+ @recovery_artifacts = recovery_artifacts.freeze
26
+ end
27
+ end
28
+
29
+ # rubocop:disable Layout/LineLength
30
+ # @rbs (ArtifactSet artifacts, ?warning: ^(String) -> void, ?stability_check: ^() -> bool, ?source_records: Array[GenerationInput], ?lock_sleeper: ^(Float) -> void) -> void
31
+ def initialize(artifacts, warning: ->(_message) {}, stability_check: -> { true }, source_records: [],
32
+ lock_sleeper: ->(seconds) { sleep(seconds) })
33
+ @artifacts = artifacts
34
+ @warning = warning
35
+ @stability_check = stability_check
36
+ @source_records = source_records
37
+ @lock_sleeper = lock_sleeper
38
+ @locks = [] #: Array[File]
39
+ @locked_paths = [] #: Array[String]
40
+ @records = [] #: Array[Hash[Symbol, Object?]]
41
+ @committed = false
42
+ @temporary_sequence = 0
43
+ end
44
+ # rubocop:enable Layout/LineLength
45
+
46
+ # @rbs () -> void
47
+ def commit
48
+ @committed = false
49
+ prepare_records
50
+ validate_source_boundaries!
51
+ acquire_locks
52
+ prepare_records
53
+ validate_source_boundaries!
54
+ validate_locked_targets!
55
+ ensure_sources_stable!("before staging outputs")
56
+ stage_records
57
+ ensure_sources_stable!("while staging outputs")
58
+ backup_records
59
+ sync_directories!
60
+ install_group(:non_marker)
61
+ sync_directories!
62
+ ensure_sources_stable!("before parser publication")
63
+ install_group(:parser)
64
+ sync_directories!
65
+ ensure_sources_stable!("after parser publication")
66
+ install_group(:manifest)
67
+ sync_directories!
68
+ ensure_sources_stable!("after manifest publication")
69
+ @committed = true
70
+ cleanup_success
71
+ rescue StandardError, SignalException => e
72
+ @committed ? report_post_commit_failure(e) : handle_failure(e)
73
+ ensure
74
+ release_locks
75
+ end
76
+
77
+ private
78
+
79
+ # @rbs () -> void
80
+ def prepare_records
81
+ @records = @artifacts.map do |artifact|
82
+ target = resolved_target(artifact.path)
83
+ {
84
+ artifact: artifact, target: target, directory: File.dirname(target),
85
+ stage: nil, backup: nil, backed_up: false, installed: false,
86
+ existed: File.exist?(target), mode: target_mode(artifact, target)
87
+ }
88
+ end
89
+ grouped = @records.group_by do |record|
90
+ portable_target_key(record.fetch(:target))
91
+ end
92
+ collision = grouped.find { |_key, records| records.length > 1 }
93
+ return unless collision
94
+
95
+ kinds = collision.fetch(1).map { |record| record.fetch(:artifact).kind }.join(", ")
96
+ targets = collision.fetch(1).map { |record| record.fetch(:target) }.join(", ")
97
+ raise Error, "(generation):1:1: artifact targets collide: #{targets} (#{kinds})"
98
+ end
99
+
100
+ # @rbs (Artifact artifact, String target) -> Integer
101
+ def target_mode(artifact, target)
102
+ return artifact.mode if artifact.mode
103
+ return File.stat(target).mode & 0o7777 if File.exist?(target)
104
+
105
+ mask = File.umask
106
+ File.umask(mask)
107
+ 0o666 & ~mask
108
+ end
109
+
110
+ # @rbs () -> void
111
+ def acquire_locks
112
+ lock_paths = @records.map { |record| lock_path(record.fetch(:target)) }.uniq.sort
113
+ validate_lock_boundaries!(lock_paths)
114
+ @locked_paths = lock_paths
115
+ @locks = [] #: Array[File]
116
+ lock_paths.each do |path|
117
+ flags = File::RDWR | File::CREAT
118
+ flags |= File.const_get(:NOFOLLOW) if File.const_defined?(:NOFOLLOW)
119
+ lock = File.new(path, flags, 0o600)
120
+ @locks << lock
121
+ validate_lock_identity!(path, lock)
122
+ acquire_lock(lock, path)
123
+ validate_lock_identity!(path, lock)
124
+ end
125
+ end
126
+
127
+ # @rbs (File lock, String path) -> void
128
+ def acquire_lock(lock, path)
129
+ until lock.flock(File::LOCK_EX | File::LOCK_NB)
130
+ unless @stability_check.call
131
+ raise SourceChanged, "(generation):1:1: generation cancelled while waiting for lock: #{path}"
132
+ end
133
+
134
+ @lock_sleeper.call(0.05)
135
+ end
136
+ end
137
+
138
+ # @rbs (String target) -> String
139
+ def lock_path(target)
140
+ digest = Digest::SHA256.hexdigest(target).slice(0, 16)
141
+ File.join(File.dirname(target), ".ibex-generation-#{digest}.lock")
142
+ end
143
+
144
+ # @rbs () -> void
145
+ def stage_records
146
+ @records.each do |record|
147
+ temporary = create_temporary_file(record.fetch(:directory), ".stage")
148
+ begin
149
+ record[:stage] = temporary.path.dup
150
+ temporary.binmode
151
+ temporary.write(record.fetch(:artifact).content)
152
+ temporary.flush
153
+ temporary.chmod(record.fetch(:mode))
154
+ temporary.fsync
155
+ ensure
156
+ temporary.close
157
+ end
158
+ end
159
+ end
160
+
161
+ # @rbs () -> void
162
+ def backup_records
163
+ @records.each do |record|
164
+ target = record.fetch(:target)
165
+ next unless File.exist?(target)
166
+
167
+ validate_existing_target!(target)
168
+ backup = create_backup_link(target, record.fetch(:directory))
169
+ record[:backup] = backup
170
+ record[:backed_up] = true
171
+ end
172
+ end
173
+
174
+ # @rbs (String directory, String suffix) -> File
175
+ def create_temporary_file(directory, suffix)
176
+ with_temporary_path(directory, suffix) do |path|
177
+ flags = File::WRONLY | File::CREAT | File::EXCL
178
+ flags |= File.const_get(:NOFOLLOW) if File.const_defined?(:NOFOLLOW)
179
+ return File.new(path, flags, 0o600)
180
+ end
181
+ raise Error, "(generation):1:1: temporary file is unavailable in #{directory}"
182
+ end
183
+
184
+ # @rbs (String target, String directory) -> String
185
+ def create_backup_link(target, directory)
186
+ with_temporary_path(directory, ".backup") do |path|
187
+ File.link(target, path)
188
+ return path
189
+ end
190
+ raise Error, "(generation):1:1: backup path is unavailable in #{directory}"
191
+ end
192
+
193
+ # @rbs (String directory, String suffix) { (String) -> void } -> void
194
+ def with_temporary_path(directory, suffix)
195
+ 100.times do
196
+ @temporary_sequence += 1
197
+ name = ".ibex-generation-#{Process.pid}-#{object_id.to_s(36)}-#{@temporary_sequence.to_s(36)}#{suffix}"
198
+ yield File.join(directory, name)
199
+ rescue Errno::EEXIST
200
+ next
201
+ end
202
+ end
203
+
204
+ # @rbs (Symbol group) -> void
205
+ def install_group(group)
206
+ selected = @records.select { |record| publication_group(record.fetch(:artifact)) == group }
207
+ selected.sort_by { |record| record.fetch(:target) }.each do |record|
208
+ File.rename(record.fetch(:stage), record.fetch(:target))
209
+ record[:stage] = nil
210
+ record[:installed] = true
211
+ end
212
+ end
213
+
214
+ # @rbs (Artifact artifact) -> Symbol
215
+ def publication_group(artifact)
216
+ return :manifest if artifact.kind == :manifest
217
+ return :parser if artifact.kind == :parser
218
+
219
+ :non_marker
220
+ end
221
+
222
+ # @rbs () -> void
223
+ def sync_directories!
224
+ @records.map { |record| record.fetch(:directory) }.uniq.sort.each do |directory|
225
+ File.open(directory, File::RDONLY, &:fsync)
226
+ end
227
+ end
228
+
229
+ # @rbs () -> void
230
+ def cleanup_success
231
+ failures = cleanup_artifacts(preserve_backups: false)
232
+ sync_failures = sync_directories_best_effort
233
+ messages = failures + sync_failures
234
+ warn_best_effort("generation cleanup incomplete: #{messages.join('; ')}") unless messages.empty?
235
+ end
236
+
237
+ # @rbs (Exception error) -> void
238
+ def report_post_commit_failure(error)
239
+ warn_best_effort("generation committed but cleanup failed: #{error.message}")
240
+ end
241
+
242
+ # @rbs (String message) -> void
243
+ def warn_best_effort(message)
244
+ @warning.call(message)
245
+ rescue StandardError
246
+ nil
247
+ end
248
+
249
+ # @rbs () -> void
250
+ def release_locks
251
+ @locks.reverse_each do |lock|
252
+ lock.flock(File::LOCK_UN)
253
+ lock.close
254
+ rescue StandardError
255
+ nil
256
+ end
257
+ @locks = [] #: Array[File]
258
+ @locked_paths = [] #: Array[String]
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ # Failure recovery and best-effort cleanup for GenerationTransaction.
6
+ module GenerationTransactionRecovery
7
+ private
8
+
9
+ # @rbs (Exception cause) -> bot
10
+ def handle_failure(cause)
11
+ rollback_failures = rollback
12
+ cleanup_failures = cleanup_artifacts(preserve_backups: !rollback_failures.empty?)
13
+ sync_failures = sync_directories_best_effort
14
+ raise cause if source_change_clean?(cause, rollback_failures, cleanup_failures, sync_failures)
15
+
16
+ recovery = recovery_artifacts
17
+ details = failure_details(cause, rollback_failures, cleanup_failures, sync_failures, recovery)
18
+ raise GenerationTransaction::Error.new(
19
+ "(generation):1:1: #{details.join(' | ')}",
20
+ rollback_failed: !rollback_failures.empty?, recovery_artifacts: recovery
21
+ ), cause: cause
22
+ end
23
+
24
+ # @rbs (Exception cause, Array[String] rollback, Array[String] cleanup, Array[String] sync) -> bool
25
+ def source_change_clean?(cause, rollback, cleanup, sync)
26
+ cause.is_a?(GenerationTransaction::SourceChanged) && rollback.empty? && cleanup.empty? && sync.empty?
27
+ end
28
+
29
+ # rubocop:disable Layout/LineLength
30
+ # @rbs (Exception cause, Array[String] rollback, Array[String] cleanup, Array[String] sync, Array[String] recovery) -> Array[String]
31
+ def failure_details(cause, rollback, cleanup, sync, recovery)
32
+ details = [cause.message]
33
+ details << "rollback failed: #{rollback.join('; ')}" unless rollback.empty?
34
+ details << "cleanup failed: #{cleanup.join('; ')}" unless cleanup.empty?
35
+ details << "directory sync failed: #{sync.join('; ')}" unless sync.empty?
36
+ details << "recovery artifacts: #{recovery.join(', ')}" unless recovery.empty?
37
+ details
38
+ end
39
+ # rubocop:enable Layout/LineLength
40
+
41
+ # @rbs () -> Array[String]
42
+ def recovery_artifacts
43
+ @records.filter_map do |record|
44
+ backup = record[:backup]
45
+ backup if backup && File.exist?(backup)
46
+ end
47
+ end
48
+
49
+ # @rbs () -> Array[String]
50
+ def rollback
51
+ failures = [] #: Array[String]
52
+ @records.reverse_each do |record|
53
+ rollback_record(record)
54
+ rescue StandardError => e
55
+ failures << "#{record.fetch(:target)}: #{e.message}"
56
+ end
57
+ failures
58
+ end
59
+
60
+ # @rbs (Hash[Symbol, Object?] record) -> void
61
+ def rollback_record(record)
62
+ target = record.fetch(:target) #: String
63
+ if record.fetch(:backed_up)
64
+ if record.fetch(:installed)
65
+ File.rename(record.fetch(:backup).to_s, target)
66
+ else
67
+ File.unlink(record.fetch(:backup).to_s)
68
+ end
69
+ record[:backup] = nil
70
+ record[:backed_up] = false
71
+ record[:installed] = false
72
+ elsif record.fetch(:installed)
73
+ File.unlink(target)
74
+ record[:installed] = false
75
+ end
76
+ end
77
+
78
+ # @rbs (preserve_backups: bool) -> Array[String]
79
+ def cleanup_artifacts(preserve_backups:)
80
+ failures = [] #: Array[String]
81
+ @records.each do |record|
82
+ paths = [record[:stage]]
83
+ paths << record[:backup] unless preserve_backups
84
+ paths.compact.each { |path| remove_artifact(path, failures) }
85
+ end
86
+ failures
87
+ end
88
+
89
+ # @rbs (String path, Array[String] failures) -> void
90
+ def remove_artifact(path, failures)
91
+ File.unlink(path)
92
+ rescue Errno::ENOENT
93
+ nil
94
+ rescue StandardError => e
95
+ failures << "#{path}: #{e.message}"
96
+ end
97
+
98
+ # @rbs () -> Array[String]
99
+ def sync_directories_best_effort
100
+ failures = [] #: Array[String]
101
+ @records.map { |record| record.fetch(:directory) }.uniq.sort.each do |directory|
102
+ File.open(directory, File::RDONLY, &:fsync)
103
+ rescue StandardError => e
104
+ failures << "#{directory}: #{e.message}"
105
+ end
106
+ failures
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ # Canonical target, source, symlink, and lock identity checks for generation.
6
+ module GenerationTransactionValidation
7
+ # @rbs!
8
+ # private def lock_path: (String) -> String
9
+
10
+ private
11
+
12
+ # Follow a final symlink while preserving the symlink path itself during publication.
13
+ # @rbs (String path, ?Hash[String, bool] seen, ?validate_links: bool) -> String
14
+ def resolved_target(path, seen = {}, validate_links: true)
15
+ expanded = File.expand_path(path)
16
+ if File.symlink?(expanded)
17
+ raise Errno::ELOOP, expanded if seen[expanded]
18
+
19
+ seen[expanded] = true
20
+ linked = File.expand_path(File.readlink(expanded), File.dirname(expanded))
21
+ return resolved_target(linked, seen, validate_links: validate_links)
22
+ end
23
+ return existing_target(expanded, validate_links) if File.exist?(expanded)
24
+
25
+ parent = File.realpath(File.dirname(expanded))
26
+ raise Errno::ENOTDIR, parent unless File.directory?(parent)
27
+
28
+ File.join(parent, File.basename(expanded))
29
+ end
30
+
31
+ # @rbs (String expanded, bool validate_links) -> String
32
+ def existing_target(expanded, validate_links)
33
+ target = File.realpath(expanded)
34
+ validate_existing_target!(target) if validate_links
35
+ target
36
+ end
37
+
38
+ # @rbs (String target) -> void
39
+ def validate_existing_target!(target)
40
+ stat = File.stat(target)
41
+ unless stat.file?
42
+ raise GenerationTransaction::Error,
43
+ "(generation):1:1: output target is not a regular file: #{target}"
44
+ end
45
+ return unless stat.nlink > 1
46
+
47
+ raise GenerationTransaction::Error,
48
+ "(generation):1:1: output target has multiple hard links: #{target}"
49
+ end
50
+
51
+ # @rbs () -> void
52
+ def validate_source_boundaries!
53
+ source_paths = @source_records.map(&:path)
54
+ @records.each do |record|
55
+ target = record.fetch(:target)
56
+ collision = source_paths.find { |source| paths_alias?(target, source) }
57
+ next unless collision
58
+
59
+ raise GenerationTransaction::Error,
60
+ "(generation):1:1: artifact target aliases generation input: #{target} (#{collision})"
61
+ end
62
+ end
63
+
64
+ # @rbs (Array[String] lock_paths) -> void
65
+ def validate_lock_boundaries!(lock_paths)
66
+ protected_paths = protected_generation_paths
67
+ lock_paths.each do |path|
68
+ if File.symlink?(path)
69
+ raise GenerationTransaction::Error,
70
+ "(generation):1:1: generation lock must not be a symlink: #{path}"
71
+ end
72
+
73
+ collision = protected_paths.find { |protected| paths_alias?(path, protected) }
74
+ next unless collision
75
+
76
+ raise GenerationTransaction::Error,
77
+ "(generation):1:1: generation lock aliases a protected path: #{path} (#{collision})"
78
+ end
79
+ end
80
+
81
+ # @rbs (String path, File lock) -> void
82
+ def validate_lock_identity!(path, lock)
83
+ path_stat = File.lstat(path)
84
+ lock_stat = lock.stat
85
+ unless stable_lock_identity?(path_stat, lock_stat)
86
+ raise GenerationTransaction::Error,
87
+ "(generation):1:1: generation lock identity changed while opening: #{path}"
88
+ end
89
+ return unless protected_generation_stats.any? { |stat| same_inode?(stat, lock_stat) }
90
+
91
+ raise GenerationTransaction::Error,
92
+ "(generation):1:1: generation lock aliases a protected inode: #{path}"
93
+ end
94
+
95
+ # @rbs () -> Array[String]
96
+ def protected_generation_paths
97
+ @source_records.map(&:path) + @records.map { |record| record.fetch(:target) }
98
+ end
99
+
100
+ # @rbs () -> Array[File::Stat]
101
+ def protected_generation_stats
102
+ protected_generation_paths.filter_map { |path| File.stat(path) if File.exist?(path) }
103
+ end
104
+
105
+ # @rbs (File::Stat path_stat, File::Stat lock_stat) -> bool
106
+ def stable_lock_identity?(path_stat, lock_stat)
107
+ path_stat.file? && path_stat.nlink == 1 && same_inode?(path_stat, lock_stat)
108
+ end
109
+
110
+ # @rbs (File::Stat left, File::Stat right) -> bool
111
+ def same_inode?(left, right)
112
+ left.dev == right.dev && left.ino == right.ino
113
+ end
114
+
115
+ # @rbs (String left, String right) -> bool
116
+ def paths_alias?(left, right)
117
+ return true if File.expand_path(left) == File.expand_path(right)
118
+ return true if portable_target_key(left) == portable_target_key(right)
119
+ return false unless File.exist?(left) && File.exist?(right)
120
+
121
+ File.identical?(left, right)
122
+ rescue SystemCallError
123
+ true
124
+ end
125
+
126
+ # Reject target names that collide on common case-insensitive or
127
+ # normalization-insensitive filesystems, even when the current filesystem
128
+ # would permit both names.
129
+ # @rbs (String path) -> [String, String]
130
+ def portable_target_key(path)
131
+ basename = File.basename(path)
132
+ folded = if basename.encoding == Encoding::UTF_8 && basename.valid_encoding?
133
+ basename.unicode_normalize(:nfc).downcase(:fold).unicode_normalize(:nfc)
134
+ else
135
+ basename.downcase
136
+ end
137
+ [File.dirname(path), folded]
138
+ end
139
+
140
+ # @rbs (String phase) -> void
141
+ def ensure_sources_stable!(phase)
142
+ validate_active_locks!(phase)
143
+ validate_current_targets!(phase)
144
+ return if @stability_check.call
145
+
146
+ raise GenerationTransaction::SourceChanged, "(generation):1:1: source changed #{phase}"
147
+ end
148
+
149
+ # @rbs (String phase) -> void
150
+ def validate_active_locks!(phase)
151
+ @locked_paths.zip(@locks).each do |path, lock|
152
+ next if stable_lock_identity?(File.lstat(path), lock.stat)
153
+
154
+ raise GenerationTransaction::SourceChanged,
155
+ "(generation):1:1: generation lock changed #{phase}: #{path}"
156
+ end
157
+ rescue SystemCallError => e
158
+ raise GenerationTransaction::SourceChanged,
159
+ "(generation):1:1: cannot revalidate generation lock #{phase}: #{e.message}"
160
+ end
161
+
162
+ # @rbs (String phase) -> void
163
+ def validate_current_targets!(phase)
164
+ sources = @source_records.map(&:path)
165
+ @records.each do |record|
166
+ validate_current_target!(record, sources, phase)
167
+ end
168
+ rescue SystemCallError => e
169
+ raise GenerationTransaction::SourceChanged,
170
+ "(generation):1:1: cannot revalidate artifact target #{phase}: #{e.message}"
171
+ end
172
+
173
+ # @rbs (Hash[Symbol, Object?] record, Array[String] sources, String phase) -> void
174
+ def validate_current_target!(record, sources, phase)
175
+ artifact = record.fetch(:artifact) #: Artifact
176
+ current = resolved_target(artifact.path, validate_links: false)
177
+ unless current == record.fetch(:target)
178
+ raise GenerationTransaction::SourceChanged,
179
+ "(generation):1:1: artifact target changed #{phase}: #{artifact.path}"
180
+ end
181
+ return unless sources.any? { |source| paths_alias?(current, source) }
182
+
183
+ raise GenerationTransaction::SourceChanged,
184
+ "(generation):1:1: artifact target aliases an input #{phase}: #{artifact.path}"
185
+ end
186
+
187
+ # @rbs () -> void
188
+ def validate_locked_targets!
189
+ current = @records.map { |record| lock_path(record.fetch(:target)) }.uniq.sort
190
+ return if current == @locked_paths
191
+
192
+ raise GenerationTransaction::Error,
193
+ "(generation):1:1: output target changed while acquiring generation locks"
194
+ end
195
+ end
196
+ end