hecks 4.0.0 → 6.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/bluebook/pizzas.bluebook +1 -0
- data/exe/hecks +15 -3
- data/lib/hecks/adapters/driven/claude_code.rb +1 -1
- data/lib/hecks/adapters/driven/d1.rb +2 -1
- data/lib/hecks/adapters/driven/folder.rb +35 -28
- data/lib/hecks/adapters/driven/folder_overlay.rb +51 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +99 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +32 -36
- data/lib/hecks/adapters/driven/heki/journal.rb +45 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +1 -1
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -2
- data/lib/hecks/adapters/driven/key_keeper.rb +42 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +8 -2
- data/lib/hecks/adapters/driven/lambda/remote_dispatcher.rb +185 -0
- data/lib/hecks/adapters/driven/lambda.rb +14 -2
- data/lib/hecks/adapters/driven/memory/dispatch_claims.rb +25 -0
- data/lib/hecks/adapters/driven/memory.rb +15 -4
- data/lib/hecks/adapters/driven/postgres/codec.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/journal.rb +46 -0
- data/lib/hecks/adapters/driven/postgres/outbox.rb +31 -5
- data/lib/hecks/adapters/driven/postgres/projection.rb +43 -0
- data/lib/hecks/adapters/driven/postgres/repository.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +2 -1
- data/lib/hecks/adapters/driven/postgres/set_dialect.rb +44 -0
- data/lib/hecks/adapters/driven/postgres.adapter +8 -0
- data/lib/hecks/adapters/driven/postgres.rb +8 -3
- data/lib/hecks/adapters/driven/postgres_dump/connection.rb +57 -0
- data/lib/hecks/adapters/driven/postgres_dump.rb +117 -0
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/boot.rb +6 -3
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/class_methods.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/dialect.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/chain_sql.rb +8 -4
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/domain_rename.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_integrity.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_store.rb +3 -3
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/mint_transaction.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/sql_templates.rb +6 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/context.rb +15 -5
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/coverage_check.rb +13 -11
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/era_resolver.rb +23 -6
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/merge_coordinator.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/minter.rb +12 -9
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/refusals.rb +13 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager.rb +6 -6
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/query_phases.rb +8 -2
- data/lib/hecks/adapters/driven/postgres_era.adapter +7 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era.rb +14 -13
- data/lib/hecks/adapters/driven/prism.rb +4 -3
- data/lib/hecks/adapters/driven/projection/read_model_walk.rb +126 -0
- data/lib/hecks/adapters/driven/projection/row_decoding.rb +57 -0
- data/lib/hecks/adapters/driven/set_transform/assignments.rb +101 -0
- data/lib/hecks/adapters/driven/set_transform/expressions.rb +138 -0
- data/lib/hecks/adapters/driven/set_transform.rb +81 -0
- data/lib/hecks/adapters/driven/sql_keyset.rb +61 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +28 -9
- data/lib/hecks/adapters/driven/sqlite/codec.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/ddl.rb +7 -0
- data/lib/hecks/adapters/driven/sqlite/dialect.rb +2 -1
- data/lib/hecks/adapters/driven/sqlite/dispatch_claims.rb +28 -0
- data/lib/hecks/adapters/driven/sqlite/journal.rb +46 -0
- data/lib/hecks/adapters/driven/sqlite/outbox.rb +3 -2
- data/lib/hecks/adapters/driven/sqlite/projection.rb +9 -135
- data/lib/hecks/adapters/driven/sqlite/repository.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +16 -4
- data/lib/hecks/adapters/driven/sqlite/set_dialect.rb +39 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +2 -0
- data/lib/hecks/adapters/driven/sqlite.rb +11 -2
- data/lib/hecks/adapters/driven.rb +8 -3
- data/lib/hecks/adapters/driving/cli.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +23 -16
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +25 -24
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +9 -5
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +15 -7
- data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +49 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +4 -2
- data/lib/hecks/adapters/driving/github_webhook.rb +2 -1
- data/lib/hecks/adapters/driving/handle/verb_methods.rb +46 -0
- data/lib/hecks/adapters/driving/handle.rb +9 -17
- data/lib/hecks/adapters/driving/json.rb +19 -17
- data/lib/hecks/adapters/driving/launcher_options.rb +37 -32
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +3 -1
- data/lib/hecks/adapters/driving/mcp_scope.rb +8 -7
- data/lib/hecks/adapters/driving/newcomer.rb +6 -5
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +13 -12
- data/lib/hecks/adapters/driving/ruby/bulk_verbs.rb +127 -0
- data/lib/hecks/adapters/driving/ruby/{chapter.rb → declaration.rb} +27 -27
- data/lib/hecks/adapters/driving/ruby/query_rows.rb +27 -0
- data/lib/hecks/adapters/driving/ruby.rb +25 -24
- data/lib/hecks/adapters/driving/usage_cache.rb +14 -3
- data/lib/hecks/assembly.rb +101 -0
- data/lib/hecks/behaviors/dsl.rb +13 -3
- data/lib/hecks/behaviors/expectations/boot.rb +7 -6
- data/lib/hecks/behaviors/expectations/checks.rb +1 -1
- data/lib/hecks/behaviors/expectations/dispatching.rb +10 -9
- data/lib/hecks/behaviors/expectations/qualify.rb +9 -9
- data/lib/hecks/behaviors/expectations.rb +24 -10
- data/lib/hecks/behaviors/ir.rb +4 -2
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +31 -4
- data/lib/hecks/bench/environment.rb +6 -2
- data/lib/hecks/bench/postgres_probe.rb +3 -1
- data/lib/hecks/bench/report.rb +4 -1
- data/lib/hecks/bench/suite.rb +9 -3
- data/lib/hecks/bluebook/acknowledgements.rb +81 -0
- data/lib/hecks/bluebook/aggregate.rb +13 -4
- data/lib/hecks/bluebook/ask_resolution.rb +17 -17
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -5
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +11 -7
- data/lib/hecks/bluebook/assembly/marks.rb +10 -1
- data/lib/hecks/bluebook/assembly/scalars.rb +8 -2
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +10 -10
- data/lib/hecks/bluebook/behaviour/aggregate.rb +2 -1
- data/lib/hecks/bluebook/behaviour/attribute.rb +12 -0
- data/lib/hecks/bluebook/behaviour/command.rb +1 -1
- data/lib/hecks/bluebook/behaviour/{chapter.rb → declaration.rb} +17 -17
- data/lib/hecks/bluebook/behaviour/domain_port.rb +1 -1
- data/lib/hecks/bluebook/behaviour/entity.rb +1 -0
- data/lib/hecks/bluebook/behaviour/hecksagon.rb +8 -1
- data/lib/hecks/bluebook/behaviour/process_manager.rb +4 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +4 -1
- data/lib/hecks/bluebook/behaviour/traits.rb +13 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +17 -16
- data/lib/hecks/bluebook/capabilities/package_declared.rb +65 -0
- data/lib/hecks/bluebook/capabilities.rb +32 -132
- data/lib/hecks/bluebook/command.rb +22 -7
- data/lib/hecks/bluebook/context_map.rb +104 -0
- data/lib/hecks/bluebook/{chapter.rb → declaration.rb} +13 -13
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +28 -4
- data/lib/hecks/bluebook/dsl/aggregate_builder/draining.rb +2 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder/one_of_reference.rb +54 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_field_sealing.rb +11 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_sealing.rb +15 -5
- data/lib/hecks/bluebook/dsl/aggregate_builder/relationships.rb +21 -3
- data/lib/hecks/bluebook/dsl/aggregate_builder/rules.rb +22 -22
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +2 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +27 -22
- data/lib/hecks/bluebook/dsl/bluebook_builder/pending_givens.rb +22 -22
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +27 -21
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +3 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/event_shapes.rb +5 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +3 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/hop_tails.rb +13 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +4 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +38 -84
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/query_hops.rb +5 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/references.rb +6 -6
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +6 -6
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +3 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +67 -67
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +48 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +28 -15
- data/lib/hecks/bluebook/dsl/context_map_builder.rb +73 -0
- data/lib/hecks/bluebook/dsl/entity_builder/rules.rb +17 -17
- data/lib/hecks/bluebook/dsl/entity_builder/scope.rb +3 -3
- data/lib/hecks/bluebook/dsl/entity_builder/sealing.rb +2 -2
- data/lib/hecks/bluebook/dsl/entity_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder/{chapter_scope.rb → bluebook_scope.rb} +14 -14
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +33 -27
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +6 -2
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +4 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +14 -8
- data/lib/hecks/bluebook/dsl/read_model_builder/sealing.rb +6 -11
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +1 -2
- data/lib/hecks/bluebook/dsl/word_gate.rb +2 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl.rb +3 -2
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +4 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +5 -3
- data/lib/hecks/bluebook/expression/ast_reader/blocks.rb +41 -0
- data/lib/hecks/bluebook/expression/ast_reader.rb +2 -10
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -4
- data/lib/hecks/bluebook/expression/evaluator/parser.rb +4 -1
- data/lib/hecks/bluebook/expression/evaluator.rb +13 -9
- data/lib/hecks/bluebook/expression/projection.json +6 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +50 -2
- data/lib/hecks/bluebook/expression/resolver/lookups.rb +2 -1
- data/lib/hecks/bluebook/expression/resolver/operations.rb +1 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +21 -15
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +4 -1
- data/lib/hecks/bluebook/expression.rb +1 -1
- data/lib/hecks/bluebook/hecksagon.rb +48 -23
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/judge/cells.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge/identity.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +27 -27
- data/lib/hecks/bluebook/meta_validator/plan.rb +5 -5
- data/lib/hecks/bluebook/meta_validator/readings/fields.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +11 -3
- data/lib/hecks/bluebook/meta_validator/reconstruction/reading.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +19 -19
- data/lib/hecks/bluebook/meta_validator/shapes.rb +6 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot/disk_cache.rb +18 -15
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +21 -15
- data/lib/hecks/bluebook/meta_validator/verdict_cache/prebuilt.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +11 -10
- data/lib/hecks/bluebook/meta_validator.rb +25 -22
- data/lib/hecks/bluebook/model_check/aggregate_checks.rb +68 -0
- data/lib/hecks/bluebook/model_check/ask_checks.rb +6 -5
- data/lib/hecks/bluebook/model_check/client_profile.rb +8 -42
- data/lib/hecks/bluebook/model_check/context_checks.rb +97 -0
- data/lib/hecks/bluebook/model_check/context_map_checks.rb +158 -0
- data/lib/hecks/bluebook/model_check/lifecycle_checks.rb +27 -5
- data/lib/hecks/bluebook/model_check/one_of_checks.rb +106 -0
- data/lib/hecks/bluebook/model_check/orphan_checks.rb +129 -0
- data/lib/hecks/bluebook/model_check/redundant_relationships.rb +38 -0
- data/lib/hecks/bluebook/model_check/reference_checks.rb +11 -1
- data/lib/hecks/bluebook/model_check/saga_checks.rb +8 -3
- data/lib/hecks/bluebook/model_check/verbs.rb +4 -4
- data/lib/hecks/bluebook/model_check.rb +32 -12
- data/lib/hecks/bluebook/named_pattern.rb +12 -1
- data/lib/hecks/bluebook/one_of_union.rb +35 -0
- data/lib/hecks/bluebook/pattern_subset.rb +12 -3
- data/lib/hecks/bluebook/project_loader.rb +3 -2
- data/lib/hecks/bluebook/project_register.rb +21 -5
- data/lib/hecks/bluebook/query.rb +4 -1
- data/lib/hecks/bluebook/read_model.rb +5 -5
- data/lib/hecks/bluebook/reference.rb +3 -3
- data/lib/hecks/bluebook/smoke_test.rb +15 -13
- data/lib/hecks/bluebook/synthesizer.rb +48 -13
- data/lib/hecks/bluebook/translation_expression/parser.rb +14 -5
- data/lib/hecks/bluebook.rb +6 -3
- data/lib/hecks/bulk/bluebook/adapters/row_sweeper.adapter +7 -0
- data/lib/hecks/bulk/bluebook/bulk.behaviors +18 -0
- data/lib/hecks/bulk/bluebook/bulk.bluebook +126 -0
- data/lib/hecks/bulk/bluebook/bulk.hecksagon +11 -0
- data/lib/hecks/bulk/bluebook/bulk.ports.hecksagon +13 -0
- data/lib/hecks/bulk/plan.rb +60 -0
- data/lib/hecks/bulk/row_sweeper.rb +34 -0
- data/lib/hecks/bulk/set_arguments.rb +68 -0
- data/lib/hecks/bulk/set_eligibility.rb +50 -0
- data/lib/hecks/bulk/set_planner.rb +100 -0
- data/lib/hecks/bulk/set_sweep.rb +78 -0
- data/lib/hecks/bulk/sweep.rb +67 -0
- data/lib/hecks/cli/check_era.rb +6 -6
- data/lib/hecks/cli/command_table.rb +2 -2
- data/lib/hecks/cli/console.rb +11 -6
- data/lib/hecks/cli/document.rb +14 -9
- data/lib/hecks/cli/domain_writer.rb +4 -1
- data/lib/hecks/cli/expected_era.rb +139 -0
- data/lib/hecks/cli/expression_projection.rb +2 -2
- data/lib/hecks/cli/follow.rb +3 -3
- data/lib/hecks/cli/history.rb +4 -1
- data/lib/hecks/cli/interview_draft.rb +8 -1
- data/lib/hecks/cli/interview_record.rb +7 -3
- data/lib/hecks/cli/interview_run.rb +7 -5
- data/lib/hecks/cli/interview_session/proposals.rb +5 -2
- data/lib/hecks/cli/interview_session.rb +3 -2
- data/lib/hecks/cli/interview_shape/lifecycle.rb +92 -0
- data/lib/hecks/cli/interview_shape.rb +19 -73
- data/lib/hecks/cli/model_check/examination.rb +63 -14
- data/lib/hecks/cli/model_check/sources.rb +4 -2
- data/lib/hecks/cli/model_check.rb +1 -1
- data/lib/hecks/cli/persistence_notice.rb +45 -0
- data/lib/hecks/cli/present.rb +1 -1
- data/lib/hecks/cli/project_cli/launcher_source.rb +12 -10
- data/lib/hecks/cli/project_cli/templates.rb +24 -3
- data/lib/hecks/cli/project_cli.rb +11 -13
- data/lib/hecks/cli/project_diagrams.rb +20 -20
- data/lib/hecks/cli/project_glossary.rb +14 -14
- data/lib/hecks/cli/reference.rb +1 -1
- data/lib/hecks/cli/refresh_projections.rb +16 -11
- data/lib/hecks/cli/run.rb +27 -5
- data/lib/hecks/cli/seed_semantics_corpus.rb +14 -4
- data/lib/hecks/cli/shape.rb +3 -4
- data/lib/hecks/cli/smoke_http/transport.rb +3 -2
- data/lib/hecks/cli/statements.rb +7 -7
- data/lib/hecks/cli/stores.rb +70 -2
- data/lib/hecks/cli/stress_concurrency_specs/reporting.rb +4 -1
- data/lib/hecks/codemod.rb +3 -3
- data/lib/hecks/composer/adapters/claude_composer.rb +3 -2
- data/lib/hecks/composer/agent.rb +1 -1
- data/lib/hecks/composer/assistant.rb +2 -2
- data/lib/hecks/composer/behavior_run.rb +7 -1
- data/lib/hecks/composer/bluebook/composer.bluebook +18 -25
- data/lib/hecks/composer/bluebook/composer.hecksagon +1 -1
- data/lib/hecks/composer/board.rb +6 -6
- data/lib/hecks/composer/board_edit/command_text.rb +4 -1
- data/lib/hecks/composer/board_edit/source.rb +18 -9
- data/lib/hecks/composer/board_edit.rb +2 -2
- data/lib/hecks/composer/check.rb +22 -22
- data/lib/hecks/composer/domains.rb +1 -1
- data/lib/hecks/composer/live.rb +19 -13
- data/lib/hecks/composer/outline.rb +4 -1
- data/lib/hecks/composer/page.html +1 -1
- data/lib/hecks/composer/panels.rb +6 -3
- data/lib/hecks/composer/parse.rb +53 -36
- data/lib/hecks/composer/session.rb +22 -16
- data/lib/hecks/composer/toolbox.rb +9 -6
- data/lib/hecks/composer/web.rb +3 -2
- data/lib/hecks/composer/workbench.rb +12 -9
- data/lib/hecks/composer.rb +1 -1
- data/lib/hecks/construct.rb +7 -7
- data/lib/hecks/corpus/adapters.rb +2 -1
- data/lib/hecks/corpus/routes.rb +25 -5
- data/lib/hecks/corpus/rust.rb +59 -24
- data/lib/hecks/corpus/sources.rb +12 -11
- data/lib/hecks/corpus/worlds.rb +2 -1
- data/lib/hecks/corpus.rb +30 -22
- data/lib/hecks/deploy/bluebook/deploy.bluebook +304 -518
- data/lib/hecks/doc/reference/harvester.rb +4 -1
- data/lib/hecks/doc/reference/readme.rb +11 -6
- data/lib/hecks/doc/reference/rendering.rb +11 -5
- data/lib/hecks/doc/reference.rb +7 -5
- data/lib/hecks/embryonaut_bluebook/lock.rb +5 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +7 -1
- data/lib/hecks/embryonaut_bluebook/registry/changelog.rb +4 -1
- data/lib/hecks/embryonaut_bluebook/registry/versions.rb +13 -2
- data/lib/hecks/embryonaut_bluebook/registry.rb +5 -2
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/vendor.rb +5 -2
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +15 -1
- data/lib/hecks/embryonaut_bluebook.rb +26 -12
- data/lib/hecks/engine_agreement.rb +4 -1
- data/lib/hecks/forms/app/commands.rb +35 -2
- data/lib/hecks/forms/app/records.rb +12 -12
- data/lib/hecks/forms/app/responses.rb +4 -2
- data/lib/hecks/forms/app.rb +22 -19
- data/lib/hecks/forms/command_form_renderer.rb +2 -2
- data/lib/hecks/forms/field_controls.rb +6 -1
- data/lib/hecks/forms/field_shape.rb +14 -6
- data/lib/hecks/forms/html.rb +8 -7
- data/lib/hecks/forms/html_pattern.rb +80 -0
- data/lib/hecks/forms/index_renderer.rb +11 -11
- data/lib/hecks/forms/params.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +4 -1
- data/lib/hecks/forms/query_form_renderer.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/reference_options.rb +2 -1
- data/lib/hecks/forms/text_shape.rb +4 -1
- data/lib/hecks/forms/value_object_shape.rb +1 -1
- data/lib/hecks/forms.rb +7 -7
- data/lib/hecks/fqn.rb +8 -2
- data/lib/hecks/framework/bluebook/change.bluebook +10 -15
- data/lib/hecks/framework/bluebook/change.hecksagon +1 -1
- data/lib/hecks/framework/bluebook/compliance.bluebook +27 -126
- data/lib/hecks/framework/bluebook/console_settings.bluebook +199 -114
- data/lib/hecks/framework/bluebook/framework.hecksagon +7 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +36 -14
- data/lib/hecks/framework/bluebook/identity.bluebook +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +123 -19
- data/lib/hecks/framework/bluebook/privacy.key_keeper.adapter +7 -0
- data/lib/hecks/framework/bluebook/privacy.ports.hecksagon +19 -0
- data/lib/hecks/framework/compliance.oidc.json +6 -36
- data/lib/hecks/framework/console_settings.oidc.json +39 -24
- data/lib/hecks/framework/privacy.oidc.json +25 -0
- data/lib/hecks/framework.rb +24 -8
- data/lib/hecks/framework_bluebooks.rb +156 -0
- data/lib/hecks/freezer.rb +7 -1
- data/lib/hecks/fuzzing/combination_miner.rb +9 -6
- data/lib/hecks/fuzzing/concurrent_dispatch/world_copy.rb +3 -1
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +2 -2
- data/lib/hecks/fuzzing/domain_generator.rb +8 -3
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/failure_triage.rb +13 -6
- data/lib/hecks/fuzzing/form_census/{chapter_forms.rb → bluebook_forms.rb} +12 -12
- data/lib/hecks/fuzzing/form_census.rb +14 -12
- data/lib/hecks/fuzzing/generated_domain_check.rb +2 -1
- data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +5 -5
- data/lib/hecks/fuzzing/isolated_boot/scratch_schema.rb +19 -9
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +13 -8
- data/lib/hecks/fuzzing/isolated_boot.rb +4 -5
- data/lib/hecks/fuzzing/mutation/checks.rb +2 -1
- data/lib/hecks/fuzzing/mutation/finders.rb +25 -8
- data/lib/hecks/fuzzing/mutation/observation.rb +3 -1
- data/lib/hecks/fuzzing/mutation/source_map.rb +3 -1
- data/lib/hecks/fuzzing/nondeterministic.rb +10 -4
- data/lib/hecks/fuzzing/outside_world.rb +1 -1
- data/lib/hecks/fuzzing/projection_agreement/binding.rb +63 -0
- data/lib/hecks/fuzzing/projection_agreement/comparison.rb +104 -0
- data/lib/hecks/fuzzing/projection_agreement.rb +68 -0
- data/lib/hecks/fuzzing/properties/aggregation_recompute.rb +1 -1
- data/lib/hecks/fuzzing/properties/bulk_parity.rb +118 -0
- data/lib/hecks/fuzzing/properties/catalog.rb +7 -0
- data/lib/hecks/fuzzing/properties/cursor_walks.rb +56 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +39 -1
- data/lib/hecks/fuzzing/properties/group_by_recompute.rb +6 -2
- data/lib/hecks/fuzzing/properties/grouped_reduction_recompute.rb +88 -0
- data/lib/hecks/fuzzing/properties/guards.rb +4 -1
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +12 -2
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +2 -1
- data/lib/hecks/fuzzing/properties/mutation_recompute.rb +3 -2
- data/lib/hecks/fuzzing/properties/piece_invariants.rb +3 -2
- data/lib/hecks/fuzzing/properties/query_rows.rb +5 -3
- data/lib/hecks/fuzzing/properties/querying.rb +6 -3
- data/lib/hecks/fuzzing/properties/read_model_names.rb +8 -2
- data/lib/hecks/fuzzing/properties/read_models.rb +2 -1
- data/lib/hecks/fuzzing/properties/refusals.rb +35 -0
- data/lib/hecks/fuzzing/properties/role_checks.rb +3 -2
- data/lib/hecks/fuzzing/properties/set_transform_parity.rb +73 -0
- data/lib/hecks/fuzzing/properties/tenant_scope.rb +3 -2
- data/lib/hecks/fuzzing/properties.rb +6 -0
- data/lib/hecks/fuzzing/qa_settings.rb +4 -4
- data/lib/hecks/fuzzing/replay/fan_out.rb +4 -3
- data/lib/hecks/fuzzing/replay/filters.rb +2 -2
- data/lib/hecks/fuzzing/replay/guard_check.rb +2 -2
- data/lib/hecks/fuzzing/replay/mutation_trace.rb +23 -10
- data/lib/hecks/fuzzing/replay/observation.rb +11 -7
- data/lib/hecks/fuzzing/replay/read_only_steps.rb +40 -3
- data/lib/hecks/fuzzing/replay/refusal_trace.rb +44 -0
- data/lib/hecks/fuzzing/replay/role_check.rb +2 -2
- data/lib/hecks/fuzzing/replay/session.rb +38 -18
- data/lib/hecks/fuzzing/replay/validation_reports.rb +61 -0
- data/lib/hecks/fuzzing/replay.rb +2 -2
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +3 -3
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +18 -8
- data/lib/hecks/fuzzing/self_consistency/sagas.rb +2 -2
- data/lib/hecks/fuzzing/self_consistency/value_objects.rb +9 -2
- data/lib/hecks/fuzzing/self_consistency.rb +4 -8
- data/lib/hecks/fuzzing/sequence_generator/adversary/argument_mutations.rb +15 -5
- data/lib/hecks/fuzzing/sequence_generator/adversary/caller_draw.rb +14 -10
- data/lib/hecks/fuzzing/sequence_generator/adversary/precedence_mutations.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator/argument_drawing.rb +2 -1
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +21 -13
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +3 -3
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -2
- data/lib/hecks/fuzzing/sequence_generator.rb +1 -2
- data/lib/hecks/fuzzing/shrinker.rb +4 -1
- data/lib/hecks/fuzzing/value_generator.rb +14 -8
- data/lib/hecks/fuzzing.rb +1 -0
- data/lib/hecks/grammar/dialects.rb +56 -0
- data/lib/hecks/grammar/evolve/arguments.rb +1 -1
- data/lib/hecks/grammar/evolve/keywords.rb +1 -1
- data/lib/hecks/grammar/evolve/seeds.rb +34 -8
- data/lib/hecks/grammar/expression.bluebook +3 -0
- data/lib/hecks/grammar/expression_operators.json +1071 -1
- data/lib/hecks/grammar/operators.rb +45 -22
- data/lib/hecks/grammar/translation.bluebook +2 -0
- data/lib/hecks/grammar/translation_ledger.rb +45 -0
- data/lib/hecks/grammar/translation_rules.json +646 -0
- data/lib/hecks/grammar.rb +27 -23
- data/lib/hecks/hecks/adapters/claude_agents.adapter +7 -0
- data/lib/hecks/hecks/adapters/claude_agents.rb +119 -0
- data/lib/hecks/hecks/adapters/claude_transcript.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/cache_clearing.rb +32 -0
- data/lib/hecks/hecks/adapters/codebase/cargo_tests.rb +50 -0
- data/lib/hecks/hecks/adapters/codebase/composing.rb +3 -2
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +17 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/declarations.rb +144 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +5 -4
- data/lib/hecks/hecks/adapters/codebase/language.rb +1 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +7 -1
- data/lib/hecks/hecks/adapters/codebase/previewing.rb +62 -0
- data/lib/hecks/hecks/adapters/codebase/promotion/gate.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/reading.rb +94 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +6 -2
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +5 -2
- data/lib/hecks/hecks/adapters/codebase/search.rb +91 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +37 -2
- data/lib/hecks/hecks/adapters/codebase/static_directory.rb +59 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +2 -2
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +30 -11
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +41 -3
- data/lib/hecks/hecks/adapters/console_capture.rb +27 -0
- data/lib/hecks/hecks/adapters/deploy_plans.rb +12 -3
- data/lib/hecks/hecks/adapters/deploy_toolchain/answers.rb +14 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/scripts.rb +6 -3
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +3 -3
- data/lib/hecks/hecks/adapters/desktop_alert/alert.rb +50 -0
- data/lib/hecks/hecks/adapters/desktop_alert.adapter +7 -0
- data/lib/hecks/hecks/adapters/desktop_alert.rb +99 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +4 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +19 -12
- data/lib/hecks/hecks/adapters/git.rb +5 -5
- data/lib/hecks/hecks/adapters/git_checkout.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_checkout.rb +99 -0
- data/lib/hecks/hecks/adapters/git_landing/merger.rb +85 -0
- data/lib/hecks/hecks/adapters/git_landing.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_landing.rb +103 -0
- data/lib/hecks/hecks/adapters/git_launcher.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_launcher.rb +82 -0
- data/lib/hecks/hecks/adapters/git_refs.rb +8 -4
- data/lib/hecks/hecks/adapters/git_train/bench.rb +76 -0
- data/lib/hecks/hecks/adapters/git_train/carriage.rb +116 -0
- data/lib/hecks/hecks/adapters/git_train/local_branch.rb +69 -0
- data/lib/hecks/hecks/adapters/git_train/reading.rb +26 -0
- data/lib/hecks/hecks/adapters/git_train/survey.rb +60 -0
- data/lib/hecks/hecks/adapters/git_train.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_train.rb +88 -0
- data/lib/hecks/hecks/adapters/git_worktrees.rb +81 -0
- data/lib/hecks/hecks/adapters/host_http.rb +5 -4
- data/lib/hecks/hecks/adapters/in_process_access.rb +3 -2
- data/lib/hecks/hecks/adapters/in_process_boot.rb +62 -35
- data/lib/hecks/hecks/adapters/in_process_operations.rb +30 -4
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +7 -7
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +14 -7
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +9 -7
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +17 -5
- data/lib/hecks/hecks/adapters/journal_store/readings/attestation.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/readings/scaffolding.rb +7 -4
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +2 -2
- data/lib/hecks/hecks/adapters/journal_store.rb +2 -2
- data/lib/hecks/hecks/adapters/ledger/chain.rb +63 -0
- data/lib/hecks/hecks/adapters/ledger/journal.rb +105 -0
- data/lib/hecks/hecks/adapters/ledger.adapter +7 -0
- data/lib/hecks/hecks/adapters/ledger.rb +89 -0
- data/lib/hecks/hecks/adapters/local_files.rb +15 -1
- data/lib/hecks/hecks/adapters/pg_admin.rb +7 -1
- data/lib/hecks/hecks/adapters/rails_app_files.rb +64 -0
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +4 -1
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +9 -9
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +4 -4
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +3 -3
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +4 -1
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +1 -1
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +34 -5
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +5 -3
- data/lib/hecks/hecks/adapters/settings_reading.adapter +7 -0
- data/lib/hecks/hecks/adapters/settings_reading.rb +142 -0
- data/lib/hecks/hecks/adapters/shell.rb +3 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +8 -8
- data/lib/hecks/hecks/adapters/world_clearing.rb +2 -1
- data/lib/hecks/hecks/agents.bluebook +246 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +554 -586
- data/lib/hecks/hecks/composer.behaviors +10 -10
- data/lib/hecks/hecks/context_map.hecksagon +7 -6
- data/lib/hecks/hecks/custodian.behaviors +96 -1
- data/lib/hecks/hecks/custodian.bluebook +553 -157
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +3 -4
- data/lib/hecks/hecks/hecks.behaviors +2 -2
- data/lib/hecks/hecks/hecks.bluebook +9 -14
- data/lib/hecks/hecks/hecks.contextmap +25 -0
- data/lib/hecks/hecks/hecks.hecksagon +139 -6
- data/lib/hecks/hecks/hecks.world +22 -21
- data/lib/hecks/hecks/ledger.bluebook +190 -0
- data/lib/hecks/hecks/rehearsal.bluebook +43 -77
- data/lib/hecks/hecks/shapes.json +17 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +3 -3
- data/lib/hecks/hecks/tooling.bluebook +170 -174
- data/lib/hecks/hecks/translations/2-148748.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-15a2e4.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-1b447c.bluebook +5 -0
- data/lib/hecks/hecks/translations/2-240474.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-44446d.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-46ca26.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-473eab.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-75cc28.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-781c07.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-84c18b.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-aa5fff.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-bfdb48.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-cd96d8.bluebook +10 -0
- data/lib/hecks/hecks/translations/2-d43f71.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-eea577.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-f53248.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-fbb677.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-ffdf12.bluebook +6 -0
- data/lib/hecks/ir.rb +8 -1
- data/lib/hecks/kernel_capabilities/templates.rb +4 -3
- data/lib/hecks/kernel_capabilities.rb +6 -6
- data/lib/hecks/language/adapter.bluebook +61 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +36 -6
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +1 -1
- data/lib/hecks/language/bluebook/bluebook.behaviors +6 -6
- data/lib/hecks/language/bluebook/bluebook.bluebook +28 -51
- data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
- data/lib/hecks/language/bluebook/command.bluebook +34 -2
- data/lib/hecks/language/bluebook/entity.bluebook +2 -2
- data/lib/hecks/language/bluebook/policy.behaviors +2 -2
- data/lib/hecks/language/bluebook/policy.bluebook +3 -3
- data/lib/hecks/language/bluebook/process_manager.bluebook +3 -3
- data/lib/hecks/language/bluebook/projection.bluebook +11 -10
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +1 -1
- data/lib/hecks/language/bluebook/syntax.bluebook +3 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +287 -6
- data/lib/hecks/language/context_map/context_map.bluebook +90 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +2 -2
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +8 -8
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +1 -1
- data/lib/hecks/language/sentence/sentence.bluebook +210 -0
- data/lib/hecks/language/world/world.bluebook +6 -6
- data/lib/hecks/literal.rb +8 -2
- data/lib/hecks/naming.rb +44 -14
- data/lib/hecks/persistence_legacy_fixture/postgres_stores.rb +7 -6
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +6 -2
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/agent/answers.rb +2 -2
- data/lib/hecks/ports/agent.rb +2 -2
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +67 -0
- data/lib/hecks/ports/authorization.rb +3 -3
- data/lib/hecks/ports/dispatch.port +5 -0
- data/lib/hecks/ports/dispatch.rb +28 -0
- data/lib/hecks/ports/key_vault.rb +9 -6
- data/lib/hecks/ports/loading.rb +23 -7
- data/lib/hecks/ports/persistence/append_only.rb +18 -1
- data/lib/hecks/ports/persistence/binding_policy.rb +70 -11
- data/lib/hecks/ports/persistence/codec_boundary.rb +3 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +10 -10
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +4 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +11 -11
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +6 -6
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +32 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +0 -1
- data/lib/hecks/ports/persistence/remote_runtime.rb +3 -2
- data/lib/hecks/ports/persistence/set_statement.rb +29 -0
- data/lib/hecks/ports/persistence/set_transforming.rb +30 -0
- data/lib/hecks/ports/persistence/state_codec.rb +3 -3
- data/lib/hecks/ports/persistence.rb +0 -4
- data/lib/hecks/ports/projection.rb +4 -1
- data/lib/hecks/ports/query/cursor.rb +131 -0
- data/lib/hecks/ports/query/in_memory.rb +17 -10
- data/lib/hecks/ports/query.rb +4 -2
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projection_files/expression_tables.rb +3 -3
- data/lib/hecks/projection_files/sentence_table.rb +17 -0
- data/lib/hecks/projection_files.rb +16 -13
- data/lib/hecks/projections/bootstrap_table.rb +3 -3
- data/lib/hecks/projections/command_schema/shape.rb +95 -0
- data/lib/hecks/projections/command_schema.rb +74 -0
- data/lib/hecks/projections/context_map_diagram.rb +135 -0
- data/lib/hecks/projections/context_relationships.rb +125 -0
- data/lib/hecks/projections/deploy/box/access.rb +7 -4
- data/lib/hecks/projections/deploy/box/caddy.rb +2 -1
- data/lib/hecks/projections/deploy/box/compose.rb +4 -8
- data/lib/hecks/projections/deploy/box/edge.rb +2 -1
- data/lib/hecks/projections/deploy/box/hosting.rb +4 -2
- data/lib/hecks/projections/deploy/box/hosting_parts.rb +27 -12
- data/lib/hecks/projections/deploy/box/logging.rb +2 -3
- data/lib/hecks/projections/deploy/box/roll_recipe.rb +1 -3
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +1 -16
- data/lib/hecks/projections/deploy/box/settings/containers.rb +29 -15
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +5 -6
- data/lib/hecks/projections/deploy/box/settings/types.rb +5 -10
- data/lib/hecks/projections/deploy/box/settings.rb +9 -8
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +10 -14
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +0 -1
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -3
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +7 -6
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +2 -6
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +2 -6
- data/lib/hecks/projections/deploy/box.rb +16 -9
- data/lib/hecks/projections/deploy/fargate/assembly.rb +7 -2
- data/lib/hecks/projections/deploy/fargate/cdn/options.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/check.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/containers/rendering.rb +8 -5
- data/lib/hecks/projections/deploy/fargate/containers/routing.rb +2 -1
- data/lib/hecks/projections/deploy/fargate/monitoring/reading.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/sections.rb +7 -4
- data/lib/hecks/projections/deploy/fargate/settings/ids.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +13 -4
- data/lib/hecks/projections/deploy/fargate/stack/blocks.rb +6 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +2 -1
- data/lib/hecks/projections/deploy/fargate/stack.rb +11 -6
- data/lib/hecks/projections/deploy/fargate/yaml.rb +10 -3
- data/lib/hecks/projections/deploy/fargate.rb +8 -3
- data/lib/hecks/projections/deploy/lambda/stack/blocks.rb +4 -2
- data/lib/hecks/projections/deploy/lambda/stack/deploy_chain.rb +2 -1
- data/lib/hecks/projections/deploy/lambda/stack/reading.rb +12 -9
- data/lib/hecks/projections/deploy/lambda/stack/reading_web.rb +7 -2
- data/lib/hecks/projections/deploy/lambda/stack/web.rb +2 -1
- data/lib/hecks/projections/deploy/lambda.rb +8 -3
- data/lib/hecks/projections/deploy/preview/containers.rb +14 -1
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +2 -2
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +8 -6
- data/lib/hecks/projections/deploy/preview/settings.rb +23 -5
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +1 -1
- data/lib/hecks/projections/deploy/preview/yaml_text.rb +2 -1
- data/lib/hecks/projections/deploy/scripts/settings.rb +5 -2
- data/lib/hecks/projections/deploy/shared.rb +12 -1
- data/lib/hecks/projections/deploy/shared_database_instance.rb +1 -1
- data/lib/hecks/projections/deploy/smoke.rb +3 -1
- data/lib/hecks/projections/deploy/template_diff/normalizer.rb +4 -1
- data/lib/hecks/projections/deploy/templates/lambda/mint_era_shared.tmpl +2 -2
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/deploy/vercel.rb +1 -1
- data/lib/hecks/projections/diagrams/dispatch.rb +5 -3
- data/lib/hecks/projections/diagrams/statecharts.rb +8 -3
- data/lib/hecks/projections/diagrams/structure.rb +9 -6
- data/lib/hecks/projections/diagrams.rb +9 -7
- data/lib/hecks/projections/glossary/entries.rb +4 -4
- data/lib/hecks/projections/glossary/html/page.rb +4 -1
- data/lib/hecks/projections/glossary/html/parser.rb +13 -4
- data/lib/hecks/projections/glossary/html/sections.rb +4 -1
- data/lib/hecks/projections/glossary/html.rb +10 -3
- data/lib/hecks/projections/glossary/markdown.rb +4 -3
- data/lib/hecks/projections/glossary/mermaid.rb +8 -5
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sensitivity.rb +11 -4
- data/lib/hecks/projections/glossary/sentences/actions.rb +5 -2
- data/lib/hecks/projections/glossary/sentences/queries.rb +2 -1
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +15 -6
- data/lib/hecks/projections/glossary/sentences.rb +6 -3
- data/lib/hecks/projections/glossary/slugs.rb +8 -1
- data/lib/hecks/projections/glossary.rb +3 -3
- data/lib/hecks/projections/model.rb +10 -6
- data/lib/hecks/projections/oidc.rb +19 -7
- data/lib/hecks/projections/parser_table.rb +10 -10
- data/lib/hecks/projections/phrase_table.rb +105 -0
- data/lib/hecks/projections/plain_english.rb +14 -4
- data/lib/hecks/projections/rails_app.rb +77 -0
- data/lib/hecks/projections/reference.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +1 -1
- data/lib/hecks/projections/rust_vocabulary/tables.rb +20 -0
- data/lib/hecks/projections/rust_vocabulary.rb +14 -17
- data/lib/hecks/projections/site/admin.rb +7 -7
- data/lib/hecks/projections/site/admin_cms.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/blocks.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/checks.rb +9 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/composition.rb +20 -18
- data/lib/hecks/projections/site/cms_editor/destructive.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/drafts.rb +6 -2
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/limits.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +3 -1
- data/lib/hecks/projections/site/cms_editor/pickers.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/preview.rb +10 -4
- data/lib/hecks/projections/site/cms_editor/relations.rb +3 -2
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +32 -25
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/schema.rb +31 -29
- data/lib/hecks/projections/site/cms_editor/setting.rb +34 -27
- data/lib/hecks/projections/site/cms_editor/targets.rb +9 -6
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +3 -3
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +2 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +8 -3
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +4 -4
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +14 -14
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +12 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +12 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +1 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +2 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +11 -11
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +3 -3
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +8 -2
- data/lib/hecks/projections/site/cms_editor.rb +19 -16
- data/lib/hecks/projections/site/edge/checks.rb +1 -1
- data/lib/hecks/projections/site/edge/function_checks.rb +1 -1
- data/lib/hecks/projections/site/edge/function_code.rb +4 -1
- data/lib/hecks/projections/site/edge/pattern.rb +4 -1
- data/lib/hecks/projections/site/edge/route_checks.rb +2 -2
- data/lib/hecks/projections/site/edge/rules.rb +1 -1
- data/lib/hecks/projections/site/edge.rb +14 -8
- data/lib/hecks/projections/site/live_edge.rb +2 -2
- data/lib/hecks/projections/site/payload/fields/definitions.rb +1 -1
- data/lib/hecks/projections/site/payload/lifecycle/text.rb +1 -1
- data/lib/hecks/projections/site/payload/model.rb +16 -12
- data/lib/hecks/projections/site/payload/specs.rb +4 -2
- data/lib/hecks/projections/site/payload_driver.rb +9 -8
- data/lib/hecks/projections/site/probe.rb +12 -3
- data/lib/hecks/projections/site/regions.rb +9 -1
- data/lib/hecks/projections/site/role_probe.rb +7 -7
- data/lib/hecks/projections/site/root_rows.rb +4 -4
- data/lib/hecks/projections/site/routes_ts/listings.rb +13 -6
- data/lib/hecks/projections/site/routes_ts.rb +6 -3
- data/lib/hecks/projections/site/site_admin.rb +1 -1
- data/lib/hecks/projections/site/site_cdn.rb +3 -2
- data/lib/hecks/projections/site/site_host.rb +1 -1
- data/lib/hecks/projections/site/site_root.rb +4 -4
- data/lib/hecks/projections/site/table/checks.rb +4 -1
- data/lib/hecks/projections/site/table/navigation_checks.rb +2 -2
- data/lib/hecks/projections/site/table/row_builder.rb +7 -7
- data/lib/hecks/projections/site/table/row_defaults.rb +1 -1
- data/lib/hecks/projections/site/table.rb +32 -26
- data/lib/hecks/projections/statements/actions.rb +4 -1
- data/lib/hecks/projections/statements/relationships.rb +15 -2
- data/lib/hecks/projections/statements.rb +2 -2
- data/lib/hecks/projections/vocabulary.rb +6 -6
- data/lib/hecks/projections/world_skeleton.rb +68 -0
- data/lib/hecks/projections.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +12 -9
- data/lib/hecks/projector/cli_projector/claims.rb +8 -8
- data/lib/hecks/projector/cli_projector/command_specs.rb +9 -5
- data/lib/hecks/projector/cli_projector/listing.rb +20 -8
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/query_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/usage.rb +12 -8
- data/lib/hecks/projector/cli_projector.rb +2 -2
- data/lib/hecks/projector/docs_projector/behaviour.rb +5 -4
- data/lib/hecks/projector/docs_projector/closing.rb +3 -3
- data/lib/hecks/projector/docs_projector.rb +12 -7
- data/lib/hecks/projector/exporter/adapters.rb +15 -2
- data/lib/hecks/projector/exporter/bindings.rb +39 -134
- data/lib/hecks/projector/exporter/plugins.rb +55 -0
- data/lib/hecks/projector/exporter.rb +2 -0
- data/lib/hecks/projector/narrate_projector/commands.rb +4 -3
- data/lib/hecks/projector/narrate_projector/reactions.rb +2 -2
- data/lib/hecks/projector/narrate_projector.rb +9 -8
- data/lib/hecks/projector/target.rb +8 -8
- data/lib/hecks/projector.rb +9 -9
- data/lib/hecks/quality_control/adapters/agent.rb +2 -1
- data/lib/hecks/quality_control/adapters/github_checks.adapter +1 -1
- data/lib/hecks/quality_control/adapters/github_issues.adapter +1 -1
- data/lib/hecks/quality_control/adapters/github_issues.rb +6 -2
- data/lib/hecks/quality_control/adapters/target_tools.rb +9 -3
- data/lib/hecks/quality_control/cli/child.rb +1 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty/verdict.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_generated_domains/evaluation.rb +12 -2
- data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_log_bug/proof.rb +16 -3
- data/lib/hecks/quality_control/cli/qa_mine_combinations/booting.rb +4 -2
- data/lib/hecks/quality_control/cli/qa_mutate.rb +161 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode/report.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +2 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_tick/output.rb +28 -26
- data/lib/hecks/quality_control/cli/qa_tick/preflight.rb +12 -3
- data/lib/hecks/quality_control/cli/qa_tick.rb +24 -8
- data/lib/hecks/quality_control/quality_control.bluebook +93 -92
- data/lib/hecks/quality_control/quality_control.hecksagon +3 -3
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +3 -2
- data/lib/hecks/query_ir/rules.rb +9 -9
- data/lib/hecks/query_ir/touchpoints.rb +5 -2
- data/lib/hecks/query_ir.rb +3 -3
- data/lib/hecks/query_specification/common/comparison.rb +1 -1
- data/lib/hecks/query_specification/common/dsl.rb +3 -4
- data/lib/hecks/query_specification/hop_path.rb +7 -4
- data/lib/hecks/rails_app/bluebook/rails_app.bluebook +60 -0
- data/lib/hecks/rails_app/bluebook/rails_app.hecksagon +4 -0
- data/lib/hecks/refused.rb +104 -0
- data/lib/hecks/release/approval.rb +9 -2
- data/lib/hecks/release/gem_pin.rb +7 -3
- data/lib/hecks/release/lane.rb +1 -1
- data/lib/hecks/release/runner/clean_tree.rb +4 -1
- data/lib/hecks/release/runner/console.rb +2 -1
- data/lib/hecks/router/aggregate_reader.rb +3 -4
- data/lib/hecks/router/namespace_installer.rb +31 -17
- data/lib/hecks/router.rb +2 -10
- data/lib/hecks/runtime/adapter_lookup.rb +30 -7
- data/lib/hecks/runtime/adapter_namespace.rb +21 -0
- data/lib/hecks/runtime/asking.rb +30 -0
- data/lib/hecks/runtime/caller.rb +22 -0
- data/lib/hecks/runtime/capability_graph.rb +2 -1
- data/lib/hecks/runtime/collecting.rb +86 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +13 -8
- data/lib/hecks/runtime/command_interpreter/delegation.rb +46 -17
- data/lib/hecks/runtime/command_interpreter/hydration.rb +17 -21
- data/lib/hecks/runtime/command_interpreter/list_mutations.rb +2 -1
- data/lib/hecks/runtime/command_interpreter/persisting.rb +13 -12
- data/lib/hecks/runtime/command_interpreter.rb +17 -10
- data/lib/hecks/runtime/command_rules/admissibility.rb +17 -10
- data/lib/hecks/runtime/command_rules/authorization.rb +8 -6
- data/lib/hecks/runtime/command_rules/guard_state.rb +10 -10
- data/lib/hecks/runtime/command_rules/invariants.rb +3 -1
- data/lib/hecks/runtime/command_rules/missing_reference.rb +25 -0
- data/lib/hecks/runtime/command_rules/one_of_references.rb +44 -0
- data/lib/hecks/runtime/command_rules/references.rb +14 -14
- data/lib/hecks/runtime/command_rules.rb +2 -0
- data/lib/hecks/runtime/declarations.rb +66 -0
- data/lib/hecks/runtime/dependency_planning/analyzer.rb +2 -1
- data/lib/hecks/runtime/dependency_planning/expression_reads.rb +3 -2
- data/lib/hecks/runtime/dependency_planning/mutation_analysis.rb +2 -1
- data/lib/hecks/runtime/dispatch_claim.rb +72 -0
- data/lib/hecks/runtime/dispatch_context.rb +41 -0
- data/lib/hecks/runtime/dispatcher/queries.rb +17 -0
- data/lib/hecks/runtime/dispatcher/routing.rb +34 -12
- data/lib/hecks/runtime/dispatcher/validating.rb +73 -0
- data/lib/hecks/runtime/dispatcher.rb +39 -23
- data/lib/hecks/runtime/entity_element/location.rb +15 -7
- data/lib/hecks/runtime/entity_element/mutations.rb +4 -1
- data/lib/hecks/runtime/entity_interpreter/enforcement.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter/locating.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter/saving.rb +5 -5
- data/lib/hecks/runtime/entity_interpreter.rb +11 -4
- data/lib/hecks/runtime/errors.rb +30 -17
- data/lib/hecks/runtime/identity.rb +9 -0
- data/lib/hecks/runtime/instance.rb +3 -2
- data/lib/hecks/runtime/interpreting.rb +63 -6
- data/lib/hecks/runtime/invocation_builder.rb +7 -4
- data/lib/hecks/runtime/invocation_facts.rb +13 -8
- data/lib/hecks/runtime/loader/boot_steps.rb +3 -2
- data/lib/hecks/runtime/loader/described.rb +4 -0
- data/lib/hecks/runtime/loader.rb +52 -62
- data/lib/hecks/runtime/outbox/redrive.rb +105 -0
- data/lib/hecks/runtime/outbox/relay.rb +11 -39
- data/lib/hecks/runtime/outbox/runnable.rb +60 -0
- data/lib/hecks/runtime/outbox.rb +15 -1
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -1
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +8 -35
- data/lib/hecks/runtime/policy_interpreter.rb +8 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +27 -13
- data/lib/hecks/runtime/query_interpreter/outside_answer.rb +1 -1
- data/lib/hecks/runtime/query_interpreter/paging.rb +42 -0
- data/lib/hecks/runtime/query_interpreter/plugin_answer.rb +54 -0
- data/lib/hecks/runtime/query_interpreter/store_specification.rb +32 -0
- data/lib/hecks/runtime/query_interpreter.rb +16 -9
- data/lib/hecks/runtime/reaction_invocation/target_resolution.rb +10 -4
- data/lib/hecks/runtime/reaction_invocation.rb +5 -2
- data/lib/hecks/runtime/read_model_interpreter/head_ordering.rb +1 -1
- data/lib/hecks/runtime/read_model_interpreter/reductions.rb +23 -15
- data/lib/hecks/runtime/read_model_interpreter/shaping.rb +18 -7
- data/lib/hecks/runtime/read_model_interpreter.rb +5 -2
- data/lib/hecks/runtime/reference_hop.rb +3 -2
- data/lib/hecks/runtime/refusal_wording.rb +15 -6
- data/lib/hecks/runtime/registry/adapter_capabilities.rb +43 -0
- data/lib/hecks/runtime/registry/ask_verification.rb +15 -4
- data/lib/hecks/runtime/registry/{chapter_verification.rb → bluebook_verification.rb} +23 -23
- data/lib/hecks/runtime/registry/context_maps.rb +30 -0
- data/lib/hecks/runtime/registry/declarations.rb +8 -6
- data/lib/hecks/runtime/registry/durability_warnings.rb +18 -2
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +27 -24
- data/lib/hecks/runtime/registry/plugin_query_verification.rb +45 -0
- data/lib/hecks/runtime/registry/providers.rb +34 -99
- data/lib/hecks/runtime/registry/query_verification.rb +30 -21
- data/lib/hecks/runtime/registry/runtime_state.rb +4 -3
- data/lib/hecks/runtime/registry/saga_persistence.rb +7 -16
- data/lib/hecks/runtime/registry/verification.rb +9 -5
- data/lib/hecks/runtime/registry/world_defaults.rb +8 -5
- data/lib/hecks/runtime/registry.rb +52 -27
- data/lib/hecks/runtime/row_sweep.rb +104 -0
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +5 -4
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +13 -10
- data/lib/hecks/runtime/saga_interpreter/redrive.rb +141 -0
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +35 -6
- data/lib/hecks/runtime/saga_interpreter.rb +36 -4
- data/lib/hecks/runtime/saga_pending_dispatch.rb +4 -2
- data/lib/hecks/runtime/scalar_field.rb +73 -0
- data/lib/hecks/runtime/tenant_scope.rb +2 -1
- data/lib/hecks/runtime/validation.rb +24 -0
- data/lib/hecks/runtime/value/admission.rb +11 -11
- data/lib/hecks/runtime/value/coercion.rb +7 -7
- data/lib/hecks/runtime/value/field_checks.rb +13 -13
- data/lib/hecks/runtime/value/invariant_violation.rb +4 -1
- data/lib/hecks/runtime/value/references.rb +7 -4
- data/lib/hecks/runtime/value/unwrapping.rb +60 -0
- data/lib/hecks/runtime/value/validation.rb +7 -4
- data/lib/hecks/runtime/value.rb +3 -27
- data/lib/hecks/runtime/wasm_plugin.rb +98 -0
- data/lib/hecks/runtime.rb +14 -14
- data/lib/hecks/rust_build/command_schemas.rb +27 -0
- data/lib/hecks/rust_build/conformance/surface.rb +50 -7
- data/lib/hecks/rust_build/conformance.rb +25 -3
- data/lib/hecks/rust_build/conformance_fuzz.rb +2 -2
- data/lib/hecks/rust_build/domain_name.rb +4 -1
- data/lib/hecks/rust_build/host.rb +15 -9
- data/lib/hecks/rust_build/native_build.rb +2 -1
- data/lib/hecks/rust_build/prebuilt_binary.rb +25 -5
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +11 -10
- data/lib/hecks/rust_build/project_rust/domain_loader.rb +15 -2
- data/lib/hecks/rust_build/project_rust/root_mod/notes.rb +1 -1
- data/lib/hecks/rust_build/project_rust/target_ir.rb +12 -3
- data/lib/hecks/rust_build/project_rust.rb +39 -19
- data/lib/hecks/rust_build/target_dir.rb +40 -0
- data/lib/hecks/rust_build/wasm.rb +46 -17
- data/lib/hecks/rust_build/wasm_browser.rb +8 -2
- data/lib/hecks/rust_build.rb +29 -2
- data/lib/hecks/sentence_form.rb +212 -0
- data/lib/hecks/served_options.rb +2 -1
- data/lib/hecks/shape_ledger.rb +149 -0
- data/lib/hecks/site/bluebook/site.bluebook +39 -62
- data/lib/hecks/site/bluebook/site.ports.hecksagon +1 -1
- data/lib/hecks/sme/bluebook/sme.bluebook +7 -1
- data/lib/hecks/storehouse/audit.rb +4 -1
- data/lib/hecks/storehouse/bluebook_words.rb +35 -0
- data/lib/hecks/storehouse/dispatching.rb +6 -3
- data/lib/hecks/storehouse/introspection.rb +4 -4
- data/lib/hecks/storehouse/querying.rb +2 -2
- data/lib/hecks/storehouse.rb +14 -4
- data/lib/hecks/switches.rb +21 -0
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +5 -9
- data/lib/hecks/text.rb +57 -0
- data/lib/hecks/tickets/bluebook/tickets.bluebook +30 -51
- data/lib/hecks/tickets/bluebook/tickets.hecksagon +1 -1
- data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +1 -1
- data/lib/hecks/tools/argument_gate_matrix/dispatching.rb +1 -1
- data/lib/hecks/tools/argument_gate_matrix/rows.rb +7 -7
- data/lib/hecks/tools/argument_gate_matrix/violations.rb +1 -1
- data/lib/hecks/tools/argument_gate_matrix.rb +3 -3
- data/lib/hecks/tools/ci_gate_decision.rb +5 -2
- data/lib/hecks/tools/ci_gates.rb +1 -1
- data/lib/hecks/tools/comment_style/caps_words.rb +11 -8
- data/lib/hecks/tools/comment_style/dictionary.rb +7 -2
- data/lib/hecks/tools/comment_style/fixing.rb +10 -5
- data/lib/hecks/tools/comment_style/long_blocks.rb +1 -1
- data/lib/hecks/tools/comment_style/masking.rb +5 -5
- data/lib/hecks/tools/comment_style/run.rb +4 -1
- data/lib/hecks/tools/comment_style/source_file.rb +10 -3
- data/lib/hecks/tools/comment_style/structure_checks.rb +13 -4
- data/lib/hecks/tools/comment_style/text_checks.rb +1 -1
- data/lib/hecks/tools/comment_style.rb +25 -2
- data/lib/hecks/tools/deploy_recipe/generation.rb +1 -1
- data/lib/hecks/tools/deploy_recipe/targets.rb +1 -1
- data/lib/hecks/tools/deploy_recipe/worlds.rb +15 -9
- data/lib/hecks/tools/deploy_recipe.rb +17 -10
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +1 -1
- data/lib/hecks/tools/deploy_recipe_lint/makefile.rb +7 -1
- data/lib/hecks/tools/drop_implicit_append_fields.rb +4 -1
- data/lib/hecks/tools/era_projection_backfill.rb +11 -6
- data/lib/hecks/tools/era_reattest.rb +11 -6
- data/lib/hecks/tools/fuzz_sweep/findings.rb +18 -2
- data/lib/hecks/tools/fuzz_sweep/options.rb +5 -1
- data/lib/hecks/tools/fuzz_sweep.rb +17 -5
- data/lib/hecks/tools/gate.rb +27 -5
- data/lib/hecks/tools/hoist_local_givens/rewriting.rb +3 -2
- data/lib/hecks/tools/hoist_local_givens.rb +2 -4
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +1 -1
- data/lib/hecks/tools/lanes/rows.rb +9 -3
- data/lib/hecks/tools/lanes/ruleset.rb +1 -1
- data/lib/hecks/tools/lanes/watch_workflow.rb +1 -1
- data/lib/hecks/tools/lanes/workflow.rb +1 -1
- data/lib/hecks/tools/lanes.rb +1 -1
- data/lib/hecks/tools/oidc_manifests.rb +16 -19
- data/lib/hecks/tools/regeneration_run.rb +22 -17
- data/lib/hecks/tools/release_only.rb +5 -2
- data/lib/hecks/tools/rust_comment_style/caps_words.rb +5 -5
- data/lib/hecks/tools/rust_comment_style/fixing.rb +1 -1
- data/lib/hecks/tools/rust_comment_style/masking.rb +7 -4
- data/lib/hecks/tools/rust_comment_style/source_file.rb +7 -2
- data/lib/hecks/tools/rust_comment_style/text_checks.rb +1 -1
- data/lib/hecks/tools/rust_comment_style/tokenizer.rb +4 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +16 -13
- data/lib/hecks/tools/site_routes/root_files.rb +14 -13
- data/lib/hecks/tools/site_routes/templates.rb +5 -4
- data/lib/hecks/tools/site_routes.rb +18 -15
- data/lib/hecks/tools/tail_merge.rb +3 -3
- data/lib/hecks/tools/tenant_provisioning.rb +6 -6
- data/lib/hecks/tools/tools_doc.rb +16 -16
- data/lib/hecks/tools/translation_audit/domain_loading.rb +3 -3
- data/lib/hecks/tools/translation_audit.rb +15 -4
- data/lib/hecks/tools/translation_scaffold/reporting.rb +4 -1
- data/lib/hecks/tools/translation_scaffold.rb +4 -14
- data/lib/hecks/vendoring/git_source.rb +7 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +110 -3
- data/lib/hecks.rb +37 -18
- data/lib/rubocop/cop/hecks/inline_regexp.rb +29 -0
- data/lib/rubocop/cop/hecks/no_transport_in_runtime.rb +78 -0
- data/lib/rubocop/cop/hecks/rails_model_rules.rb +46 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
- data/lib/rubocop/cop/hecks/train_wreck.rb +69 -0
- data/lib/rubocop/cop/hecks/unnamed_pipeline.rb +51 -0
- data/prebuilt/31cf81e12f16236ad26249be590cfea3799c9db67432e6a4ef54b801c355b6ae.marshal +0 -0
- data/prebuilt/68ecb2bd3abb1a221007db4f2b0375834c13aaccac8f2b99778e8517f11aa113.marshal +0 -0
- data/prebuilt/verdicts-0328c2403383837d780ad5ed81d1da351c56436b894cf9015b5d56b8f1f1bf10.json +1 -0
- data/qa/settings.yml +9 -0
- data/rust/Cargo.lock +8 -1
- data/rust/Cargo.toml +6 -7
- data/rust/build/src/binding_pass.rs +13 -17
- data/rust/build/src/cargo_sync.rs +1 -1
- data/rust/build/src/lineage_pass.rs +4 -71
- data/rust/build/src/pipeline.rs +99 -70
- data/rust/build/src/resolve.rs +85 -29
- data/rust/build/src/seams_pass.rs +117 -99
- data/rust/build/src/world_pass.rs +21 -12
- data/rust/codegen/src/domain_generator.rs +30 -2
- data/rust/codegen/src/expr_emitter.rs +6 -0
- data/rust/codegen/src/json_codec.rs +48 -10
- data/rust/codegen/src/main.rs +41 -34
- data/rust/codegen/src/manifest.rs +1 -1
- data/rust/codegen/src/naming.rs +5 -5
- data/rust/codegen/src/one_of_references.rs +124 -0
- data/rust/codegen/src/presenters.rs +335 -0
- data/rust/codegen/src/queries.rs +56 -7
- data/rust/codegen/src/reactions.rs +19 -14
- data/rust/codegen/src/read_models.rs +20 -71
- data/rust/codegen/src/registry.rs +89 -15
- data/rust/codegen/src/sidecars.rs +1 -1
- data/rust/expr/Cargo.lock +54 -0
- data/rust/expr/Cargo.toml +52 -0
- data/rust/{src/kernel → expr/src}/attribute_shapes/composite.rs +45 -14
- data/rust/{src/kernel → expr/src}/attribute_shapes/list.rs +1 -1
- data/rust/{src/kernel → expr/src}/attribute_shapes/mod.rs +2 -2
- data/rust/{src/kernel → expr/src}/attribute_shapes/optional.rs +1 -1
- data/rust/{src/kernel → expr/src}/attribute_shapes/scalar.rs +3 -3
- data/rust/{src/kernel → expr/src}/expr.rs +30 -8
- data/rust/{src/kernel → expr/src}/expression_operators/arithmetic.rs +41 -18
- data/rust/{src/kernel → expr/src}/expression_operators/comparison.rs +54 -8
- data/rust/{src/kernel → expr/src}/expression_operators/enumeration.rs +73 -6
- data/rust/{src/kernel → expr/src}/expression_operators/logical.rs +2 -2
- data/rust/expr/src/expression_operators/membership.rs +33 -0
- data/rust/{src/kernel → expr/src}/expression_operators/mod.rs +2 -2
- data/rust/expr/src/expression_operators/pattern_match.rs +179 -0
- data/rust/{src/kernel → expr/src}/expression_operators/positional.rs +8 -6
- data/rust/{src/kernel → expr/src}/expression_operators/presence.rs +7 -6
- data/rust/{src/kernel → expr/src}/expression_operators/sign_test.rs +14 -4
- data/rust/{src/kernel → expr/src}/expression_operators/sized.rs +16 -9
- data/rust/{src/kernel → expr/src}/expression_operators/text.rs +30 -17
- data/rust/{src/kernel → expr/src}/expression_operators/to_string.rs +8 -6
- data/rust/expr/src/lib.rs +17 -0
- data/rust/expr/src/refusal.rs +62 -0
- data/rust/expr/src/rendering.rs +135 -0
- data/rust/{src/kernel → expr/src}/ruby_float.rs +7 -6
- data/rust/host/Cargo.lock +137 -123
- data/rust/host/Cargo.toml +7 -3
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/core/Cargo.toml +64 -0
- data/rust/host/core/clippy.toml +4 -0
- data/rust/host/core/src/arg_limits.rs +48 -0
- data/rust/host/core/src/clock.rs +146 -0
- data/rust/host/core/src/dispatch.rs +920 -0
- data/rust/host/core/src/expr_json.rs +1137 -0
- data/rust/host/{src → core/src}/host.rs +13 -14
- data/rust/host/{src → core/src}/ir.rs +227 -18
- data/rust/host/core/src/journal.rs +126 -0
- data/rust/host/{src → core/src}/lambda_client.rs +27 -96
- data/rust/host/core/src/lib.rs +37 -0
- data/rust/host/{src → core/src}/needs.rs +66 -20
- data/rust/host/core/src/plugins.rs +269 -0
- data/rust/host/core/src/presentation.rs +471 -0
- data/rust/host/{src → core/src}/presentation_write.rs +75 -43
- data/rust/host/{src → core/src}/query_step.rs +54 -16
- data/rust/host/{src → core/src}/reference_validate.rs +6 -6
- data/rust/host/{src → core/src}/storage_shape.rs +2 -2
- data/rust/host/core/src/store/memory.rs +635 -0
- data/rust/host/core/src/store.rs +418 -0
- data/rust/host/{src → core/src}/ui_schema.rs +8 -8
- data/rust/host/core/src/validation.rs +129 -0
- data/rust/host/core/src/wasm_runner.rs +410 -0
- data/rust/host/fuzz/.gitignore +4 -0
- data/rust/host/fuzz/Cargo.lock +4479 -0
- data/rust/host/fuzz/Cargo.toml +42 -0
- data/rust/host/fuzz/README.md +55 -0
- data/rust/host/fuzz/fuzz_targets/postgres_equivalence.rs +24 -0
- data/rust/host/fuzz/src/backend.rs +273 -0
- data/rust/host/fuzz/src/equivalence.rs +195 -0
- data/rust/host/fuzz/src/footprint.rs +182 -0
- data/rust/host/fuzz/src/lib.rs +17 -0
- data/rust/host/fuzz/src/ops.rs +345 -0
- data/rust/host/fuzz/tests/smoke.rs +50 -0
- data/rust/host/src/api.rs +316 -105
- data/rust/host/src/auth/google_signin.rs +183 -0
- data/rust/host/src/auth.rs +527 -370
- data/rust/host/src/aws_lambda_invoker.rs +61 -0
- data/rust/host/src/bin/expr_harness.rs +9 -4
- data/rust/host/src/bin/ledger.rs +84 -0
- data/rust/host/src/bin/lineage_harness.rs +17 -21
- data/rust/host/src/bin/mint_harness.rs +11 -36
- data/rust/host/src/bin/validate_harness.rs +1 -6
- data/rust/host/src/boot.rs +465 -174
- data/rust/host/src/boundary_fuzz/ir.rs +10 -5
- data/rust/host/src/boundary_fuzz/server.rs +6 -5
- data/rust/host/src/checkout.rs +4 -1
- data/rust/host/src/commerce_ir.rs +126 -105
- data/rust/host/src/{dispatch.rs → dispatch_tests.rs} +391 -824
- data/rust/host/src/extension.rs +3 -4
- data/rust/host/src/hecksagon_boundary_tests.rs +149 -0
- data/rust/host/src/ledger.rs +683 -0
- data/rust/host/src/lib.rs +21 -28
- data/rust/host/src/payments/tests.rs +136 -38
- data/rust/host/src/payments.rs +36 -32
- data/rust/host/src/platform_signin/extension.rs +1 -1
- data/rust/host/src/platform_signin/mapping_tests.rs +12 -9
- data/rust/host/src/platform_signin/session.rs +7 -14
- data/rust/host/src/platform_signin/verify.rs +0 -2
- data/rust/host/src/presentation_tests.rs +382 -0
- data/rust/host/src/rate_limit.rs +18 -2
- data/rust/host/src/server.rs +287 -36
- data/rust/host/src/{journal.rs → store/journal_sql.rs} +21 -127
- data/rust/host/src/{mint.rs → store/mint.rs} +12 -12
- data/rust/host/src/store/postgres.rs +493 -0
- data/rust/host/src/store.rs +97 -0
- data/rust/host/src/store_conformance.rs +1265 -0
- data/rust/host/src/web/commerce.rs +10 -18
- data/rust/host/src/web/newsletter/cooldown.rs +107 -22
- data/rust/host/src/web/newsletter/naming.rs +104 -0
- data/rust/host/src/web/newsletter/tests.rs +53 -0
- data/rust/host/src/web/newsletter.rs +73 -50
- data/rust/host/src/web/newsletter_send.rs +12 -13
- data/rust/host/src/web/registration_receipt.rs +2 -2
- data/rust/host/src/web/registrations.rs +754 -375
- data/rust/host/src/web/resend_webhook.rs +3 -3
- data/rust/host/src/web/signin_tests.rs +131 -0
- data/rust/host/src/web.rs +1479 -149
- data/rust/lsp/README.md +27 -27
- data/rust/lsp/src/diagnostics.rs +75 -15
- data/rust/lsp/src/main.rs +101 -5
- data/rust/parser/fuzz/Cargo.toml +2 -2
- data/rust/parser/fuzz/README.md +3 -3
- data/rust/parser/fuzz/fuzz_targets/{chapter.rs → bluebook.rs} +4 -4
- data/rust/parser/fuzz/fuzz_targets/resolve.rs +2 -2
- data/rust/parser/src/build/named_pattern.rs +22 -0
- data/rust/parser/src/build/references.rs +52 -0
- data/rust/parser/src/diag.rs +22 -1
- data/rust/parser/src/emit.rs +68 -4
- data/rust/parser/src/expr/ast_json.rs +10 -0
- data/rust/parser/src/expr/resolver.rs +49 -9
- data/rust/parser/src/ir.rs +34 -7
- data/rust/parser/src/keywords.rs +454 -457
- data/rust/parser/src/lex.rs +115 -87
- data/rust/parser/src/main.rs +180 -32
- data/rust/parser/src/parse/adapter.rs +28 -0
- data/rust/parser/src/parse/aggregate.rs +55 -35
- data/rust/parser/src/parse/command.rs +100 -15
- data/rust/parser/src/parse/{chapter.rs → declaration.rs} +85 -54
- data/rust/parser/src/parse/domain_port.rs +60 -9
- data/rust/parser/src/parse/entity.rs +35 -32
- data/rust/parser/src/parse/file.rs +1 -1
- data/rust/parser/src/parse/hecksagon.rs +15 -5
- data/rust/parser/src/parse/mod.rs +84 -2
- data/rust/parser/src/parse/process_manager.rs +1 -1
- data/rust/parser/src/parse/query.rs +13 -3
- data/rust/parser/src/parse/read_model.rs +2 -1
- data/rust/parser/src/phrases.rs +1258 -0
- data/rust/parser/src/render_ruby.rs +540 -0
- data/rust/parser/src/sentence/changes.rs +437 -0
- data/rust/parser/src/sentence/command_step.rs +206 -0
- data/rust/parser/src/sentence/complete.rs +276 -0
- data/rust/parser/src/sentence/entity.rs +41 -0
- data/rust/parser/src/sentence/examples.rs +1012 -0
- data/rust/parser/src/sentence/format.rs +882 -0
- data/rust/parser/src/sentence/lower.rs +1366 -0
- data/rust/parser/src/sentence/mod.rs +444 -0
- data/rust/parser/src/sentence/names.rs +69 -0
- data/rust/parser/src/sentence/options.rs +154 -0
- data/rust/parser/src/sentence/pattern.rs +433 -0
- data/rust/parser/src/sentence/process.rs +196 -0
- data/rust/parser/src/sentence/queries.rs +431 -0
- data/rust/parser/src/sentence/report.rs +283 -0
- data/rust/parser/src/sentence/token.rs +106 -0
- data/rust/parser/src/sentence/values.rs +498 -0
- data/rust/src/exemplar/queries.rs +1 -0
- data/rust/src/exemplar/reactions.rs +1 -1
- data/rust/src/exemplar/registry.rs +30 -0
- data/rust/src/kernel/cli.rs +233 -20
- data/rust/src/kernel/collecting.rs +174 -0
- data/rust/src/kernel/dispatch.rs +55 -27
- data/rust/src/kernel/json.rs +15 -1
- data/rust/src/kernel/mod.rs +8 -67
- data/rust/src/kernel/named_query.rs +225 -11
- data/rust/src/kernel/orchestrate.rs +213 -25
- data/rust/src/kernel/orchestrate_guard_tests.rs +3 -0
- data/rust/src/kernel/present.rs +261 -0
- data/rust/src/kernel/query_cursor.rs +251 -0
- data/rust/src/kernel/query_ordering.rs +6 -6
- data/rust/src/kernel/read_model.rs +162 -17
- data/rust/src/kernel/repository.rs +107 -17
- data/rust/src/kernel/vocab/mod.rs +4 -0
- data/rust/src/kernel/vocab/reducer.rs +109 -0
- data/rust/src/kernel/vocab/refusal_class.rs +124 -0
- data/rust/src/kernel/vocab/refusal_template.rs +2 -2
- data/rust/src/lib.rs +1 -1
- data/rust/web/Cargo.lock +47 -0
- metadata +331 -93
- data/lib/hecks/chapters.rb +0 -147
- data/lib/hecks/gate/stages.yml +0 -234
- data/lib/hecks/hecks/governance.world +0 -7
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +0 -137
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +0 -59
- data/lib/hecks/ports/persistence/postgres_dump.rb +0 -100
- data/lib/hecks/projections/deploy/box/templates/tunnel-egress.yaml.tmpl +0 -11
- data/lib/hecks/projector/exporter/capability_values.rb +0 -55
- data/lib/hecks/runtime/remote_dispatcher.rb +0 -176
- data/prebuilt/02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal +0 -0
- data/prebuilt/bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +0 -1
- data/rust/host/src/clock.rs +0 -8
- data/rust/host/src/expr_json.rs +0 -1983
- data/rust/host/src/presentation.rs +0 -842
- data/rust/host/src/wasm_runner.rs +0 -206
- data/rust/src/kernel/expression_operators/membership.rs +0 -20
- data/rust/src/kernel/expression_operators/pattern_match.rs +0 -108
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/events.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/head_writes.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/journal.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/base_sql.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/field_cache.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/head_compiler.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/merge_writes.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/provisioning.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/resumable_backfill.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_installer.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_sql.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/sagas.rb +0 -0
- /data/rust/host/{src → core/src}/field_hints.rs +0 -0
- /data/rust/host/{src → core/src}/log.rs +0 -0
- /data/rust/host/{src → core/src}/reference_transform.rs +0 -0
|
@@ -1,722 +1,10 @@
|
|
|
1
|
-
//!
|
|
2
|
-
//!
|
|
3
|
-
|
|
4
|
-
use crate::host::Host;
|
|
5
|
-
use crate::journal;
|
|
6
|
-
use crate::lambda_client::{self};
|
|
7
|
-
use crate::wasm_runner;
|
|
8
|
-
use std::path::Path;
|
|
9
|
-
use tokio::sync::Mutex;
|
|
10
|
-
use tokio_postgres::Client;
|
|
11
|
-
|
|
12
|
-
pub struct Outcome {
|
|
13
|
-
pub result: serde_json::Value,
|
|
14
|
-
pub accepted: bool,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/// Who a command is dispatched for: the role they state and, when the host knows who they are, the
|
|
18
|
-
/// actor Governance holds assignments for. Both absent is an unidentified caller.
|
|
19
|
-
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
20
|
-
pub struct Caller<'a> {
|
|
21
|
-
pub role: Option<&'a str>,
|
|
22
|
-
pub actor_id: Option<&'a str>,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/// The role an unidentified caller is dispatched under when roles are enforced: no command declares
|
|
26
|
-
/// it, so a command that declares any role refuses it.
|
|
27
|
-
pub const ANONYMOUS_ROLE: &str = "Anonymous";
|
|
28
|
-
|
|
29
|
-
/// What the host does about the role a command declares (`HECKS_ROLE_ENFORCEMENT`).
|
|
30
|
-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
31
|
-
pub enum Enforcement {
|
|
32
|
-
/// A caller with no role is unchecked, as before.
|
|
33
|
-
Off,
|
|
34
|
-
/// An unidentified or unassigned caller is let through and logged as `would_refuse_role`; a
|
|
35
|
-
/// caller that states a role is checked exactly as under `Enforce`, so shadow never accepts
|
|
36
|
-
/// what `Off` would not.
|
|
37
|
-
Shadow,
|
|
38
|
-
/// An unidentified or unassigned caller is refused.
|
|
39
|
-
Enforce,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
impl Enforcement {
|
|
43
|
-
/// Reads `off` (the default), `shadow` or `enforce`; anything else is `Off`.
|
|
44
|
-
pub fn parse(value: Option<&str>) -> Self {
|
|
45
|
-
match value.map(|v| v.trim().to_ascii_lowercase()).as_deref() {
|
|
46
|
-
Some("shadow") => Self::Shadow,
|
|
47
|
-
Some("enforce") => Self::Enforce,
|
|
48
|
-
_ => Self::Off,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
pub fn from_env() -> Self {
|
|
53
|
-
static MODE: std::sync::OnceLock<Enforcement> = std::sync::OnceLock::new();
|
|
54
|
-
*MODE.get_or_init(|| Self::parse(std::env::var("HECKS_ROLE_ENFORCEMENT").ok().as_deref()))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// The caller the kernel is asked to check: an unidentified caller becomes the anonymous role
|
|
58
|
-
/// unless enforcement is off.
|
|
59
|
-
pub fn effective(self, caller: Caller<'_>) -> Caller<'_> {
|
|
60
|
-
if self != Self::Off && caller.role.is_none() && caller.actor_id.is_none() {
|
|
61
|
-
return Caller {
|
|
62
|
-
role: Some(ANONYMOUS_ROLE),
|
|
63
|
-
actor_id: None,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
caller
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/// Where a dispatch comes from. A host-internal dispatch is the host acting on its own behalf (a
|
|
71
|
-
/// signup, a provisioning step, a presentation write); it names no caller and is never subject to
|
|
72
|
-
/// `HECKS_ROLE_ENFORCEMENT`, which guards what callers outside the host may do.
|
|
73
|
-
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
74
|
-
enum Origin {
|
|
75
|
-
External,
|
|
76
|
-
HostInternal,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/// Whether the kernel refused the command for the caller's role.
|
|
80
|
-
fn refused_for_role(result: &serde_json::Value) -> bool {
|
|
81
|
-
result
|
|
82
|
-
.get("refusals")
|
|
83
|
-
.and_then(|r| r.as_array())
|
|
84
|
-
.is_some_and(|all| {
|
|
85
|
-
all.iter()
|
|
86
|
-
.any(|r| r.get("kind").and_then(|k| k.as_str()) == Some("Unauthorized"))
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Distinguishes a module refusal from a kernel failure to run at all.
|
|
91
|
-
#[derive(Debug, PartialEq)]
|
|
92
|
-
enum KernelAnswer {
|
|
93
|
-
Accepted,
|
|
94
|
-
Refused,
|
|
95
|
-
Failed(String),
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// A run that never started has no `refusals` array at all, so absence of
|
|
99
|
-
// refusals doesn't by itself mean the run succeeded.
|
|
100
|
-
fn classify(result: &serde_json::Value) -> KernelAnswer {
|
|
101
|
-
if let Some(error) = result.get("error") {
|
|
102
|
-
return KernelAnswer::Failed(
|
|
103
|
-
error
|
|
104
|
-
.as_str()
|
|
105
|
-
.map_or_else(|| error.to_string(), str::to_string),
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
let refused = result
|
|
109
|
-
.get("refusals")
|
|
110
|
-
.and_then(|r| r.as_array())
|
|
111
|
-
.is_some_and(|r| !r.is_empty());
|
|
112
|
-
if refused {
|
|
113
|
-
KernelAnswer::Refused
|
|
114
|
-
} else {
|
|
115
|
-
KernelAnswer::Accepted
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// A kernel failure becomes `Err`, not a result with no instances read as
|
|
120
|
-
// an empty world.
|
|
121
|
-
fn parse_kernel_output(output: &str) -> anyhow::Result<serde_json::Value> {
|
|
122
|
-
let result: serde_json::Value = serde_json::from_str(output)?;
|
|
123
|
-
match classify(&result) {
|
|
124
|
-
KernelAnswer::Failed(message) => {
|
|
125
|
-
anyhow::bail!("the kernel failed before it could run the command: {message}")
|
|
126
|
-
}
|
|
127
|
-
KernelAnswer::Accepted | KernelAnswer::Refused => Ok(result),
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Kept as one opaque object under the journal's `args` column; the kernel
|
|
132
|
-
// unwraps `to`/`with` itself, so this needs no journal schema migration.
|
|
133
|
-
pub fn routed_invocation(
|
|
134
|
-
to: serde_json::Value,
|
|
135
|
-
facts: serde_json::Value,
|
|
136
|
-
) -> anyhow::Result<serde_json::Value> {
|
|
137
|
-
if !facts.is_object() {
|
|
138
|
-
anyhow::bail!("with must be an object of command facts");
|
|
139
|
-
}
|
|
140
|
-
let mut invocation = serde_json::Map::new();
|
|
141
|
-
invocation.insert("to".to_string(), to);
|
|
142
|
-
invocation.insert("with".to_string(), facts);
|
|
143
|
-
Ok(serde_json::Value::Object(invocation))
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// No `to` route: used only when a command creates a new aggregate, so
|
|
147
|
-
// there is no receiver yet to route to.
|
|
148
|
-
pub fn facts_invocation(facts: serde_json::Value) -> anyhow::Result<serde_json::Value> {
|
|
149
|
-
if !facts.is_object() {
|
|
150
|
-
anyhow::bail!("with must be an object of command facts");
|
|
151
|
-
}
|
|
152
|
-
let mut invocation = serde_json::Map::new();
|
|
153
|
-
invocation.insert("with".to_string(), facts);
|
|
154
|
-
Ok(serde_json::Value::Object(invocation))
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Route-aware entry point; `handle` below stays the compatibility path
|
|
158
|
-
// for callers still sending the older mixed args shape.
|
|
159
|
-
pub async fn handle_routed(
|
|
160
|
-
host: Host<'_>,
|
|
161
|
-
verb: &str,
|
|
162
|
-
to: serde_json::Value,
|
|
163
|
-
facts: serde_json::Value,
|
|
164
|
-
role: Option<&str>,
|
|
165
|
-
) -> anyhow::Result<Outcome> {
|
|
166
|
-
handle(host, verb, routed_invocation(to, facts)?, role).await
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/// `handle_routed` for a caller the host can name.
|
|
170
|
-
pub async fn handle_routed_as(
|
|
171
|
-
host: Host<'_>,
|
|
172
|
-
verb: &str,
|
|
173
|
-
to: serde_json::Value,
|
|
174
|
-
facts: serde_json::Value,
|
|
175
|
-
caller: Caller<'_>,
|
|
176
|
-
) -> anyhow::Result<Outcome> {
|
|
177
|
-
handle_as(host, verb, routed_invocation(to, facts)?, caller).await
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Facts-only counterpart to `handle_routed`.
|
|
181
|
-
pub async fn handle_facts(
|
|
182
|
-
host: Host<'_>,
|
|
183
|
-
verb: &str,
|
|
184
|
-
facts: serde_json::Value,
|
|
185
|
-
role: Option<&str>,
|
|
186
|
-
) -> anyhow::Result<Outcome> {
|
|
187
|
-
handle(host, verb, facts_invocation(facts)?, role).await
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/// `handle_facts` for a caller the host can name.
|
|
191
|
-
pub async fn handle_facts_as(
|
|
192
|
-
host: Host<'_>,
|
|
193
|
-
verb: &str,
|
|
194
|
-
facts: serde_json::Value,
|
|
195
|
-
caller: Caller<'_>,
|
|
196
|
-
) -> anyhow::Result<Outcome> {
|
|
197
|
-
handle_as(host, verb, facts_invocation(facts)?, caller).await
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// A Mutex, not a bare Arc<Client>: `handle` needs `Client::transaction`,
|
|
201
|
-
// which takes `&mut Client`. Locking also means concurrent callers on
|
|
202
|
-
// this connection can't interleave statements.
|
|
203
|
-
//
|
|
204
|
-
// `config` is required, not optional: `main.rs` already refused to boot
|
|
205
|
-
// unless the configured domain/era is provisioned and current, so by the
|
|
206
|
-
// time `handle` runs that schema is guaranteed to exist.
|
|
207
|
-
//
|
|
208
|
-
// `role` is optional, matching `Adapters::Lambda::Client#dispatch`'s own
|
|
209
|
-
// `role: nil` default: a step with no role looks the same on the wire
|
|
210
|
-
// whether or not a caller is bound.
|
|
211
|
-
//
|
|
212
|
-
// This entry point and `handle_facts`/`handle_routed` are the host's own: a call with no role is a
|
|
213
|
-
// host-internal dispatch and is unchecked in every enforcement mode. A call that names a role is
|
|
214
|
-
// still checked against it. Callers outside the host go through the `_as` entry points.
|
|
215
|
-
pub async fn handle(
|
|
216
|
-
host: Host<'_>,
|
|
217
|
-
verb: &str,
|
|
218
|
-
args: serde_json::Value,
|
|
219
|
-
role: Option<&str>,
|
|
220
|
-
) -> anyhow::Result<Outcome> {
|
|
221
|
-
let origin = if role.is_none() {
|
|
222
|
-
Origin::HostInternal
|
|
223
|
-
} else {
|
|
224
|
-
Origin::External
|
|
225
|
-
};
|
|
226
|
-
dispatch_in(
|
|
227
|
-
Enforcement::from_env(),
|
|
228
|
-
origin,
|
|
229
|
-
host,
|
|
230
|
-
verb,
|
|
231
|
-
args,
|
|
232
|
-
Caller {
|
|
233
|
-
role,
|
|
234
|
-
actor_id: None,
|
|
235
|
-
},
|
|
236
|
-
)
|
|
237
|
-
.await
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/// Dispatches for a caller, applying `HECKS_ROLE_ENFORCEMENT`: unidentified callers are refused (or,
|
|
241
|
-
/// in shadow, logged as would-be refusals and let through).
|
|
242
|
-
pub async fn handle_as(
|
|
243
|
-
host: Host<'_>,
|
|
244
|
-
verb: &str,
|
|
245
|
-
args: serde_json::Value,
|
|
246
|
-
caller: Caller<'_>,
|
|
247
|
-
) -> anyhow::Result<Outcome> {
|
|
248
|
-
dispatch_in(
|
|
249
|
-
Enforcement::from_env(),
|
|
250
|
-
Origin::External,
|
|
251
|
-
host,
|
|
252
|
-
verb,
|
|
253
|
-
args,
|
|
254
|
-
caller,
|
|
255
|
-
)
|
|
256
|
-
.await
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async fn dispatch_in(
|
|
260
|
-
mode: Enforcement,
|
|
261
|
-
origin: Origin,
|
|
262
|
-
host: Host<'_>,
|
|
263
|
-
verb: &str,
|
|
264
|
-
args: serde_json::Value,
|
|
265
|
-
caller: Caller<'_>,
|
|
266
|
-
) -> anyhow::Result<Outcome> {
|
|
267
|
-
let checked = if origin == Origin::HostInternal {
|
|
268
|
-
caller
|
|
269
|
-
} else {
|
|
270
|
-
mode.effective(caller)
|
|
271
|
-
};
|
|
272
|
-
let outcome = run(host, verb, args.clone(), checked).await?;
|
|
273
|
-
// Only a caller that stated no role (unidentified, or an actor Governance holds no grant for)
|
|
274
|
-
// is let through; a stated role that does not match stays refused, as it is under `Off`.
|
|
275
|
-
if mode == Enforcement::Shadow
|
|
276
|
-
&& caller.role.is_none()
|
|
277
|
-
&& !outcome.accepted
|
|
278
|
-
&& refused_for_role(&outcome.result)
|
|
279
|
-
{
|
|
280
|
-
crate::log::error(
|
|
281
|
-
"would_refuse_role",
|
|
282
|
-
serde_json::json!({
|
|
283
|
-
"verb": verb, "role": checked.role, "actor_id": checked.actor_id,
|
|
284
|
-
"refusals": outcome.result.get("refusals"),
|
|
285
|
-
}),
|
|
286
|
-
);
|
|
287
|
-
return run(host, verb, args, Caller::default()).await;
|
|
288
|
-
}
|
|
289
|
-
Ok(outcome)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async fn run(
|
|
293
|
-
host: Host<'_>,
|
|
294
|
-
verb: &str,
|
|
295
|
-
args: serde_json::Value,
|
|
296
|
-
caller: Caller<'_>,
|
|
297
|
-
) -> anyhow::Result<Outcome> {
|
|
298
|
-
let Host {
|
|
299
|
-
client,
|
|
300
|
-
wasm_path,
|
|
301
|
-
config,
|
|
302
|
-
invoker,
|
|
303
|
-
..
|
|
304
|
-
} = host;
|
|
305
|
-
let role = caller.role;
|
|
306
|
-
let mut guard = client.lock().await;
|
|
307
|
-
let txn = guard.transaction().await?;
|
|
308
|
-
|
|
309
|
-
// Locks the whole read-then-append sequence, not just the INSERT:
|
|
310
|
-
// two concurrent invocations could otherwise both rehydrate against
|
|
311
|
-
// the same prior steps and both pass a uniqueness check the domain
|
|
312
|
-
// thinks it's enforcing. Domain-scoped so unrelated domains sharing
|
|
313
|
-
// one Postgres instance don't serialize against each other.
|
|
314
|
-
txn.execute(
|
|
315
|
-
"SELECT pg_advisory_xact_lock(hashtext('hecks_lambda_journal.' || $1::text))",
|
|
316
|
-
&[&config.domain],
|
|
317
|
-
)
|
|
318
|
-
.await?;
|
|
319
|
-
|
|
320
|
-
// Reads the last snapshot plus only the journal rows after it,
|
|
321
|
-
// instead of replaying the whole history every time. `None` covers
|
|
322
|
-
// a brand-new domain and one with pre-snapshot history alike; both
|
|
323
|
-
// fall back to one full replay, which then leaves a snapshot behind.
|
|
324
|
-
let snapshot = journal::load_snapshot(&txn).await?;
|
|
325
|
-
// `sagas_backfilled` is a latch, not a live check of whether
|
|
326
|
-
// `hecks_lambda_sagas` is empty (empty is the ordinary state, not a
|
|
327
|
-
// signal). Only matters for a domain with real history predating
|
|
328
|
-
// saga durability, where the snapshot cache would otherwise never
|
|
329
|
-
// re-derive sagas from the journal.
|
|
330
|
-
let needs_saga_backfill = snapshot.as_ref().is_some_and(|s| !s.sagas_backfilled);
|
|
331
|
-
let mut steps = match &snapshot {
|
|
332
|
-
Some(s) if !needs_saga_backfill => journal::load_steps_after(&txn, s.ordinal).await?,
|
|
333
|
-
_ => journal::load_steps(&txn).await?,
|
|
334
|
-
};
|
|
335
|
-
// Role goes only on this outermost step; replayed history already
|
|
336
|
-
// carries whatever role it was dispatched with. Built as a mutable
|
|
337
|
-
// step, not inlined into the `json!` literal, so a roleless call
|
|
338
|
-
// still omits the `"role"` key entirely rather than sending null.
|
|
339
|
-
// A fact the command `needs` is answered here, before the kernel's gates read the arguments and
|
|
340
|
-
// before the step is journaled, so a replay re-dispatches the recorded answer (ADR 0081).
|
|
341
|
-
let mut args = args;
|
|
342
|
-
if let Some(domain_ir) = crate::ir::ir() {
|
|
343
|
-
crate::needs::fill_needs(domain_ir, verb, &mut args, &crate::needs::ProcessClock);
|
|
344
|
-
// An argument with a declared default is filled the same way, so the journal holds it too.
|
|
345
|
-
crate::needs::fill_defaults(domain_ir, verb, &mut args);
|
|
346
|
-
}
|
|
347
|
-
let mut step = serde_json::json!({ "verb": verb, "args": args.clone() });
|
|
348
|
-
if let Some(role) = role {
|
|
349
|
-
step["role"] = serde_json::Value::String(role.to_string());
|
|
350
|
-
}
|
|
351
|
-
if let Some(actor_id) = caller.actor_id {
|
|
352
|
-
step["actor_id"] = serde_json::Value::String(actor_id.to_string());
|
|
353
|
-
}
|
|
354
|
-
// Stamped only on this live step; replayed history already carries
|
|
355
|
-
// its own real timestamp, and re-stamping today's time onto a
|
|
356
|
-
// replay would be wrong, not just redundant.
|
|
357
|
-
step["occurred_at"] = serde_json::Value::String(crate::auth::httpdate_now());
|
|
358
|
-
steps.push(step);
|
|
359
|
-
// Must match `steps`' own choice above: a full replay needs an empty
|
|
360
|
-
// seed, or the kernel replays into a world that already has every
|
|
361
|
-
// record being created again, refusing each as a false AlreadyExists.
|
|
362
|
-
let seed = if needs_saga_backfill {
|
|
363
|
-
serde_json::json!({})
|
|
364
|
-
} else {
|
|
365
|
-
snapshot
|
|
366
|
-
.as_ref()
|
|
367
|
-
.map_or_else(|| serde_json::json!({}), |s| s.seed.clone())
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
// Read inside the same advisory-locked transaction: the lock covers
|
|
371
|
-
// saga state too, not just the aggregate journal/snapshot.
|
|
372
|
-
let saga_rows = journal::load_sagas(&txn).await?;
|
|
373
|
-
let sagas_seed = serde_json::json!(saga_rows
|
|
374
|
-
.iter()
|
|
375
|
-
.map(|r| serde_json::json!({
|
|
376
|
-
"process_manager": r.process_manager,
|
|
377
|
-
"correlation": r.correlation,
|
|
378
|
-
"state": r.state,
|
|
379
|
-
"memory": r.memory,
|
|
380
|
-
"completed_compensations": r.completed_compensations,
|
|
381
|
-
}))
|
|
382
|
-
.collect::<Vec<_>>());
|
|
383
|
-
|
|
384
|
-
let mut input = serde_json::json!({ "seed": seed, "steps": steps, "sagas": sagas_seed });
|
|
385
|
-
// Lets the kernel answer a reaction's command that needs a fact the same way (ADR 0081).
|
|
386
|
-
if let Some(domain_ir) = crate::ir::ir() {
|
|
387
|
-
let table = crate::needs::table(domain_ir);
|
|
388
|
-
if table.as_object().is_some_and(|t| !t.is_empty()) {
|
|
389
|
-
input["needs"] = table;
|
|
390
|
-
}
|
|
391
|
-
let defaults = crate::needs::defaults_table(domain_ir);
|
|
392
|
-
if defaults.as_object().is_some_and(|t| !t.is_empty()) {
|
|
393
|
-
input["defaults"] = defaults;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
let input = input.to_string();
|
|
397
|
-
// `wasm_runner::run` is sync, and wasmtime-wasi's sync bridge spins up
|
|
398
|
-
// its own tokio runtime internally — fatal on a thread already
|
|
399
|
-
// driving one. `spawn_blocking` moves it off this async runtime.
|
|
400
|
-
let owned_wasm_path = wasm_path.to_path_buf();
|
|
401
|
-
let output =
|
|
402
|
-
tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input)).await??;
|
|
403
|
-
// A kernel failure returns here, before anything is written: dropping `txn`
|
|
404
|
-
// rolls it back, so no journal row, snapshot, saga or mutation is saved.
|
|
405
|
-
let mut result = parse_kernel_output(&output)?;
|
|
406
|
-
|
|
407
|
-
// See journal.rs's own header: every prior step in this replay
|
|
408
|
-
// already succeeded once, deterministically, so the only step that
|
|
409
|
-
// can legitimately show up in `refusals` is the new one just
|
|
410
|
-
// appended last.
|
|
411
|
-
let accepted = classify(&result) == KernelAnswer::Accepted;
|
|
412
|
-
|
|
413
|
-
if accepted {
|
|
414
|
-
let ordinal = journal::append(&txn, verb, &args).await?;
|
|
415
|
-
|
|
416
|
-
// The kernel's "instances" output is already the exact seed
|
|
417
|
-
// shape, so nothing here re-derives or filters it.
|
|
418
|
-
let new_seed = result
|
|
419
|
-
.get("instances")
|
|
420
|
-
.cloned()
|
|
421
|
-
.unwrap_or_else(|| serde_json::json!({}));
|
|
422
|
-
journal::save_snapshot(&txn, ordinal, &new_seed).await?;
|
|
423
|
-
|
|
424
|
-
// Persisted in the same transaction, so a saga checkpoint can
|
|
425
|
-
// never commit independently of the command that produced it.
|
|
426
|
-
// Reconciled against `saga_rows` (pre-run state): present now
|
|
427
|
-
// is upserted, present before but absent now is deleted.
|
|
428
|
-
if let Some(saga_snapshot) = result.get("saga_snapshot").and_then(|v| v.as_array()) {
|
|
429
|
-
let mut still_present: std::collections::HashSet<(String, String)> =
|
|
430
|
-
std::collections::HashSet::new();
|
|
431
|
-
for entry in saga_snapshot {
|
|
432
|
-
let process_manager = entry
|
|
433
|
-
.get("process_manager")
|
|
434
|
-
.and_then(|v| v.as_str())
|
|
435
|
-
.ok_or_else(|| {
|
|
436
|
-
anyhow::anyhow!("saga_snapshot entry missing \"process_manager\": {entry}")
|
|
437
|
-
})?;
|
|
438
|
-
let correlation = entry
|
|
439
|
-
.get("correlation")
|
|
440
|
-
.and_then(|v| v.as_str())
|
|
441
|
-
.ok_or_else(|| {
|
|
442
|
-
anyhow::anyhow!("saga_snapshot entry missing \"correlation\": {entry}")
|
|
443
|
-
})?;
|
|
444
|
-
let state = entry.get("state").and_then(|v| v.as_str()).ok_or_else(|| {
|
|
445
|
-
anyhow::anyhow!("saga_snapshot entry missing \"state\": {entry}")
|
|
446
|
-
})?;
|
|
447
|
-
let memory = entry
|
|
448
|
-
.get("memory")
|
|
449
|
-
.cloned()
|
|
450
|
-
.unwrap_or_else(|| serde_json::json!({}));
|
|
451
|
-
let completed_compensations = entry
|
|
452
|
-
.get("completed_compensations")
|
|
453
|
-
.cloned()
|
|
454
|
-
.unwrap_or_else(|| serde_json::json!([]));
|
|
455
|
-
|
|
456
|
-
journal::save_saga(
|
|
457
|
-
&txn,
|
|
458
|
-
process_manager,
|
|
459
|
-
correlation,
|
|
460
|
-
state,
|
|
461
|
-
&memory,
|
|
462
|
-
&completed_compensations,
|
|
463
|
-
)
|
|
464
|
-
.await?;
|
|
465
|
-
still_present.insert((process_manager.to_string(), correlation.to_string()));
|
|
466
|
-
}
|
|
467
|
-
for row in &saga_rows {
|
|
468
|
-
let key = (row.process_manager.clone(), row.correlation.clone());
|
|
469
|
-
if !still_present.contains(&key) {
|
|
470
|
-
journal::delete_saga(&txn, &row.process_manager, &row.correlation).await?;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// One entry per step in `steps`, so the last entry is this
|
|
476
|
-
// call's own new step. Skipped when `config.era` is `None`: a
|
|
477
|
-
// domain with nothing lineage-capable bound never provisions a
|
|
478
|
-
// head-snapshot table to write into (ADR 0034).
|
|
479
|
-
let step_mutations = if config.era.is_some() {
|
|
480
|
-
result
|
|
481
|
-
.get("mutations")
|
|
482
|
-
.and_then(|m| m.as_array())
|
|
483
|
-
.and_then(|steps| steps.last())
|
|
484
|
-
.and_then(|last| last.as_array())
|
|
485
|
-
.cloned()
|
|
486
|
-
.unwrap_or_default()
|
|
487
|
-
} else {
|
|
488
|
-
Vec::new()
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
for mutation in &step_mutations {
|
|
492
|
-
let aggregate = mutation
|
|
493
|
-
.get("aggregate")
|
|
494
|
-
.and_then(|v| v.as_str())
|
|
495
|
-
.ok_or_else(|| {
|
|
496
|
-
anyhow::anyhow!("mutation record missing \"aggregate\": {mutation}")
|
|
497
|
-
})?;
|
|
498
|
-
// `era.is_some()` means the lineage subsystem exists, not that
|
|
499
|
-
// this aggregate has a mirror: `mint` only provisions head
|
|
500
|
-
// snapshots for the capable set, so mirroring anything else
|
|
501
|
-
// would upsert into a relation nobody created.
|
|
502
|
-
if !config.mirrors(aggregate) {
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
let id = mutation
|
|
506
|
-
.get("id")
|
|
507
|
-
.and_then(|v| v.as_str())
|
|
508
|
-
.ok_or_else(|| anyhow::anyhow!("mutation record missing \"id\": {mutation}"))?;
|
|
509
|
-
let operation = mutation
|
|
510
|
-
.get("operation")
|
|
511
|
-
.and_then(|v| v.as_str())
|
|
512
|
-
.ok_or_else(|| {
|
|
513
|
-
anyhow::anyhow!("mutation record missing \"operation\": {mutation}")
|
|
514
|
-
})?;
|
|
515
|
-
let state = mutation
|
|
516
|
-
.get("state")
|
|
517
|
-
.ok_or_else(|| anyhow::anyhow!("mutation record missing \"state\": {mutation}"))?;
|
|
518
|
-
|
|
519
|
-
journal::append_lineage_mutation(
|
|
520
|
-
&txn,
|
|
521
|
-
config,
|
|
522
|
-
&journal::Mutation {
|
|
523
|
-
aggregate,
|
|
524
|
-
id,
|
|
525
|
-
operation,
|
|
526
|
-
state,
|
|
527
|
-
},
|
|
528
|
-
)
|
|
529
|
-
.await?;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Only this step's own matches, same reason as `step_mutations`
|
|
534
|
-
// above: replaying a prior step's reaction would re-invoke a sibling
|
|
535
|
-
// Lambda on every later replay. Captured before commit, delivered after.
|
|
536
|
-
let pending_cross_domain = result
|
|
537
|
-
.get("cross_domain_reactions")
|
|
538
|
-
.and_then(|r| r.as_array())
|
|
539
|
-
.and_then(|steps| steps.last())
|
|
540
|
-
.and_then(|last| last.as_array())
|
|
541
|
-
.cloned()
|
|
542
|
-
.unwrap_or_default();
|
|
543
|
-
|
|
544
|
-
// Commits (and releases the advisory lock) whether or not anything
|
|
545
|
-
// was appended — a refused command still needs the lock released
|
|
546
|
-
// for the next invocation to proceed.
|
|
547
|
-
txn.commit().await?;
|
|
548
|
-
|
|
549
|
-
// Delivered after commit, deliberately: the local command already
|
|
550
|
-
// succeeded and is durable, so a cross-Lambda notification is
|
|
551
|
-
// best-effort, not a precondition of it. A fault that survives every
|
|
552
|
-
// retry still fails this call via `?`, but never unwinds an
|
|
553
|
-
// already-committed transaction.
|
|
554
|
-
// Every reaction gets its own attempt even after an earlier fails:
|
|
555
|
-
// returning early would silently drop the rest, undelivered and
|
|
556
|
-
// never dead-lettered (`SafeDepositBox.Surrender` fires two from one
|
|
557
|
-
// command). `first_failure` remembers only the first error without
|
|
558
|
-
// cutting the loop short.
|
|
559
|
-
let mut cross_domain_deliveries = Vec::new();
|
|
560
|
-
let mut first_failure: Option<anyhow::Error> = None;
|
|
561
|
-
for reaction in &pending_cross_domain {
|
|
562
|
-
match lambda_client::deliver_with_retry(invoker, reaction).await {
|
|
563
|
-
Ok(record) => {
|
|
564
|
-
if let Some(response) = result.as_object_mut() {
|
|
565
|
-
if let Some(reactions) =
|
|
566
|
-
response.get_mut("reactions").and_then(|r| r.as_array_mut())
|
|
567
|
-
{
|
|
568
|
-
reactions.push(serde_json::json!({
|
|
569
|
-
"policy": record.policy, "on": reaction.get("on").and_then(|v| v.as_str()).unwrap_or_default(),
|
|
570
|
-
"trigger": record.target_verb, "delivered": record.delivered, "reason": record.reason,
|
|
571
|
-
}));
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
cross_domain_deliveries.push(record.to_json());
|
|
575
|
-
}
|
|
576
|
-
Err(failure) => {
|
|
577
|
-
let error_text = format!("{:#}", failure.error);
|
|
578
|
-
crate::log::error(
|
|
579
|
-
"cross_domain_delivery_failed",
|
|
580
|
-
serde_json::json!({
|
|
581
|
-
"policy": failure.policy, "target_domain": failure.target_domain,
|
|
582
|
-
"target_verb": failure.target_verb, "attempts": failure.attempts, "error": error_text,
|
|
583
|
-
}),
|
|
584
|
-
);
|
|
585
|
-
journal::record_dead_letter(
|
|
586
|
-
&*guard,
|
|
587
|
-
&failure.policy,
|
|
588
|
-
&failure.target_domain,
|
|
589
|
-
&failure.target_verb,
|
|
590
|
-
&failure.payload,
|
|
591
|
-
&error_text,
|
|
592
|
-
failure.attempts as i32,
|
|
593
|
-
)
|
|
594
|
-
.await?;
|
|
595
|
-
if first_failure.is_none() {
|
|
596
|
-
first_failure = Some(failure.error);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
if let Some(error) = first_failure {
|
|
602
|
-
return Err(error);
|
|
603
|
-
}
|
|
604
|
-
if let Some(response) = result.as_object_mut() {
|
|
605
|
-
response.insert(
|
|
606
|
-
"cross_domain_deliveries".to_string(),
|
|
607
|
-
serde_json::Value::Array(cross_domain_deliveries),
|
|
608
|
-
);
|
|
609
|
-
}
|
|
1
|
+
//! The dispatch path against a real Postgres and the Memory store: what `rust_host_core`'s
|
|
2
|
+
//! dispatch does is only observable through a backend, and the backends live here.
|
|
610
3
|
|
|
611
|
-
|
|
612
|
-
Ok(Outcome { result, accepted })
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// One line per dispatched command: what was asked (verb, role — never
|
|
616
|
-
// the facts) and what the domain answered.
|
|
617
|
-
fn log_command(verb: &str, role: Option<&str>, accepted: bool, result: &serde_json::Value) {
|
|
618
|
-
let fields = command_fields(verb, role, accepted, result);
|
|
619
|
-
if accepted {
|
|
620
|
-
crate::log::info("command", fields);
|
|
621
|
-
} else {
|
|
622
|
-
crate::log::error("command", fields);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
fn command_fields(
|
|
627
|
-
verb: &str,
|
|
628
|
-
role: Option<&str>,
|
|
629
|
-
accepted: bool,
|
|
630
|
-
result: &serde_json::Value,
|
|
631
|
-
) -> serde_json::Value {
|
|
632
|
-
let events: Vec<serde_json::Value> = result
|
|
633
|
-
.get("events")
|
|
634
|
-
.and_then(|v| v.as_array())
|
|
635
|
-
.map(|items| {
|
|
636
|
-
items
|
|
637
|
-
.iter()
|
|
638
|
-
.filter_map(|item| item.get("name").cloned())
|
|
639
|
-
.collect()
|
|
640
|
-
})
|
|
641
|
-
.unwrap_or_default();
|
|
642
|
-
serde_json::json!({
|
|
643
|
-
"verb": verb, "role": role, "accepted": accepted, "events": events,
|
|
644
|
-
"refusals": result.get("refusals").cloned().unwrap_or_else(|| serde_json::json!([])),
|
|
645
|
-
})
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Read-only: the same seed path `handle` uses, minus the new step and
|
|
649
|
-
// any write. No advisory lock needed since nothing here writes.
|
|
650
|
-
// Returns the whole kernel result verbatim, not just `instances`, so a
|
|
651
|
-
// caller can tell an empty `refusals` from one that was never checked.
|
|
652
|
-
pub async fn read(client: &Mutex<Client>, wasm_path: &Path) -> anyhow::Result<serde_json::Value> {
|
|
653
|
-
let guard = client.lock().await;
|
|
654
|
-
let snapshot = journal::load_snapshot(&*guard).await?;
|
|
655
|
-
|
|
656
|
-
if let Some(s) = &snapshot {
|
|
657
|
-
let steps_since = journal::load_steps_after(&*guard, s.ordinal).await?;
|
|
658
|
-
drop(guard);
|
|
659
|
-
|
|
660
|
-
if steps_since.is_empty() {
|
|
661
|
-
// No wasm invocation needed: the snapshot's seed already is
|
|
662
|
-
// this domain's current "instances" output. Empty
|
|
663
|
-
// events/refusals is correct too — a read reports current
|
|
664
|
-
// state, never event/refusal history.
|
|
665
|
-
return Ok(serde_json::json!({ "instances": s.seed, "events": [], "refusals": [] }));
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Self-healing: if steps ever go unaccounted for after the last
|
|
669
|
-
// snapshot, seeding from it and replaying just the gap is
|
|
670
|
-
// still correct and cheap.
|
|
671
|
-
let input = serde_json::json!({ "seed": &s.seed, "steps": steps_since }).to_string();
|
|
672
|
-
let owned_wasm_path = wasm_path.to_path_buf();
|
|
673
|
-
let output =
|
|
674
|
-
tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input))
|
|
675
|
-
.await??;
|
|
676
|
-
return parse_kernel_output(&output);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// No snapshot: either a brand-new domain, or one with history from
|
|
680
|
-
// before this snapshot cache existed — either way, one full replay
|
|
681
|
-
// catches it up.
|
|
682
|
-
let steps = journal::load_steps(&*guard).await?;
|
|
683
|
-
drop(guard);
|
|
684
|
-
let input = serde_json::json!({ "steps": steps }).to_string();
|
|
685
|
-
let owned_wasm_path = wasm_path.to_path_buf();
|
|
686
|
-
let output =
|
|
687
|
-
tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input)).await??;
|
|
688
|
-
parse_kernel_output(&output)
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// A declared query, answered against current state. Seeds from `read`
|
|
692
|
-
// rather than replaying history itself, so it sees exactly the state a
|
|
693
|
-
// read would report and pays at most one wasm invocation.
|
|
694
|
-
pub async fn query(
|
|
695
|
-
client: &Mutex<Client>,
|
|
696
|
-
wasm_path: &Path,
|
|
697
|
-
question: &str,
|
|
698
|
-
args: serde_json::Value,
|
|
699
|
-
) -> anyhow::Result<serde_json::Value> {
|
|
700
|
-
// A fact the query `needs` is answered before the kernel checks its arguments (ADR 0081).
|
|
701
|
-
let mut args = args;
|
|
702
|
-
if let Some(domain_ir) = crate::ir::ir() {
|
|
703
|
-
crate::needs::fill_query_needs(domain_ir, question, &mut args, &crate::needs::ProcessClock);
|
|
704
|
-
}
|
|
705
|
-
let state = read(client, wasm_path).await?;
|
|
706
|
-
let seed = state
|
|
707
|
-
.get("instances")
|
|
708
|
-
.cloned()
|
|
709
|
-
.unwrap_or_else(|| serde_json::json!({}));
|
|
710
|
-
let input = serde_json::json!({ "seed": seed, "steps": [{ "query": question, "args": args }] })
|
|
711
|
-
.to_string();
|
|
712
|
-
let owned_wasm_path = wasm_path.to_path_buf();
|
|
713
|
-
let output =
|
|
714
|
-
tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input)).await??;
|
|
715
|
-
parse_kernel_output(&output)
|
|
716
|
-
}
|
|
4
|
+
#![allow(unused_imports)]
|
|
717
5
|
|
|
718
6
|
#[cfg(test)]
|
|
719
|
-
pub
|
|
7
|
+
pub mod tests {
|
|
720
8
|
|
|
721
9
|
#[test]
|
|
722
10
|
fn a_command_log_line_names_the_verb_events_and_refusals_but_never_the_facts() {
|
|
@@ -732,15 +20,22 @@ pub(crate) mod tests {
|
|
|
732
20
|
assert_eq!(fields["refusals"][0]["kind"], "AlreadyExists");
|
|
733
21
|
assert!(!fields.to_string().contains("a@b.c"));
|
|
734
22
|
}
|
|
735
|
-
use
|
|
23
|
+
use crate::host::Host;
|
|
24
|
+
use crate::journal;
|
|
736
25
|
use crate::journal::LineageConfig;
|
|
26
|
+
use crate::lambda_client;
|
|
737
27
|
use crate::lambda_client::LambdaInvoker;
|
|
738
|
-
use
|
|
28
|
+
use crate::store::journal_sql;
|
|
29
|
+
use crate::store::{Accepted, DeadLetter, LineageWrite, Store, StoreExt, StoreFixtures};
|
|
30
|
+
use crate::wasm_runner;
|
|
31
|
+
use rust_host_core::dispatch::*;
|
|
32
|
+
use std::path::Path;
|
|
33
|
+
use tokio_postgres::{Client, NoTls};
|
|
739
34
|
|
|
740
35
|
// A throwaway Postgres database per test, uniquely named so
|
|
741
36
|
// `cargo test`'s default parallelism doesn't race two tests against
|
|
742
37
|
// the same journal table.
|
|
743
|
-
pub
|
|
38
|
+
pub async fn scratch_db(name: &str) -> Store {
|
|
744
39
|
let (admin, conn) = tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
|
|
745
40
|
.await
|
|
746
41
|
.expect("connect to postgres");
|
|
@@ -762,14 +57,29 @@ pub(crate) mod tests {
|
|
|
762
57
|
tokio::spawn(async move {
|
|
763
58
|
let _ = conn.await;
|
|
764
59
|
});
|
|
765
|
-
|
|
766
|
-
|
|
60
|
+
journal_sql::ensure_schema(&client).await.unwrap();
|
|
61
|
+
crate::store::postgres_store(client)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// An empty in-memory store, for tests that need a journal and nothing Postgres-specific.
|
|
65
|
+
pub fn memory_store() -> Store {
|
|
66
|
+
Store::memory(crate::store::MemoryStore::new())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// The config a memory-backed host runs under: no era, so nothing mirrors into lineage.
|
|
70
|
+
pub fn memory_config(domain: &str) -> LineageConfig {
|
|
71
|
+
LineageConfig {
|
|
72
|
+
domain: domain.to_string(),
|
|
73
|
+
era: None,
|
|
74
|
+
mirrored: None,
|
|
75
|
+
seal_every: None,
|
|
76
|
+
}
|
|
767
77
|
}
|
|
768
78
|
|
|
769
79
|
// Not Ruby's real provisioning — just enough structure for
|
|
770
|
-
// `
|
|
80
|
+
// `journal_sql::current_era` and `append_lineage_mutation` to succeed,
|
|
771
81
|
// without reproducing Ruby's full DDL.
|
|
772
|
-
pub
|
|
82
|
+
pub async fn provision_lineage(
|
|
773
83
|
client: &Client,
|
|
774
84
|
domain: &str,
|
|
775
85
|
era: i32,
|
|
@@ -791,7 +101,7 @@ pub(crate) mod tests {
|
|
|
791
101
|
.await
|
|
792
102
|
.unwrap();
|
|
793
103
|
|
|
794
|
-
let journal_table = format!("hecks_journal_{}",
|
|
104
|
+
let journal_table = format!("hecks_journal_{}", journal_sql::snake(domain));
|
|
795
105
|
client
|
|
796
106
|
.batch_execute(&format!(
|
|
797
107
|
"CREATE TABLE IF NOT EXISTS \"{journal_table}\" (
|
|
@@ -800,7 +110,8 @@ pub(crate) mod tests {
|
|
|
800
110
|
aggregate text NOT NULL,
|
|
801
111
|
aggregate_id text NOT NULL,
|
|
802
112
|
operation text NOT NULL,
|
|
803
|
-
state jsonb
|
|
113
|
+
state jsonb,
|
|
114
|
+
mirrors jsonb
|
|
804
115
|
)"
|
|
805
116
|
))
|
|
806
117
|
.await
|
|
@@ -809,9 +120,9 @@ pub(crate) mod tests {
|
|
|
809
120
|
for name in aggregate_storage_names {
|
|
810
121
|
// Domain-qualified (ADR 0059), matching what a real write
|
|
811
122
|
// targets.
|
|
812
|
-
let snapshot_table =
|
|
123
|
+
let snapshot_table = journal_sql::qualified_name(
|
|
813
124
|
domain,
|
|
814
|
-
&format!("{}_head_snapshot_{era}",
|
|
125
|
+
&format!("{}_head_snapshot_{era}", journal_sql::snake(name)),
|
|
815
126
|
);
|
|
816
127
|
client
|
|
817
128
|
.batch_execute(&format!(
|
|
@@ -854,6 +165,7 @@ pub(crate) mod tests {
|
|
|
854
165
|
domain: "Banking".to_string(),
|
|
855
166
|
era: Some(1),
|
|
856
167
|
mirrored: None,
|
|
168
|
+
seal_every: None,
|
|
857
169
|
};
|
|
858
170
|
let refused = handle_facts(
|
|
859
171
|
Host::new(&client, &wasm, &everything, &invoker),
|
|
@@ -885,6 +197,7 @@ pub(crate) mod tests {
|
|
|
885
197
|
domain: "Banking".to_string(),
|
|
886
198
|
era: Some(1),
|
|
887
199
|
mirrored: Some(std::collections::BTreeSet::new()),
|
|
200
|
+
seal_every: None,
|
|
888
201
|
};
|
|
889
202
|
let outcome = handle_facts(
|
|
890
203
|
Host::new(&client, &wasm, &declared, &invoker),
|
|
@@ -930,6 +243,7 @@ pub(crate) mod tests {
|
|
|
930
243
|
domain: "Banking".to_string(),
|
|
931
244
|
era: Some(1),
|
|
932
245
|
mirrored: Some(std::iter::once("Banking::Customer".to_string()).collect()),
|
|
246
|
+
seal_every: None,
|
|
933
247
|
};
|
|
934
248
|
|
|
935
249
|
handle_facts(
|
|
@@ -955,19 +269,44 @@ pub(crate) mod tests {
|
|
|
955
269
|
assert_eq!(mirrored, 1);
|
|
956
270
|
}
|
|
957
271
|
|
|
958
|
-
fn test_config(domain: &str, era: i32) -> LineageConfig {
|
|
272
|
+
pub fn test_config(domain: &str, era: i32) -> LineageConfig {
|
|
959
273
|
LineageConfig {
|
|
960
274
|
domain: domain.to_string(),
|
|
961
275
|
era: Some(era),
|
|
962
276
|
mirrored: None,
|
|
277
|
+
seal_every: None,
|
|
963
278
|
}
|
|
964
279
|
}
|
|
965
280
|
|
|
966
|
-
|
|
281
|
+
#[test]
|
|
282
|
+
fn present_hands_the_kernel_the_answer_under_present_and_answers_what_it_returns() {
|
|
283
|
+
let answer = serde_json::json!({"instances": {"Shop::Item#a": {"name": {"value": "x"}}}});
|
|
284
|
+
let mut sent = String::new();
|
|
285
|
+
let presented = present_with(&answer, |input| {
|
|
286
|
+
sent = input.to_string();
|
|
287
|
+
Ok(r#"{"instances":{"Shop::Item#a":{"name":"x"}}}"#.to_string())
|
|
288
|
+
})
|
|
289
|
+
.unwrap();
|
|
290
|
+
assert_eq!(
|
|
291
|
+
serde_json::from_str::<serde_json::Value>(&sent).unwrap(),
|
|
292
|
+
serde_json::json!({ "present": answer })
|
|
293
|
+
);
|
|
294
|
+
assert_eq!(presented["instances"]["Shop::Item#a"]["name"], "x");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#[test]
|
|
298
|
+
fn present_is_an_error_when_the_kernel_fails() {
|
|
299
|
+
let failed = present_with(&serde_json::json!({}), |_| {
|
|
300
|
+
Ok(r#"{"error":"expected a top-level {\"steps\": [...]} object"}"#.to_string())
|
|
301
|
+
});
|
|
302
|
+
assert!(failed.is_err());
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
pub fn wasm_path() -> std::path::PathBuf {
|
|
967
306
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../dist/banking.wasm")
|
|
968
307
|
}
|
|
969
308
|
|
|
970
|
-
fn register(reference: &str) -> serde_json::Value {
|
|
309
|
+
pub fn register(reference: &str) -> serde_json::Value {
|
|
971
310
|
serde_json::json!({
|
|
972
311
|
"reference": { "value": reference },
|
|
973
312
|
"name": { "given": "Ada", "family": "Lovelace" },
|
|
@@ -979,9 +318,8 @@ pub(crate) mod tests {
|
|
|
979
318
|
// query answered against whatever state the journal currently holds.
|
|
980
319
|
#[tokio::test]
|
|
981
320
|
async fn a_declared_query_answers_against_current_state() {
|
|
982
|
-
let client =
|
|
983
|
-
|
|
984
|
-
let config = test_config("Banking", 1);
|
|
321
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
322
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
985
323
|
|
|
986
324
|
handle(
|
|
987
325
|
Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
|
|
@@ -1063,7 +401,7 @@ pub(crate) mod tests {
|
|
|
1063
401
|
assert!(outcome.accepted);
|
|
1064
402
|
assert_eq!(outcome.result["refusals"].as_array().unwrap().len(), 0);
|
|
1065
403
|
let guard = client.lock().await;
|
|
1066
|
-
let steps =
|
|
404
|
+
let steps = journal_sql::load_steps(&*guard).await.unwrap();
|
|
1067
405
|
assert_eq!(steps.len(), 1);
|
|
1068
406
|
|
|
1069
407
|
// The lineage write itself: a row landed in the era-tagged,
|
|
@@ -1105,20 +443,59 @@ pub(crate) mod tests {
|
|
|
1105
443
|
assert_eq!(id, "CUST-0001");
|
|
1106
444
|
}
|
|
1107
445
|
|
|
446
|
+
// The seal is part of the commit, not a tool run afterwards: the second accepted command
|
|
447
|
+
// leaves a seal through the second lineage row, and the chain walks clean from it.
|
|
448
|
+
#[tokio::test]
|
|
449
|
+
async fn a_commit_seals_the_lineage_chain_every_so_many_rows() {
|
|
450
|
+
let client = scratch_db("rust_host_dispatch_seals").await;
|
|
451
|
+
provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
|
|
452
|
+
let config = LineageConfig {
|
|
453
|
+
seal_every: Some(2),
|
|
454
|
+
..test_config("Banking", 1)
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
for id in ["CUST-0001", "CUST-0002", "CUST-0003"] {
|
|
458
|
+
let outcome = handle(
|
|
459
|
+
Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
|
|
460
|
+
"Banking::Customer.Register",
|
|
461
|
+
register(id),
|
|
462
|
+
None,
|
|
463
|
+
)
|
|
464
|
+
.await
|
|
465
|
+
.unwrap();
|
|
466
|
+
assert!(outcome.accepted, "{id} should be accepted");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let guard = client.lock().await;
|
|
470
|
+
let seals = crate::ledger::anchors(&*guard, "Banking").await.unwrap();
|
|
471
|
+
assert_eq!(
|
|
472
|
+
seals.iter().map(|seal| seal.through).collect::<Vec<_>>(),
|
|
473
|
+
vec![2],
|
|
474
|
+
"one seal, taken by the commit that made two rows unsealed"
|
|
475
|
+
);
|
|
476
|
+
assert_eq!(
|
|
477
|
+
crate::ledger::verify(&*guard, "Banking").await.unwrap(),
|
|
478
|
+
crate::ledger::Finding::Verified(crate::ledger::Report {
|
|
479
|
+
seals: 1,
|
|
480
|
+
entries: 2,
|
|
481
|
+
unsealed: 1
|
|
482
|
+
})
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
1108
486
|
// Proves `occurred_at` end to end through the real wasm module: the
|
|
1109
487
|
// string this crate stamps and sends is the one that comes back out
|
|
1110
488
|
// in the returned event, not just what the in-kernel unit tests prove.
|
|
1111
489
|
#[tokio::test]
|
|
1112
490
|
async fn occurred_at_is_stamped_onto_every_returned_event_with_this_crate_s_own_real_clock() {
|
|
1113
|
-
let client =
|
|
1114
|
-
provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
|
|
491
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1115
492
|
|
|
1116
|
-
let before = crate::
|
|
493
|
+
let before = crate::clock::iso8601_now();
|
|
1117
494
|
let outcome = handle(
|
|
1118
495
|
Host::new(
|
|
1119
496
|
&client,
|
|
1120
497
|
&wasm_path(),
|
|
1121
|
-
&
|
|
498
|
+
&crate::dispatch_tests::tests::memory_config("Banking"),
|
|
1122
499
|
&lambda_client::NeverInvoker,
|
|
1123
500
|
),
|
|
1124
501
|
"Banking::Customer.Register",
|
|
@@ -1127,7 +504,7 @@ pub(crate) mod tests {
|
|
|
1127
504
|
)
|
|
1128
505
|
.await
|
|
1129
506
|
.unwrap();
|
|
1130
|
-
let after = crate::
|
|
507
|
+
let after = crate::clock::iso8601_now();
|
|
1131
508
|
|
|
1132
509
|
assert!(outcome.accepted, "{:?}", outcome.result["refusals"]);
|
|
1133
510
|
let events = outcome.result["events"].as_array().unwrap();
|
|
@@ -1146,9 +523,8 @@ pub(crate) mod tests {
|
|
|
1146
523
|
|
|
1147
524
|
#[tokio::test]
|
|
1148
525
|
async fn rehydrates_prior_history_before_evaluating_the_new_command() {
|
|
1149
|
-
let client =
|
|
1150
|
-
|
|
1151
|
-
let config = test_config("Banking", 1);
|
|
526
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
527
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
1152
528
|
|
|
1153
529
|
let first = handle(
|
|
1154
530
|
Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
|
|
@@ -1181,7 +557,7 @@ pub(crate) mod tests {
|
|
|
1181
557
|
"duplicate registration should be refused, proving prior state was rehydrated: {:?}",
|
|
1182
558
|
second.result
|
|
1183
559
|
);
|
|
1184
|
-
let steps =
|
|
560
|
+
let steps = client.journal_steps().await;
|
|
1185
561
|
assert_eq!(
|
|
1186
562
|
steps.len(),
|
|
1187
563
|
1,
|
|
@@ -1214,11 +590,11 @@ pub(crate) mod tests {
|
|
|
1214
590
|
);
|
|
1215
591
|
|
|
1216
592
|
let guard = client.lock().await;
|
|
1217
|
-
let snapshot =
|
|
593
|
+
let snapshot = journal_sql::load_snapshot(&*guard)
|
|
1218
594
|
.await
|
|
1219
595
|
.unwrap()
|
|
1220
596
|
.expect("a snapshot should exist after the first accepted command");
|
|
1221
|
-
let tail =
|
|
597
|
+
let tail = journal_sql::load_steps_after(&*guard, snapshot.ordinal)
|
|
1222
598
|
.await
|
|
1223
599
|
.unwrap();
|
|
1224
600
|
assert!(tail.is_empty(), "the snapshot should already reflect every accepted command, leaving nothing to replay");
|
|
@@ -1232,7 +608,7 @@ pub(crate) mod tests {
|
|
|
1232
608
|
);
|
|
1233
609
|
}
|
|
1234
610
|
|
|
1235
|
-
let final_snapshot =
|
|
611
|
+
let final_snapshot = journal_sql::load_snapshot(&*client.lock().await)
|
|
1236
612
|
.await
|
|
1237
613
|
.unwrap()
|
|
1238
614
|
.unwrap();
|
|
@@ -1309,7 +685,7 @@ pub(crate) mod tests {
|
|
|
1309
685
|
.unwrap();
|
|
1310
686
|
assert!(first.accepted, "{:?}", first.result);
|
|
1311
687
|
|
|
1312
|
-
let whole_world =
|
|
688
|
+
let whole_world = journal_sql::load_snapshot(&*client.lock().await)
|
|
1313
689
|
.await
|
|
1314
690
|
.unwrap()
|
|
1315
691
|
.unwrap()
|
|
@@ -1420,7 +796,7 @@ pub(crate) mod tests {
|
|
|
1420
796
|
.await
|
|
1421
797
|
.unwrap();
|
|
1422
798
|
assert!(second.accepted, "{:?}", second.result);
|
|
1423
|
-
let after =
|
|
799
|
+
let after = journal_sql::load_snapshot(&*client.lock().await)
|
|
1424
800
|
.await
|
|
1425
801
|
.unwrap()
|
|
1426
802
|
.unwrap()
|
|
@@ -1435,7 +811,7 @@ pub(crate) mod tests {
|
|
|
1435
811
|
}
|
|
1436
812
|
|
|
1437
813
|
// The snapshot's ordinal and exact seed text, and how many commands are journaled.
|
|
1438
|
-
async fn snapshot_and_journal(client: &
|
|
814
|
+
async fn snapshot_and_journal(client: &Store) -> (i64, String, i64) {
|
|
1439
815
|
let guard = client.lock().await;
|
|
1440
816
|
let snapshot = guard
|
|
1441
817
|
.query_one("SELECT ordinal, seed::text FROM hecks_lambda_snapshot", &[])
|
|
@@ -1457,9 +833,8 @@ pub(crate) mod tests {
|
|
|
1457
833
|
// appended — silently violating the uniqueness the domain
|
|
1458
834
|
// itself enforces. With the lock serializing the whole
|
|
1459
835
|
// read-then-append sequence, exactly one must be accepted.
|
|
1460
|
-
let client =
|
|
1461
|
-
|
|
1462
|
-
let config = test_config("Banking", 1);
|
|
836
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
837
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
1463
838
|
let wasm_path = wasm_path();
|
|
1464
839
|
|
|
1465
840
|
let (first, second) = tokio::join!(
|
|
@@ -1483,7 +858,7 @@ pub(crate) mod tests {
|
|
|
1483
858
|
"exactly one of two concurrent duplicate registrations should be accepted: {:?} / {:?}",
|
|
1484
859
|
first.result, second.result
|
|
1485
860
|
);
|
|
1486
|
-
let steps =
|
|
861
|
+
let steps = client.journal_steps().await;
|
|
1487
862
|
assert_eq!(
|
|
1488
863
|
steps.len(),
|
|
1489
864
|
1,
|
|
@@ -1493,14 +868,13 @@ pub(crate) mod tests {
|
|
|
1493
868
|
|
|
1494
869
|
#[tokio::test]
|
|
1495
870
|
async fn read_reflects_prior_dispatches_without_persisting_anything_new() {
|
|
1496
|
-
let client =
|
|
1497
|
-
provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
|
|
871
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1498
872
|
|
|
1499
873
|
handle(
|
|
1500
874
|
Host::new(
|
|
1501
875
|
&client,
|
|
1502
876
|
&wasm_path(),
|
|
1503
|
-
&
|
|
877
|
+
&crate::dispatch_tests::tests::memory_config("Banking"),
|
|
1504
878
|
&lambda_client::NeverInvoker,
|
|
1505
879
|
),
|
|
1506
880
|
"Banking::Customer.Register",
|
|
@@ -1527,19 +901,19 @@ pub(crate) mod tests {
|
|
|
1527
901
|
|
|
1528
902
|
// The sharp proof a read never appends: the journal is exactly
|
|
1529
903
|
// as long after the read as it was before it.
|
|
1530
|
-
let steps =
|
|
904
|
+
let steps = client.journal_steps().await;
|
|
1531
905
|
assert_eq!(steps.len(), 1, "a read must never persist a journal row");
|
|
1532
906
|
}
|
|
1533
907
|
|
|
1534
908
|
// Records every call and answers with a canned "nothing refused"
|
|
1535
909
|
// body, proving a cross-domain policy reaches `lambda_client::deliver`
|
|
1536
910
|
// with the right name/payload end to end, short of a real AWS Lambda.
|
|
1537
|
-
struct RecordingInvoker {
|
|
911
|
+
pub struct RecordingInvoker {
|
|
1538
912
|
calls: std::sync::Mutex<Vec<(String, String)>>,
|
|
1539
913
|
}
|
|
1540
914
|
|
|
1541
915
|
impl RecordingInvoker {
|
|
1542
|
-
fn new() -> Self {
|
|
916
|
+
pub fn new() -> Self {
|
|
1543
917
|
Self {
|
|
1544
918
|
calls: std::sync::Mutex::new(Vec::new()),
|
|
1545
919
|
}
|
|
@@ -1566,15 +940,8 @@ pub(crate) mod tests {
|
|
|
1566
940
|
|
|
1567
941
|
#[tokio::test]
|
|
1568
942
|
async fn a_cross_domain_policy_delivers_through_the_real_rehydrate_replay_path() {
|
|
1569
|
-
let client =
|
|
1570
|
-
|
|
1571
|
-
&*client.lock().await,
|
|
1572
|
-
"Banking",
|
|
1573
|
-
1,
|
|
1574
|
-
&["Customer", "Account"],
|
|
1575
|
-
)
|
|
1576
|
-
.await;
|
|
1577
|
-
let config = test_config("Banking", 1);
|
|
943
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
944
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
1578
945
|
let invoker = RecordingInvoker::new();
|
|
1579
946
|
|
|
1580
947
|
handle(
|
|
@@ -1646,7 +1013,7 @@ pub(crate) mod tests {
|
|
|
1646
1013
|
.find(|r| r["policy"] == "ReviewOnFreeze")
|
|
1647
1014
|
.expect("ReviewOnFreeze should appear in \"reactions\" too");
|
|
1648
1015
|
assert_eq!(merged["on"], "AccountFrozen");
|
|
1649
|
-
assert_eq!(merged["trigger"], "Compliance::
|
|
1016
|
+
assert_eq!(merged["trigger"], "Compliance::Review.Open");
|
|
1650
1017
|
assert_eq!(merged["delivered"], true);
|
|
1651
1018
|
|
|
1652
1019
|
{
|
|
@@ -1657,9 +1024,10 @@ pub(crate) mod tests {
|
|
|
1657
1024
|
let sent: serde_json::Value = serde_json::from_str(payload).unwrap();
|
|
1658
1025
|
// Fully qualified, not the bare "Compliance.OpenReview":
|
|
1659
1026
|
// generated match arms are always "Domain::Aggregate.Command",
|
|
1660
|
-
// and Compliance's own aggregate is named `
|
|
1661
|
-
assert_eq!(sent["verb"], "Compliance::
|
|
1662
|
-
assert_eq!(sent["args"]["
|
|
1027
|
+
// and Compliance's own aggregate is named `Review`.
|
|
1028
|
+
assert_eq!(sent["verb"], "Compliance::Review.Open");
|
|
1029
|
+
assert_eq!(sent["args"]["subject_kind"], "account_freeze");
|
|
1030
|
+
assert_eq!(sent["args"]["subject_key"], "acct-freeze-me");
|
|
1663
1031
|
}
|
|
1664
1032
|
|
|
1665
1033
|
// Never re-delivered on a later replay: `pending_cross_domain`
|
|
@@ -1687,9 +1055,8 @@ pub(crate) mod tests {
|
|
|
1687
1055
|
// registry — real, corpus-declared.
|
|
1688
1056
|
#[tokio::test]
|
|
1689
1057
|
async fn a_role_gated_command_is_actually_checked_against_the_caller_role() {
|
|
1690
|
-
let client =
|
|
1691
|
-
|
|
1692
|
-
let config = test_config("Banking", 1);
|
|
1058
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1059
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
1693
1060
|
|
|
1694
1061
|
// The correct role succeeds, same as every other registration,
|
|
1695
1062
|
// just now with a caller actually bound and matching.
|
|
@@ -1734,7 +1101,7 @@ pub(crate) mod tests {
|
|
|
1734
1101
|
|
|
1735
1102
|
// And the refused command was never persisted — same discipline
|
|
1736
1103
|
// every other refusal in this file is held to.
|
|
1737
|
-
let steps =
|
|
1104
|
+
let steps = client.journal_steps().await;
|
|
1738
1105
|
assert_eq!(
|
|
1739
1106
|
steps.len(),
|
|
1740
1107
|
1,
|
|
@@ -1742,6 +1109,25 @@ pub(crate) mod tests {
|
|
|
1742
1109
|
);
|
|
1743
1110
|
}
|
|
1744
1111
|
|
|
1112
|
+
// ADR 0081 keeps a needed fact the caller supplied, but only the host and its peers may: a
|
|
1113
|
+
// request's `now` would move a time-window guard.
|
|
1114
|
+
#[test]
|
|
1115
|
+
fn only_a_request_from_outside_the_host_has_its_needed_facts_replaced() {
|
|
1116
|
+
let ir = serde_json::json!({"name": "Lease", "aggregates": [{"name": "Slot", "commands": [
|
|
1117
|
+
{"name": "Take", "attributes": [{"name": "now", "type": "Integer"}],
|
|
1118
|
+
"needs": [{"fact": "now"}]}
|
|
1119
|
+
]}]});
|
|
1120
|
+
let fill = |origin: Origin| {
|
|
1121
|
+
let mut args = serde_json::json!({"with": {"now": 42}});
|
|
1122
|
+
let clock = crate::needs::FixedClock(7);
|
|
1123
|
+
fill_arguments(origin, &ir, "Lease::Slot.Take", &mut args, &clock);
|
|
1124
|
+
args["with"]["now"].clone()
|
|
1125
|
+
};
|
|
1126
|
+
assert_eq!(fill(Origin::Request), serde_json::json!(7));
|
|
1127
|
+
assert_eq!(fill(Origin::External), serde_json::json!(42));
|
|
1128
|
+
assert_eq!(fill(Origin::HostInternal), serde_json::json!(42));
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1745
1131
|
#[test]
|
|
1746
1132
|
fn enforcement_reads_off_shadow_and_enforce_and_treats_anything_else_as_off() {
|
|
1747
1133
|
assert_eq!(Enforcement::parse(None), Enforcement::Off);
|
|
@@ -1778,9 +1164,8 @@ pub(crate) mod tests {
|
|
|
1778
1164
|
#[tokio::test]
|
|
1779
1165
|
async fn an_unidentified_caller_is_refused_when_enforced_and_let_through_when_off_or_shadowed()
|
|
1780
1166
|
{
|
|
1781
|
-
let client =
|
|
1782
|
-
|
|
1783
|
-
let config = test_config("Banking", 1);
|
|
1167
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1168
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
1784
1169
|
let go = |mode: Enforcement, id: &'static str| {
|
|
1785
1170
|
let client = &client;
|
|
1786
1171
|
let config = &config;
|
|
@@ -1825,7 +1210,7 @@ pub(crate) mod tests {
|
|
|
1825
1210
|
shadowed.result
|
|
1826
1211
|
);
|
|
1827
1212
|
|
|
1828
|
-
let steps =
|
|
1213
|
+
let steps = client.journal_steps().await;
|
|
1829
1214
|
assert_eq!(
|
|
1830
1215
|
steps.len(),
|
|
1831
1216
|
2,
|
|
@@ -1836,9 +1221,8 @@ pub(crate) mod tests {
|
|
|
1836
1221
|
// Shadow may only let through what Off would: a caller that stated a wrong role is refused.
|
|
1837
1222
|
#[tokio::test]
|
|
1838
1223
|
async fn shadow_refuses_a_stated_wrong_role_and_still_lets_an_unidentified_caller_through() {
|
|
1839
|
-
let client =
|
|
1840
|
-
|
|
1841
|
-
let config = test_config("Banking", 1);
|
|
1224
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1225
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
1842
1226
|
let go = |mode: Enforcement, origin: Origin, caller: Caller<'static>, id: &'static str| {
|
|
1843
1227
|
let client = &client;
|
|
1844
1228
|
let config = &config;
|
|
@@ -1891,7 +1275,7 @@ pub(crate) mod tests {
|
|
|
1891
1275
|
nobody.result
|
|
1892
1276
|
);
|
|
1893
1277
|
|
|
1894
|
-
let steps =
|
|
1278
|
+
let steps = client.journal_steps().await;
|
|
1895
1279
|
assert_eq!(
|
|
1896
1280
|
steps.len(),
|
|
1897
1281
|
1,
|
|
@@ -1903,9 +1287,8 @@ pub(crate) mod tests {
|
|
|
1903
1287
|
// unidentified caller is refused under enforce.
|
|
1904
1288
|
#[tokio::test]
|
|
1905
1289
|
async fn a_host_internal_dispatch_is_unchecked_under_enforce_but_an_external_one_is_refused() {
|
|
1906
|
-
let client =
|
|
1907
|
-
|
|
1908
|
-
let config = test_config("Banking", 1);
|
|
1290
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1291
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
1909
1292
|
let go = |mode: Enforcement, origin: Origin, id: &'static str| {
|
|
1910
1293
|
let client = &client;
|
|
1911
1294
|
let config = &config;
|
|
@@ -2216,15 +1599,8 @@ pub(crate) mod tests {
|
|
|
2216
1599
|
#[tokio::test]
|
|
2217
1600
|
async fn a_cross_domain_delivery_that_exhausts_every_retry_dead_letters_and_still_fails_the_invocation(
|
|
2218
1601
|
) {
|
|
2219
|
-
let client =
|
|
2220
|
-
|
|
2221
|
-
&*client.lock().await,
|
|
2222
|
-
"Banking",
|
|
2223
|
-
1,
|
|
2224
|
-
&["Customer", "Account"],
|
|
2225
|
-
)
|
|
2226
|
-
.await;
|
|
2227
|
-
let config = test_config("Banking", 1);
|
|
1602
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1603
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
2228
1604
|
let invoker = AlwaysFailingInvoker::new();
|
|
2229
1605
|
|
|
2230
1606
|
handle(
|
|
@@ -2284,42 +1660,28 @@ pub(crate) mod tests {
|
|
|
2284
1660
|
// The local command still committed: cross-domain delivery runs
|
|
2285
1661
|
// strictly after commit, so a delivery failure never unwinds it.
|
|
2286
1662
|
// The dead letter existing at all proves the match already ran.
|
|
2287
|
-
let
|
|
2288
|
-
let dead_letters = guard
|
|
2289
|
-
.query("SELECT policy, target_domain, target_verb, attempts FROM hecks_cross_domain_dead_letters", &[])
|
|
2290
|
-
.await
|
|
2291
|
-
.unwrap();
|
|
1663
|
+
let dead_letters = client.dead_letter_rows().await;
|
|
2292
1664
|
assert_eq!(
|
|
2293
1665
|
dead_letters.len(),
|
|
2294
1666
|
1,
|
|
2295
1667
|
"exactly one exhausted delivery should have been dead-lettered: {dead_letters:?}"
|
|
2296
1668
|
);
|
|
2297
|
-
let policy
|
|
2298
|
-
let target_domain: String = dead_letters[0].get(1);
|
|
2299
|
-
let target_verb: String = dead_letters[0].get(2);
|
|
2300
|
-
let attempts: i32 = dead_letters[0].get(3);
|
|
1669
|
+
let (policy, target_domain, target_verb, attempts) = dead_letters[0].clone();
|
|
2301
1670
|
assert_eq!(policy, "ReviewOnFreeze");
|
|
2302
1671
|
assert_eq!(target_domain, "Compliance");
|
|
2303
|
-
assert_eq!(target_verb, "Compliance::
|
|
1672
|
+
assert_eq!(target_verb, "Compliance::Review.Open");
|
|
2304
1673
|
assert_eq!(attempts, lambda_client::MAX_DELIVERY_ATTEMPTS as i32);
|
|
2305
1674
|
}
|
|
2306
1675
|
|
|
2307
1676
|
// Pins a real drop: a second cross-domain reaction from the same
|
|
2308
1677
|
// step must not go dark just because the first one's delivery
|
|
2309
1678
|
// exhausted its retries. `SafeDepositBox.Surrender` emits both
|
|
2310
|
-
// `BoxSurrendered` (-> Compliance) and `
|
|
1679
|
+
// `BoxSurrendered` (-> Compliance) and `KeyReturnRequested` (-> Notifications)
|
|
2311
1680
|
// from one command.
|
|
2312
1681
|
#[tokio::test]
|
|
2313
1682
|
async fn a_failed_cross_domain_delivery_does_not_drop_a_sibling_reaction_from_the_same_step() {
|
|
2314
|
-
let client =
|
|
2315
|
-
|
|
2316
|
-
&*client.lock().await,
|
|
2317
|
-
"Banking",
|
|
2318
|
-
1,
|
|
2319
|
-
&["Customer", "SafeDepositBox"],
|
|
2320
|
-
)
|
|
2321
|
-
.await;
|
|
2322
|
-
let config = test_config("Banking", 1);
|
|
1683
|
+
let client = crate::dispatch_tests::tests::memory_store();
|
|
1684
|
+
let config = crate::dispatch_tests::tests::memory_config("Banking");
|
|
2323
1685
|
let invoker = AlwaysFailingInvoker::new();
|
|
2324
1686
|
|
|
2325
1687
|
handle(
|
|
@@ -2375,19 +1737,224 @@ pub(crate) mod tests {
|
|
|
2375
1737
|
the fix under test is exactly this: a fresh Vec would only ever show one policy's worth"
|
|
2376
1738
|
);
|
|
2377
1739
|
|
|
2378
|
-
let
|
|
2379
|
-
|
|
2380
|
-
.query(
|
|
2381
|
-
"SELECT policy FROM hecks_cross_domain_dead_letters ORDER BY policy",
|
|
2382
|
-
&[],
|
|
2383
|
-
)
|
|
1740
|
+
let policies: Vec<String> = client
|
|
1741
|
+
.dead_letter_rows()
|
|
2384
1742
|
.await
|
|
2385
|
-
.
|
|
2386
|
-
|
|
1743
|
+
.into_iter()
|
|
1744
|
+
.map(|row| row.0)
|
|
1745
|
+
.collect();
|
|
2387
1746
|
assert_eq!(
|
|
2388
1747
|
policies,
|
|
2389
1748
|
vec!["FlagKeyReturn".to_string(), "ReviewOnBoxSurrender".to_string()],
|
|
2390
1749
|
"BOTH sibling reactions must be dead-lettered, not just whichever ran first: {policies:?}"
|
|
2391
1750
|
);
|
|
2392
1751
|
}
|
|
1752
|
+
/// The wasm plugin fixture (spec/fixtures/rust_host/pluginned): its kernel and its IR, which
|
|
1753
|
+
/// carries the doubler plugin that answers `Ledger.Doubled` and refuses `Ledger.Tripled`.
|
|
1754
|
+
fn pluginned() -> (std::path::PathBuf, serde_json::Value) {
|
|
1755
|
+
let dist = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../dist");
|
|
1756
|
+
let ir = std::fs::read_to_string(dist.join("pluginned.ir.json"))
|
|
1757
|
+
.expect("build the pluginned fixture");
|
|
1758
|
+
(
|
|
1759
|
+
dist.join("pluginned.wasm"),
|
|
1760
|
+
serde_json::from_str(&ir).unwrap(),
|
|
1761
|
+
)
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
async fn ask_pluginned(
|
|
1765
|
+
client: &Store,
|
|
1766
|
+
question: &str,
|
|
1767
|
+
args: serde_json::Value,
|
|
1768
|
+
) -> serde_json::Value {
|
|
1769
|
+
let (wasm, ir) = pluginned();
|
|
1770
|
+
let asked = serde_json::json!({ "query": question, "args": args });
|
|
1771
|
+
crate::query_step::answer(&asked, Some(&ir), "Pluginned", client, &wasm)
|
|
1772
|
+
.await
|
|
1773
|
+
.unwrap()
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/// A query a wasm plugin answers, end to end: a command through the real kernel stores the
|
|
1777
|
+
/// ledger, and the query step hands the plugin that record's state, read back from Postgres.
|
|
1778
|
+
#[tokio::test]
|
|
1779
|
+
async fn a_plugin_answers_with_the_referenced_record_as_stored() {
|
|
1780
|
+
let client = scratch_db("rust_host_dispatch_test_plugin").await;
|
|
1781
|
+
provision_lineage(&*client.lock().await, "Pluginned", 1, &["Ledger"]).await;
|
|
1782
|
+
let (wasm, _) = pluginned();
|
|
1783
|
+
let config = test_config("Pluginned", 1);
|
|
1784
|
+
let opened = handle(
|
|
1785
|
+
Host::new(&client, &wasm, &config, &lambda_client::NeverInvoker),
|
|
1786
|
+
"Pluginned::Ledger.Open",
|
|
1787
|
+
serde_json::json!({ "name": { "value": "house" }, "note": { "value": "rent" } }),
|
|
1788
|
+
None,
|
|
1789
|
+
)
|
|
1790
|
+
.await
|
|
1791
|
+
.unwrap();
|
|
1792
|
+
assert!(
|
|
1793
|
+
opened.accepted,
|
|
1794
|
+
"opening the ledger: {}",
|
|
1795
|
+
opened.result["refusals"]
|
|
1796
|
+
);
|
|
1797
|
+
|
|
1798
|
+
let answer = ask_pluginned(
|
|
1799
|
+
&client,
|
|
1800
|
+
"Pluginned::Ledger.Doubled",
|
|
1801
|
+
serde_json::json!({ "entry": "house", "amount": 21 }),
|
|
1802
|
+
)
|
|
1803
|
+
.await;
|
|
1804
|
+
|
|
1805
|
+
assert_eq!(
|
|
1806
|
+
answer["queries"][0]["rows"],
|
|
1807
|
+
serde_json::json!([{ "amount": 42, "note": "rent" }])
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
#[tokio::test]
|
|
1812
|
+
async fn a_plugin_refusal_and_a_missing_record_come_back_as_refusals() {
|
|
1813
|
+
let client = scratch_db("rust_host_dispatch_test_plugin_refusals").await;
|
|
1814
|
+
provision_lineage(&*client.lock().await, "Pluginned", 1, &["Ledger"]).await;
|
|
1815
|
+
|
|
1816
|
+
let refused = ask_pluginned(
|
|
1817
|
+
&client,
|
|
1818
|
+
"Pluginned::Ledger.Tripled",
|
|
1819
|
+
serde_json::json!({ "amount": 1 }),
|
|
1820
|
+
)
|
|
1821
|
+
.await;
|
|
1822
|
+
let missing = ask_pluginned(
|
|
1823
|
+
&client,
|
|
1824
|
+
"Pluginned::Ledger.Doubled",
|
|
1825
|
+
serde_json::json!({ "entry": "nowhere", "amount": 1 }),
|
|
1826
|
+
)
|
|
1827
|
+
.await;
|
|
1828
|
+
|
|
1829
|
+
assert_eq!(refused["refusals"][0]["kind"], "PluginRefusal");
|
|
1830
|
+
let refusal = refused["refusals"][0]["error"].as_str().unwrap();
|
|
1831
|
+
assert!(
|
|
1832
|
+
refusal.contains("doubler does not answer Pluginned::Ledger.Tripled"),
|
|
1833
|
+
"{refusal}"
|
|
1834
|
+
);
|
|
1835
|
+
assert_eq!(missing["refusals"][0]["kind"], "NotFound");
|
|
1836
|
+
assert_eq!(
|
|
1837
|
+
missing["refusals"][0]["error"],
|
|
1838
|
+
"entry: no Ledger \"nowhere\""
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
#[cfg(test)]
|
|
1844
|
+
mod preview_tests {
|
|
1845
|
+
use crate::dispatch_tests::tests::{memory_config, memory_store, register, wasm_path};
|
|
1846
|
+
use crate::host::Host;
|
|
1847
|
+
use crate::journal;
|
|
1848
|
+
use crate::lambda_client;
|
|
1849
|
+
use crate::store::{Accepted, DeadLetter, LineageWrite, Store, StoreExt, StoreFixtures};
|
|
1850
|
+
use crate::wasm_runner;
|
|
1851
|
+
use rust_host_core::dispatch::*;
|
|
1852
|
+
use std::path::Path;
|
|
1853
|
+
|
|
1854
|
+
// The client envelope a preview of `verb` answers for `invocation`.
|
|
1855
|
+
async fn preview_of(
|
|
1856
|
+
client: &Store,
|
|
1857
|
+
verb: &str,
|
|
1858
|
+
invocation: serde_json::Value,
|
|
1859
|
+
) -> serde_json::Value {
|
|
1860
|
+
let config = memory_config("Banking");
|
|
1861
|
+
let wasm = wasm_path();
|
|
1862
|
+
let host = Host::new(client, &wasm, &config, &lambda_client::NeverInvoker);
|
|
1863
|
+
let outcome = preview_as(host, verb, invocation, Caller::default())
|
|
1864
|
+
.await
|
|
1865
|
+
.unwrap();
|
|
1866
|
+
crate::validation::answer(&outcome.result)
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
async fn preview(client: &Store, facts: serde_json::Value) -> serde_json::Value {
|
|
1870
|
+
preview_of(
|
|
1871
|
+
client,
|
|
1872
|
+
"Banking::Customer.Register",
|
|
1873
|
+
facts_invocation(facts).unwrap(),
|
|
1874
|
+
)
|
|
1875
|
+
.await
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
async fn register_for_real(client: &Store, id: &str) -> Outcome {
|
|
1879
|
+
let config = memory_config("Banking");
|
|
1880
|
+
let wasm = wasm_path();
|
|
1881
|
+
let host = Host::new(client, &wasm, &config, &lambda_client::NeverInvoker);
|
|
1882
|
+
handle_facts(host, "Banking::Customer.Register", register(id), None)
|
|
1883
|
+
.await
|
|
1884
|
+
.unwrap()
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
#[tokio::test]
|
|
1888
|
+
async fn a_preview_of_an_acceptable_command_answers_ok_and_keeps_nothing() {
|
|
1889
|
+
let client = memory_store();
|
|
1890
|
+
assert_eq!(
|
|
1891
|
+
preview(&client, register("CUST-P1")).await,
|
|
1892
|
+
serde_json::json!({ "ok": true, "refusals": [] })
|
|
1893
|
+
);
|
|
1894
|
+
assert!(
|
|
1895
|
+
register_for_real(&client, "CUST-P1").await.accepted,
|
|
1896
|
+
"the preview left a record behind, so the real registration was refused"
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
#[tokio::test]
|
|
1901
|
+
async fn a_preview_of_a_refused_command_answers_why_with_its_kind_and_stage() {
|
|
1902
|
+
let client = memory_store();
|
|
1903
|
+
register_for_real(&client, "CUST-P2").await;
|
|
1904
|
+
let answer = preview(&client, register("CUST-P2")).await;
|
|
1905
|
+
assert_eq!(answer["ok"], false);
|
|
1906
|
+
let refusal = &answer["refusals"][0];
|
|
1907
|
+
assert_eq!(refusal["refusal"], "AlreadyExists");
|
|
1908
|
+
assert_eq!(
|
|
1909
|
+
(refusal["kind"].as_str(), refusal["stage"].as_str()),
|
|
1910
|
+
(Some("refused"), Some("state"))
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
#[tokio::test]
|
|
1915
|
+
async fn a_preview_names_an_unknown_argument_in_the_arguments_stage() {
|
|
1916
|
+
let client = memory_store();
|
|
1917
|
+
let mut facts = register("CUST-P3");
|
|
1918
|
+
facts["bogus"] = serde_json::json!(1);
|
|
1919
|
+
let answer = preview(&client, facts).await;
|
|
1920
|
+
let refusal = &answer["refusals"][0];
|
|
1921
|
+
assert_eq!(refusal["refusal"], "UnknownArgument");
|
|
1922
|
+
assert_eq!(refusal["stage"], "arguments");
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
#[tokio::test]
|
|
1926
|
+
async fn a_preview_against_an_existing_record_is_routed_to_it() {
|
|
1927
|
+
let client = memory_store();
|
|
1928
|
+
register_for_real(&client, "CUST-P4").await;
|
|
1929
|
+
let close = routed_invocation(serde_json::json!("CUST-P4"), serde_json::json!({})).unwrap();
|
|
1930
|
+
assert_eq!(
|
|
1931
|
+
preview_of(&client, "Banking::Customer.Close", close).await,
|
|
1932
|
+
serde_json::json!({ "ok": true, "refusals": [] })
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
#[tokio::test]
|
|
1937
|
+
async fn a_preview_against_a_record_that_does_not_exist_is_kind_not_found() {
|
|
1938
|
+
let client = memory_store();
|
|
1939
|
+
let close = routed_invocation(serde_json::json!("NOBODY"), serde_json::json!({})).unwrap();
|
|
1940
|
+
let answer = preview_of(&client, "Banking::Customer.Close", close).await;
|
|
1941
|
+
assert_eq!(answer["ok"], false);
|
|
1942
|
+
assert_eq!(answer["refusals"][0]["kind"], "not_found");
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// The body protocol: `"dry_run": true` beside a command, answered as the client envelope even
|
|
1946
|
+
// when the request also asks for `bare`.
|
|
1947
|
+
#[tokio::test]
|
|
1948
|
+
async fn the_body_protocol_answers_a_dry_run_as_the_client_envelope() {
|
|
1949
|
+
let client = memory_store();
|
|
1950
|
+
let config = memory_config("Banking");
|
|
1951
|
+
let wasm = wasm_path();
|
|
1952
|
+
let host = Host::new(&client, &wasm, &config, &lambda_client::NeverInvoker);
|
|
1953
|
+
let body = serde_json::json!({
|
|
1954
|
+
"verb": "Banking::Customer.Register", "with": register("CUST-P5"),
|
|
1955
|
+
"dry_run": true, "bare": true,
|
|
1956
|
+
});
|
|
1957
|
+
let answer = crate::server::dispatch_body(body, host).await.unwrap();
|
|
1958
|
+
assert_eq!(answer, serde_json::json!({ "ok": true, "refusals": [] }));
|
|
1959
|
+
}
|
|
2393
1960
|
}
|