hecks 1.3.0 → 1.5.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 +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -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 +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- 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/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -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 +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- 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 +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- 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 +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- 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 +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- 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 +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -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 +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- 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 +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- 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/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -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 +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- 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 +34 -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 +95 -6
- 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 +48 -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 +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -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 +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -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 +61 -15
- 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 +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- 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 +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -23,7 +23,7 @@ module Hecks
|
|
|
23
23
|
raise Malformed, "#{@path}: json error: #{e.message}"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# Temp-file-plus-rename, fsynced
|
|
26
|
+
# Temp-file-plus-rename, fsynced ahead of the `File.rename` call: a reader can
|
|
27
27
|
# only ever see the last complete snapshot or the one before it,
|
|
28
28
|
# never a truncated or partial one — `File.binwrite`'s old
|
|
29
29
|
# truncate-then-write left a window, proportional to the whole
|
|
@@ -25,12 +25,18 @@ module Hecks
|
|
|
25
25
|
|
|
26
26
|
attr_reader :aggregate, :path, :events
|
|
27
27
|
|
|
28
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate this store persists
|
|
29
|
+
# @param settings [Hash] adapter settings; `dir:`/`"dir"` (a storage directory, `"data"`
|
|
30
|
+
# by default) and `domain:`/`"domain"` (the saga-persistence scope, defaulting to
|
|
31
|
+
# `aggregate.name`) are read
|
|
32
|
+
# @param root [String, nil] the directory `settings[:dir]` resolves relative to when it
|
|
33
|
+
# is not absolute; defaults to the process's current working directory
|
|
28
34
|
def initialize(aggregate:, settings: {}, root: nil)
|
|
29
35
|
@aggregate = aggregate
|
|
30
36
|
@path = resolve_path(settings, root)
|
|
31
37
|
@journal_path = "#{@path}.journal"
|
|
32
38
|
@events = []
|
|
33
|
-
#
|
|
39
|
+
# The optional saga-persistence capability's own scoping (§2/§4)
|
|
34
40
|
# — falls back to the aggregate's own name for a directly-
|
|
35
41
|
# instantiated adapter (specs), same fallback shape Postgres's
|
|
36
42
|
# own @domain already uses.
|
|
@@ -47,6 +53,10 @@ module Hecks
|
|
|
47
53
|
FileUtils.mkdir_p(File.dirname(@path))
|
|
48
54
|
end
|
|
49
55
|
|
|
56
|
+
# Reads one record's current projected state.
|
|
57
|
+
#
|
|
58
|
+
# @param id [String, Object] the record's identity, compared as `id.to_s`
|
|
59
|
+
# @return [Runtime::Instance, nil] the stored record, or nil when no record has that id
|
|
50
60
|
def find(id)
|
|
51
61
|
record = store[id.to_s]
|
|
52
62
|
return nil unless record
|
|
@@ -54,24 +64,45 @@ module Hecks
|
|
|
54
64
|
instance(id.to_s, record)
|
|
55
65
|
end
|
|
56
66
|
|
|
67
|
+
# Lists every record currently projected, sorted by id then reordered as requested.
|
|
68
|
+
#
|
|
69
|
+
# @param order_by [String, Symbol, nil] an attribute name to sort by; nil keeps id order
|
|
70
|
+
# @param direction [Symbol] `:asc` or `:desc`
|
|
71
|
+
# @return [Array<Runtime::Instance>] the stored records; `[]` when there are none
|
|
57
72
|
def all(order_by: nil, direction: :asc)
|
|
58
73
|
records = store.sort_by { |id, _| id }.map { |id, record| instance(id, record) }
|
|
59
74
|
InMemoryOrdering.ordered(records, aggregate: @aggregate, order_by: order_by, direction: direction)
|
|
60
75
|
end
|
|
61
76
|
|
|
77
|
+
# Counts the records currently projected.
|
|
78
|
+
#
|
|
79
|
+
# @return [Integer] number of stored records, not of journal entries
|
|
62
80
|
def count = store.size
|
|
63
81
|
|
|
82
|
+
# Answers a declared query specification against the projected records.
|
|
83
|
+
#
|
|
64
84
|
# `registry: context[:registry]` — Memory's own `query` already
|
|
65
85
|
# threads this through; Heki's own never did, which made
|
|
66
86
|
# `none_in_state?` (Ports::Query::InMemory) unconditionally
|
|
67
87
|
# return `true` (its own graceful "no registry, no way to look
|
|
68
|
-
# the target up" default) for
|
|
88
|
+
# the target up" default) for every `none_in_state` where-clause
|
|
69
89
|
# against a Heki-backed aggregate — silently excluding nothing,
|
|
70
90
|
# always, no matter the actual target state.
|
|
91
|
+
#
|
|
92
|
+
# @param specification [QuerySpecification::Common::Options,
|
|
93
|
+
# Bluebook::Behaviour::ReadModel::FilteredOptions] the declared query specification
|
|
94
|
+
# @param args [Hash{Symbol => Object}] bound values for the specification's placeholders
|
|
95
|
+
# @param context [Hash{Symbol => Object}] call context; `:registry` is read and passed
|
|
96
|
+
# through for registry-aware comparisons
|
|
97
|
+
# @return [Array<Runtime::Instance>] the matching records, ordered and paged
|
|
71
98
|
def query(specification, args = {}, context: {})
|
|
72
99
|
Ports::Query::InMemory.execute(all, specification, args, registry: context[:registry])
|
|
73
100
|
end
|
|
74
101
|
|
|
102
|
+
# Writes one entry to the durable journal, before any projection of it.
|
|
103
|
+
#
|
|
104
|
+
# @param entry [Persistence::Entry] the save or delete to journal
|
|
105
|
+
# @return [Persistence::Entry] `entry`, unchanged
|
|
75
106
|
def append(entry)
|
|
76
107
|
@entry_mirrors = entry.mirrors
|
|
77
108
|
append_entry(entry.operation, entry.id, entry.state)
|
|
@@ -80,18 +111,42 @@ module Hecks
|
|
|
80
111
|
@entry_mirrors = nil
|
|
81
112
|
end
|
|
82
113
|
|
|
114
|
+
# Applies one journaled entry to the current-state snapshot.
|
|
115
|
+
#
|
|
83
116
|
# Reads fresh rather than trusting the memoized `store` — under
|
|
84
117
|
# `with_lock`, another process may have projected a snapshot since
|
|
85
118
|
# this one last read it, and mutating *its* stale copy would
|
|
86
119
|
# overwrite that write on disk rather than layer on top of it.
|
|
120
|
+
#
|
|
121
|
+
# On a delete, though, the record `current.delete` finds is already
|
|
122
|
+
# gone: every caller (`#delete` above, `AppendOnly#delete`) appends
|
|
123
|
+
# before it projects, so the fresh read above has already replayed
|
|
124
|
+
# this very entry off the journal. What the memoized `store` last
|
|
125
|
+
# held — from before this call — is read up front, for the return
|
|
126
|
+
# value only; it plays no part in what gets written.
|
|
127
|
+
#
|
|
128
|
+
# @param entry [Persistence::Entry] the save or delete to materialize
|
|
129
|
+
# @return [Runtime::Instance, nil] on a save, the newly stored record; on a delete, the
|
|
130
|
+
# removed record, or nil when no record had that id
|
|
87
131
|
def project(entry)
|
|
132
|
+
removed = entry.delete? ? store[entry.id] : nil
|
|
88
133
|
current = read
|
|
89
|
-
entry.save?
|
|
134
|
+
if entry.save?
|
|
135
|
+
current[entry.id] = Ports::Persistence::StateCodec.encode(@aggregate, entry.state)
|
|
136
|
+
projected = Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: entry.state)
|
|
137
|
+
else
|
|
138
|
+
current.delete(entry.id)
|
|
139
|
+
projected = removed && instance(entry.id, removed)
|
|
140
|
+
end
|
|
90
141
|
write(current)
|
|
91
142
|
@store = current
|
|
92
|
-
|
|
143
|
+
projected
|
|
93
144
|
end
|
|
94
145
|
|
|
146
|
+
# Journals and projects an instance's state under the file lock.
|
|
147
|
+
#
|
|
148
|
+
# @param instance [Runtime::Instance] the record to persist
|
|
149
|
+
# @return [Runtime::Instance] `instance`, unchanged
|
|
95
150
|
def save(instance)
|
|
96
151
|
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
97
152
|
with_lock do
|
|
@@ -101,6 +156,11 @@ module Hecks
|
|
|
101
156
|
instance
|
|
102
157
|
end
|
|
103
158
|
|
|
159
|
+
# Journals and projects the removal of one record, if it exists, under the file lock.
|
|
160
|
+
#
|
|
161
|
+
# @param id [String, Object] the record's identity, compared as `id.to_s`
|
|
162
|
+
# @return [Boolean] true when a record was found and deleted, false when there was none
|
|
163
|
+
# and nothing was journaled
|
|
104
164
|
def delete(id)
|
|
105
165
|
return false unless find(id)
|
|
106
166
|
|
|
@@ -112,20 +172,55 @@ module Hecks
|
|
|
112
172
|
true
|
|
113
173
|
end
|
|
114
174
|
|
|
175
|
+
# Records one emitted event in this adapter's in-memory event log.
|
|
176
|
+
#
|
|
177
|
+
# @param event [Runtime::Event] the event to record
|
|
178
|
+
# @return [Array<Runtime::Event>] the adapter's in-memory event log, including `event`
|
|
115
179
|
def record_event(event) = @events << event
|
|
116
180
|
|
|
117
|
-
# ── the
|
|
181
|
+
# ── the optional saga-persistence capability (§2) — Heki's own
|
|
118
182
|
# shape (a sibling snapshot+journal file pair, `SagaStore`,
|
|
119
183
|
# heki/saga_store.rb) rather than a table in a store this adapter
|
|
120
184
|
# doesn't have.
|
|
185
|
+
#
|
|
186
|
+
# @param process_manager [String, Symbol] the process manager's name, compared as
|
|
187
|
+
# `.to_s`
|
|
188
|
+
# @param correlation [String, Symbol, Object] the instance's correlation value, compared
|
|
189
|
+
# as `.to_s`
|
|
190
|
+
# @param state [String, Symbol] the saga's current state name, compared as `.to_s`
|
|
191
|
+
# @param memory [Hash] the saga's working memory to persist
|
|
192
|
+
# @param completed_compensations [Array] the ledger of completed compensable legs;
|
|
193
|
+
# `[]` when none
|
|
194
|
+
# @return [Hash{String => Hash}] `SagaStore`'s internal records Hash after the write;
|
|
195
|
+
# callers ignore it
|
|
121
196
|
def save_saga(process_manager:, correlation:, state:, memory:, completed_compensations: [])
|
|
122
197
|
saga_store.save_saga(@domain, process_manager.to_s, correlation.to_s, state.to_s, memory, completed_compensations)
|
|
123
198
|
end
|
|
124
199
|
|
|
200
|
+
# Removes a finished saga instance's checkpoint; a missing one is not an error.
|
|
201
|
+
#
|
|
202
|
+
# @param process_manager [String, Symbol] the process manager's name, compared as
|
|
203
|
+
# `.to_s`
|
|
204
|
+
# @param correlation [String, Symbol, Object] the instance's correlation value, compared
|
|
205
|
+
# as `.to_s`
|
|
206
|
+
# @return [Hash{String => Hash}] `SagaStore`'s internal records Hash after the delete;
|
|
207
|
+
# callers ignore it
|
|
125
208
|
def delete_saga(process_manager:, correlation:)
|
|
126
209
|
saga_store.delete_saga(@domain, process_manager.to_s, correlation.to_s)
|
|
127
210
|
end
|
|
128
211
|
|
|
212
|
+
# Yields every checkpointed saga instance of this domain, for `Registry
|
|
213
|
+
# #rehydrate_sagas!` to restore at boot.
|
|
214
|
+
#
|
|
215
|
+
# @yieldparam process_manager [String] the process manager's name
|
|
216
|
+
# @yieldparam correlation [String] the instance's correlation value
|
|
217
|
+
# @yieldparam state [String] the saga's state name
|
|
218
|
+
# @yieldparam memory [Hash{Symbol => Object}] the saga's memory, Symbol keys at every
|
|
219
|
+
# depth
|
|
220
|
+
# @yieldparam completed_compensations [Array] the completed-compensation ledger, `[]`
|
|
221
|
+
# when none was recorded
|
|
222
|
+
# @return [Enumerator, Hash{String => Hash}] an enumerator over the same five values
|
|
223
|
+
# when no block is given; otherwise `SagaStore`'s internal records Hash
|
|
129
224
|
def each_saga(&) = saga_store.each_saga(@domain, &)
|
|
130
225
|
|
|
131
226
|
private
|
|
@@ -134,11 +229,13 @@ module Hecks
|
|
|
134
229
|
@saga_store ||= SagaStore.new(File.dirname(@path))
|
|
135
230
|
end
|
|
136
231
|
|
|
232
|
+
# `record` is the snapshot/journal's own string-keyed JSON — decoded
|
|
233
|
+
# deep through the state codec, never symbolized one level by hand.
|
|
137
234
|
def instance(id, record)
|
|
138
235
|
Runtime::Instance.new(
|
|
139
236
|
aggregate: @aggregate,
|
|
140
237
|
id: id,
|
|
141
|
-
state:
|
|
238
|
+
state: Ports::Persistence::StateCodec.decode(@aggregate, record)
|
|
142
239
|
)
|
|
143
240
|
end
|
|
144
241
|
|
|
@@ -152,10 +249,10 @@ module Hecks
|
|
|
152
249
|
end
|
|
153
250
|
|
|
154
251
|
# `dir: :default` — a bare Symbol, the framework's own convention
|
|
155
|
-
# for "a
|
|
156
|
-
# fallback" —
|
|
252
|
+
# for "a declared value that resolves by convention, never a silent
|
|
253
|
+
# fallback" — would otherwise crash `File.join` outright
|
|
157
254
|
# (`TypeError: no implicit conversion of Symbol into String`):
|
|
158
|
-
# `resolve_path` only ever checked for a
|
|
255
|
+
# `resolve_path` only ever checked for a missing `dir` setting,
|
|
159
256
|
# never a Symbol one. Treated the same as no setting at all — falls
|
|
160
257
|
# back to the existing "data" default, not a new special case.
|
|
161
258
|
def resolve_path(settings, root)
|
|
@@ -1,18 +1,28 @@
|
|
|
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.
|
|
8
8
|
module IdentityRegistry
|
|
9
9
|
module_function
|
|
10
10
|
|
|
11
|
+
# Looks up the id of the identity an authenticated (issuer, subject) pair is linked to,
|
|
12
|
+
# querying the Identity framework bluebook's own `ResolvedBy`.
|
|
13
|
+
#
|
|
11
14
|
# `nil` for a pair nothing has linked, the first match otherwise —
|
|
12
15
|
# `ResolvedBy` is a lookup by the exact (issuer, subject) an
|
|
13
16
|
# authenticated token carries, not a listing, so more than one row
|
|
14
17
|
# would mean two links share a pair, which `Link`'s own natural
|
|
15
18
|
# key already prevents by construction.
|
|
19
|
+
#
|
|
20
|
+
# @param registry [Runtime::Registry] the booted registry, queried for the linked
|
|
21
|
+
# identity's id
|
|
22
|
+
# @param issuer [String] the OIDC issuer that authenticated the caller, compared as a
|
|
23
|
+
# String
|
|
24
|
+
# @param subject [String] the OIDC subject the issuer vouches for, compared as a String
|
|
25
|
+
# @return [String, nil] the linked identity's id, or nil if nothing has linked this pair
|
|
16
26
|
def resolve(registry, issuer:, subject:)
|
|
17
27
|
rows = Runtime::Dispatcher.new(registry).query(
|
|
18
28
|
"Identity::ExternalIdentifier.ResolvedBy",
|
|
@@ -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,10 +16,24 @@ module Hecks
|
|
|
16
16
|
module InMemoryOrdering
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# Orders decoded records by a declared attribute, then by id as the total-order
|
|
20
|
+
# tiebreaker.
|
|
21
|
+
#
|
|
22
|
+
# order_by is a runtime value (an HTTP query param, in the
|
|
20
23
|
# console's case), not framework-authored bluebook source — see
|
|
21
24
|
# postgres.rb's own all for the full reasoning. Whitelisted the
|
|
22
25
|
# identical way before FieldPath.dig ever runs.
|
|
26
|
+
#
|
|
27
|
+
# @param records [Array<Runtime::Instance>] the records to order
|
|
28
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `records` belong to, checked for
|
|
29
|
+
# `order_by`'s attribute
|
|
30
|
+
# @param order_by [String, Symbol, nil] a dotted attribute path to sort by; nil returns
|
|
31
|
+
# `records` unchanged
|
|
32
|
+
# @param direction [Symbol] `:asc` or `:desc`
|
|
33
|
+
# @return [Array<Runtime::Instance>] `records`, ordered by `order_by` then id; unchanged
|
|
34
|
+
# when `order_by` is nil
|
|
35
|
+
# @raise [Runtime::WiringError] if `order_by` names no attribute of `aggregate` and is
|
|
36
|
+
# not its lifecycle field
|
|
23
37
|
def ordered(records, aggregate:, order_by:, direction:)
|
|
24
38
|
return records unless order_by
|
|
25
39
|
|
|
@@ -36,6 +50,14 @@ module Hecks
|
|
|
36
50
|
end
|
|
37
51
|
end
|
|
38
52
|
|
|
53
|
+
# Resolves the dotted path `FieldPath.dig` should read to compare a value object field.
|
|
54
|
+
#
|
|
55
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `field` belongs to
|
|
56
|
+
# @param field [String, Symbol] a dotted order_by path, such as `"price"` or
|
|
57
|
+
# `"price.cents"`
|
|
58
|
+
# @return [String] `field` unchanged for an already-dotted path, the lifecycle field, or
|
|
59
|
+
# an attribute with no value-object type; otherwise `"<name>.<member>"` naming the
|
|
60
|
+
# attribute's numeric member, its sole member, or the bare `"value"` convention
|
|
39
61
|
def sortable_path(aggregate, field)
|
|
40
62
|
name, *path = field.to_s.split(".")
|
|
41
63
|
return field.to_s unless path.empty?
|
|
@@ -46,7 +68,7 @@ module Hecks
|
|
|
46
68
|
vo = aggregate.value_object(attribute.type)
|
|
47
69
|
return field.to_s unless vo
|
|
48
70
|
|
|
49
|
-
# Numeric member first, then the
|
|
71
|
+
# Numeric member first, then the sole attribute whatever it is
|
|
50
72
|
# named (single-attribute value objects strictly answer `.value`
|
|
51
73
|
# — the same generalization `SqlQueryBuilder#query_expression`
|
|
52
74
|
# makes for the column side, kept in lockstep so Memory and SQL
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
# The real `key_vault` fulfillment for a single running process — a
|
|
6
|
+
# table of key material, keyed by an opaque reference never exposed to
|
|
7
|
+
# a bluebook or an event. Cryptoshredding only works if the key
|
|
8
|
+
# genuinely leaves memory when destroyed: `destroy` deletes the Hash
|
|
9
|
+
# entry outright, not merely a flag, so a `fetch` afterward has
|
|
10
|
+
# nothing left to decrypt with.
|
|
11
|
+
#
|
|
12
|
+
# **Not crash-durable** — a process restart loses every key this
|
|
13
|
+
# adapter ever issued, along with every ciphertext under it. A
|
|
14
|
+
# deployment that needs destruction to survive a restart (or to leave
|
|
15
|
+
# a physical-destruction audit trail) backs this port with a real
|
|
16
|
+
# `KMS` or `HSM` instead; this default asks for nothing external to
|
|
17
|
+
# run, the same tradeoff `SecureRandomIdentity` already makes for
|
|
18
|
+
# identity minting.
|
|
19
|
+
module InProcessKeyVault
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# Mints and stores a fresh symmetric key, returning only an opaque handle to it.
|
|
23
|
+
#
|
|
24
|
+
# @param subject_id [String] the data subject the key is being issued for; held
|
|
25
|
+
# alongside the key material for {#destroy}'s own bookkeeping, never returned
|
|
26
|
+
# @return [String] an opaque key reference; the key material itself never leaves this
|
|
27
|
+
# adapter
|
|
28
|
+
def issue(subject_id:)
|
|
29
|
+
key_reference = SecureRandom.uuid
|
|
30
|
+
(@keys ||= {})[key_reference] = { subject_id: subject_id, secret: SecureRandom.hex(32) }
|
|
31
|
+
key_reference
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Looks up the live key material behind a reference, for encrypting or decrypting.
|
|
35
|
+
#
|
|
36
|
+
# @param key_reference [String] the opaque reference {#issue} returned
|
|
37
|
+
# @return [String, nil] the key's hex-encoded secret, or nil once {#destroy} has run
|
|
38
|
+
def fetch(key_reference) = (@keys ||= {})[key_reference]&.fetch(:secret)
|
|
39
|
+
|
|
40
|
+
# Irrevocably deletes a key's material, so {#fetch} can never answer for it again.
|
|
41
|
+
#
|
|
42
|
+
# @param key_reference [String] the opaque reference {#issue} returned
|
|
43
|
+
# @return [Boolean] true when a key was held and is now gone; false when this
|
|
44
|
+
# reference was already destroyed, or never issued
|
|
45
|
+
def destroy(key_reference:) = !(@keys ||= {}).delete(key_reference).nil? # rubocop:disable Naming/PredicateMethod
|
|
46
|
+
|
|
47
|
+
# Resets the vault to empty, forgetting every key this process ever issued.
|
|
48
|
+
#
|
|
49
|
+
# @return [void]
|
|
50
|
+
def reset! = @keys = {}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -3,41 +3,94 @@ 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
|
+
# ## Resolving the function name
|
|
13
|
+
#
|
|
14
|
+
# Function name is computed unless it is named — `"hecks-#{domain}"`,
|
|
13
15
|
# lowercased, matches bin/project_deploy's own `stack_name`
|
|
14
16
|
# exactly (bin/project_deploy: `stack_name = "hecks-#{domain_name}"`,
|
|
15
17
|
# `domain_name = File.basename(domain)`). `domain` here is the
|
|
16
|
-
# bluebook's
|
|
18
|
+
# bluebook's own declared name (`Embryonaut`, not the directory);
|
|
17
19
|
# today's real corpus has directory name == declared name
|
|
18
20
|
# lowercased for every domain that deploys, so `.downcase` alone
|
|
19
21
|
# reproduces the same string bin/project_deploy computes from the
|
|
20
|
-
# directory
|
|
21
|
-
#
|
|
22
|
-
#
|
|
22
|
+
# directory.
|
|
23
|
+
#
|
|
24
|
+
# ## When the assumption breaks
|
|
25
|
+
#
|
|
26
|
+
# That assumption is not always true, and when it breaks nothing
|
|
27
|
+
# about it is recoverable from here. `bin/project_deploy` honours
|
|
28
|
+
# a `.world`'s own `stack_prefix`/`stack_name` — settings that
|
|
29
|
+
# exist precisely so a domain whose AWS identity predates a rename
|
|
30
|
+
# keeps targeting the stack that is actually live rather than
|
|
31
|
+
# standing up a second, empty one beside it. A real one:
|
|
32
|
+
# embryonautfoundersapp deploys as `hecksagain-embryonaut`, and no
|
|
33
|
+
# value of `domain` (or of `DOMAIN_NAME`, the deployed-Lambda
|
|
34
|
+
# override callers already pass) can make this computation produce
|
|
35
|
+
# a name with no dash after "hecks". Every Ruby-side read and
|
|
36
|
+
# dispatch for that app has therefore been invoking a function
|
|
37
|
+
# that does not exist — found live, and visible in the deployed
|
|
38
|
+
# journal having never received a single row.
|
|
39
|
+
#
|
|
40
|
+
# ## Naming it explicitly
|
|
41
|
+
#
|
|
42
|
+
# So the name can be named, in the one place the rest of this
|
|
43
|
+
# deployment is already described: the `.world`'s own
|
|
44
|
+
# `persisted_by("Lambda")`/`dispatched_by("Lambda")` block, beside
|
|
45
|
+
# `region`. Given, it wins outright; absent, the computation above
|
|
46
|
+
# is unchanged, which is every domain whose stack name was never
|
|
47
|
+
# pinned.
|
|
23
48
|
class Client
|
|
24
|
-
|
|
49
|
+
# @param domain [String, Symbol] the bluebook's own declared domain name; computes
|
|
50
|
+
# the function name when `function` is not given
|
|
51
|
+
# @param region [String] the AWS region to invoke in
|
|
52
|
+
# @param function [String, Symbol, nil] an explicit Lambda function name, from a
|
|
53
|
+
# `.world`'s `persisted_by("Lambda")`/`dispatched_by("Lambda")` block; nil computes
|
|
54
|
+
# `"hecks-#{domain.downcase}"`
|
|
55
|
+
def initialize(domain:, region:, function: nil)
|
|
25
56
|
require "aws-sdk-lambda"
|
|
26
|
-
@function_name = "hecks-#{domain.to_s.downcase}"
|
|
57
|
+
@function_name = function.to_s.empty? ? "hecks-#{domain.to_s.downcase}" : function.to_s
|
|
27
58
|
@client = Aws::Lambda::Client.new(region: region)
|
|
28
59
|
end
|
|
29
60
|
|
|
30
|
-
#
|
|
61
|
+
# The function this client actually invokes — read by
|
|
62
|
+
# `Runtime::WiringError` messages and worth asserting on
|
|
63
|
+
# directly, since "which function did we call" is otherwise
|
|
64
|
+
# unanswerable from outside.
|
|
65
|
+
attr_reader :function_name
|
|
66
|
+
|
|
67
|
+
# Reads the whole domain's current state and event log from the remote function.
|
|
68
|
+
#
|
|
69
|
+
# **The whole domain, every time** — matches dispatch::read's own
|
|
31
70
|
# rehydrate-the-full-journal design (Phase 1, rust/host). No
|
|
32
|
-
# caching here, deliberately
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
71
|
+
# caching here, deliberately: memoizing this across calls once
|
|
72
|
+
# silently served stale reads for a warm web process's whole
|
|
73
|
+
# lifetime after a write happened elsewhere — see Lambda#instances's
|
|
74
|
+
# own comment on the real, live bug that caught it.
|
|
75
|
+
#
|
|
76
|
+
# @return [Hash{String => Object}] the parsed JSON response; the deployed function's
|
|
77
|
+
# own top-level keys (`"instances"`, `"events"`, …)
|
|
78
|
+
# @raise [Runtime::WiringError] if the function reports a `functionError` (see
|
|
79
|
+
# `invoke`)
|
|
37
80
|
def read
|
|
38
81
|
invoke({ "read" => true })
|
|
39
82
|
end
|
|
40
83
|
|
|
84
|
+
# Dispatches one command to the remote function and returns its parsed response.
|
|
85
|
+
#
|
|
86
|
+
# @param verb [String] the fully-qualified verb to dispatch
|
|
87
|
+
# @param args [Hash] the command's declared arguments
|
|
88
|
+
# @param role [String, Symbol, nil] the role to dispatch as; omitted from the payload
|
|
89
|
+
# when nil
|
|
90
|
+
# @return [Hash{String => Object}] the parsed JSON response, including a `"refusals"`
|
|
91
|
+
# array and a `"mutations"` array
|
|
92
|
+
# @raise [Runtime::WiringError] if the function reports a `functionError` (see
|
|
93
|
+
# `invoke`)
|
|
41
94
|
def dispatch(verb, args, role: nil)
|
|
42
95
|
payload = { "verb" => verb, "args" => args }
|
|
43
96
|
payload["role"] = role if role
|