hecks 1.3.0 → 1.4.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/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +170 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +180 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +11 -11
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +35 -35
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +22 -2
|
@@ -2,17 +2,17 @@ require "time"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
|
-
#
|
|
5
|
+
# **The `authorization` port, fulfilled by governance** — same registry,
|
|
6
6
|
# same boot, so "ask Governance" is a dispatch against records
|
|
7
7
|
# already sitting in the store this adapter is handed, not a bridge
|
|
8
8
|
# to a second runtime. `Runtime::Dispatcher.new(registry)` is cheap
|
|
9
9
|
# to build fresh per call (`Registry#capability_graph`'s own
|
|
10
10
|
# neighbor, `#repository`, does the same kind of on-demand build) —
|
|
11
11
|
# nothing here holds one across calls, so there is no boot-order
|
|
12
|
-
# dependency on
|
|
12
|
+
# dependency on when Governance's bluebook loads relative to this
|
|
13
13
|
# adapter, only that it has by the time `holds_role?` is called.
|
|
14
14
|
#
|
|
15
|
-
#
|
|
15
|
+
# An active assignment, not merely a historical one : `RoleAssignment`
|
|
16
16
|
# answers with every assignment an actor has ever held
|
|
17
17
|
# (`AssignmentsForActor`'s own description — "currently or
|
|
18
18
|
# historically") and leaves `ends_at` for the caller to read, the
|
|
@@ -21,7 +21,7 @@ module Hecks
|
|
|
21
21
|
module GovernanceAuthorization
|
|
22
22
|
module_function
|
|
23
23
|
|
|
24
|
-
# `as_of` and `scope` are
|
|
24
|
+
# `as_of` and `scope` are both optional, same opt-in shape
|
|
25
25
|
# `refuse_role_mismatch` already gives `actor_id` itself — an
|
|
26
26
|
# unbound `as_of` skips the `starts_at` check and an unbound
|
|
27
27
|
# `scope` skips the `scope` check, exactly the behavior before
|
|
@@ -31,7 +31,7 @@ module Hecks
|
|
|
31
31
|
# for why the dispatch path must not consult the clock itself.
|
|
32
32
|
def holds_role?(registry, actor_id:, role:, as_of: nil, scope: nil)
|
|
33
33
|
rows = Runtime::Dispatcher.new(registry).query(
|
|
34
|
-
|
|
34
|
+
provided_verb(registry, :assignments),
|
|
35
35
|
actor_id: { value: actor_id.to_s }
|
|
36
36
|
)
|
|
37
37
|
|
|
@@ -43,20 +43,20 @@ module Hecks
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
# `scope`
|
|
46
|
+
# `scope` unchecked when not stated, same as every other opt-in
|
|
47
47
|
# field here — a caller that never says which scope it is acting
|
|
48
48
|
# in gets the pre-scope behavior: any live assignment for the role
|
|
49
49
|
# authorizes, everywhere. A caller that does state one only
|
|
50
|
-
# authorizes against an assignment granted for
|
|
50
|
+
# authorizes against an assignment granted for that scope.
|
|
51
51
|
def in_scope?(row, scope)
|
|
52
52
|
scope.nil? || row[:scope][:value] == scope.to_s
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
# `starts_at`
|
|
55
|
+
# `starts_at` is a free-text string in the bluebook (`Timestamp`'s
|
|
56
56
|
# only invariant is "present", not any particular format) — parsed
|
|
57
57
|
# here with `Time.parse` rather than compared lexically, since
|
|
58
58
|
# nothing guarantees every caller writes it zero-padded ISO 8601.
|
|
59
|
-
#
|
|
59
|
+
# Fails closed : a `starts_at` that does not parse is treated as
|
|
60
60
|
# not-yet-started rather than silently ignored, the same direction
|
|
61
61
|
# every other check in this method already fails.
|
|
62
62
|
def started?(row, as_of)
|
|
@@ -67,21 +67,21 @@ module Hecks
|
|
|
67
67
|
false
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
#
|
|
70
|
+
# **The other half** — may role X act as role Y. `RoleTransition.Allowed`
|
|
71
71
|
# is identified by the exact pair, so at most one row ever comes
|
|
72
72
|
# back ; still read as `.any?` rather than trusting that structurally,
|
|
73
73
|
# the same defensiveness `holds_role?` already has to have anyway
|
|
74
74
|
# since `AssignmentsForActor` can return several.
|
|
75
75
|
def authorized_as?(registry, from_role:, to_role:)
|
|
76
76
|
rows = Runtime::Dispatcher.new(registry).query(
|
|
77
|
-
|
|
77
|
+
provided_verb(registry, :transitions),
|
|
78
78
|
from_role: { value: from_role.to_s }, to_role: { value: to_role.to_s }
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
rows.any? { |row| row[:ends_at].nil? }
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
#
|
|
84
|
+
# The role itself, not just a yes/no about one — the same
|
|
85
85
|
# `AssignmentsForActor` query `holds_role?` runs, just returning
|
|
86
86
|
# the live (non-revoked) row's `role_name` instead of comparing it
|
|
87
87
|
# against a caller-supplied guess. `nil` for no live assignment at
|
|
@@ -89,13 +89,31 @@ module Hecks
|
|
|
89
89
|
# a default) is domain-specific and does not belong here.
|
|
90
90
|
def live_role_for(registry, actor_id:)
|
|
91
91
|
rows = Runtime::Dispatcher.new(registry).query(
|
|
92
|
-
|
|
92
|
+
provided_verb(registry, :assignments),
|
|
93
93
|
actor_id: { value: actor_id.to_s }
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
live = rows.find { |row| row[:ends_at].nil? }
|
|
97
97
|
live && live[:role_name][:value]
|
|
98
98
|
end
|
|
99
|
+
|
|
100
|
+
# The verb, read from the provider's own declaration — `provides
|
|
101
|
+
# "authorization", assignments: ..., transitions: ...` on whichever
|
|
102
|
+
# loaded chapter declares it (Governance's, in every boot today).
|
|
103
|
+
# Exactly one provider, the same "the runtime will not choose for
|
|
104
|
+
# you" rule `Ports::Authorization.adapter` applies to adapters.
|
|
105
|
+
def provided_verb(registry, key)
|
|
106
|
+
providers = registry.authorization_providers
|
|
107
|
+
unless providers.size == 1
|
|
108
|
+
raise Runtime::WiringError,
|
|
109
|
+
"#{providers.size} loaded chapters provide \"authorization\"" \
|
|
110
|
+
"#{" (#{providers.map(&:name).sort.join(', ')})" unless providers.empty?} — " \
|
|
111
|
+
"a role lookup needs exactly one (framework members declaring it: " \
|
|
112
|
+
"#{Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION).join(', ')})"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
providers.first.provided_verb(Bluebook::Capabilities::AUTHORIZATION, key)
|
|
116
|
+
end
|
|
99
117
|
end
|
|
100
118
|
end
|
|
101
119
|
end
|
|
@@ -11,13 +11,13 @@ module Hecks
|
|
|
11
11
|
|
|
12
12
|
File.readlines(@journal_path, chomp: true).reject(&:empty?).map do |line|
|
|
13
13
|
value = JSON.parse(line)
|
|
14
|
-
state = value["state"]
|
|
14
|
+
state = Ports::Persistence::StateCodec.decode(@aggregate, value["state"])
|
|
15
15
|
Ports::Persistence::Entry.new(operation: value.fetch("operation"), id: value.fetch("id"), state: state,
|
|
16
16
|
mirrors: value["mirrors"])
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# An
|
|
20
|
+
# An explicit, opt-in maintenance operation — never run
|
|
21
21
|
# automatically after an ordinary save/delete. Heki's journal is
|
|
22
22
|
# not a disposable write-ahead log: it is this adapter's own
|
|
23
23
|
# answer to `entries`, and `entries` is a real port contract
|
|
@@ -26,7 +26,7 @@ module Hecks
|
|
|
26
26
|
# and `Registry#projection_current?` to catch a projection up to
|
|
27
27
|
# its authoritative source, and by `bin/history` to show "every
|
|
28
28
|
# journal entry a domain's append-only adapters hold" — the same
|
|
29
|
-
# contract Postgres/Sqlite/D1 uphold by way of a journal
|
|
29
|
+
# contract Postgres/Sqlite/D1 uphold by way of a journal table
|
|
30
30
|
# that is never pruned. A real example (`examples/banking`,
|
|
31
31
|
# `persisted_by("Heki")` + `projected_by("SqliteProjection")`)
|
|
32
32
|
# depends on this today. Compacting throws that full history away
|
|
@@ -95,7 +95,8 @@ module Hecks
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def append_entry(operation, id, state)
|
|
98
|
-
|
|
98
|
+
encoded = Ports::Persistence::StateCodec.encode(@aggregate, state)
|
|
99
|
+
line = "#{JSON.generate(operation: operation, id: id.to_s, state: encoded, mirrors: @entry_mirrors)}\n"
|
|
99
100
|
|
|
100
101
|
# One write, not JSON-then-newline as two: two concurrent
|
|
101
102
|
# appends can only interleave *between* writes, never inside
|
|
@@ -4,8 +4,8 @@ require_relative "journal"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Adapters
|
|
6
6
|
class Heki
|
|
7
|
-
#
|
|
8
|
-
# a
|
|
7
|
+
# The optional saga-persistence capability (§2), Heki's own shape —
|
|
8
|
+
# a sibling snapshot+journal file pair, built the exact same way an
|
|
9
9
|
# aggregate's own persistence already is: `Snapshot`/`Journal`
|
|
10
10
|
# (heki/snapshot.rb, heki/journal.rb) operate generically on
|
|
11
11
|
# `@path`/`@journal_path`/`@entry_mirrors` and never touch
|
|
@@ -15,16 +15,16 @@ module Hecks
|
|
|
15
15
|
# Reserved file name (`hecks_saga_instances.heki`, matching the
|
|
16
16
|
# `hecks_`-prefix convention every other new saga table in this
|
|
17
17
|
# work uses) avoids colliding with any real aggregate's own
|
|
18
|
-
# `storage_name`. Lives in the
|
|
18
|
+
# `storage_name`. Lives in the same directory an aggregate's own
|
|
19
19
|
# `.heki` file would (`File.dirname(@path)`, `Heki`'s own call
|
|
20
20
|
# below) — which, since Heki's `resolve_path` has no per-domain
|
|
21
|
-
# component at all, is typically shared across
|
|
21
|
+
# component at all, is typically shared across every domain
|
|
22
22
|
# booted from the same `root`. `domain` is therefore carried
|
|
23
23
|
# inside each record and filtered on read, the same reason
|
|
24
24
|
# Postgres's own `hecks_saga_instances` keeps an explicit `domain`
|
|
25
25
|
# column under schema isolation (§3).
|
|
26
26
|
#
|
|
27
|
-
#
|
|
27
|
+
# One flat records hash, keyed by a composite string (Heki's own
|
|
28
28
|
# snapshot format is id-keyed, not tuple-keyed) — never exposed
|
|
29
29
|
# outside this class; `each_saga` yields the five real fields a
|
|
30
30
|
# caller actually wants, not the internal key shape.
|
|
@@ -30,7 +30,7 @@ module Hecks
|
|
|
30
30
|
@path = resolve_path(settings, root)
|
|
31
31
|
@journal_path = "#{@path}.journal"
|
|
32
32
|
@events = []
|
|
33
|
-
#
|
|
33
|
+
# The optional saga-persistence capability's own scoping (§2/§4)
|
|
34
34
|
# — falls back to the aggregate's own name for a directly-
|
|
35
35
|
# instantiated adapter (specs), same fallback shape Postgres's
|
|
36
36
|
# own @domain already uses.
|
|
@@ -65,7 +65,7 @@ module Hecks
|
|
|
65
65
|
# threads this through; Heki's own never did, which made
|
|
66
66
|
# `none_in_state?` (Ports::Query::InMemory) unconditionally
|
|
67
67
|
# return `true` (its own graceful "no registry, no way to look
|
|
68
|
-
# the target up" default) for
|
|
68
|
+
# the target up" default) for every `none_in_state` where-clause
|
|
69
69
|
# against a Heki-backed aggregate — silently excluding nothing,
|
|
70
70
|
# always, no matter the actual target state.
|
|
71
71
|
def query(specification, args = {}, context: {})
|
|
@@ -86,7 +86,11 @@ module Hecks
|
|
|
86
86
|
# overwrite that write on disk rather than layer on top of it.
|
|
87
87
|
def project(entry)
|
|
88
88
|
current = read
|
|
89
|
-
entry.save?
|
|
89
|
+
if entry.save?
|
|
90
|
+
current[entry.id] = Ports::Persistence::StateCodec.encode(@aggregate, entry.state)
|
|
91
|
+
else
|
|
92
|
+
current.delete(entry.id)
|
|
93
|
+
end
|
|
90
94
|
write(current)
|
|
91
95
|
@store = current
|
|
92
96
|
entry
|
|
@@ -114,7 +118,7 @@ module Hecks
|
|
|
114
118
|
|
|
115
119
|
def record_event(event) = @events << event
|
|
116
120
|
|
|
117
|
-
# ── the
|
|
121
|
+
# ── the optional saga-persistence capability (§2) — Heki's own
|
|
118
122
|
# shape (a sibling snapshot+journal file pair, `SagaStore`,
|
|
119
123
|
# heki/saga_store.rb) rather than a table in a store this adapter
|
|
120
124
|
# doesn't have.
|
|
@@ -134,11 +138,13 @@ module Hecks
|
|
|
134
138
|
@saga_store ||= SagaStore.new(File.dirname(@path))
|
|
135
139
|
end
|
|
136
140
|
|
|
141
|
+
# `record` is the snapshot/journal's own string-keyed JSON — decoded
|
|
142
|
+
# deep through the state codec, never symbolized one level by hand.
|
|
137
143
|
def instance(id, record)
|
|
138
144
|
Runtime::Instance.new(
|
|
139
145
|
aggregate: @aggregate,
|
|
140
146
|
id: id,
|
|
141
|
-
state:
|
|
147
|
+
state: Ports::Persistence::StateCodec.decode(@aggregate, record)
|
|
142
148
|
)
|
|
143
149
|
end
|
|
144
150
|
|
|
@@ -152,10 +158,10 @@ module Hecks
|
|
|
152
158
|
end
|
|
153
159
|
|
|
154
160
|
# `dir: :default` — a bare Symbol, the framework's own convention
|
|
155
|
-
# for "a
|
|
161
|
+
# for "a declared value that resolves by convention, never a silent
|
|
156
162
|
# fallback" — used to crash `File.join` outright
|
|
157
163
|
# (`TypeError: no implicit conversion of Symbol into String`):
|
|
158
|
-
# `resolve_path` only ever checked for a
|
|
164
|
+
# `resolve_path` only ever checked for a missing `dir` setting,
|
|
159
165
|
# never a Symbol one. Treated the same as no setting at all — falls
|
|
160
166
|
# back to the existing "data" default, not a new special case.
|
|
161
167
|
def resolve_path(settings, root)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Adapters
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# The `identity_resolution` port, fulfilled by the Identity framework
|
|
4
|
+
# bluebook — same registry, same boot, so this is a dispatch against
|
|
5
5
|
# records already sitting in the store this adapter is handed, not a
|
|
6
6
|
# bridge to a second runtime. Same reasoning `GovernanceAuthorization`
|
|
7
7
|
# gives for itself, one port over.
|
|
@@ -6,7 +6,7 @@ require_relative "../../runtime/errors"
|
|
|
6
6
|
|
|
7
7
|
module Hecks
|
|
8
8
|
module Adapters
|
|
9
|
-
#
|
|
9
|
+
# Shared by every adapter that holds decoded Ruby records rather than
|
|
10
10
|
# running real SQL (Memory, Lambda, Heki) — the identical dotted-path
|
|
11
11
|
# value-object member-picking Postgres/Sqlite/D1's own
|
|
12
12
|
# order_expression does (numeric member wins, else the one-field
|
|
@@ -16,7 +16,7 @@ module Hecks
|
|
|
16
16
|
module InMemoryOrdering
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
|
-
# order_by
|
|
19
|
+
# order_by is a runtime value (an HTTP query param, in the
|
|
20
20
|
# console's case), not framework-authored bluebook source — see
|
|
21
21
|
# postgres.rb's own all for the full reasoning. Whitelisted the
|
|
22
22
|
# identical way before FieldPath.dig ever runs.
|
|
@@ -46,7 +46,7 @@ module Hecks
|
|
|
46
46
|
vo = aggregate.value_object(attribute.type)
|
|
47
47
|
return field.to_s unless vo
|
|
48
48
|
|
|
49
|
-
# Numeric member first, then the
|
|
49
|
+
# Numeric member first, then the sole attribute whatever it is
|
|
50
50
|
# named (single-attribute value objects strictly answer `.value`
|
|
51
51
|
# — the same generalization `SqlQueryBuilder#query_expression`
|
|
52
52
|
# makes for the column side, kept in lockstep so Memory and SQL
|
|
@@ -3,31 +3,56 @@ require "json"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
5
|
class Lambda
|
|
6
|
-
#
|
|
6
|
+
# **The thin AWS client** — shared by this adapter's own read methods
|
|
7
7
|
# (Lambda#find/#all/#count/#query) and Runtime::RemoteDispatcher's
|
|
8
8
|
# write methods. One Lambda invoke, one JSON round trip, nothing
|
|
9
9
|
# domain-specific: neither caller needs to know an AWS SDK is
|
|
10
10
|
# involved at all.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# Function name is computed unless it is named — `"hecks-#{domain}"`,
|
|
13
13
|
# lowercased, matches bin/project_deploy's own `stack_name`
|
|
14
14
|
# exactly (bin/project_deploy: `stack_name = "hecks-#{domain_name}"`,
|
|
15
15
|
# `domain_name = File.basename(domain)`). `domain` here is the
|
|
16
|
-
# bluebook's
|
|
16
|
+
# bluebook's own declared name (`Embryonaut`, not the directory);
|
|
17
17
|
# today's real corpus has directory name == declared name
|
|
18
18
|
# lowercased for every domain that deploys, so `.downcase` alone
|
|
19
19
|
# reproduces the same string bin/project_deploy computes from the
|
|
20
|
-
# directory
|
|
21
|
-
#
|
|
22
|
-
#
|
|
20
|
+
# directory.
|
|
21
|
+
#
|
|
22
|
+
# That assumption is not always true, and when it breaks nothing
|
|
23
|
+
# about it is recoverable from here. `bin/project_deploy` honours
|
|
24
|
+
# a `.world`'s own `stack_prefix`/`stack_name` — settings that
|
|
25
|
+
# exist precisely so a domain whose AWS identity predates a rename
|
|
26
|
+
# keeps targeting the stack that is actually live rather than
|
|
27
|
+
# standing up a second, empty one beside it. A real one:
|
|
28
|
+
# embryonautfoundersapp deploys as `hecksagain-embryonaut`, and no
|
|
29
|
+
# value of `domain` (or of `DOMAIN_NAME`, the deployed-Lambda
|
|
30
|
+
# override callers already pass) can make this computation produce
|
|
31
|
+
# a name with no dash after "hecks". Every Ruby-side read and
|
|
32
|
+
# dispatch for that app has therefore been invoking a function
|
|
33
|
+
# that does not exist — found live, and visible in the deployed
|
|
34
|
+
# journal having never received a single row.
|
|
35
|
+
#
|
|
36
|
+
# So the name can be named, in the one place the rest of this
|
|
37
|
+
# deployment is already described: the `.world`'s own
|
|
38
|
+
# `persisted_by("Lambda")`/`dispatched_by("Lambda")` block, beside
|
|
39
|
+
# `region`. Given, it wins outright; absent, the computation above
|
|
40
|
+
# is unchanged, which is every domain whose stack name was never
|
|
41
|
+
# pinned.
|
|
23
42
|
class Client
|
|
24
|
-
def initialize(domain:, region:)
|
|
43
|
+
def initialize(domain:, region:, function: nil)
|
|
25
44
|
require "aws-sdk-lambda"
|
|
26
|
-
@function_name = "hecks-#{domain.to_s.downcase}"
|
|
45
|
+
@function_name = function.to_s.empty? ? "hecks-#{domain.to_s.downcase}" : function.to_s
|
|
27
46
|
@client = Aws::Lambda::Client.new(region: region)
|
|
28
47
|
end
|
|
29
48
|
|
|
30
|
-
#
|
|
49
|
+
# The function this client actually invokes — read by
|
|
50
|
+
# `Runtime::WiringError` messages and worth asserting on
|
|
51
|
+
# directly, since "which function did we call" is precisely the
|
|
52
|
+
# thing that used to be unanswerable from outside.
|
|
53
|
+
attr_reader :function_name
|
|
54
|
+
|
|
55
|
+
# **The whole domain, every time** — matches dispatch::read's own
|
|
31
56
|
# rehydrate-the-full-journal design (Phase 1, rust/host). No
|
|
32
57
|
# caching here, deliberately not even per-request: Lambda#all
|
|
33
58
|
# used to memoize this across calls, which silently served
|
|
@@ -6,8 +6,8 @@ require_relative "../../runtime/instance"
|
|
|
6
6
|
|
|
7
7
|
module Hecks
|
|
8
8
|
module Adapters
|
|
9
|
-
#
|
|
10
|
-
# own adapter, resolved through the
|
|
9
|
+
# **The read-side half of lambda routing** — `persisted_by("Lambda")`'s
|
|
10
|
+
# own adapter, resolved through the same `Ports::Persistence`
|
|
11
11
|
# machinery `persisted_by("Postgres")`/`persisted_by("Memory")`
|
|
12
12
|
# already use (`Runtime::Registry#repository`, `RepositoryFactory
|
|
13
13
|
# .build`) — no new framework plumbing, just a new adapter class at
|
|
@@ -16,7 +16,7 @@ module Hecks
|
|
|
16
16
|
# compose through one shared `Client` per domain rather than each
|
|
17
17
|
# inventing its own AWS wiring.
|
|
18
18
|
#
|
|
19
|
-
#
|
|
19
|
+
# **Read-only, deliberately** — `append`/`project` raise rather than
|
|
20
20
|
# silently no-op. A write reaching this class would mean
|
|
21
21
|
# `Runtime::CommandInterpreter` ran locally against a Lambda-routed
|
|
22
22
|
# domain, which is exactly the bypass `Runtime::RemoteDispatcher`
|
|
@@ -39,46 +39,44 @@ module Hecks
|
|
|
39
39
|
else
|
|
40
40
|
aggregate.name
|
|
41
41
|
end
|
|
42
|
-
region =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# TWO DIFFERENT "domain"s, deliberately not conflated: `domain`
|
|
51
|
-
# (this aggregate's OWN bluebook name — "Identity", "Governance")
|
|
42
|
+
region = setting(settings, :region, "us-east-1")
|
|
43
|
+
# Named, when this deployment's function isn't `hecks-<domain>`
|
|
44
|
+
# — `Client`'s own comment has the real case that needs it.
|
|
45
|
+
# Absent (every domain whose stack name was never pinned), the
|
|
46
|
+
# computation below is exactly as it was.
|
|
47
|
+
function = setting(settings, :function, nil)
|
|
48
|
+
# Two different "domain"s, deliberately not conflated: `domain`
|
|
49
|
+
# (this aggregate's own bluebook name — "Identity", "Governance")
|
|
52
50
|
# only ever prefixes the instances lookup, since that's how
|
|
53
51
|
# rust/host's own `Store::instances()` keys every record
|
|
54
52
|
# (`registry.rb`'s own `"#{a[:domain_name]}::#{a[:name]}#"`
|
|
55
53
|
# dump format, unchanged by which chapter attached it). The
|
|
56
|
-
#
|
|
57
|
-
# and Identity aggregates are compiled into the
|
|
54
|
+
# function to actually call is a different question: Governance
|
|
55
|
+
# and Identity aggregates are compiled into the attaching
|
|
58
56
|
# domain's own Lambda (one merged `Store` per target — Phase 0's
|
|
59
57
|
# framework-bluebook work), never a function of their own, so
|
|
60
58
|
# `settings[:domain]` is the wrong signal for `Client.new`.
|
|
61
59
|
# `root` is the boot's own directory (`Registry#root`, shared by
|
|
62
|
-
#
|
|
60
|
+
# every bluebook in one registry regardless of which one
|
|
63
61
|
# attached it) — `File.basename(root)` reproduces the exact
|
|
64
62
|
# same string `bin/project_deploy`'s own `stack_name` computes
|
|
65
|
-
# from the domain
|
|
66
|
-
# different functions for the same deploy... on a
|
|
63
|
+
# from the domain path, so the two can never name two
|
|
64
|
+
# different functions for the same deploy... on a local boot,
|
|
67
65
|
# where `root` is a real project directory. Inside the deployed
|
|
68
|
-
# Lambda itself `root` is
|
|
66
|
+
# Lambda itself `root` is always `/var/task` (every Lambda's own
|
|
69
67
|
# fixed code root, regardless of domain) — `File.basename` gives
|
|
70
68
|
# "task", not the domain name, and invokes the wrong function
|
|
71
69
|
# entirely. A real, live AccessDeniedException on
|
|
72
70
|
# "hecks-task" caught this: invisible through every earlier
|
|
73
71
|
# phase's own verify step, all run from a local boot, until
|
|
74
|
-
# WebFunction became the first Ruby process to
|
|
72
|
+
# WebFunction became the first Ruby process to ever make this
|
|
75
73
|
# exact call from inside a deployed Lambda. `DOMAIN_NAME` (set
|
|
76
74
|
# by bin/project_deploy's own WebFunction Environment) is the
|
|
77
75
|
# real, unambiguous signal in that specific context; the
|
|
78
76
|
# root-basename heuristic stays as the local-boot fallback,
|
|
79
77
|
# unchanged.
|
|
80
78
|
function_domain = ENV["DOMAIN_NAME"] || (root ? File.basename(root) : domain)
|
|
81
|
-
@client = Client.new(domain: function_domain, region: region)
|
|
79
|
+
@client = Client.new(domain: function_domain, region: region, function: function)
|
|
82
80
|
@prefix = "#{domain}::#{aggregate.hecks_name}#"
|
|
83
81
|
end
|
|
84
82
|
|
|
@@ -105,18 +103,29 @@ module Hecks
|
|
|
105
103
|
# 1 — there is no local write-ahead log for `recover!` to replay),
|
|
106
104
|
# `append`/`project` raise rather than silently no-op.
|
|
107
105
|
|
|
106
|
+
# A `.world` block's settings arrive symbol-keyed from the DSL and
|
|
107
|
+
# string-keyed from a round-tripped export, so every read has to
|
|
108
|
+
# accept both — one helper rather than the same five lines per
|
|
109
|
+
# key.
|
|
110
|
+
def setting(settings, key, fallback)
|
|
111
|
+
return settings[key] if settings.key?(key)
|
|
112
|
+
return settings[key.to_s] if settings.key?(key.to_s)
|
|
113
|
+
|
|
114
|
+
fallback
|
|
115
|
+
end
|
|
116
|
+
|
|
108
117
|
private
|
|
109
118
|
|
|
110
|
-
#
|
|
119
|
+
# Re-fetched every call, deliberately not memoized across them —
|
|
111
120
|
# this adapter itself is long-lived (one instance per aggregate,
|
|
112
|
-
# held by the registry `RUNTIME = Hecks.boot(...)` builds
|
|
121
|
+
# held by the registry `RUNTIME = Hecks.boot(...)` builds once
|
|
113
122
|
# per Lambda web process — WebFunction's own top-level constant,
|
|
114
123
|
# reused warm across every HTTP request that process serves, not
|
|
115
124
|
# rebuilt per request the way a memoize-for-one-request comment
|
|
116
125
|
# here used to assume). A real, live bug caught this: a mutation
|
|
117
126
|
# dispatched fine (RemoteDispatcher always calls the dispatch
|
|
118
|
-
# Lambda fresh) and the very next `.all` on the
|
|
119
|
-
# container kept returning the state from
|
|
127
|
+
# Lambda fresh) and the very next `.all` on the same warm
|
|
128
|
+
# container kept returning the state from before that mutation,
|
|
120
129
|
# forever, until the container cold-started — memoizing here
|
|
121
130
|
# made every write invisible to every read on a warm container.
|
|
122
131
|
# Keyed by bare id (the part after "Domain::Aggregate#"), not the
|
|
@@ -130,15 +139,12 @@ module Hecks
|
|
|
130
139
|
end.to_h
|
|
131
140
|
end
|
|
132
141
|
|
|
133
|
-
# Lambda's own JSON response is already Ruby-decoded with
|
|
134
|
-
# keys (plain `JSON.parse`, no `symbolize_names:`) —
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
# round trip through `JSON.generate`/`JSON.parse` is simpler and
|
|
138
|
-
# safer than a hand-rolled deep-symbolize helper for what is not
|
|
139
|
-
# a hot path.
|
|
142
|
+
# Lambda's own JSON response is already Ruby-decoded with string
|
|
143
|
+
# keys (plain `JSON.parse`, no `symbolize_names:`) — decoded through
|
|
144
|
+
# the state codec (PR A3), the same IR-driven spelling every other
|
|
145
|
+
# adapter's read produces.
|
|
140
146
|
def build_instance(id, state)
|
|
141
|
-
Runtime::Instance.new(aggregate: @aggregate, id: id, state:
|
|
147
|
+
Runtime::Instance.new(aggregate: @aggregate, id: id, state: Ports::Persistence::StateCodec.decode(@aggregate, state))
|
|
142
148
|
end
|
|
143
149
|
end
|
|
144
150
|
end
|
|
@@ -5,13 +5,13 @@ require_relative "../../runtime/instance"
|
|
|
5
5
|
|
|
6
6
|
module Hecks
|
|
7
7
|
module Adapters
|
|
8
|
-
# A
|
|
8
|
+
# **A browser-hosted domain's own declared intent** — not a second Memory
|
|
9
9
|
# wearing a different name. Ruby has no way to reach a real browser's
|
|
10
10
|
# `window.localStorage` at all (it is per-tab, per-origin, JS-only,
|
|
11
11
|
# unreachable over any network the way D1's own REST API is) — so
|
|
12
12
|
# this Ruby-side adapter is honestly a stand-in: in-process, ephemeral,
|
|
13
13
|
# mechanically identical to Memory. What earns it a name of its own
|
|
14
|
-
# is what it
|
|
14
|
+
# is what it declares, not what it happens to do in Ruby: `persisted_by
|
|
15
15
|
# "LocalStorage"` says "this domain expects real, durable, single-
|
|
16
16
|
# device storage the moment it's actually running where it's meant to
|
|
17
17
|
# run" — the same distinction Heki (real local durability) already
|
|
@@ -20,18 +20,18 @@ module Hecks
|
|
|
20
20
|
# model_check` all get a domain that boots and behaves correctly
|
|
21
21
|
# against this adapter; only a real browser gets the real durability.
|
|
22
22
|
#
|
|
23
|
-
#
|
|
23
|
+
# The real browser half lives outside this file entirely: `rust/web`'s
|
|
24
24
|
# `dispatch(json)` (docs/implemented/decisions/0015) takes an optional
|
|
25
25
|
# `"seed"` (the exact `"instances"` shape it also answers with) plus
|
|
26
26
|
# `"steps"` — a host rehydrates from a prior snapshot and replays only
|
|
27
27
|
# the new command(s), rather than the whole history every call. A
|
|
28
28
|
# page bound to this adapter is expected to hold that snapshot in
|
|
29
29
|
# `window.localStorage` itself (get on load, set after every
|
|
30
|
-
# `dispatch`) — the seed/instances round trip
|
|
30
|
+
# `dispatch`) — the seed/instances round trip is the adapter, once
|
|
31
31
|
# you're in the one runtime that can actually reach the storage this
|
|
32
32
|
# name promises.
|
|
33
33
|
class LocalStorage
|
|
34
|
-
#
|
|
34
|
+
# Tenant-capable trivially, same reasoning as Memory's own — a
|
|
35
35
|
# browser tab is exactly one origin, exactly one user; there is no
|
|
36
36
|
# second tenant this in-process Hash could ever confuse a first
|
|
37
37
|
# one with.
|
|
@@ -54,7 +54,7 @@ module Hecks
|
|
|
54
54
|
InMemoryOrdering.ordered(@records.values, aggregate: @aggregate, order_by: order_by, direction: direction)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
#
|
|
57
|
+
# The decision the guide asks for, made explicitly: no compiled
|
|
58
58
|
# dialect of its own, same as Heki/Memory — a personal-scale local
|
|
59
59
|
# store answering by walking `all` is correct on day one, and
|
|
60
60
|
# nothing about a browser tab's own data volume asks for pushdown.
|
|
@@ -62,21 +62,24 @@ module Hecks
|
|
|
62
62
|
Ports::Query::InMemory.execute(all, specification, args, registry: context[:registry])
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# Through the state codec, the same as Memory (see its `append`).
|
|
65
66
|
def append(entry)
|
|
66
|
-
|
|
67
|
+
copied = Ports::Persistence::Entry.new(operation: entry.operation, id: entry.id,
|
|
68
|
+
state: copy(entry.state), mirrors: entry.mirrors)
|
|
69
|
+
@entries << copied
|
|
67
70
|
entry
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
def project(entry)
|
|
71
74
|
if entry.save?
|
|
72
|
-
@records[entry.id] = Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: entry.state
|
|
75
|
+
@records[entry.id] = Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: copy(entry.state))
|
|
73
76
|
else
|
|
74
77
|
@records.delete(entry.id)
|
|
75
78
|
end
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
def save(instance)
|
|
79
|
-
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state
|
|
82
|
+
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: copy(instance.state))
|
|
80
83
|
append(entry)
|
|
81
84
|
project(entry)
|
|
82
85
|
end
|
|
@@ -108,7 +111,11 @@ module Hecks
|
|
|
108
111
|
self
|
|
109
112
|
end
|
|
110
113
|
|
|
111
|
-
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def copy(state) = Ports::Persistence::StateCodec.copy(@aggregate, state)
|
|
117
|
+
|
|
118
|
+
# Not lineage_capable? — deliberately absent, the same trade Heki
|
|
112
119
|
# makes and states plainly (writing-an-adapter.md's own section on
|
|
113
120
|
# it): a domain bound here has no edge for its own shape to travel
|
|
114
121
|
# across if it ever changes; that must be hand-migrated, or the
|