hecks 3.13.0 → 4.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/examples/pizzas/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +1 -7
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/named_pattern.rb +33 -0
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +3 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/interview_aggregate.rb +4 -4
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/framework/bluebook/change.bluebook +2 -2
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +58 -37
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +15 -92
- data/lib/hecks/hecks/hecks.bluebook +2 -2
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +10 -16
- data/lib/hecks/hecks/tooling.bluebook +22 -22
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
- data/lib/hecks/language/bluebook/command.bluebook +1 -0
- data/lib/hecks/language/bluebook/entity.bluebook +1 -0
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +8 -7
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +51 -51
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value.rb +13 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/site/bluebook/site.bluebook +5 -5
- data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
- data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +300 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +145 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +606 -205
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +990 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +191 -0
- data/rust/host/src/payments/tests.rs +1445 -275
- data/rust/host/src/payments.rs +409 -121
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +483 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -0
- data/rust/parser/src/build/named_pattern.rs +26 -0
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -510
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +43 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +229 -112
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +230 -148
- data/rust/src/kernel/mod.rs +46 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
- data/rust/host/src/approval.rs +0 -652
|
@@ -14,7 +14,7 @@ module Hecks
|
|
|
14
14
|
module EraCheck
|
|
15
15
|
module_function
|
|
16
16
|
|
|
17
|
-
# Runs
|
|
17
|
+
# Runs the era gate over every bluebook in a registry.
|
|
18
18
|
#
|
|
19
19
|
# Looks up each bluebook's own source separately: `attaches` can share a boot between
|
|
20
20
|
# the domain's own bluebook and a differently-sourced one, so one file read once and reused
|
|
@@ -23,27 +23,12 @@ module Hecks
|
|
|
23
23
|
# @param registry [Runtime::Registry] the registry being booted
|
|
24
24
|
# @param directory [String] the domain's bluebook directory, searched for each source
|
|
25
25
|
# @return [void]
|
|
26
|
-
# @raise [Runtime::WiringError] if a
|
|
27
|
-
#
|
|
28
|
-
# source, or `era_check!` refuses the boot
|
|
26
|
+
# @raise [Runtime::WiringError] if a persistence binding cannot be resolved, a lineage-bound
|
|
27
|
+
# bluebook has no findable source, or `era_check!` refuses the boot
|
|
29
28
|
def check!(registry, directory)
|
|
30
|
-
check_compute_rules_for_registry!(registry)
|
|
31
29
|
check_lineage!(registry, directory)
|
|
32
30
|
end
|
|
33
31
|
|
|
34
|
-
# Refuses the boot when any bluebook's compute rule is bound away from Postgres.
|
|
35
|
-
#
|
|
36
|
-
# Runs unconditionally for every registry, unlike check_lineage!'s
|
|
37
|
-
# capability-gated pass below (ADR 0031).
|
|
38
|
-
#
|
|
39
|
-
# @param registry [Runtime::Registry] the registry whose bluebooks are all checked
|
|
40
|
-
# @return [void]
|
|
41
|
-
# @raise [Runtime::WiringError] if an aggregate carrying a compute rule is bound to an
|
|
42
|
-
# adapter that is not lineage-capable, or its persistence binding cannot be resolved
|
|
43
|
-
def check_compute_rules_for_registry!(registry)
|
|
44
|
-
registry.bluebooks.each_value { |bluebook| check_compute_rules!(registry, bluebook) }
|
|
45
|
-
end
|
|
46
|
-
|
|
47
32
|
# Hands every bluebook, with its own source text, to its adapter's era check.
|
|
48
33
|
#
|
|
49
34
|
# Capability-gated (ADR 0031): check_bluebook! below still returns
|
|
@@ -222,29 +207,6 @@ module Hecks
|
|
|
222
207
|
"it could be found (checked #{directory.inspect} and the framework registry)"
|
|
223
208
|
end
|
|
224
209
|
|
|
225
|
-
# Refuses the boot when one bluebook's compute rule is bound away from Postgres.
|
|
226
|
-
#
|
|
227
|
-
# A compute rule's SQL is its only implementation, so it cannot
|
|
228
|
-
# boot on any adapter but Postgres, independent of shape checks.
|
|
229
|
-
#
|
|
230
|
-
# @param registry [Runtime::Registry] the registry holding the declared translations
|
|
231
|
-
# and the aggregate's bindings
|
|
232
|
-
# @param bluebook [Bluebook::Chapter] the bluebook whose aggregates are checked
|
|
233
|
-
# @return [void]
|
|
234
|
-
# @raise [Runtime::WiringError] if an aggregate carrying a compute rule is bound to an
|
|
235
|
-
# adapter that is not lineage-capable, or its persistence binding cannot be resolved
|
|
236
|
-
def check_compute_rules!(registry, bluebook)
|
|
237
|
-
bluebook.aggregates.each do |aggregate|
|
|
238
|
-
lineage = Ports::Persistence::Lineage.for(registry, bluebook.name, aggregate)
|
|
239
|
-
next unless lineage&.computes?
|
|
240
|
-
|
|
241
|
-
adapter = adapter_for(registry, bluebook.name, aggregate)
|
|
242
|
-
next if lineage_capable?(registry, adapter)
|
|
243
|
-
|
|
244
|
-
raise WiringError, "compute rules require the Postgres adapter; #{aggregate.name} is bound to #{adapter}"
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
210
|
# Names the adapter an aggregate's authoritative persistence bind points at.
|
|
249
211
|
#
|
|
250
212
|
# @param registry [Runtime::Registry] the registry holding the domain's hecksagon
|
|
@@ -12,7 +12,7 @@ module Hecks
|
|
|
12
12
|
# attribute that is new outright, never a path that existed and moved.
|
|
13
13
|
#
|
|
14
14
|
# @param path [String, Symbol] a bare attribute name or a dotted value-object member path
|
|
15
|
-
# @return [Boolean] true when a rename, move, convert,
|
|
15
|
+
# @return [Boolean] true when a rename, move, convert, or drop names the path or
|
|
16
16
|
# its top-level attribute as its source, or a backfill names the top-level attribute
|
|
17
17
|
def explains?(path)
|
|
18
18
|
path = path.to_s
|
|
@@ -30,8 +30,8 @@ module Hecks
|
|
|
30
30
|
# the same way a backfill would.
|
|
31
31
|
#
|
|
32
32
|
# @param path [String, Symbol] the name of a top-level attribute new in the current shape
|
|
33
|
-
# @return [Boolean] true when a rename, move, convert or
|
|
34
|
-
# attribute, or a backfill names it
|
|
33
|
+
# @return [Boolean] true when a rename, move, convert or derive lands a value in
|
|
34
|
+
# that attribute, or a backfill names it
|
|
35
35
|
def fills?(path)
|
|
36
36
|
path = path.to_s
|
|
37
37
|
|
|
@@ -42,13 +42,13 @@ module Hecks
|
|
|
42
42
|
|
|
43
43
|
private
|
|
44
44
|
|
|
45
|
-
# Every path a move, convert
|
|
45
|
+
# Every path a move, convert or drop reads from.
|
|
46
46
|
def sources
|
|
47
|
-
[*@moves, *@converts
|
|
47
|
+
[*@moves, *@converts].map(&:from) + @drops.map(&:to_s)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
# Every path a move, convert or
|
|
51
|
-
def destinations = [*@moves, *@converts, *@rules.
|
|
50
|
+
# Every path a move, convert or derive writes to.
|
|
51
|
+
def destinations = [*@moves, *@converts, *@rules.derives].map(&:to)
|
|
52
52
|
|
|
53
53
|
def backfilled?(name) = @rules.backfills.any? { |backfill| backfill.name.to_s == name }
|
|
54
54
|
end
|
|
@@ -2,12 +2,13 @@ module Hecks
|
|
|
2
2
|
module Ports
|
|
3
3
|
module Persistence
|
|
4
4
|
class Lineage
|
|
5
|
-
# The reference semantics of rename, move, convert, drop and backfill, applied in
|
|
6
|
-
# order to a deep copy of one entry's state.
|
|
5
|
+
# The reference semantics of derive, rename, move, convert, drop and backfill, applied in
|
|
6
|
+
# that order to a deep copy of one entry's state.
|
|
7
7
|
module Rewrite
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
def rewrite(state)
|
|
11
|
+
apply_derives(state)
|
|
11
12
|
apply_renames(state, @renames)
|
|
12
13
|
@moves.each { |move| apply_move(state, move) }
|
|
13
14
|
@converts.each { |convert| apply_convert(state, convert) }
|
|
@@ -17,6 +18,28 @@ module Hecks
|
|
|
17
18
|
state
|
|
18
19
|
end
|
|
19
20
|
|
|
21
|
+
# Every derive reads the state as stored, so the values are all computed before the
|
|
22
|
+
# first is written; a derive with no value writes nothing.
|
|
23
|
+
def apply_derives(state)
|
|
24
|
+
computed = @rules.derives.map { |derive| [derive, Bluebook::TranslationExpression.evaluate(derive.tree, state)] }
|
|
25
|
+
computed.each do |derive, value|
|
|
26
|
+
next if value.nil?
|
|
27
|
+
|
|
28
|
+
top, member = derive.to.split(".", 2)
|
|
29
|
+
insert(state, top.to_sym, member, value, rule: "derive #{derive.to}")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The id a `rekey_to` gives a record: the value of its derive, read from the stored state;
|
|
34
|
+
# the record keeps its id where the derive has no value or the edge has no `rekey_to`.
|
|
35
|
+
def rekeyed_id(entry)
|
|
36
|
+
rekey = @rules.rekeys.first
|
|
37
|
+
return entry.id unless rekey
|
|
38
|
+
|
|
39
|
+
derive = @rules.derives.find { |candidate| candidate.to == rekey.to }
|
|
40
|
+
Bluebook::TranslationExpression.evaluate(derive.tree, entry.state) || entry.id
|
|
41
|
+
end
|
|
42
|
+
|
|
20
43
|
def deep_dup(node)
|
|
21
44
|
case node
|
|
22
45
|
when Hash then node.transform_values { |value| deep_dup(value) }
|
|
@@ -6,18 +6,18 @@ module Hecks
|
|
|
6
6
|
# an unknown keyword raises ArgumentError.
|
|
7
7
|
#
|
|
8
8
|
# - `retypes` — type names meaning the same shape
|
|
9
|
-
# - `
|
|
10
|
-
# - `rekeys`
|
|
9
|
+
# - `derives` — new fields computed from the record's own text
|
|
10
|
+
# - `rekeys` - the field whose derived value becomes the id
|
|
11
11
|
# - `backfills` — defaults for a new attribute
|
|
12
12
|
# - `ancestor_name` — the aggregate's name before the edge, if renamed
|
|
13
13
|
# - `ancestor_storage_name` — snake_case storage name of `ancestor_name`
|
|
14
|
-
Rules = Struct.new(:retypes, :
|
|
14
|
+
Rules = Struct.new(:retypes, :derives, :rekeys, :backfills, :ancestor_name, :ancestor_storage_name,
|
|
15
15
|
keyword_init: true) do
|
|
16
16
|
# Builds the rules with every list defaulting to empty.
|
|
17
17
|
#
|
|
18
18
|
# @param given [Hash] any of the members; an unknown key raises ArgumentError
|
|
19
19
|
# @return [Rules]
|
|
20
|
-
def self.build(**given) = new(retypes: [],
|
|
20
|
+
def self.build(**given) = new(retypes: [], derives: [], rekeys: [], backfills: [], **given)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -59,17 +59,23 @@ module Hecks
|
|
|
59
59
|
ancestor_storage_name = renamed_aggregate ? Naming.snake(declared.was) : nil
|
|
60
60
|
|
|
61
61
|
new(declared.renames, declared.moves, declared.converts, declared.drops,
|
|
62
|
-
|
|
63
|
-
backfills: declared.backfills,
|
|
64
|
-
ancestor_name: ancestor_name, ancestor_storage_name: ancestor_storage_name)
|
|
62
|
+
**optional_rules(declared), ancestor_name: ancestor_name, ancestor_storage_name: ancestor_storage_name)
|
|
65
63
|
end
|
|
66
64
|
|
|
65
|
+
# The rule lists an edge carries beyond renames, moves, converts and drops.
|
|
66
|
+
def self.optional_rules(declared)
|
|
67
|
+
{ retypes: declared.retypes, derives: declared.derives,
|
|
68
|
+
rekeys: declared.rekeys, backfills: declared.backfills }
|
|
69
|
+
end
|
|
70
|
+
private_class_method :optional_rules
|
|
71
|
+
|
|
67
72
|
# @param renames [Hash{Symbol => Symbol}] old attribute name to new name
|
|
68
73
|
# @param moves [Array<Bluebook::TranslationMove>] from/to paths across a value object
|
|
69
74
|
# @param converts [Array<Bluebook::TranslationConvert>] moves mapped through a values table
|
|
70
75
|
# @param drops [Array<Symbol>] bare or dotted paths whose data is discarded
|
|
71
76
|
# @param rules [Hash] the optional rule lists, as `Rules` names them: `retypes:`,
|
|
72
|
-
# `
|
|
77
|
+
# `derives:`, `rekeys:`, `backfills:`, `ancestor_name:` and
|
|
78
|
+
# `ancestor_storage_name:`
|
|
73
79
|
# @raise [ArgumentError] on a keyword `Rules` does not name
|
|
74
80
|
def initialize(renames, moves = [], converts = [], drops = [], **rules)
|
|
75
81
|
@renames = renames
|
|
@@ -79,14 +85,6 @@ module Hecks
|
|
|
79
85
|
@rules = Rules.build(**rules)
|
|
80
86
|
end
|
|
81
87
|
|
|
82
|
-
# Reports whether any rule in this edge is a compute.
|
|
83
|
-
#
|
|
84
|
-
# Compute has no in-process implementation; an aggregate carrying
|
|
85
|
-
# one refuses to boot anywhere but Postgres.
|
|
86
|
-
#
|
|
87
|
-
# @return [Boolean] true when the edge declares at least one compute rule
|
|
88
|
-
def computes? = !@rules.computes.empty?
|
|
89
|
-
|
|
90
88
|
# Reports whether this edge rewrites the aggregate's identity.
|
|
91
89
|
#
|
|
92
90
|
# The single source of truth for this; other call sites ask here
|
|
@@ -95,23 +93,16 @@ module Hecks
|
|
|
95
93
|
# @return [Boolean] true when the edge declares at least one rekey rule
|
|
96
94
|
def rekey? = !@rules.rekeys.empty?
|
|
97
95
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# Only the first rule is read, the same one-per-aggregate assumption
|
|
101
|
-
# `compute` makes; more than one is a DSL-level decision to arbitrate elsewhere.
|
|
102
|
-
#
|
|
103
|
-
# @return [String, nil] the first rekey rule's SQL; nil when the edge declares no rekey
|
|
104
|
-
def rekey_sql = @rules.rekeys.first&.sql
|
|
105
|
-
|
|
106
|
-
# Rewrites one journal entry's state from the held shape into the current one.
|
|
96
|
+
# Rewrites one journal entry from the held shape into the current one.
|
|
107
97
|
#
|
|
108
|
-
# The reference semantics for rename, move, convert, drop, and the
|
|
109
|
-
# `
|
|
98
|
+
# The reference semantics for derive, rename, move, convert, drop, `was:` and the id a
|
|
99
|
+
# `rekey_to` takes.
|
|
110
100
|
#
|
|
111
101
|
# @param entry [Ports::Persistence::Entry] the entry as stored, with Symbol
|
|
112
102
|
# top-level keys and String keys nested in a value-object Hash
|
|
113
103
|
# @return [Ports::Persistence::Entry] a new entry with translated state and the same
|
|
114
|
-
# `operation
|
|
104
|
+
# `operation` and `mirrors`, and the same `id` unless a `rekey_to` gives it a value;
|
|
105
|
+
# `entry` itself when it is not a save or has no state
|
|
115
106
|
# @raise [Runtime::WiringError] if a convert value is missing from its `values` table, or a
|
|
116
107
|
# move/convert would nest under a destination already holding a non-Hash value
|
|
117
108
|
def translate(entry)
|
|
@@ -120,8 +111,9 @@ module Hecks
|
|
|
120
111
|
# Deep, not shallow: a move or convert reaches into a nested
|
|
121
112
|
# value-object hash, and a shallow dup would quietly mutate
|
|
122
113
|
# the caller's copy of the original entry.
|
|
114
|
+
id = rekeyed_id(entry)
|
|
123
115
|
state = rewrite(deep_dup(entry.state))
|
|
124
|
-
Entry.new(operation: entry.operation, id:
|
|
116
|
+
Entry.new(operation: entry.operation, id: id, state: state, mirrors: entry.mirrors)
|
|
125
117
|
end
|
|
126
118
|
|
|
127
119
|
# Reports whether a declared retype pairs two type names as the same shape.
|
|
@@ -29,18 +29,6 @@ module Hecks
|
|
|
29
29
|
)
|
|
30
30
|
SQL
|
|
31
31
|
|
|
32
|
-
# The Layer-3 approvals of translation edges.
|
|
33
|
-
APPROVALS_SQL = <<~SQL.freeze
|
|
34
|
-
CREATE TABLE IF NOT EXISTS hecks_approvals (
|
|
35
|
-
domain text NOT NULL,
|
|
36
|
-
from_label text NOT NULL,
|
|
37
|
-
to_label text NOT NULL,
|
|
38
|
-
edge_digest text NOT NULL,
|
|
39
|
-
reviewed_ordinal bigint NOT NULL,
|
|
40
|
-
approved_at timestamptz NOT NULL DEFAULT now()
|
|
41
|
-
)
|
|
42
|
-
SQL
|
|
43
|
-
|
|
44
32
|
# An owned sequence default, not generated always as identity, which
|
|
45
33
|
# partitioned tables only support from Postgres 17.
|
|
46
34
|
JOURNAL_SQL = <<~SQL.freeze
|
|
@@ -76,7 +76,7 @@ module Hecks
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def rename_rows!
|
|
79
|
-
%w[hecks_eras hecks_era_texts
|
|
79
|
+
%w[hecks_eras hecks_era_texts].each do |table|
|
|
80
80
|
@db.exec_params("UPDATE #{table} SET domain = $1 WHERE domain = $2", [@domain, @formerly_known_as])
|
|
81
81
|
end
|
|
82
82
|
# hecks_attestations is created lazily on first reattest!, so it may not exist.
|
|
@@ -6,8 +6,8 @@ module Hecks
|
|
|
6
6
|
module Adapters
|
|
7
7
|
class PostgresEra
|
|
8
8
|
class Lineage
|
|
9
|
-
# CRUD over `hecks_eras`/`hecks_era_texts
|
|
10
|
-
# integrity, archiving,
|
|
9
|
+
# CRUD over `hecks_eras`/`hecks_era_texts`: era text
|
|
10
|
+
# integrity, archiving, and once-only era naming.
|
|
11
11
|
module EraStore
|
|
12
12
|
# Verifies each held text's digest before returning it; a missing digest is backfilled.
|
|
13
13
|
def eras
|
|
@@ -48,26 +48,6 @@ module Hecks
|
|
|
48
48
|
)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
# Always inserts a new row; `approval_for` picks the latest for a shape pair.
|
|
52
|
-
def record_approval!(from:, to:, edge_digest:)
|
|
53
|
-
@db.exec_params(
|
|
54
|
-
"INSERT INTO hecks_approvals (domain, from_label, to_label, edge_digest, reviewed_ordinal) " \
|
|
55
|
-
"VALUES ($1, $2, $3, $4, $5)",
|
|
56
|
-
[@domain, from, to, edge_digest, last_ordinal]
|
|
57
|
-
)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def approval_for(from:, to:)
|
|
61
|
-
rows = @db.exec_params(
|
|
62
|
-
"SELECT edge_digest, reviewed_ordinal FROM hecks_approvals " \
|
|
63
|
-
"WHERE domain = $1 AND from_label = $2 AND to_label = $3 ORDER BY approved_at DESC, reviewed_ordinal DESC LIMIT 1",
|
|
64
|
-
[@domain, from, to]
|
|
65
|
-
)
|
|
66
|
-
return nil if rows.ntuples.zero?
|
|
67
|
-
|
|
68
|
-
{ edge_digest: rows[0]["edge_digest"], reviewed_ordinal: rows[0]["reviewed_ordinal"].to_i }
|
|
69
|
-
end
|
|
70
|
-
|
|
71
51
|
# Sets hash/label only once; nothing ever recomputes a stored name to verify it.
|
|
72
52
|
def mint_name!(ordinal, hash, label)
|
|
73
53
|
@db.exec_params(
|
|
@@ -28,8 +28,6 @@ module Hecks
|
|
|
28
28
|
include FieldCache
|
|
29
29
|
include TransformInstaller
|
|
30
30
|
|
|
31
|
-
JOURNAL_COLUMNS = "ordinal, era, aggregate, aggregate_id, operation, state, mirrors".freeze
|
|
32
|
-
|
|
33
31
|
# Longer identifiers are silently truncated by Postgres, so `qualified_name` hashes them.
|
|
34
32
|
POSTGRES_IDENTIFIER_LIMIT = 63
|
|
35
33
|
|
|
@@ -100,7 +98,15 @@ module Hecks
|
|
|
100
98
|
return full if full.bytesize <= POSTGRES_IDENTIFIER_LIMIT
|
|
101
99
|
|
|
102
100
|
digest = Digest::SHA256.hexdigest(full)[0, 8]
|
|
103
|
-
"#{full
|
|
101
|
+
"#{whole_characters(full, POSTGRES_IDENTIFIER_LIMIT - digest.bytesize - 1)}_#{digest}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The longest prefix of `text` made of whole characters that fits in `limit` bytes, so a
|
|
105
|
+
# multi-byte character is dropped rather than cut in half.
|
|
106
|
+
def whole_characters(text, limit)
|
|
107
|
+
bytes = text.byteslice(0, limit)
|
|
108
|
+
bytes = bytes.byteslice(0, bytes.bytesize - 1) until bytes.valid_encoding?
|
|
109
|
+
bytes
|
|
104
110
|
end
|
|
105
111
|
|
|
106
112
|
def quote(name) = PG::Connection.quote_ident(name.to_s)
|
|
@@ -2,7 +2,6 @@ require_relative "../../../../../../runtime/registry"
|
|
|
2
2
|
require_relative "../../storage_shape"
|
|
3
3
|
require_relative "../../translation/audit"
|
|
4
4
|
require_relative "../../translation/scaffold"
|
|
5
|
-
require_relative "../../translation/approval_file"
|
|
6
5
|
require_relative "context"
|
|
7
6
|
require_relative "refusals"
|
|
8
7
|
|
|
@@ -11,7 +10,7 @@ module Hecks
|
|
|
11
10
|
class PostgresEra
|
|
12
11
|
module LineageManager
|
|
13
12
|
# Names the source era, resolves its one outgoing translation edge, and mints the
|
|
14
|
-
# next era once
|
|
13
|
+
# next era once coverage and the audited chain check out.
|
|
15
14
|
module Minter
|
|
16
15
|
include Refusals
|
|
17
16
|
|
|
@@ -20,8 +19,7 @@ module Hecks
|
|
|
20
19
|
# @param context [Context] the era check so far; `latest` is the latest held era, named
|
|
21
20
|
# here if it has no name yet
|
|
22
21
|
# @return [Integer] the ordinal of the era minted
|
|
23
|
-
# @raise [Runtime::WiringError] if the edge, its
|
|
24
|
-
# refuses
|
|
22
|
+
# @raise [Runtime::WiringError] if the edge, its coverage or its audit refuses
|
|
25
23
|
def mint!(context)
|
|
26
24
|
ensure_named!(context.lineage, context.latest)
|
|
27
25
|
context = context.with(latest: context.lineage.eras.last)
|
|
@@ -41,29 +39,6 @@ module Hecks
|
|
|
41
39
|
edges.first
|
|
42
40
|
end
|
|
43
41
|
|
|
44
|
-
# Refuses a mint whose edge carries a compute or rekey rule without an approval.
|
|
45
|
-
#
|
|
46
|
-
# A compute or rekey's only verification is a human-approved audit sample. Two approvals
|
|
47
|
-
# satisfy it: one recorded in the journal that matches the edge and the journal's current
|
|
48
|
-
# tip, or a committed `translations/<edge>.approval` that matches the edge's digest and
|
|
49
|
-
# records a passed rehearsal on a compatible host release. A committed approval that
|
|
50
|
-
# applies is written into the journal, so the journal stays the single history.
|
|
51
|
-
def ensure_compute_rekey_approved!(context, edge)
|
|
52
|
-
return unless Translation::ApprovalFile.needs_rehearsal?(edge)
|
|
53
|
-
|
|
54
|
-
approval = context.lineage.approval_for(from: edge.from, to: edge.to)
|
|
55
|
-
digest = Translation::Audit.edge_digest(edge)
|
|
56
|
-
tip = context.lineage.last_ordinal
|
|
57
|
-
return if approved_at_tip?(approval, digest, tip)
|
|
58
|
-
|
|
59
|
-
if Translation::ApprovalFile.applicable(context.directory, edge)
|
|
60
|
-
context.lineage.record_approval!(from: edge.from, to: edge.to, edge_digest: digest)
|
|
61
|
-
return
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
refuse_unapproved!(context, edge, approval, tip)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
42
|
# Diffs the held era against the current shape and writes the edge file —
|
|
68
43
|
# confident rules inline, ambiguities as parse-refusing `unresolved` lines.
|
|
69
44
|
def scaffold!(_registry, bluebook, lineage, latest, directory)
|
|
@@ -104,10 +79,9 @@ module Hecks
|
|
|
104
79
|
chain
|
|
105
80
|
end
|
|
106
81
|
|
|
107
|
-
# The edge leaving the held era, once resolved
|
|
82
|
+
# The edge leaving the held era, once resolved and found to cover the diff.
|
|
108
83
|
def verified_edge!(context)
|
|
109
84
|
edge = resolve_edge!(context)
|
|
110
|
-
ensure_compute_rekey_approved!(context, edge)
|
|
111
85
|
check_coverage!(context.registry, context.bluebook, shadow(context.latest[:held_text]), edge)
|
|
112
86
|
edge
|
|
113
87
|
end
|
|
@@ -2,8 +2,8 @@ module Hecks
|
|
|
2
2
|
module Adapters
|
|
3
3
|
class PostgresEra
|
|
4
4
|
module LineageManager
|
|
5
|
-
# The refusals a mint raises: toward the scaffold when no edge exists, and for a forked
|
|
6
|
-
# stale
|
|
5
|
+
# The refusals a mint raises: toward the scaffold when no edge exists, and for a forked or
|
|
6
|
+
# stale edge. Each is built from the era check's `Context`.
|
|
7
7
|
module Refusals
|
|
8
8
|
# Refuses toward the authoring loop, or, under HECKS_SCAFFOLD=1, scaffolds the
|
|
9
9
|
# missing edge first and names the file it wrote.
|
|
@@ -37,30 +37,6 @@ module Hecks
|
|
|
37
37
|
"re-run hecks scaffold_translation", minting: false)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# Whether the journal already holds an approval of this edge at the journal's tip.
|
|
41
|
-
def approved_at_tip?(approval, digest, tip)
|
|
42
|
-
approval && approval[:edge_digest] == digest && approval[:reviewed_ordinal] == tip
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def refuse_unapproved!(context, edge, approval, tip)
|
|
46
|
-
if approval && approval[:edge_digest] == Translation::Audit.edge_digest(edge)
|
|
47
|
-
raise era_refusal(context, "the journal advanced past the approved " \
|
|
48
|
-
"review (ordinal #{approval[:reviewed_ordinal]} reviewed, #{tip} now) — the samples a " \
|
|
49
|
-
"human approved no longer cover the data; re-run hecks audit_translation with --approve")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
mismatch = Translation::ApprovalFile.host_mismatch(context.directory, edge)
|
|
53
|
-
raise era_refusal(context, unapproved_reason(mismatch))
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def unapproved_reason(mismatch)
|
|
57
|
-
return "the committed approval does not apply — #{mismatch}" if mismatch
|
|
58
|
-
|
|
59
|
-
"this edge carries a compute or rekey " \
|
|
60
|
-
"rule, and the audit's human-approved sample is its only verification — run " \
|
|
61
|
-
"hecks audit_translation with --approve, then boot again"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
40
|
# Builds the refusal; a mint's own refusals say "cannot mint era N of X", the
|
|
65
41
|
# edge-resolution ones say "cannot boot X".
|
|
66
42
|
def era_refusal(context, text, minting: true)
|
|
@@ -9,7 +9,7 @@ module Hecks
|
|
|
9
9
|
# Checks one aggregate's records across the edge for id conservation and, where a
|
|
10
10
|
# per-record comparison is possible, agreement with the reference transform.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# Rekeyed aggregates are exempt from the comparison.
|
|
13
13
|
#
|
|
14
14
|
# @param violations [Array<String>] collector this method appends messages to
|
|
15
15
|
# @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
|
|
@@ -24,7 +24,7 @@ module Hecks
|
|
|
24
24
|
check_id_conservation!(violations, aggregate, before, after, rekeyed: rekeyed)
|
|
25
25
|
|
|
26
26
|
return unless declared
|
|
27
|
-
# A
|
|
27
|
+
# A rekeyed record's `after` cannot be looked up by its old id.
|
|
28
28
|
return if rekeyed
|
|
29
29
|
|
|
30
30
|
check_value_preservation!(violations, aggregate, declared, before, after)
|
|
@@ -56,12 +56,11 @@ module Hecks
|
|
|
56
56
|
# @return [void]
|
|
57
57
|
def check_value_preservation!(violations, aggregate, declared, before, after)
|
|
58
58
|
rules = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
|
|
59
|
-
compute_paths = compute_paths_of(declared)
|
|
60
59
|
|
|
61
60
|
before.each do |id, state|
|
|
62
61
|
next unless after.key?(id)
|
|
63
62
|
|
|
64
|
-
diverged = divergence(rules, id, state, after[id]
|
|
63
|
+
diverged = divergence(rules, id, state, after[id])
|
|
65
64
|
next if diverged.empty?
|
|
66
65
|
|
|
67
66
|
violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
|
|
@@ -75,28 +74,6 @@ module Hecks
|
|
|
75
74
|
# @return [Hash{String => Object}, nil] a deep copy with String keys at every depth
|
|
76
75
|
def normalize(state) = JSON.parse(JSON.generate(state))
|
|
77
76
|
|
|
78
|
-
# Removes the paths a compute owns from a normalized state, in place.
|
|
79
|
-
#
|
|
80
|
-
# A dotted path removes only its own member, never the whole top-level key, so sibling
|
|
81
|
-
# members stay under the equivalence check and silent loss there is still caught.
|
|
82
|
-
#
|
|
83
|
-
# @param state [Hash{String => Object}] a normalized state; mutated
|
|
84
|
-
# @param paths [Array<String>] bare (`"price_cents"`) or dotted (`"price.cents"`)
|
|
85
|
-
# compute paths
|
|
86
|
-
# @return [Hash{String => Object}] `state` itself, with those paths deleted
|
|
87
|
-
def strip_compute_paths(state, paths)
|
|
88
|
-
paths.each do |path|
|
|
89
|
-
segments = path.split(".")
|
|
90
|
-
if segments.length == 1
|
|
91
|
-
state.delete(segments.first)
|
|
92
|
-
else
|
|
93
|
-
parent = segments[0..-2].reduce(state) { |node, segment| node.is_a?(Hash) ? node[segment] : nil }
|
|
94
|
-
parent.delete(segments.last) if parent.is_a?(Hash)
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
state
|
|
98
|
-
end
|
|
99
|
-
|
|
100
77
|
private
|
|
101
78
|
|
|
102
79
|
# The message for a rekeying edge whose record count changed, or nil.
|
|
@@ -118,15 +95,11 @@ module Hecks
|
|
|
118
95
|
"(lost #{lost.sort.inspect}, gained #{gained.sort.inspect})"
|
|
119
96
|
end
|
|
120
97
|
|
|
121
|
-
def compute_paths_of(declared)
|
|
122
|
-
declared.computes.flat_map { |compute| [compute.from, compute.to] }.map(&:to_s)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
98
|
# The keys at which one record's translated state differs from the reference transform.
|
|
126
|
-
def divergence(rules, id, state, translated
|
|
99
|
+
def divergence(rules, id, state, translated)
|
|
127
100
|
entry = Ports::Persistence::Entry.new(operation: "save", id: id, state: state.transform_keys(&:to_sym))
|
|
128
|
-
expected =
|
|
129
|
-
actual =
|
|
101
|
+
expected = normalize(rules.translate(entry).state)
|
|
102
|
+
actual = normalize(translated)
|
|
130
103
|
(expected.keys | actual.keys).reject { |key| expected[key] == actual[key] }
|
|
131
104
|
end
|
|
132
105
|
end
|
|
@@ -8,7 +8,7 @@ module Hecks
|
|
|
8
8
|
#
|
|
9
9
|
# @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
|
|
10
10
|
# @param declared [Bluebook::TranslationAggregate, nil] this edge's rules; the
|
|
11
|
-
# destinations of its renames, moves, converts and
|
|
11
|
+
# destinations of its renames, moves, converts and derives count as fed
|
|
12
12
|
# @param after [Hash{String => Hash}] translated state per record id
|
|
13
13
|
# @return [Array<String>] names of unfed attributes, in declaration order; `[]` when
|
|
14
14
|
# every attribute is fed or `after` holds no records
|
|
@@ -25,12 +25,12 @@ module Hecks
|
|
|
25
25
|
# The top-level attribute names an edge's destinations feed.
|
|
26
26
|
#
|
|
27
27
|
# @param declared [Bluebook::TranslationAggregate, nil] this edge's rules
|
|
28
|
-
# @return [Array<String>] the destinations of renames, moves, converts and
|
|
28
|
+
# @return [Array<String>] the destinations of renames, moves, converts and derives, cut to
|
|
29
29
|
# their top-level name; `[]` when there is no edge
|
|
30
30
|
def fed_names(declared)
|
|
31
31
|
return [] unless declared
|
|
32
32
|
|
|
33
|
-
rules = [declared.moves, declared.converts, declared.
|
|
33
|
+
rules = [declared.moves, declared.converts, declared.derives].flat_map { |list| list.map(&:to) }
|
|
34
34
|
(declared.renames.values.map(&:to_s) + rules).map { |path| path.to_s.split(".").first }
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -3,12 +3,11 @@ require "json"
|
|
|
3
3
|
require_relative "audit/layer_one"
|
|
4
4
|
require_relative "audit/layer_two"
|
|
5
5
|
require_relative "audit/unfed_report"
|
|
6
|
-
require_relative "audit/approval_digest"
|
|
7
6
|
|
|
8
7
|
module Hecks
|
|
9
8
|
module Translation
|
|
10
|
-
# Derives the audit's assertions per layer (audit/layer_one, layer_two, unfed_report
|
|
11
|
-
#
|
|
9
|
+
# Derives the audit's assertions per layer (audit/layer_one, layer_two, unfed_report);
|
|
10
|
+
# Layer 3, the human-approved sample, is assembled in `check`.
|
|
12
11
|
module Audit
|
|
13
12
|
Verdict = Struct.new(:violations, :dropped, :unfed, :samples, keyword_init: true) do
|
|
14
13
|
# Reports whether the mechanical layers found nothing to refuse.
|
|
@@ -23,7 +22,6 @@ module Hecks
|
|
|
23
22
|
extend LayerOne
|
|
24
23
|
extend LayerTwo
|
|
25
24
|
extend UnfedReport
|
|
26
|
-
extend ApprovalDigest
|
|
27
25
|
|
|
28
26
|
module_function
|
|
29
27
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Translation
|
|
3
|
+
# Compiles a `Bluebook::TranslationExpression` tree into a Postgres text expression over a
|
|
4
|
+
# jsonb record. The compiled form yields NULL wherever the expression has no value, and
|
|
5
|
+
# agrees with `TranslationExpression.evaluate` on every record.
|
|
6
|
+
module ExpressionSql
|
|
7
|
+
UPPER = ("A".."Z").to_a.join.freeze
|
|
8
|
+
LOWER = UPPER.downcase.freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# @param tree [Array] a tree `Bluebook::TranslationExpression.parse` returned
|
|
13
|
+
# @param record [String] the SQL that names the jsonb record the expression reads
|
|
14
|
+
# @return [String] a SQL expression of type text
|
|
15
|
+
def compile(tree, record)
|
|
16
|
+
case tree.first
|
|
17
|
+
when "lit" then text(tree[1])
|
|
18
|
+
when "null" then "NULL::text"
|
|
19
|
+
when "ref" then reference(tree[1], record)
|
|
20
|
+
else function(tree.first, tree.drop(1), record)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def function(name, arguments, record)
|
|
25
|
+
return lookup(arguments, record) if name == "lookup"
|
|
26
|
+
|
|
27
|
+
parts = arguments.map { |argument| compile(argument, record) }
|
|
28
|
+
case name
|
|
29
|
+
when "lower" then "translate(#{parts.first}, #{text(UPPER)}, #{text(LOWER)})"
|
|
30
|
+
when "upper" then "translate(#{parts.first}, #{text(LOWER)}, #{text(UPPER)})"
|
|
31
|
+
when "concat" then "(#{parts.join(" || ")})"
|
|
32
|
+
else "replace(#{parts.join(", ")})"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Only text counts as a value: a number, object, array or null reads as no value.
|
|
37
|
+
def reference(path, record)
|
|
38
|
+
at = RuleCompiler.path_literal(path)
|
|
39
|
+
"(CASE WHEN jsonb_typeof(#{record} #> #{at}) = 'string' THEN #{record} #>> #{at} END)"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def lookup(arguments, record)
|
|
43
|
+
key, table, fallback = arguments
|
|
44
|
+
branches = table.map { |from, to| "WHEN #{text(from)} THEN #{text(to)}" }.join(" ")
|
|
45
|
+
"(SELECT CASE WHEN __v IS NULL THEN NULL ELSE CASE __v #{branches} ELSE #{compile(fallback, record)} END END " \
|
|
46
|
+
"FROM (SELECT #{compile(key, record)} AS __v) __lookup)"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def text(value) = "#{RuleCompiler.text_literal(value)}::text"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|