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.
- checksums.yaml +4 -4
- data/README.md +95 -272
- data/docs/architecture.md +187 -35
- data/docs/bison-import.md +103 -0
- data/docs/comparison-policy.md +127 -0
- data/docs/configuration-model.md +59 -0
- data/docs/configuration-report.md +28 -0
- data/docs/conflict-explanation-reviews/v1/records/README.md +28 -0
- data/docs/conflict-explanation-study.md +62 -0
- data/docs/construction-profiling.md +157 -0
- data/docs/cst-migration.md +3 -4
- data/docs/cst.md +24 -0
- data/docs/decisions/0000-template.md +24 -0
- data/docs/decisions/0001-separate-ir-pipeline.md +34 -0
- data/docs/decisions/0002-opaque-user-code-boundary.md +32 -0
- data/docs/decisions/0003-self-hosted-grammar-frontend.md +32 -0
- data/docs/decisions/0004-shared-semantic-and-lossless-source-model.md +33 -0
- data/docs/decisions/0005-contained-grammar-composition.md +34 -0
- data/docs/decisions/0006-bounded-structural-grammar-lowering.md +32 -0
- data/docs/decisions/0007-shared-parser-construction-pipeline.md +33 -0
- data/docs/decisions/0008-versioned-runtime-package-boundary.md +36 -0
- data/docs/decisions/0009-isolated-parser-sessions.md +34 -0
- data/docs/decisions/0010-committed-runtime-observation.md +34 -0
- data/docs/decisions/0011-versioned-semantic-action-boundary.md +37 -0
- data/docs/decisions/0012-bounded-nonexecuting-analysis.md +37 -0
- data/docs/decisions/0013-transactional-generation-publication.md +37 -0
- data/docs/decisions/0014-versioned-generated-lexer.md +35 -0
- data/docs/decisions/0015-worker-isolated-browser-analysis.md +32 -0
- data/docs/decisions/0016-red-green-concrete-syntax.md +34 -0
- data/docs/decisions/0017-persistent-syntax-artifacts.md +35 -0
- data/docs/decisions/0018-conservative-incremental-syntax-reuse.md +38 -0
- data/docs/decisions/0019-runtime-syntax-session-boundary.md +51 -0
- data/docs/decisions/0020-grammar-ir-parser-contract.md +65 -0
- data/docs/decisions/0021-data-only-parser-table-sidecar.md +50 -0
- data/docs/decisions/0022-manifest-bound-verification-report.md +77 -0
- data/docs/decisions/0023-syntax-only-repair-results.md +48 -0
- data/docs/decisions/0024-direct-ielr-construction.md +42 -0
- data/docs/decisions/README.md +74 -0
- data/docs/declarative-configuration.md +262 -0
- data/docs/development.md +179 -2
- data/docs/direct-ielr-decision.md +120 -0
- data/docs/direct-multi-entry-decision.md +82 -0
- data/docs/editor-setup.md +2 -1
- data/docs/error-ux-review-rubric-v1.md +117 -0
- data/docs/error-ux-reviews/v1/records/README.md +22 -0
- data/docs/error-ux-round2-review-status-v1.json +27 -0
- data/docs/error-ux-round2-reviews/v1/records/README.md +30 -0
- data/docs/error-ux-round2-v1.json +1581 -0
- data/docs/error-ux-round2.md +111 -0
- data/docs/error-ux.md +54 -0
- data/docs/getting-started.md +80 -0
- data/docs/grammar-reference.md +184 -13
- data/docs/ielr-design.md +43 -0
- data/docs/ielr.md +92 -0
- data/docs/ja/bison-import.md +51 -0
- data/docs/ja/stable-api.md +53 -0
- data/docs/lexer-construction-profile.md +109 -0
- data/docs/lexer-migration.md +5 -0
- data/docs/maturity.md +128 -0
- data/docs/project-site-strategy.md +54 -0
- data/docs/racc-migration-evidence.md +45 -0
- data/docs/racc-migration.md +11 -0
- data/docs/release-readiness.md +126 -68
- data/docs/repair-semantics.md +123 -0
- data/docs/runtime-abi-evolution.md +307 -0
- data/docs/stability.md +144 -34
- data/docs/status.md +43 -0
- data/docs/syntax-sessions.md +183 -0
- data/docs/table-artifact.md +125 -0
- data/docs/test-interactions.md +219 -0
- data/docs/verification-report.md +137 -0
- data/docs/verifier-trust-boundary.md +199 -0
- data/docs/workloads.md +132 -0
- data/lib/ibex/analysis/digraph.rb +128 -0
- data/lib/ibex/analysis.rb +1 -0
- data/lib/ibex/bison_import/importer.rb +516 -0
- data/lib/ibex/bison_import/tokenizer.rb +260 -0
- data/lib/ibex/bison_import.rb +200 -0
- data/lib/ibex/bounded_subprocess.rb +171 -0
- data/lib/ibex/cli/ambiguity.rb +14 -5
- data/lib/ibex/cli/analysis.rb +134 -0
- data/lib/ibex/cli/bison_import.rb +121 -0
- data/lib/ibex/cli/config.rb +128 -0
- data/lib/ibex/cli/diagnostics.rb +31 -11
- data/lib/ibex/cli/documentation.rb +11 -8
- data/lib/ibex/cli/equiv.rb +169 -0
- data/lib/ibex/cli/error_messages.rb +11 -4
- data/lib/ibex/cli/explain.rb +14 -5
- data/lib/ibex/cli/fix.rb +203 -0
- data/lib/ibex/cli/formatting.rb +11 -10
- data/lib/ibex/cli/fuzz.rb +200 -0
- data/lib/ibex/cli/fuzz_regressions.rb +145 -0
- data/lib/ibex/cli/generation_artifacts.rb +53 -2
- data/lib/ibex/cli/generation_error_messages.rb +1 -1
- data/lib/ibex/cli/grammar_tests.rb +32 -14
- data/lib/ibex/cli/ir_tools.rb +5 -61
- data/lib/ibex/cli/outputs.rb +60 -33
- data/lib/ibex/cli/reduce.rb +202 -0
- data/lib/ibex/cli/reduce_reporting.rb +71 -0
- data/lib/ibex/cli/samples.rb +49 -23
- data/lib/ibex/cli/verify.rb +116 -0
- data/lib/ibex/cli/watch.rb +6 -5
- data/lib/ibex/cli.rb +357 -56
- data/lib/ibex/codegen/action_locations.rb +1 -1
- data/lib/ibex/codegen/action_method_source.rb +5 -6
- data/lib/ibex/codegen/action_source.rb +3 -2
- data/lib/ibex/codegen/ambiguity.rb +5 -4
- data/lib/ibex/codegen/explain.rb +123 -55
- data/lib/ibex/codegen/generated_action_abi.rb +10 -5
- data/lib/ibex/codegen/rbs.rb +15 -7
- data/lib/ibex/codegen/report.rb +25 -13
- data/lib/ibex/codegen/ruby.rb +28 -64
- data/lib/ibex/codegen/ruby_actions.rb +9 -6
- data/lib/ibex/codegen/ruby_syntax.rb +15 -9
- data/lib/ibex/configuration/analysis_grammar.rb +35 -0
- data/lib/ibex/configuration/explanation.rb +421 -0
- data/lib/ibex/configuration/inspector.rb +232 -0
- data/lib/ibex/configuration.rb +580 -0
- data/lib/ibex/coverage/collector.rb +21 -11
- data/lib/ibex/coverage/event_stream.rb +13 -7
- data/lib/ibex/coverage/report.rb +26 -18
- data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
- data/lib/ibex/delta_reducer.rb +99 -0
- data/lib/ibex/diff.rb +140 -0
- data/lib/ibex/equiv/machine.rb +135 -0
- data/lib/ibex/equiv.rb +373 -0
- data/lib/ibex/fix.rb +557 -0
- data/lib/ibex/frontend/ast.rb +22 -2
- data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
- data/lib/ibex/frontend/diagnostic.rb +1 -1
- data/lib/ibex/frontend/formatter.rb +72 -26
- data/lib/ibex/frontend/generated_parser.rb +147 -117
- data/lib/ibex/frontend/generated_parser_base.rb +7 -3
- data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
- data/lib/ibex/frontend/generation.rb +1 -1
- data/lib/ibex/frontend/parser/declarations.rb +17 -2
- data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
- data/lib/ibex/frontend/regenerator.rb +1 -0
- data/lib/ibex/frontend/resolution.rb +2 -1
- data/lib/ibex/frontend/resolver.rb +1 -1
- data/lib/ibex/frontend/rule_documentation.rb +2 -1
- data/lib/ibex/frontend/source_cursor.rb +2 -2
- data/lib/ibex/frontend/source_span.rb +9 -1
- data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
- data/lib/ibex/frontend/token_adapter.rb +10 -0
- data/lib/ibex/frontend.rb +1 -0
- data/lib/ibex/fuzz.rb +219 -0
- data/lib/ibex/generation_input.rb +1 -1
- data/lib/ibex/generation_manifest.rb +36 -25
- data/lib/ibex/generation_transaction.rb +1 -1
- data/lib/ibex/generation_transaction_recovery.rb +4 -4
- data/lib/ibex/generation_transaction_validation.rb +2 -2
- data/lib/ibex/grammar_tests.rb +1 -1
- data/lib/ibex/ir/automaton_ir.rb +61 -19
- data/lib/ibex/ir/grammar_ir.rb +76 -45
- data/lib/ibex/ir/lexer_ir.rb +5 -5
- data/lib/ibex/ir/parser_contract.rb +135 -0
- data/lib/ibex/ir/serialize.rb +203 -76
- data/lib/ibex/ir/validator/automaton.rb +64 -34
- data/lib/ibex/ir/validator/base.rb +51 -33
- data/lib/ibex/ir/validator/grammar.rb +113 -83
- data/lib/ibex/ir/validator/lexer.rb +2 -2
- data/lib/ibex/ir/validator.rb +2 -1
- data/lib/ibex/ir.rb +16 -3
- data/lib/ibex/lalr/build_metrics.rb +50 -2
- data/lib/ibex/lalr/builder.rb +191 -56
- data/lib/ibex/lalr/conflict_search.rb +15 -4
- data/lib/ibex/lalr/counterexample.rb +19 -14
- data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
- data/lib/ibex/lalr/goto_follows.rb +229 -0
- data/lib/ibex/lalr/ielr/annotator.rb +214 -0
- data/lib/ibex/lalr/ielr/bits.rb +28 -0
- data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
- data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
- data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
- data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
- data/lib/ibex/lalr/ielr/split_state.rb +20 -0
- data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
- data/lib/ibex/lalr/ielr_partition.rb +22 -8
- data/lib/ibex/lalr/inadequacy_report.rb +50 -0
- data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
- data/lib/ibex/lalr/lr0_collection.rb +121 -0
- data/lib/ibex/lalr/unreachable_states.rb +80 -0
- data/lib/ibex/lalr.rb +52 -1
- data/lib/ibex/location.rb +3 -3
- data/lib/ibex/lsp/document_handlers.rb +9 -7
- data/lib/ibex/lsp/initialization_handlers.rb +9 -7
- data/lib/ibex/lsp/navigation_handlers.rb +25 -11
- data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
- data/lib/ibex/lsp/position_codec.rb +6 -2
- data/lib/ibex/lsp/request_handlers.rb +3 -2
- data/lib/ibex/lsp/request_support.rb +14 -7
- data/lib/ibex/lsp/server.rb +13 -11
- data/lib/ibex/lsp/symbol_index.rb +13 -13
- data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
- data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
- data/lib/ibex/lsp/transport.rb +13 -3
- data/lib/ibex/lsp/workspace.rb +5 -4
- data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
- data/lib/ibex/lsp.rb +1 -0
- data/lib/ibex/messages/en.yml +28 -0
- data/lib/ibex/messages/ja.yml +28 -0
- data/lib/ibex/messages.rb +68 -0
- data/lib/ibex/metrics.rb +175 -0
- data/lib/ibex/normalize/declarations.rb +2 -0
- data/lib/ibex/normalize/diagnostics.rb +63 -38
- data/lib/ibex/normalize/expander.rb +5 -4
- data/lib/ibex/normalize/expression.rb +8 -6
- data/lib/ibex/normalize/grammar_builder.rb +26 -0
- data/lib/ibex/normalize/inline_expansion.rb +100 -49
- data/lib/ibex/normalize/lexer.rb +3 -3
- data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
- data/lib/ibex/normalize/parameter_lowering.rb +40 -23
- data/lib/ibex/normalize/parameters.rb +31 -4
- data/lib/ibex/normalize/parser_configuration.rb +70 -0
- data/lib/ibex/normalize.rb +10 -20
- data/lib/ibex/racc_migration/report.rb +6 -3
- data/lib/ibex/rake_task.rb +1 -1
- data/lib/ibex/samples.rb +43 -13
- data/lib/ibex/table_artifact/builder.rb +266 -0
- data/lib/ibex/table_artifact/cst_projection.rb +70 -0
- data/lib/ibex/table_artifact/document.rb +39 -0
- data/lib/ibex/table_artifact/executor.rb +187 -0
- data/lib/ibex/table_artifact/serializer.rb +57 -0
- data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
- data/lib/ibex/table_artifact/validator/support.rb +86 -0
- data/lib/ibex/table_artifact/validator/tables.rb +238 -0
- data/lib/ibex/table_artifact/validator.rb +253 -0
- data/lib/ibex/table_artifact.rb +88 -0
- data/lib/ibex/table_simulation/result.rb +6 -2
- data/lib/ibex/table_simulation/step.rb +3 -1
- data/lib/ibex/tables.rb +14 -0
- data/lib/ibex/verifiable_generation_bundle.rb +86 -0
- data/lib/ibex/verification_report/builder.rb +159 -0
- data/lib/ibex/verification_report/canonical_ir.rb +123 -0
- data/lib/ibex/verification_report/logical_path.rb +68 -0
- data/lib/ibex/verification_report/validator.rb +329 -0
- data/lib/ibex/verification_report.rb +69 -0
- data/lib/ibex/verify/action_correspondence.rb +138 -0
- data/lib/ibex/verify/language_witness.rb +295 -0
- data/lib/ibex/verify/reference_collection.rb +190 -0
- data/lib/ibex/verify/result.rb +73 -0
- data/lib/ibex/verify/verifier.rb +598 -0
- data/lib/ibex/verify.rb +15 -0
- data/lib/ibex/version.rb +1 -1
- data/lib/ibex/watch/runner.rb +10 -1
- data/lib/ibex/watch/source_snapshot.rb +13 -8
- data/lib/ibex.rb +10 -0
- data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
- data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
- data/schema/bison-import-v1.schema.json +106 -0
- data/schema/conflict-explanation-review-v1.schema.json +196 -0
- data/schema/conflict-explanation-study-v1.schema.json +528 -0
- data/schema/construction-profile-v1.schema.json +779 -0
- data/schema/diff-v1.schema.json +41 -0
- data/schema/direct-ielr-decision-v1.schema.json +200 -0
- data/schema/equiv-v1.schema.json +64 -0
- data/schema/error-ux-review-v1.schema.json +609 -0
- data/schema/error-ux-round2-review-v1.schema.json +191 -0
- data/schema/error-ux-round2-v1.schema.json +519 -0
- data/schema/explain-v1.schema.json +63 -1
- data/schema/fix-v1.schema.json +64 -0
- data/schema/fix-v2.schema.json +66 -0
- data/schema/fix-v3.schema.json +101 -0
- data/schema/fuzz-regression-v1.schema.json +79 -0
- data/schema/fuzz-v1.schema.json +114 -0
- data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
- data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
- data/schema/grammar-ir.schema.json +271 -0
- data/schema/ielr-benchmark-v1.schema.json +85 -0
- data/schema/lexer-profile-v1.schema.json +491 -0
- data/schema/metrics-v1.schema.json +60 -0
- data/schema/reduce-v1.schema.json +22 -0
- data/schema/reduce-v2.schema.json +63 -0
- data/schema/table-artifact-v1.schema.json +377 -0
- data/schema/verification-report-v1.schema.json +222 -0
- data/schema/verify-v1.schema.json +42 -0
- data/sig/ibex/analysis/digraph.rbs +19 -0
- data/sig/ibex/bison_import/importer.rbs +140 -0
- data/sig/ibex/bison_import/tokenizer.rbs +91 -0
- data/sig/ibex/bison_import.rbs +95 -0
- data/sig/ibex/bounded_subprocess.rbs +76 -0
- data/sig/ibex/cli/ambiguity.rbs +6 -0
- data/sig/ibex/cli/analysis.rbs +43 -0
- data/sig/ibex/cli/bison_import.rbs +32 -0
- data/sig/ibex/cli/config.rbs +35 -0
- data/sig/ibex/cli/diagnostics.rbs +16 -3
- data/sig/ibex/cli/documentation.rbs +5 -1
- data/sig/ibex/cli/equiv.rbs +49 -0
- data/sig/ibex/cli/error_messages.rbs +6 -0
- data/sig/ibex/cli/explain.rbs +6 -0
- data/sig/ibex/cli/fix.rbs +53 -0
- data/sig/ibex/cli/formatting.rbs +5 -1
- data/sig/ibex/cli/fuzz.rbs +47 -0
- data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
- data/sig/ibex/cli/generation_artifacts.rbs +21 -2
- data/sig/ibex/cli/generation_error_messages.rbs +2 -2
- data/sig/ibex/cli/grammar_tests.rbs +15 -3
- data/sig/ibex/cli/ir_tools.rbs +7 -19
- data/sig/ibex/cli/outputs.rbs +9 -3
- data/sig/ibex/cli/reduce.rbs +60 -0
- data/sig/ibex/cli/reduce_reporting.rbs +27 -0
- data/sig/ibex/cli/samples.rbs +8 -0
- data/sig/ibex/cli/verify.rbs +28 -0
- data/sig/ibex/cli/watch.rbs +2 -2
- data/sig/ibex/cli.rbs +79 -5
- data/sig/ibex/codegen/action_method_source.rbs +4 -4
- data/sig/ibex/codegen/explain.rbs +45 -25
- data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
- data/sig/ibex/codegen/rbs.rbs +12 -10
- data/sig/ibex/codegen/report.rbs +12 -12
- data/sig/ibex/codegen/ruby.rbs +18 -15
- data/sig/ibex/codegen/ruby_actions.rbs +6 -6
- data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
- data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
- data/sig/ibex/configuration/explanation.rbs +195 -0
- data/sig/ibex/configuration/inspector.rbs +53 -0
- data/sig/ibex/configuration.rbs +242 -0
- data/sig/ibex/coverage/collector.rbs +16 -14
- data/sig/ibex/coverage/event_stream.rbs +12 -10
- data/sig/ibex/coverage/report.rbs +20 -18
- data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
- data/sig/ibex/delta_reducer.rbs +38 -0
- data/sig/ibex/diff.rbs +47 -0
- data/sig/ibex/equiv/machine.rbs +54 -0
- data/sig/ibex/equiv.rbs +129 -0
- data/sig/ibex/fix.rbs +138 -0
- data/sig/ibex/frontend/ast.rbs +33 -7
- data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
- data/sig/ibex/frontend/diagnostic.rbs +2 -2
- data/sig/ibex/frontend/formatter.rbs +38 -30
- data/sig/ibex/frontend/generated_parser.rbs +87 -77
- data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
- data/sig/ibex/frontend/parser/declarations.rbs +3 -0
- data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
- data/sig/ibex/frontend/resolution.rbs +3 -2
- data/sig/ibex/frontend/source_cursor.rbs +2 -2
- data/sig/ibex/frontend/source_span.rbs +5 -2
- data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
- data/sig/ibex/frontend/token_adapter.rbs +6 -0
- data/sig/ibex/fuzz.rbs +71 -0
- data/sig/ibex/generation_input.rbs +2 -2
- data/sig/ibex/generation_manifest.rbs +35 -32
- data/sig/ibex/generation_transaction_recovery.rbs +2 -2
- data/sig/ibex/generation_transaction_validation.rbs +2 -2
- data/sig/ibex/grammar_tests.rbs +2 -2
- data/sig/ibex/ir/automaton_ir.rbs +30 -11
- data/sig/ibex/ir/grammar_ir.rbs +41 -27
- data/sig/ibex/ir/lexer_ir.rbs +8 -8
- data/sig/ibex/ir/parser_contract.rbs +69 -0
- data/sig/ibex/ir/serialize.rbs +63 -28
- data/sig/ibex/ir/validator/automaton.rbs +47 -44
- data/sig/ibex/ir/validator/base.rbs +35 -31
- data/sig/ibex/ir/validator/grammar.rbs +78 -67
- data/sig/ibex/ir/validator/lexer.rbs +4 -4
- data/sig/ibex/ir.rbs +9 -3
- data/sig/ibex/lalr/build_metrics.rbs +42 -2
- data/sig/ibex/lalr/builder.rbs +75 -24
- data/sig/ibex/lalr/conflict_search.rbs +5 -2
- data/sig/ibex/lalr/counterexample.rbs +17 -14
- data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
- data/sig/ibex/lalr/goto_follows.rbs +89 -0
- data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
- data/sig/ibex/lalr/ielr/bits.rbs +14 -0
- data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
- data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
- data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
- data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
- data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
- data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
- data/sig/ibex/lalr/ielr_partition.rbs +17 -5
- data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
- data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
- data/sig/ibex/lalr/lr0_collection.rbs +50 -0
- data/sig/ibex/lalr/unreachable_states.rbs +25 -0
- data/sig/ibex/lalr.rbs +11 -1
- data/sig/ibex/location.rbs +6 -6
- data/sig/ibex/lsp/document_handlers.rbs +8 -8
- data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
- data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
- data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
- data/sig/ibex/lsp/position_codec.rbs +8 -4
- data/sig/ibex/lsp/request_handlers.rbs +4 -4
- data/sig/ibex/lsp/request_support.rbs +8 -8
- data/sig/ibex/lsp/server.rbs +16 -16
- data/sig/ibex/lsp/symbol_index.rbs +20 -21
- data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
- data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
- data/sig/ibex/lsp/transport.rbs +18 -6
- data/sig/ibex/lsp/workspace.rbs +3 -2
- data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
- data/sig/ibex/messages.rbs +26 -0
- data/sig/ibex/metrics.rbs +50 -0
- data/sig/ibex/normalize/diagnostics.rbs +4 -4
- data/sig/ibex/normalize/expander.rbs +2 -2
- data/sig/ibex/normalize/expression.rbs +10 -8
- data/sig/ibex/normalize/grammar_builder.rbs +11 -0
- data/sig/ibex/normalize/inline_expansion.rbs +59 -50
- data/sig/ibex/normalize/lexer.rbs +4 -4
- data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
- data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
- data/sig/ibex/normalize/parameters.rbs +13 -8
- data/sig/ibex/normalize/parser_configuration.rbs +23 -0
- data/sig/ibex/normalize.rbs +11 -5
- data/sig/ibex/racc_migration/report.rbs +8 -6
- data/sig/ibex/rake_task.rbs +1 -1
- data/sig/ibex/samples.rbs +13 -4
- data/sig/ibex/table_artifact/builder.rbs +93 -0
- data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
- data/sig/ibex/table_artifact/document.rbs +27 -0
- data/sig/ibex/table_artifact/executor.rbs +66 -0
- data/sig/ibex/table_artifact/serializer.rbs +25 -0
- data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
- data/sig/ibex/table_artifact/validator/support.rbs +46 -0
- data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
- data/sig/ibex/table_artifact/validator.rbs +66 -0
- data/sig/ibex/table_artifact.rbs +32 -0
- data/sig/ibex/table_simulation/result.rbs +8 -4
- data/sig/ibex/table_simulation/step.rbs +4 -2
- data/sig/ibex/tables.rbs +10 -9
- data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
- data/sig/ibex/verification_report/builder.rbs +53 -0
- data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
- data/sig/ibex/verification_report/logical_path.rbs +34 -0
- data/sig/ibex/verification_report/validator.rbs +114 -0
- data/sig/ibex/verification_report.rbs +44 -0
- data/sig/ibex/verify/action_correspondence.rbs +50 -0
- data/sig/ibex/verify/language_witness.rbs +112 -0
- data/sig/ibex/verify/reference_collection.rbs +63 -0
- data/sig/ibex/verify/result.rbs +52 -0
- data/sig/ibex/verify/verifier.rbs +170 -0
- data/sig/ibex/verify.rbs +6 -0
- data/sig/ibex/watch/runner.rbs +12 -2
- data/sig/ibex/watch/source_snapshot.rbs +13 -9
- metadata +232 -12
- data/lib/ibex/ir/migration.rb +0 -120
- 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
|