hecks 3.14.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.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/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/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 +2 -0
- data/lib/hecks/chapters.rb +3 -3
- 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/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/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- 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 +21 -0
- 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 +3 -80
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +7 -13
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- 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/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/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/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json} +1 -1
- 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 +39 -7
- data/rust/host/src/payments/tests.rs +1427 -282
- data/rust/host/src/payments.rs +391 -112
- 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 -1
- data/rust/parser/src/build/named_pattern.rs +4 -1
- 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 -516
- 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 +22 -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 +82 -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/rust/host/src/approval.rs +0 -652
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
require_relative "../runtime/registry"
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module Ports
|
|
5
|
-
# The OIDC handshake: builds the provider URL and turns the returned code into verified claims.
|
|
6
|
-
# One adapter registry-wide answers it; it never touches the registry's Identity data.
|
|
7
|
-
module Authentication
|
|
8
|
-
NAME = "authentication".freeze
|
|
9
|
-
|
|
10
|
-
# A sign-in that could not be verified (state mismatch, failed code exchange, bad ID token).
|
|
11
|
-
class ValidationError < StandardError
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
module_function
|
|
15
|
-
|
|
16
|
-
# Builds the provider URL that starts a sign-in, with the CSRF state minted for it.
|
|
17
|
-
#
|
|
18
|
-
# The caller keeps the state and hands it back to `verify` as `expected_state`.
|
|
19
|
-
#
|
|
20
|
-
# @param registry [Runtime::Registry] the booted registry to resolve the adapter against
|
|
21
|
-
# @return [Array(String, String)] the URL to send the browser to, then the fresh
|
|
22
|
-
# `state` value embedded in it
|
|
23
|
-
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
24
|
-
# @raise [KeyError] if the adapter's required configuration is unset
|
|
25
|
-
def authorization_url(registry)
|
|
26
|
-
adapter(registry).authorization_url
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Exchanges the authorization code a provider sent back for a Hash of verified claims.
|
|
30
|
-
#
|
|
31
|
-
# @param registry [Runtime::Registry] the booted registry to resolve the adapter against
|
|
32
|
-
# @param code [String] the authorization code the provider sent back
|
|
33
|
-
# @param state [String, nil] the state parameter the provider returned; nil is refused
|
|
34
|
-
# @param expected_state [String, nil] the state `authorization_url` returned; nil is refused
|
|
35
|
-
# @return [Hash{Symbol => Object}] the verified claims: `issuer:`, `subject:`, `email:`
|
|
36
|
-
# (nil if the token carries none) and `email_verified:` -- never the raw token
|
|
37
|
-
# @raise [Ports::Authentication::ValidationError] if a state is nil or they differ, the
|
|
38
|
-
# code exchange fails, or the ID token is missing or does not verify
|
|
39
|
-
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
40
|
-
# @raise [KeyError] if required configuration is unset, or the token lacks `iss`/`sub`
|
|
41
|
-
def verify(registry, code:, state:, expected_state:)
|
|
42
|
-
adapter(registry).verify(code: code, state: state, expected_state: expected_state)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Finds the single adapter bound to this port.
|
|
46
|
-
#
|
|
47
|
-
# @raise [Runtime::WiringError] if none, or more than one, implements this port
|
|
48
|
-
def adapter(registry)
|
|
49
|
-
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
50
|
-
return registry.adapter_class(implementations.first.name) if implementations.size == 1
|
|
51
|
-
|
|
52
|
-
raise Runtime::WiringError, wiring_refusal(implementations)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Words the refusal for a port that resolves to no adapter or to several.
|
|
56
|
-
#
|
|
57
|
-
# @param implementations [Array] the adapters bound to this port
|
|
58
|
-
# @return [String] the error message
|
|
59
|
-
def wiring_refusal(implementations)
|
|
60
|
-
return "no adapter implements the #{NAME} port — nothing can authenticate a sign-in" if implementations.empty?
|
|
61
|
-
|
|
62
|
-
names = implementations.map(&:name).sort.join(", ")
|
|
63
|
-
"#{implementations.size} adapters implement the #{NAME} port (#{names}) — the runtime will not choose for you"
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require "date"
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require_relative "audit/approval_digest"
|
|
5
|
-
require_relative "../../../../../version"
|
|
6
|
-
|
|
7
|
-
module Hecks
|
|
8
|
-
module Translation
|
|
9
|
-
# The committed form of an edge's approval: `translations/<edge>.approval`, a JSON file beside
|
|
10
|
-
# the edge that a reviewer sees in the same diff.
|
|
11
|
-
#
|
|
12
|
-
# It binds to the edge's content digest and not to the journal's tip, so a production journal
|
|
13
|
-
# that keeps writing between the commit and the deploy does not void it. An edge with a compute
|
|
14
|
-
# or rekey rule also carries the rehearsal a person ran against real data, since the audit's
|
|
15
|
-
# samples are that edge's only other verification.
|
|
16
|
-
module ApprovalFile
|
|
17
|
-
EXTENSION = ".approval".freeze
|
|
18
|
-
|
|
19
|
-
# The keys of a rehearsal block; every one is required when the block is required.
|
|
20
|
-
#
|
|
21
|
-
# `host_version` is the Hecks release the rehearsal ran on, and the gate checks it: a
|
|
22
|
-
# rehearsal counts only on a host whose release shares its `major.minor` (see
|
|
23
|
-
# `host_compatible?`).
|
|
24
|
-
REHEARSAL_KEYS = %w[snapshot host_version result at].freeze
|
|
25
|
-
|
|
26
|
-
# How a `host_version` reads: `major.minor`, an optional patch, an optional pre-release or
|
|
27
|
-
# build suffix. Only the first two numbers take part in the comparison.
|
|
28
|
-
HOST_LINE = /\A(\d+)\.(\d+)(?:\.\d+)?(?:[-+][0-9A-Za-z.+-]+)?\z/
|
|
29
|
-
|
|
30
|
-
# The release the running host reports: the Hecks release this code was built for. The Rust
|
|
31
|
-
# host embeds the same string from `rust/host/HECKS_RELEASE`, and a spec fails when the two
|
|
32
|
-
# drift.
|
|
33
|
-
HOST_RELEASE = Hecks::VERSION
|
|
34
|
-
|
|
35
|
-
# An ISO 8601 time: `2026-09-28T12:30:00Z`, with optional fraction and `+HH:MM` offset. The
|
|
36
|
-
# host's approval.rs accepts the same shape.
|
|
37
|
-
TIMESTAMP = /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})\z/
|
|
38
|
-
|
|
39
|
-
extend Audit::ApprovalDigest
|
|
40
|
-
|
|
41
|
-
module_function
|
|
42
|
-
|
|
43
|
-
# Names an edge by the labels it joins; labels are hex, so the dash cannot be part of one.
|
|
44
|
-
#
|
|
45
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
46
|
-
# @return [String] `<from>-<to>`
|
|
47
|
-
def edge_name(edge) = "#{edge.from}-#{edge.to}"
|
|
48
|
-
|
|
49
|
-
# Where an edge's approval lives.
|
|
50
|
-
#
|
|
51
|
-
# @param directory [String] the domain's bluebook directory
|
|
52
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
53
|
-
# @return [String] `<directory>/translations/<edge>.approval`
|
|
54
|
-
def path_for(directory, edge) = File.join(directory, "translations", "#{edge_name(edge)}#{EXTENSION}")
|
|
55
|
-
|
|
56
|
-
# Whether the edge carries a rule whose only verification is a human's review of samples.
|
|
57
|
-
#
|
|
58
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
59
|
-
# @return [Boolean] true when any aggregate declares a compute or a rekey
|
|
60
|
-
def needs_rehearsal?(edge)
|
|
61
|
-
edge.aggregates.any? { |declared| !declared.computes.empty? || !declared.rekeys.empty? }
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Whether a rehearsal block records a run that passed, with every field named.
|
|
65
|
-
#
|
|
66
|
-
# @param rehearsal [Hash, nil] the block, string-keyed as it reads from JSON
|
|
67
|
-
# @return [Boolean] true when all of `REHEARSAL_KEYS` are non-empty Strings and `result` is
|
|
68
|
-
# `pass`
|
|
69
|
-
def rehearsed?(rehearsal)
|
|
70
|
-
return false unless rehearsal.is_a?(Hash)
|
|
71
|
-
|
|
72
|
-
REHEARSAL_KEYS.all? { |key| named?(rehearsal[key]) } && rehearsal["result"] == "pass"
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# The `major.minor` a version names.
|
|
76
|
-
#
|
|
77
|
-
# @param version [Object] a `host_version` as it reads from JSON
|
|
78
|
-
# @return [Array<Integer>, nil] `[major, minor]`, or nil when it is not a version
|
|
79
|
-
def release_line(version)
|
|
80
|
-
match = HOST_LINE.match(version) if version.is_a?(String)
|
|
81
|
-
match && [match[1].to_i, match[2].to_i]
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Whether a rehearsal ran on a host compatible with the running one.
|
|
85
|
-
#
|
|
86
|
-
# The rule is the smallest sound one: equal `major.minor`. A patch release does not change
|
|
87
|
-
# what a mint does, so a rehearsal survives it; a minor or major release may, so a rehearsal
|
|
88
|
-
# from an older host (or a newer one, which proves nothing about this one) does not.
|
|
89
|
-
#
|
|
90
|
-
# @param rehearsal [Hash] a rehearsal block, string-keyed
|
|
91
|
-
# @param host_version [String] the running host's release
|
|
92
|
-
# @return [Boolean] true when both versions parse and share `major.minor`
|
|
93
|
-
def host_compatible?(rehearsal, host_version = HOST_RELEASE)
|
|
94
|
-
line = release_line(rehearsal["host_version"])
|
|
95
|
-
!line.nil? && line == release_line(host_version)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# The refusal for a rehearsal that ran on another host line; rust/host words it the same.
|
|
99
|
-
#
|
|
100
|
-
# @param rehearsal [Hash] a rehearsal block, string-keyed
|
|
101
|
-
# @param host_version [String] the running host's release
|
|
102
|
-
# @return [String] names both versions
|
|
103
|
-
def host_refusal(rehearsal, host_version = HOST_RELEASE)
|
|
104
|
-
line = release_line(host_version)&.join(".")
|
|
105
|
-
"the rehearsal ran on Hecks #{rehearsal["host_version"]}, but this host is Hecks #{host_version}; " \
|
|
106
|
-
"a rehearsal counts only on a host of the same major.minor (#{line}.x) — " \
|
|
107
|
-
"re-run the rehearsal on this host and approve again"
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Whether a value is a String with something in it; a number or nil names nothing.
|
|
111
|
-
#
|
|
112
|
-
# @param value [Object] a field as it reads from JSON
|
|
113
|
-
# @return [Boolean] true for a String that is not blank
|
|
114
|
-
def named?(value) = value.is_a?(String) && !value.strip.empty?
|
|
115
|
-
|
|
116
|
-
# Whether a value is an ISO 8601 time that exists on the calendar.
|
|
117
|
-
#
|
|
118
|
-
# @param value [Object] a field as it reads from JSON
|
|
119
|
-
# @return [Boolean] true for a String of the `TIMESTAMP` shape with a real date and clock time
|
|
120
|
-
def timestamp?(value)
|
|
121
|
-
return false unless value.is_a?(String) && (match = TIMESTAMP.match(value))
|
|
122
|
-
|
|
123
|
-
year, month, day, hour, minute, second = match.captures.map(&:to_i)
|
|
124
|
-
Date.valid_date?(year, month, day) && hour < 24 && minute < 60 && second < 60
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Whether an approval names who approved it and when, the two things a reviewer signs.
|
|
128
|
-
#
|
|
129
|
-
# @param approval [Hash] the file, string-keyed as it reads from JSON
|
|
130
|
-
# @return [Boolean] true when `approved_by` is a non-empty String and `approved_at` a time
|
|
131
|
-
def attributed?(approval)
|
|
132
|
-
named?(approval["approved_by"]) && timestamp?(approval["approved_at"])
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Builds the JSON document of one approval.
|
|
136
|
-
#
|
|
137
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
138
|
-
# @param approved_by [String] who approved it, from their git identity
|
|
139
|
-
# @param approved_at [String] when, as an ISO 8601 UTC time
|
|
140
|
-
# @param rehearsal [Hash{String => String}, nil] the block; required for a compute or rekey
|
|
141
|
-
# @return [Hash{String => Object}] `edge`, `edge_digest`, `approved_by`, `approved_at`, and
|
|
142
|
-
# `rehearsal` when one was given
|
|
143
|
-
# @raise [ArgumentError] if `approved_by` is blank or `approved_at` is not a time, or if the
|
|
144
|
-
# edge needs a rehearsal and none that passed was given
|
|
145
|
-
def build(edge:, approved_by:, approved_at:, rehearsal: nil)
|
|
146
|
-
refuse_unattributed!(approved_by, approved_at)
|
|
147
|
-
refuse_unrehearsed!(edge, rehearsal)
|
|
148
|
-
|
|
149
|
-
document = { "edge" => edge_name(edge), "edge_digest" => edge_digest(edge),
|
|
150
|
-
"approved_by" => approved_by, "approved_at" => approved_at }
|
|
151
|
-
document["rehearsal"] = rehearsal.slice(*REHEARSAL_KEYS) if rehearsal
|
|
152
|
-
document
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Refuses an approval that does not say who approved it and when.
|
|
156
|
-
#
|
|
157
|
-
# @param approved_by [String] who approved it
|
|
158
|
-
# @param approved_at [String] when, as an ISO 8601 UTC time
|
|
159
|
-
# @return [void]
|
|
160
|
-
# @raise [ArgumentError] if either is blank or malformed
|
|
161
|
-
def refuse_unattributed!(approved_by, approved_at)
|
|
162
|
-
return if attributed?("approved_by" => approved_by, "approved_at" => approved_at)
|
|
163
|
-
|
|
164
|
-
raise ArgumentError, "an approval names who approved it (approved_by, your git identity) " \
|
|
165
|
-
"and when (approved_at, an ISO 8601 time)"
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Refuses the approval of a compute or rekey edge that carries no passing rehearsal.
|
|
169
|
-
#
|
|
170
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
171
|
-
# @param rehearsal [Hash{String => String}, nil] the rehearsal block
|
|
172
|
-
# @return [void]
|
|
173
|
-
# @raise [ArgumentError] if the edge needs a rehearsal and none that passed was given
|
|
174
|
-
def refuse_unrehearsed!(edge, rehearsal)
|
|
175
|
-
return unless needs_rehearsal?(edge) && !rehearsed?(rehearsal)
|
|
176
|
-
|
|
177
|
-
raise ArgumentError, "an edge with a compute or rekey rule is approved on a rehearsal that passed: " \
|
|
178
|
-
"name the snapshot, the host version, the result (pass) and when it ran"
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Writes an edge's approval file, replacing an earlier approval of the same edge.
|
|
182
|
-
#
|
|
183
|
-
# @param directory [String] the domain's bluebook directory
|
|
184
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
185
|
-
# @param document [Hash{String => Object}] what `build` answered
|
|
186
|
-
# @return [String] the path written
|
|
187
|
-
def write!(directory, edge, document)
|
|
188
|
-
path = path_for(directory, edge)
|
|
189
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
190
|
-
File.write(path, "#{JSON.pretty_generate(document)}\n")
|
|
191
|
-
path
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# Reads every committed approval of a domain.
|
|
195
|
-
#
|
|
196
|
-
# @param directory [String] the domain's bluebook directory
|
|
197
|
-
# @return [Array<Hash{String => Object}>] each parsed file; one that is not JSON is skipped,
|
|
198
|
-
# since a file that cannot be read approves nothing
|
|
199
|
-
def read_all(directory)
|
|
200
|
-
Dir[File.join(directory, "translations", "*#{EXTENSION}")].filter_map do |path|
|
|
201
|
-
parsed = JSON.parse(File.read(path))
|
|
202
|
-
parsed if parsed.is_a?(Hash)
|
|
203
|
-
rescue JSON::ParserError
|
|
204
|
-
nil
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# The committed approval that lets an edge mint, if there is one.
|
|
209
|
-
#
|
|
210
|
-
# @param directory [String, nil] the domain's bluebook directory
|
|
211
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
212
|
-
# @param host_version [String] the running host's release
|
|
213
|
-
# @return [Hash{String => Object}, nil] an approval whose digest is the edge's, that names
|
|
214
|
-
# who approved it and when, and whose rehearsal passed on a compatible host when the edge
|
|
215
|
-
# needs one; nil when none applies
|
|
216
|
-
def applicable(directory, edge, host_version: HOST_RELEASE)
|
|
217
|
-
return unless directory
|
|
218
|
-
|
|
219
|
-
digest = edge_digest(edge)
|
|
220
|
-
read_all(directory).find do |approval|
|
|
221
|
-
approval["edge_digest"] == digest && attributed?(approval) &&
|
|
222
|
-
(!needs_rehearsal?(edge) ||
|
|
223
|
-
(rehearsed?(approval["rehearsal"]) && host_compatible?(approval["rehearsal"], host_version)))
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# Why a committed approval that covers the edge was still not applied, when the only thing
|
|
228
|
-
# wrong is the host it was rehearsed on.
|
|
229
|
-
#
|
|
230
|
-
# @param directory [String, nil] the domain's bluebook directory
|
|
231
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
232
|
-
# @param host_version [String] the running host's release
|
|
233
|
-
# @return [String, nil] `host_refusal`'s text, or nil when no such approval exists
|
|
234
|
-
def host_mismatch(directory, edge, host_version: HOST_RELEASE)
|
|
235
|
-
return unless directory && needs_rehearsal?(edge)
|
|
236
|
-
|
|
237
|
-
digest = edge_digest(edge)
|
|
238
|
-
stale = read_all(directory).find do |approval|
|
|
239
|
-
approval["edge_digest"] == digest && stale_host?(approval, host_version)
|
|
240
|
-
end
|
|
241
|
-
host_refusal(stale["rehearsal"], host_version) if stale
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# Whether an attributed approval's passing rehearsal ran on an incompatible host.
|
|
245
|
-
#
|
|
246
|
-
# @param approval [Hash{String => Object}] one parsed approval
|
|
247
|
-
# @param host_version [String] the running host's release
|
|
248
|
-
# @return [Boolean]
|
|
249
|
-
def stale_host?(approval, host_version)
|
|
250
|
-
attributed?(approval) && rehearsed?(approval["rehearsal"]) &&
|
|
251
|
-
!host_compatible?(approval["rehearsal"], host_version)
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
require "digest"
|
|
2
|
-
require_relative "../../../../../../projector/exporter"
|
|
3
|
-
|
|
4
|
-
module Hecks
|
|
5
|
-
module Translation
|
|
6
|
-
module Audit
|
|
7
|
-
# The human gate: a compute edge cannot mint until `hecks audit_translation --approve`
|
|
8
|
-
# binds an approval to the edge's content digest and the journal's ordinal at review.
|
|
9
|
-
module ApprovalDigest
|
|
10
|
-
# Fingerprints a parsed translation edge, so an approval lapses when the edge's
|
|
11
|
-
# meaning changes but not when only a comment does.
|
|
12
|
-
#
|
|
13
|
-
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
14
|
-
# @return [String] 64 lowercase hex characters: the SHA-256 of the edge's exported
|
|
15
|
-
# JSON (`Projector::Exporter.translation_hash`)
|
|
16
|
-
def edge_digest(edge)
|
|
17
|
-
Digest::SHA256.hexdigest(JSON.generate(Projector::Exporter.translation_hash(edge)))
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|