hecks 2.9.0 → 3.0.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/exe/hecks +51 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +16 -3
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +4 -2
- data/lib/hecks/adapters/driven/prism.rb +41 -8
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
- data/lib/hecks/behaviors/expectations.rb +1 -1
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bench/cli.rb +62 -0
- data/lib/hecks/bench/environment.rb +71 -0
- data/lib/hecks/bench/postgres_probe.rb +30 -0
- data/lib/hecks/bench/report.rb +76 -0
- data/lib/hecks/bench/ruby_runner.rb +93 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +101 -0
- data/lib/hecks/bench/stats.rb +46 -0
- data/lib/hecks/bench/suite.rb +98 -0
- data/lib/hecks/bench/workload.rb +89 -0
- data/lib/hecks/bench.rb +17 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
- data/lib/hecks/bluebook/chapter.rb +6 -2
- data/lib/hecks/bluebook/domain_port.rb +36 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
- data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
- data/lib/hecks/bluebook/expression/resolver.rb +3 -2
- data/lib/hecks/bluebook/hexagon.rb +13 -5
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator.rb +9 -4
- data/lib/hecks/bluebook/model_check.rb +23 -0
- data/lib/hecks/bluebook/pattern_subset.rb +38 -0
- data/lib/hecks/bluebook/query.rb +20 -5
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/canonical_json.rb +30 -0
- data/lib/hecks/chapters.rb +102 -0
- data/lib/hecks/cli/behaviors.rb +61 -0
- data/lib/hecks/cli/canonicalise.rb +25 -0
- data/lib/hecks/cli/check_engine_agreement.rb +32 -0
- data/lib/hecks/cli/check_era.rb +88 -0
- data/lib/hecks/cli/console.rb +66 -0
- data/lib/hecks/cli/deploy_template_diff.rb +40 -0
- data/lib/hecks/cli/doc_coverage.rb +32 -0
- data/lib/hecks/cli/document.rb +2 -2
- data/lib/hecks/cli/expression_projection.rb +41 -0
- data/lib/hecks/cli/follow.rb +88 -0
- data/lib/hecks/cli/history.rb +44 -0
- data/lib/hecks/cli/ir.rb +1 -1
- data/lib/hecks/cli/mcp.rb +3 -3
- data/lib/hecks/cli/model_check.rb +7 -4
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +224 -32
- data/lib/hecks/cli/project_diagrams.rb +4 -3
- data/lib/hecks/cli/project_glossary.rb +44 -0
- data/lib/hecks/cli/reference.rb +46 -0
- data/lib/hecks/cli/refresh_projections.rb +44 -0
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
- data/lib/hecks/cli/release.rb +70 -0
- data/lib/hecks/cli/release_gem.rb +59 -0
- data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
- data/lib/hecks/cli/rspec_shard_files.rb +66 -0
- data/lib/hecks/cli/run.rb +3 -3
- data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
- data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
- data/lib/hecks/cli/shape.rb +70 -0
- data/lib/hecks/cli/smoke_http.rb +230 -0
- data/lib/hecks/cli/smoke_test.rb +5 -3
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -5
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +201 -52
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
- data/lib/hecks/deploy/oidc.json +82 -2
- data/lib/hecks/doc/reference.rb +351 -0
- data/lib/hecks/doors/cli_door.rb +196 -0
- data/lib/hecks/doors/cli_runner.rb +297 -0
- data/lib/hecks/{facade → doors}/command_request.rb +1 -1
- data/lib/hecks/{facade → doors}/handle.rb +2 -2
- data/lib/hecks/{facade → doors}/json_door.rb +4 -4
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
- data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
- data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
- data/lib/hecks/doors.rb +61 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +1 -1
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +24 -37
- data/lib/hecks/forms/app.rb +6 -6
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- data/lib/hecks/forms/field_shape.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +1 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
- data/lib/hecks/fuzzing/combination_miner.rb +86 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
- data/lib/hecks/fuzzing/differential.rb +156 -0
- data/lib/hecks/fuzzing/domain_generator.rb +615 -0
- data/lib/hecks/fuzzing/era_boundary.rb +71 -0
- data/lib/hecks/fuzzing/form_census.rb +136 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
- data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
- data/lib/hecks/fuzzing/outside_world.rb +45 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
- data/lib/hecks/fuzzing/properties/guards.rb +190 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
- data/lib/hecks/fuzzing/properties/querying.rb +175 -0
- data/lib/hecks/fuzzing/properties.rb +162 -0
- data/lib/hecks/fuzzing/qa_settings.rb +123 -0
- data/lib/hecks/fuzzing/replay.rb +505 -0
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
- data/lib/hecks/fuzzing/self_consistency.rb +339 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
- data/lib/hecks/fuzzing/shrinker.rb +166 -0
- data/lib/hecks/fuzzing/structural_skips.rb +33 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
- data/lib/hecks/fuzzing/value_generator.rb +216 -0
- data/lib/hecks/fuzzing.rb +23 -0
- data/lib/hecks/grammar/evolve.rb +336 -0
- data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
- data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
- data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
- data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
- data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
- data/lib/hecks/hecks/adapters/git.adapter +7 -0
- data/lib/hecks/hecks/adapters/git.rb +102 -0
- data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
- data/lib/hecks/hecks/adapters/host_http.rb +65 -0
- data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
- data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
- data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
- data/lib/hecks/hecks/adapters/local_files.rb +46 -0
- data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
- data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
- data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
- data/lib/hecks/hecks/adapters/shell.adapter +7 -0
- data/lib/hecks/hecks/adapters/shell.rb +49 -0
- data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
- data/lib/hecks/hecks/adapters/terminal.rb +74 -0
- data/lib/hecks/hecks/codebase.behaviors +172 -0
- data/lib/hecks/hecks/codebase.bluebook +2453 -0
- data/lib/hecks/hecks/context_map.hecksagon +13 -0
- data/lib/hecks/hecks/custodian.behaviors +179 -0
- data/lib/hecks/hecks/custodian.bluebook +1847 -0
- data/lib/hecks/hecks/deploy.behaviors +74 -0
- data/lib/hecks/hecks/deploy.world +12 -0
- data/lib/hecks/hecks/environments/memory.world +10 -0
- data/lib/hecks/hecks/governance.world +7 -0
- data/lib/hecks/hecks/hecks.behaviors +62 -0
- data/lib/hecks/hecks/hecks.bluebook +124 -0
- data/lib/hecks/hecks/hecks.hecksagon +390 -0
- data/lib/hecks/hecks/hecks.world +42 -0
- data/lib/hecks/kernel_capabilities.rb +194 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
- data/lib/hecks/language/bluebook/query.bluebook +7 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
- data/lib/hecks/mcp_stdio_guard.rb +2 -2
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
- data/lib/hecks/ports/persistence/state_codec.rb +24 -5
- data/lib/hecks/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +5 -5
- data/lib/hecks/projections/deploy/lambda.rb +43 -32
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/preview/template.rb +1 -1
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/shared.rb +1 -1
- data/lib/hecks/projections/diagrams.rb +3 -3
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/parser_table.rb +4 -4
- data/lib/hecks/projections/reference.rb +2 -3
- data/lib/hecks/projections/rust_vocabulary.rb +1 -1
- data/lib/hecks/projector/cli_projector.rb +106 -37
- data/lib/hecks/projector.rb +1 -1
- data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
- data/lib/hecks/quality_control/adapters/agent.rb +58 -0
- data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
- data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
- data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
- data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
- data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
- data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
- data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
- data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
- data/lib/hecks/quality_control/cli/child.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
- data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
- data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
- data/lib/hecks/query_ir.rb +346 -0
- data/lib/hecks/query_ir_mcp.rb +198 -0
- data/lib/hecks/release/runner/ci_publisher.rb +2 -2
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +14 -7
- data/lib/hecks/release/runner/npm_publisher.rb +6 -36
- data/lib/hecks/release/runner/preflight.rb +26 -2
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +30 -15
- data/lib/hecks/release/runner.rb +57 -6
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
- data/lib/hecks/runtime/dependency_planning.rb +20 -2
- data/lib/hecks/runtime/dispatcher.rb +53 -3
- data/lib/hecks/runtime/interpreting.rb +3 -0
- data/lib/hecks/runtime/loader.rb +65 -12
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/policy_interpreter.rb +5 -2
- data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
- data/lib/hecks/runtime/query_interpreter.rb +77 -0
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- data/lib/hecks/runtime/refusal_wording.rb +23 -1
- data/lib/hecks/runtime/registry/verification.rb +163 -4
- data/lib/hecks/runtime/registry.rb +57 -3
- data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
- data/lib/hecks/runtime/value/coercion.rb +23 -135
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +4 -1
- data/lib/hecks/runtime.rb +20 -6
- data/lib/hecks/rust_build/conformance.rb +158 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
- data/lib/hecks/rust_build/coverage.rb +218 -0
- data/lib/hecks/rust_build/native_build.rb +124 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
- data/lib/hecks/rust_build/project_rust.rb +332 -0
- data/lib/hecks/rust_build/wasm.rb +105 -0
- data/lib/hecks/rust_build/wasm_browser.rb +113 -0
- data/lib/hecks/rust_build.rb +144 -0
- data/lib/hecks/storehouse.rb +16 -16
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
- data/lib/hecks/three_zero/forms.yml +127 -0
- data/lib/hecks/three_zero.rb +12 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
- data/lib/hecks/tools/comment_style.rb +1070 -0
- data/lib/hecks/tools/corpus_report.rb +87 -0
- data/lib/hecks/tools/deploy_recipe.rb +255 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
- data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
- data/lib/hecks/tools/era_projection_backfill.rb +94 -0
- data/lib/hecks/tools/era_reattest.rb +146 -0
- data/lib/hecks/tools/evolve_run.rb +189 -0
- data/lib/hecks/tools/fuzz_sweep.rb +373 -0
- data/lib/hecks/tools/heki_compaction.rb +100 -0
- data/lib/hecks/tools/hoist_local_givens.rb +246 -0
- data/lib/hecks/tools/journal_compaction.rb +89 -0
- data/lib/hecks/tools/oidc_manifests.rb +70 -0
- data/lib/hecks/tools/query_ir_run.rb +81 -0
- data/lib/hecks/tools/regeneration_run.rb +166 -0
- data/lib/hecks/tools/rust_comment_style.rb +642 -0
- data/lib/hecks/tools/sequence_script.rb +62 -0
- data/lib/hecks/tools/tail_merge.rb +117 -0
- data/lib/hecks/tools/tenant_provisioning.rb +142 -0
- data/lib/hecks/tools/translation_audit.rb +208 -0
- data/lib/hecks/tools/translation_scaffold.rb +131 -0
- data/lib/hecks/tools.rb +86 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +32 -15
- data/qa/settings.yml +66 -0
- data/rust/.cargo/config.toml +6 -0
- data/rust/.gitignore +2 -0
- data/rust/Cargo.lock +54 -0
- data/rust/Cargo.toml +61 -0
- data/rust/build/.cargo/config.toml +3 -0
- data/rust/build/.gitignore +1 -0
- data/rust/build/Cargo.lock +7 -0
- data/rust/build/Cargo.toml +19 -0
- data/rust/build/src/build_artifact.rs +47 -0
- data/rust/build/src/cargo_sync.rs +248 -0
- data/rust/build/src/json.rs +421 -0
- data/rust/build/src/lineage_pass.rs +285 -0
- data/rust/build/src/main.rs +51 -0
- data/rust/build/src/optional_pass.rs +150 -0
- data/rust/build/src/pipeline.rs +264 -0
- data/rust/build/src/reserved_names.rs +76 -0
- data/rust/build/src/resolve.rs +124 -0
- data/rust/build/src/root.rs +25 -0
- data/rust/build/src/sidecars.rs +73 -0
- data/rust/build/src/subprocess.rs +51 -0
- data/rust/build/src/tmp.rs +31 -0
- data/rust/codegen/.cargo/config.toml +3 -0
- data/rust/codegen/.gitignore +1 -0
- data/rust/codegen/Cargo.lock +7 -0
- data/rust/codegen/Cargo.toml +20 -0
- data/rust/codegen/src/attr.rs +32 -0
- data/rust/codegen/src/bridging.rs +481 -0
- data/rust/codegen/src/commands.rs +1226 -0
- data/rust/codegen/src/constraints.rs +109 -0
- data/rust/codegen/src/dependency_planning.rs +209 -0
- data/rust/codegen/src/domain_generator.rs +1288 -0
- data/rust/codegen/src/exemplar.rs +230 -0
- data/rust/codegen/src/expr_emitter.rs +87 -0
- data/rust/codegen/src/fielded.rs +192 -0
- data/rust/codegen/src/hecks_naming.rs +33 -0
- data/rust/codegen/src/json.rs +335 -0
- data/rust/codegen/src/json_codec.rs +697 -0
- data/rust/codegen/src/literal.rs +207 -0
- data/rust/codegen/src/main.rs +290 -0
- data/rust/codegen/src/manifest.rs +227 -0
- data/rust/codegen/src/mutations.rs +784 -0
- data/rust/codegen/src/naming.rs +286 -0
- data/rust/codegen/src/ports.rs +208 -0
- data/rust/codegen/src/prelude.rs +116 -0
- data/rust/codegen/src/queries.rs +786 -0
- data/rust/codegen/src/reactions.rs +532 -0
- data/rust/codegen/src/read_models.rs +697 -0
- data/rust/codegen/src/reference_specs.rs +40 -0
- data/rust/codegen/src/registry.rs +966 -0
- data/rust/codegen/src/reserved_names.rs +76 -0
- data/rust/codegen/src/shared.rs +93 -0
- data/rust/codegen/src/skip_reason.rs +25 -0
- data/rust/codegen/src/types.rs +362 -0
- data/rust/host/.cargo/config.toml +3 -0
- data/rust/host/.gitignore +1 -0
- data/rust/host/Cargo.lock +4297 -0
- data/rust/host/Cargo.toml +61 -0
- data/rust/host/HECKS_RELEASE +1 -0
- data/rust/host/rds-ca-bundle.pem +2736 -0
- data/rust/host/src/api.rs +1596 -0
- data/rust/host/src/approval.rs +652 -0
- data/rust/host/src/auth.rs +1287 -0
- data/rust/host/src/bin/expr_harness.rs +46 -0
- data/rust/host/src/bin/lineage_harness.rs +119 -0
- data/rust/host/src/bin/mint_harness.rs +135 -0
- data/rust/host/src/checkout.rs +265 -0
- data/rust/host/src/dispatch.rs +1443 -0
- data/rust/host/src/expr_json.rs +1117 -0
- data/rust/host/src/field_hints.rs +52 -0
- data/rust/host/src/ir.rs +733 -0
- data/rust/host/src/journal.rs +874 -0
- data/rust/host/src/lambda_client.rs +378 -0
- data/rust/host/src/log.rs +147 -0
- data/rust/host/src/main.rs +474 -0
- data/rust/host/src/mint.rs +1865 -0
- data/rust/host/src/payments/keystore.rs +330 -0
- data/rust/host/src/payments/tests.rs +1400 -0
- data/rust/host/src/payments.rs +767 -0
- data/rust/host/src/presentation.rs +698 -0
- data/rust/host/src/presentation_write.rs +1431 -0
- data/rust/host/src/query_step.rs +149 -0
- data/rust/host/src/rate_limit.rs +904 -0
- data/rust/host/src/reference_transform.rs +241 -0
- data/rust/host/src/reference_validate.rs +338 -0
- data/rust/host/src/resend.rs +296 -0
- data/rust/host/src/secrets.rs +106 -0
- data/rust/host/src/server.rs +663 -0
- data/rust/host/src/storage_shape.rs +175 -0
- data/rust/host/src/test_pg.rs +40 -0
- data/rust/host/src/ui_schema.rs +1351 -0
- data/rust/host/src/wasm_runner.rs +191 -0
- data/rust/host/src/web/newsletter/cooldown.rs +126 -0
- data/rust/host/src/web/newsletter/tests.rs +104 -0
- data/rust/host/src/web/newsletter.rs +310 -0
- data/rust/host/src/web/newsletter_send.rs +357 -0
- data/rust/host/src/web/registrations.rs +1505 -0
- data/rust/host/src/web.rs +2937 -0
- data/rust/lsp/.gitignore +1 -0
- data/rust/lsp/Cargo.lock +7 -0
- data/rust/lsp/Cargo.toml +16 -0
- data/rust/lsp/README.md +133 -0
- data/rust/lsp/src/diagnostics.rs +165 -0
- data/rust/lsp/src/json.rs +385 -0
- data/rust/lsp/src/main.rs +399 -0
- data/rust/lsp/src/outline.rs +177 -0
- data/rust/lsp/src/rpc.rs +67 -0
- data/rust/parser/.cargo/config.toml +5 -0
- data/rust/parser/.gitignore +1 -0
- data/rust/parser/Cargo.lock +7 -0
- data/rust/parser/Cargo.toml +21 -0
- data/rust/parser/src/build/closed_sets.rs +27 -0
- data/rust/parser/src/build/identity.rs +276 -0
- data/rust/parser/src/build/mod.rs +12 -0
- data/rust/parser/src/build/naming.rs +141 -0
- data/rust/parser/src/build/pattern_subset.rs +161 -0
- data/rust/parser/src/build/query_derive.rs +72 -0
- data/rust/parser/src/build/query_inference.rs +269 -0
- data/rust/parser/src/build/query_options.rs +100 -0
- data/rust/parser/src/build/read_model.rs +94 -0
- data/rust/parser/src/build/references.rs +32 -0
- data/rust/parser/src/canonical.rs +114 -0
- data/rust/parser/src/diag.rs +55 -0
- data/rust/parser/src/emit.rs +953 -0
- data/rust/parser/src/expr/ast_json.rs +147 -0
- data/rust/parser/src/expr/evaluator.rs +81 -0
- data/rust/parser/src/expr/mod.rs +84 -0
- data/rust/parser/src/expr/resolver.rs +497 -0
- data/rust/parser/src/ir.rs +387 -0
- data/rust/parser/src/keywords.rs +585 -0
- data/rust/parser/src/lex.rs +662 -0
- data/rust/parser/src/main.rs +252 -0
- data/rust/parser/src/parse/aggregate.rs +612 -0
- data/rust/parser/src/parse/chapter.rs +517 -0
- data/rust/parser/src/parse/command.rs +637 -0
- data/rust/parser/src/parse/domain_port.rs +127 -0
- data/rust/parser/src/parse/entity.rs +527 -0
- data/rust/parser/src/parse/file.rs +67 -0
- data/rust/parser/src/parse/hecksagon.rs +231 -0
- data/rust/parser/src/parse/lifecycle.rs +162 -0
- data/rust/parser/src/parse/mod.rs +1262 -0
- data/rust/parser/src/parse/policy.rs +87 -0
- data/rust/parser/src/parse/process_manager.rs +305 -0
- data/rust/parser/src/parse/query.rs +100 -0
- data/rust/parser/src/parse/read_model.rs +214 -0
- data/rust/parser/src/parse/value_object.rs +250 -0
- data/rust/parser/src/ruby_value.rs +422 -0
- data/rust/project/bridging.rb +257 -0
- data/rust/project/commands.rb +877 -0
- data/rust/project/constraints.rb +86 -0
- data/rust/project/dependency_planning.rb +178 -0
- data/rust/project/domain_generator.rb +980 -0
- data/rust/project/exemplar.rb +148 -0
- data/rust/project/expr_emitter.rb +72 -0
- data/rust/project/fielded.rb +141 -0
- data/rust/project/json_codec.rb +641 -0
- data/rust/project/mutations.rb +721 -0
- data/rust/project/naming.rb +205 -0
- data/rust/project/ports.rb +65 -0
- data/rust/project/queries.rb +493 -0
- data/rust/project/reactions.rb +317 -0
- data/rust/project/read_models.rb +690 -0
- data/rust/project/reference_specs.rb +27 -0
- data/rust/project/registry.rb +396 -0
- data/rust/project/skip_reason.rb +29 -0
- data/rust/project/types.rb +225 -0
- data/rust/project/write_if_changed.rb +83 -0
- data/rust/project.rb +30 -0
- data/rust/project_rust_pipeline.rb +383 -0
- data/rust/src/exemplar/commands.rs +279 -0
- data/rust/src/exemplar/constraints.rs +18 -0
- data/rust/src/exemplar/fielded.rs +280 -0
- data/rust/src/exemplar/json.rs +277 -0
- data/rust/src/exemplar/mod.rs +17 -0
- data/rust/src/exemplar/mutations.rs +80 -0
- data/rust/src/exemplar/queries.rs +33 -0
- data/rust/src/exemplar/reactions.rs +88 -0
- data/rust/src/exemplar/read_models.rs +48 -0
- data/rust/src/exemplar/registry.rs +166 -0
- data/rust/src/exemplar/types.rs +44 -0
- data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
- data/rust/src/kernel/attribute_shapes/list.rs +18 -0
- data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
- data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
- data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
- data/rust/src/kernel/cli.rs +585 -0
- data/rust/src/kernel/dispatch.rs +1090 -0
- data/rust/src/kernel/expr.rs +458 -0
- data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
- data/rust/src/kernel/expression_operators/comparison.rs +48 -0
- data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
- data/rust/src/kernel/expression_operators/logical.rs +13 -0
- data/rust/src/kernel/expression_operators/membership.rs +18 -0
- data/rust/src/kernel/expression_operators/mod.rs +68 -0
- data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
- data/rust/src/kernel/expression_operators/positional.rs +126 -0
- data/rust/src/kernel/expression_operators/presence.rs +101 -0
- data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
- data/rust/src/kernel/expression_operators/sized.rs +35 -0
- data/rust/src/kernel/expression_operators/text.rs +79 -0
- data/rust/src/kernel/expression_operators/to_string.rs +18 -0
- data/rust/src/kernel/json.rs +779 -0
- data/rust/src/kernel/mod.rs +133 -0
- data/rust/src/kernel/named_query.rs +219 -0
- data/rust/src/kernel/orchestrate.rs +1810 -0
- data/rust/src/kernel/pattern.rs +307 -0
- data/rust/src/kernel/query_comparators.rs +206 -0
- data/rust/src/kernel/query_ordering.rs +103 -0
- data/rust/src/kernel/read_model.rs +914 -0
- data/rust/src/kernel/reference_lookup.rs +149 -0
- data/rust/src/kernel/repository.rs +468 -0
- data/rust/src/kernel/routing.rs +612 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
- data/rust/src/kernel/vocab/field_hint.rs +70 -0
- data/rust/src/kernel/vocab/mod.rs +20 -0
- data/rust/src/kernel/vocab/query_comparator.rs +65 -0
- data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
- data/rust/src/lib.rs +8 -0
- data/rust/src/main.rs +16 -0
- data/rust/web/.gitignore +1 -0
- data/rust/web/Cargo.lock +119 -0
- data/rust/web/Cargo.toml +18 -0
- data/rust/web/src/lib.rs +8 -0
- metadata +477 -12
- data/lib/hecks/facade/cli_door.rb +0 -96
- data/lib/hecks/facade/cli_runner.rb +0 -127
- data/lib/hecks/mcp_door_scope.rb +0 -95
|
@@ -101,15 +101,42 @@ module Hecks
|
|
|
101
101
|
)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
class << self
|
|
105
|
+
# The builder whose block is being evaluated, so an aggregate door a facade already
|
|
106
|
+
# installed (a repeat boot in one process) can record a qualified bind into it.
|
|
107
|
+
#
|
|
108
|
+
# @return [Bluebook::DSL::WorldBuilder, nil] nil outside a `.world` block
|
|
109
|
+
attr_reader :current
|
|
110
|
+
end
|
|
111
|
+
|
|
104
112
|
# Wraps `instance_eval` in `ConstShim`'s resolver: without it, an aggregate-qualified
|
|
105
113
|
# verb call raises `NameError`, since the bare constant has nothing to resolve to.
|
|
114
|
+
# A bare name resolves to a namespace whose `::` reaches the proxy; a qualified path
|
|
115
|
+
# (`Pizzas::Order`, handed over by an installed facade chapter's `const_missing`)
|
|
116
|
+
# is already the aggregate, so it resolves to the proxy itself.
|
|
117
|
+
#
|
|
118
|
+
# @param domain [String] the domain the world belongs to
|
|
119
|
+
# @yield the world's DSL block
|
|
120
|
+
# @return [Bluebook::World] the judged world
|
|
106
121
|
def self.build(domain, &block)
|
|
107
122
|
builder = new(domain)
|
|
108
|
-
resolver =
|
|
109
|
-
|
|
123
|
+
resolver = lambda do |name|
|
|
124
|
+
name.to_s.include?("::") ? WorldConstProxy.new(builder) : WorldConstProxy.namespace(builder)
|
|
125
|
+
end
|
|
126
|
+
evaluate(builder, resolver, &block) if block
|
|
110
127
|
builder.build
|
|
111
128
|
end
|
|
112
129
|
|
|
130
|
+
# @api private
|
|
131
|
+
def self.evaluate(builder, resolver, &block)
|
|
132
|
+
previous = @current
|
|
133
|
+
@current = builder
|
|
134
|
+
ConstShim.with(resolver) { builder.instance_eval(&block) }
|
|
135
|
+
ensure
|
|
136
|
+
@current = previous
|
|
137
|
+
end
|
|
138
|
+
private_class_method :evaluate
|
|
139
|
+
|
|
113
140
|
private
|
|
114
141
|
|
|
115
142
|
def required(value, _label)
|
|
@@ -11,7 +11,7 @@ module Hecks
|
|
|
11
11
|
|
|
12
12
|
STRATEGIES = Hecks::Vocabulary.fetch("NormalisationStrategy")
|
|
13
13
|
|
|
14
|
-
# Normalisation rules projected from the grammar chapter by
|
|
14
|
+
# Normalisation rules projected from the grammar chapter by hecks project_expression_tables.
|
|
15
15
|
RULES = JSON.parse(
|
|
16
16
|
File.read(File.join(__dir__, "projection.json")), symbolize_names: true
|
|
17
17
|
).fetch(:normalisations).map { |row| Rule.new(**row) }.freeze
|
|
@@ -201,8 +201,9 @@ module Hecks
|
|
|
201
201
|
def blank?(value)
|
|
202
202
|
return true if value.nil? || value == false
|
|
203
203
|
|
|
204
|
-
# Duck-typed: reaching across to Runtime::Value would couple the namespaces.
|
|
205
|
-
|
|
204
|
+
# Duck-typed: reaching across to Runtime::Value would couple the namespaces. A list
|
|
205
|
+
# is judged by its own emptiness, never coerced: `[0].to_h` would raise TypeError.
|
|
206
|
+
value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
|
|
206
207
|
case value
|
|
207
208
|
when String, Array, Hash then value.empty?
|
|
208
209
|
else false
|
|
@@ -32,32 +32,34 @@ module Hecks
|
|
|
32
32
|
subscriptions: -> { subscriptions.map(&:to_s) },
|
|
33
33
|
framework_members: -> { framework_members.map(&:to_s) },
|
|
34
34
|
vendored_bluebooks: -> { vendored_bluebooks.map(&:to_s) },
|
|
35
|
-
|
|
35
|
+
attached_chapters: -> { attached_chapters.map(&:to_s) },
|
|
36
|
+
bounded: :bounded?,
|
|
36
37
|
translates: -> { translates.map(&:to_s) }
|
|
37
38
|
)
|
|
38
39
|
|
|
39
40
|
attr_reader :domain, :binds, :subscriptions, :framework_members, :vendored_bluebooks,
|
|
40
|
-
:translates
|
|
41
|
+
:attached_chapters, :translates
|
|
41
42
|
|
|
42
43
|
# @param domain [String, Symbol] the domain this hecksagon wires
|
|
43
44
|
# @param binds [Array<Bluebook::Bind>] the declared adapter binds
|
|
44
45
|
# @param subscriptions [Array<String, Symbol>] the external events this domain
|
|
45
46
|
# subscribes to
|
|
46
|
-
# @param framework_members [Array<String, Symbol>]
|
|
47
|
-
# (`Governance`, `Identity`, ...) this domain attaches
|
|
47
|
+
# @param framework_members [Array<String, Symbol>] attached framework members (`Governance`)
|
|
48
48
|
# @param vendored_bluebooks [Array<String, Symbol>] the vendored embryonaut
|
|
49
49
|
# bluebook package names this domain attaches
|
|
50
|
+
# @param attached_chapters [Array<String, Symbol>] chapters the gem carries, attached by name
|
|
50
51
|
# @param bounded [Boolean] whether this chapter is an explicit bounded context
|
|
51
52
|
# (consumer-owned; `uses_framework` / `uses_embryonaut_bluebook` mark
|
|
52
53
|
# attached chapters bounded on the registry instead)
|
|
53
54
|
# @param translates [Array<String>] names of `translates` ACL blocks declared here
|
|
54
55
|
def initialize(domain:, binds: [], subscriptions: [], framework_members: [],
|
|
55
|
-
vendored_bluebooks: [], bounded: false, translates: [])
|
|
56
|
+
vendored_bluebooks: [], attached_chapters: [], bounded: false, translates: [])
|
|
56
57
|
@domain = domain.to_s
|
|
57
58
|
@binds = binds
|
|
58
59
|
@subscriptions = subscriptions
|
|
59
60
|
@framework_members = framework_members
|
|
60
61
|
@vendored_bluebooks = vendored_bluebooks
|
|
62
|
+
@attached_chapters = attached_chapters
|
|
61
63
|
@bounded = bounded ? true : false
|
|
62
64
|
@translates = Array(translates).map(&:to_s)
|
|
63
65
|
end
|
|
@@ -66,6 +68,12 @@ module Hecks
|
|
|
66
68
|
#
|
|
67
69
|
# @return [Boolean] whether `bounded` was declared on this block
|
|
68
70
|
def bounded? = @bounded
|
|
71
|
+
|
|
72
|
+
# Every chapter this hecksagon brings into its domain's registry: framework members and
|
|
73
|
+
# attached chapters alike, which resolve the same way.
|
|
74
|
+
#
|
|
75
|
+
# @return [Array<String>] the chapters' names, framework members first
|
|
76
|
+
def member_chapters = (framework_members + attached_chapters).map(&:to_s)
|
|
69
77
|
end
|
|
70
78
|
|
|
71
79
|
# The built form of a `.world` file, produced by `DSL::WorldBuilder`.
|
|
@@ -24,6 +24,7 @@ module Hecks
|
|
|
24
24
|
vision: text(@chapter[:vision]),
|
|
25
25
|
classification: text(@chapter[:classification]),
|
|
26
26
|
formerly_known_as: text(@chapter[:formerly_known_as]),
|
|
27
|
+
namespace: text(@chapter[:namespace]),
|
|
27
28
|
attaches_to: attached_contexts(@chapter),
|
|
28
29
|
provides: provisions(@chapter),
|
|
29
30
|
aggregates: declared("Aggregate", chapter_id).map { |row| aggregate(row) },
|
|
@@ -85,9 +85,12 @@ module Hecks
|
|
|
85
85
|
|
|
86
86
|
def disk_cache_key(chapters)
|
|
87
87
|
names = chapters.map { |name, _chapter| name }
|
|
88
|
-
Digest::SHA256.hexdigest("#{names.join(',')}:#{grammar_content_digest}")
|
|
88
|
+
Digest::SHA256.hexdigest("#{names.join(',')}:#{grammar_content_digest}:#{VerdictCache.code_digest}")
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
+
# The key also carries `VerdictCache.code_digest` (all of `lib/`), so an
|
|
92
|
+
# edit to Ruby that builds or judges the grammar misses too.
|
|
93
|
+
#
|
|
91
94
|
# Covers every chapter's grammar files, not just the core ones —
|
|
92
95
|
# coarser than strictly necessary, but under-covering this set would
|
|
93
96
|
# let a stale table survive a real grammar edit.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../../cache_dir"
|
|
5
|
+
require_relative "../../version"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Bluebook
|
|
9
|
+
module MetaValidator
|
|
10
|
+
# Cross-process store of the meta-domain's chapter verdicts, so a boot
|
|
11
|
+
# judges only the chapters no earlier process has judged.
|
|
12
|
+
#
|
|
13
|
+
# Only chapter verdicts live here; `Assembly.call` still runs every boot
|
|
14
|
+
# and a miss judges as a cold process does. The file name carries a digest
|
|
15
|
+
# of the judging code (every file under `lib/`, the Ruby and Hecks
|
|
16
|
+
# versions, `FORMAT`); each entry is keyed by the chapter IR's SHA-256.
|
|
17
|
+
# The file is tagged JSON, never `Marshal`, read only from a private,
|
|
18
|
+
# user-owned directory: a forged "no refusals" entry would skip validation.
|
|
19
|
+
#
|
|
20
|
+
# VerdictCache.record(key, held)
|
|
21
|
+
# VerdictCache.flush # also runs at process exit
|
|
22
|
+
module VerdictCache
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Bumped when the encoding or the entry shape changes.
|
|
26
|
+
FORMAT = 1
|
|
27
|
+
|
|
28
|
+
# Files older than this that do not match the current code digest are
|
|
29
|
+
# pruned on write.
|
|
30
|
+
STALE_AFTER = 60 * 60
|
|
31
|
+
|
|
32
|
+
# Whether the disk cache is on; `HECKS_VERDICT_CACHE=off` disables it.
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def enabled? = ENV["HECKS_VERDICT_CACHE"] != "off"
|
|
36
|
+
|
|
37
|
+
# The directory the verdict files live in.
|
|
38
|
+
#
|
|
39
|
+
# @return [String] an absolute path under `Hecks::CacheDir`
|
|
40
|
+
def dir = Hecks::CacheDir.path("hecks_verdict_cache")
|
|
41
|
+
|
|
42
|
+
# The file the current code's verdicts live in.
|
|
43
|
+
#
|
|
44
|
+
# @return [String] an absolute path
|
|
45
|
+
def path = File.join(dir, "verdicts-#{code_digest}.json")
|
|
46
|
+
|
|
47
|
+
# A digest of everything that decides a verdict: each file under
|
|
48
|
+
# `lib/` (path and bytes), the Ruby and Hecks versions and `FORMAT`.
|
|
49
|
+
# Computed once per process, at first use, which is when the judging
|
|
50
|
+
# code was loaded.
|
|
51
|
+
#
|
|
52
|
+
# @return [String] a hex SHA-256
|
|
53
|
+
def code_digest = @code_digest ||= digest_of(File.expand_path("../../..", __dir__))
|
|
54
|
+
|
|
55
|
+
# The digest `code_digest` reports, for any tree.
|
|
56
|
+
#
|
|
57
|
+
# @param lib [String] the directory whose files decide a verdict
|
|
58
|
+
# @return [String] a hex SHA-256 over `FORMAT`, the versions and every
|
|
59
|
+
# file's relative path and bytes, in sorted order
|
|
60
|
+
def digest_of(lib)
|
|
61
|
+
state = Digest::SHA256.new
|
|
62
|
+
state << "#{FORMAT}\0#{RUBY_VERSION}\0#{Hecks::VERSION}\0"
|
|
63
|
+
Dir.glob(File.join(lib, "**", "*"), File::FNM_DOTMATCH).sort.each do |file|
|
|
64
|
+
next unless File.file?(file)
|
|
65
|
+
|
|
66
|
+
state << file.delete_prefix(lib) << "\0" << File.binread(file) << "\0"
|
|
67
|
+
end
|
|
68
|
+
state.hexdigest
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Loads the stored verdicts, once per process; later calls return an
|
|
72
|
+
# empty hash so a caller that resets its own memo re-judges for real.
|
|
73
|
+
#
|
|
74
|
+
# @return [Hash{String => Hash}] verdicts by chapter key; empty when
|
|
75
|
+
# the cache is off, absent, unreadable, corrupt or not private
|
|
76
|
+
def seed
|
|
77
|
+
return {} if @seeded
|
|
78
|
+
|
|
79
|
+
@seeded = true
|
|
80
|
+
return {} unless enabled?
|
|
81
|
+
|
|
82
|
+
loaded = read
|
|
83
|
+
entries.merge!(loaded)
|
|
84
|
+
loaded.dup
|
|
85
|
+
rescue StandardError
|
|
86
|
+
{}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Remembers a freshly judged chapter verdict for the next `flush`.
|
|
90
|
+
#
|
|
91
|
+
# @param key [String] the chapter's SHA-256 key
|
|
92
|
+
# @param held [Hash] the verdict `MetaValidator.hold` returned
|
|
93
|
+
# @return [void]
|
|
94
|
+
def record(key, held)
|
|
95
|
+
return unless enabled?
|
|
96
|
+
|
|
97
|
+
entries[key] = held
|
|
98
|
+
@dirty = true
|
|
99
|
+
register_exit_flush
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Writes every known verdict when any was added since the last write.
|
|
103
|
+
# Failure of any kind leaves the cache as it was.
|
|
104
|
+
#
|
|
105
|
+
# @return [void]
|
|
106
|
+
def flush
|
|
107
|
+
return unless @dirty && enabled?
|
|
108
|
+
|
|
109
|
+
@dirty = false
|
|
110
|
+
encoded = encode(entries)
|
|
111
|
+
return unless decode(encoded) == entries
|
|
112
|
+
|
|
113
|
+
write(JSON.generate({ "format" => FORMAT, "entries" => encoded }, max_nesting: false))
|
|
114
|
+
prune
|
|
115
|
+
rescue StandardError
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Forgets the process's own bookkeeping (not the files).
|
|
120
|
+
#
|
|
121
|
+
# @return [void]
|
|
122
|
+
def reset!
|
|
123
|
+
@seeded = false
|
|
124
|
+
@dirty = false
|
|
125
|
+
@entries = nil
|
|
126
|
+
@code_digest = nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @return [Hash{String => Hash}] every verdict this process knows
|
|
130
|
+
def entries = @entries ||= {}
|
|
131
|
+
|
|
132
|
+
# Reads and decodes the current code's file.
|
|
133
|
+
#
|
|
134
|
+
# @return [Hash{String => Hash}] entries, or `{}` on any problem
|
|
135
|
+
def read
|
|
136
|
+
file = path
|
|
137
|
+
return {} unless File.file?(file) && trusted?(file)
|
|
138
|
+
|
|
139
|
+
doc = JSON.parse(File.binread(file), max_nesting: false)
|
|
140
|
+
return {} unless doc.is_a?(Hash) && doc["format"] == FORMAT
|
|
141
|
+
|
|
142
|
+
decoded = decode(doc["entries"])
|
|
143
|
+
well_formed?(decoded) ? decoded : {}
|
|
144
|
+
rescue StandardError
|
|
145
|
+
{}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Whether `file` and its directory belong to this user and nobody else
|
|
149
|
+
# can write them.
|
|
150
|
+
#
|
|
151
|
+
# @param file [String] a path
|
|
152
|
+
# @return [Boolean]
|
|
153
|
+
def trusted?(file)
|
|
154
|
+
[File.stat(file), File.stat(File.dirname(file))].all? do |stat|
|
|
155
|
+
stat.owned? && stat.mode.nobits?(0o022)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @param entries [Object] a decoded `entries` value
|
|
160
|
+
# @return [Boolean] whether it is a hash of chapter verdicts
|
|
161
|
+
def well_formed?(entries)
|
|
162
|
+
entries.is_a?(Hash) && entries.all? do |key, held|
|
|
163
|
+
key.is_a?(String) && held.is_a?(Hash) && held[:refusals].is_a?(Array) &&
|
|
164
|
+
(held[:refusals].any? || held[:declaration].is_a?(Hash))
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Writes through a PID-suffixed temp file and an atomic rename, so a
|
|
169
|
+
# concurrent reader never sees a torn file.
|
|
170
|
+
#
|
|
171
|
+
# @param text [String] the file body
|
|
172
|
+
# @return [void]
|
|
173
|
+
def write(text)
|
|
174
|
+
file = path
|
|
175
|
+
FileUtils.mkdir_p(File.dirname(file), mode: 0o700)
|
|
176
|
+
tmp = "#{file}.#{Process.pid}.tmp"
|
|
177
|
+
File.binwrite(tmp, text)
|
|
178
|
+
File.chmod(0o600, tmp)
|
|
179
|
+
File.rename(tmp, file)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Removes verdict files for other code digests once they are stale.
|
|
183
|
+
#
|
|
184
|
+
# @return [void]
|
|
185
|
+
def prune
|
|
186
|
+
keep = File.basename(path)
|
|
187
|
+
Dir.glob(File.join(dir, "verdicts-*")).each do |file|
|
|
188
|
+
next if File.basename(file) == keep || Time.now - File.mtime(file) < STALE_AFTER
|
|
189
|
+
|
|
190
|
+
File.delete(file)
|
|
191
|
+
rescue StandardError
|
|
192
|
+
next
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Encodes plain data as JSON-safe data, tagging what JSON would lose:
|
|
197
|
+
# a symbol is `{"$s" => name}`, a hash is `{"$h" => [[key, value], ...]}`
|
|
198
|
+
# (pair order is hash order).
|
|
199
|
+
#
|
|
200
|
+
# @param obj [Object] Hash, Array, String, Symbol, Integer, nil, true or false
|
|
201
|
+
# @return [Object] the tagged form
|
|
202
|
+
# @raise [ArgumentError] for any other class
|
|
203
|
+
def encode(obj)
|
|
204
|
+
case obj
|
|
205
|
+
when Symbol then { "$s" => obj.to_s }
|
|
206
|
+
when Hash then { "$h" => obj.map { |key, value| [encode(key), encode(value)] } }
|
|
207
|
+
when Array then obj.map { |item| encode(item) }
|
|
208
|
+
when String
|
|
209
|
+
plain = obj.valid_encoding? && (obj.ascii_only? || obj.encoding == Encoding::UTF_8)
|
|
210
|
+
raise ArgumentError, "unencodable string" unless plain
|
|
211
|
+
|
|
212
|
+
obj
|
|
213
|
+
when Integer, nil, true, false then obj
|
|
214
|
+
else raise ArgumentError, "unencodable #{obj.class}"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# The inverse of `encode`.
|
|
219
|
+
#
|
|
220
|
+
# @param obj [Object] the tagged form
|
|
221
|
+
# @return [Object] the original data
|
|
222
|
+
def decode(obj)
|
|
223
|
+
case obj
|
|
224
|
+
when Array then obj.map { |item| decode(item) }
|
|
225
|
+
when Hash
|
|
226
|
+
if obj.size == 1 && obj.key?("$s") then obj["$s"].to_sym
|
|
227
|
+
elsif obj.size == 1 && obj.key?("$h") then obj["$h"].to_h { |key, value| [decode(key), decode(value)] }
|
|
228
|
+
else raise ArgumentError, "untagged hash"
|
|
229
|
+
end
|
|
230
|
+
else obj
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Registers the exit hook once per process.
|
|
235
|
+
#
|
|
236
|
+
# @return [void]
|
|
237
|
+
def register_exit_flush
|
|
238
|
+
return if @exit_registered
|
|
239
|
+
|
|
240
|
+
@exit_registered = true
|
|
241
|
+
at_exit { flush }
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "digest"
|
|
2
2
|
require "json"
|
|
3
|
+
require_relative "meta_validator/verdict_cache"
|
|
3
4
|
|
|
4
5
|
module Hecks
|
|
5
6
|
module Bluebook
|
|
@@ -150,9 +151,13 @@ module Hecks
|
|
|
150
151
|
# Process-wide judging cache, keyed on a SHA-256 digest of the judged
|
|
151
152
|
# artifact so a changed bluebook is always re-judged.
|
|
152
153
|
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
|
|
154
|
+
# Seeded once per process with the chapter verdicts earlier processes
|
|
155
|
+
# stored (see `VerdictCache`); every other verdict starts empty.
|
|
156
|
+
#
|
|
157
|
+
# @return [Hash{String => Object}] refusal messages per digest for a
|
|
158
|
+
# world/port/adapter/translation (`[]` when well formed); the held
|
|
159
|
+
# verdict Hash for a chapter
|
|
160
|
+
def self.verdicts = @verdicts ||= VerdictCache.seed
|
|
156
161
|
|
|
157
162
|
# Judges `world` through the meta-domain's own `WorldJudge` door.
|
|
158
163
|
#
|
|
@@ -248,7 +253,7 @@ module Hecks
|
|
|
248
253
|
end
|
|
249
254
|
|
|
250
255
|
key = Digest::SHA256.hexdigest(JSON.generate(bluebook.to_h))
|
|
251
|
-
held = verdicts[key]
|
|
256
|
+
held = verdicts[key] || (verdicts[key] = hold(bluebook).tap { |fresh| VerdictCache.record(key, fresh) })
|
|
252
257
|
|
|
253
258
|
unless held[:refusals].empty?
|
|
254
259
|
raise DSL::Malformed,
|
|
@@ -51,10 +51,33 @@ module Hecks
|
|
|
51
51
|
findings.concat(rust_reserved_name_findings(domain_name: bluebook.name,
|
|
52
52
|
aggregate_names: bluebook.aggregates.map(&:hecks_name),
|
|
53
53
|
rust_target: rust_target, strict: strict))
|
|
54
|
+
findings.concat(external_query_findings(bluebook, rust_target: rust_target))
|
|
54
55
|
findings.concat(ClientProfile.call(bluebook, hecksagon: hecksagon)) if profile == :client
|
|
55
56
|
findings
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
# Flags a query the hecksagon binds to a port's adapter. Only the Ruby runtime asks an
|
|
60
|
+
# adapter for an answer; a Rust host would find nothing behind the query.
|
|
61
|
+
#
|
|
62
|
+
# @param bluebook [Bluebook::Chapter] the assembled chapter, its hecksagon's ports attached
|
|
63
|
+
# @param rust_target [Boolean] whether this domain has a real Rust target; a domain without
|
|
64
|
+
# one is not checked, since nothing but the Ruby runtime serves it
|
|
65
|
+
# @return [Array<Finding>] one :external_query error per bound query, none off a Rust target
|
|
66
|
+
def external_query_findings(bluebook, rust_target: false)
|
|
67
|
+
return [] unless rust_target
|
|
68
|
+
|
|
69
|
+
bluebook.aggregates.flat_map do |aggregate|
|
|
70
|
+
aggregate.ports.flat_map do |port|
|
|
71
|
+
port.answered_queries.map do |answer|
|
|
72
|
+
Finding.new(kind: :external_query, severity: :error,
|
|
73
|
+
subject: "#{aggregate.hecks_name}.#{answer.name}",
|
|
74
|
+
message: "the #{port.name} port's adapter answers this query, and only the Ruby " \
|
|
75
|
+
"runtime asks an adapter — the Rust host cannot serve it")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
58
81
|
# Flags an aggregate or domain name that collides with a Rust keyword or reserved
|
|
59
82
|
# Cargo key — both become bare module identifiers with no `r#` escape hatch.
|
|
60
83
|
#
|
|
@@ -101,6 +101,44 @@ module Hecks
|
|
|
101
101
|
nil
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# Rewrites the line anchors of a declared pattern into whole-string anchors.
|
|
105
|
+
#
|
|
106
|
+
# Ruby reads `^` and `$` as line anchors, so `"ok\n../evil"` satisfies `^[a-z]+$`; Rust's
|
|
107
|
+
# `regex` reads them as text anchors. Enforcement matches the rewritten source so both
|
|
108
|
+
# engines refuse the same values. Escaped characters and bracket classes are left alone.
|
|
109
|
+
#
|
|
110
|
+
# @param pattern [String, Symbol, #to_s] the declared `pattern:` regex source
|
|
111
|
+
# @return [String] the source with `^` as `\A` and `$` as `\z` outside classes
|
|
112
|
+
def whole_string(pattern)
|
|
113
|
+
chars = pattern.to_s.chars
|
|
114
|
+
in_class = false
|
|
115
|
+
class_start = nil
|
|
116
|
+
out = +""
|
|
117
|
+
index = 0
|
|
118
|
+
while index < chars.length
|
|
119
|
+
char = chars[index]
|
|
120
|
+
if char == "\\"
|
|
121
|
+
out << char << chars[index + 1].to_s
|
|
122
|
+
index += 2
|
|
123
|
+
next
|
|
124
|
+
end
|
|
125
|
+
if in_class
|
|
126
|
+
in_class = false if char == "]" && index != class_start
|
|
127
|
+
elsif char == "["
|
|
128
|
+
in_class = true
|
|
129
|
+
class_start = index + 1
|
|
130
|
+
class_start += 1 if chars[class_start] == "^"
|
|
131
|
+
elsif char == "^"
|
|
132
|
+
char = "\\A"
|
|
133
|
+
elsif char == "$"
|
|
134
|
+
char = "\\z"
|
|
135
|
+
end
|
|
136
|
+
out << char
|
|
137
|
+
index += 1
|
|
138
|
+
end
|
|
139
|
+
out
|
|
140
|
+
end
|
|
141
|
+
|
|
104
142
|
# Spelled out, not derived from the key: callers read these strings in refusals.
|
|
105
143
|
CONSTRUCTS = {
|
|
106
144
|
backreference: "backreference",
|
data/lib/hecks/bluebook/query.rb
CHANGED
|
@@ -25,14 +25,14 @@ module Hecks
|
|
|
25
25
|
limit: one(:limit)
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
attr_reader :name, :description, :attributes
|
|
28
|
+
attr_reader :name, :description, :attributes, :returns
|
|
29
29
|
|
|
30
30
|
# @param null_semantics [QuerySpecification::Common::NullSemantics, nil] defaults to
|
|
31
31
|
# `NullSemantics.default`
|
|
32
32
|
def initialize(name:, description: nil, attributes: [], wheres: [],
|
|
33
33
|
order_by: nil, limit: nil, offset: nil, cursor: nil,
|
|
34
34
|
authorization: nil, null_semantics: nil,
|
|
35
|
-
inspection: nil)
|
|
35
|
+
inspection: nil, returns: nil)
|
|
36
36
|
null_semantics ||= QuerySpecification::Common::NullSemantics.default
|
|
37
37
|
super(wheres: wheres, order_by: order_by, limit: limit, offset: offset, cursor: cursor,
|
|
38
38
|
authorization: authorization,
|
|
@@ -41,13 +41,28 @@ module Hecks
|
|
|
41
41
|
@hecks_name = @name
|
|
42
42
|
@description = description
|
|
43
43
|
@attributes = attributes
|
|
44
|
+
@returns = returns&.to_s
|
|
44
45
|
end
|
|
45
46
|
|
|
47
|
+
# The value object a `returns` names, without the `list_of(...)` wrapper.
|
|
48
|
+
#
|
|
49
|
+
# @return [String, nil] the value object's name, or `nil` when the query returns nothing
|
|
50
|
+
def returns_name = @returns&.sub(/\Alist_of\((.*)\)\z/, '\1')
|
|
51
|
+
|
|
52
|
+
# Says whether the answer is many rows of the returned value object.
|
|
53
|
+
#
|
|
54
|
+
# @return [Boolean] true for `returns list_of(Name)`
|
|
55
|
+
def returns_list? = !@returns.nil? && @returns.start_with?("list_of(")
|
|
56
|
+
|
|
46
57
|
# `extra_options_to_h` (count, median, group_by, scope_to) stays dynamic; `super` covers the
|
|
47
|
-
# declared emission.
|
|
58
|
+
# declared emission. `returns` follows, only when the query declares one, so a query that
|
|
59
|
+
# returns nothing keeps its wire shape.
|
|
48
60
|
#
|
|
49
|
-
# @return [Hash] the declared emission merged with `extra_options_to_h`
|
|
50
|
-
def to_h
|
|
61
|
+
# @return [Hash] the declared emission merged with `extra_options_to_h`, then `returns`
|
|
62
|
+
def to_h
|
|
63
|
+
shape = super.merge(extra_options_to_h)
|
|
64
|
+
@returns ? shape.merge(returns: @returns) : shape
|
|
65
|
+
end
|
|
51
66
|
end
|
|
52
67
|
end
|
|
53
68
|
end
|
|
@@ -23,7 +23,7 @@ module Hecks
|
|
|
23
23
|
def call(dir)
|
|
24
24
|
Dir.mktmpdir("hecks-smoke-") do |scratch|
|
|
25
25
|
isolate!(dir, scratch)
|
|
26
|
-
dispatcher = Hecks.boot(scratch,
|
|
26
|
+
dispatcher = Hecks.boot(scratch, install_doors: false)
|
|
27
27
|
domain = dispatcher.registry.bluebooks.keys.first
|
|
28
28
|
next [] unless domain
|
|
29
29
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
# A JSON document with every object's keys sorted. Key order is not semantics, so a diff a person
|
|
7
|
+
# reads should not have to notice it moved.
|
|
8
|
+
module CanonicalJson
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Sorts every Hash's keys within a parsed JSON document, recursively.
|
|
12
|
+
#
|
|
13
|
+
# @param node [Object] a value from `JSON.parse`: a `Hash`, an `Array`, or a JSON scalar
|
|
14
|
+
# @return [Object] the same shape with every nested `Hash`'s keys sorted by their string form
|
|
15
|
+
def sort_deep(node)
|
|
16
|
+
case node
|
|
17
|
+
when Hash then node.sort_by { |key, _| key.to_s }.to_h { |key, value| [key, sort_deep(value)] }
|
|
18
|
+
when Array then node.map { |item| sort_deep(item) }
|
|
19
|
+
else node
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param text [String] a JSON document
|
|
24
|
+
# @return [String] the document pretty-printed with its keys in order
|
|
25
|
+
# @raise [JSON::ParserError] if the text is not JSON
|
|
26
|
+
def pretty(text)
|
|
27
|
+
JSON.pretty_generate(sort_deep(JSON.parse(text)))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|