hecks 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +427 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +179 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +145 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +438 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +306 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +54 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +125 -0
- data/lib/hecks/behaviors/expectations.rb +340 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
- data/lib/hecks/bluebook/assembly/marks.rb +228 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +91 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +78 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
- data/lib/hecks/bluebook/expression/projection.json +218 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +781 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +542 -0
- data/lib/hecks/bluebook/model_check.rb +445 -0
- data/lib/hecks/bluebook/pattern_subset.rb +184 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +128 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +66 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +410 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +119 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +166 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +341 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +177 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +84 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +161 -0
- data/lib/hecks/forms/port_argument.rb +46 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +119 -0
- data/lib/hecks/forms/record_table.rb +68 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
- data/lib/hecks/fuzzing/properties.rb +1192 -0
- data/lib/hecks/fuzzing/replay.rb +668 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
- data/lib/hecks/fuzzing/value_generator.rb +200 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +324 -0
- data/lib/hecks/grammar/expression.bluebook +420 -0
- data/lib/hecks/grammar/expression_operators.json +1648 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +471 -0
- data/lib/hecks/language/bluebook/entity.bluebook +392 -0
- data/lib/hecks/language/bluebook/policy.bluebook +189 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +333 -0
- data/lib/hecks/language/port.bluebook +120 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +174 -0
- data/lib/hecks/ports/access_control.port +9 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +8 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +7 -0
- data/lib/hecks/ports/authorization.rb +62 -0
- data/lib/hecks/ports/clock.port +5 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +5 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +5 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +5 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +5 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +138 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +23 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/plugin.rb +54 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
- data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +96 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +642 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +44 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +446 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +158 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +411 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +92 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +107 -0
- data/lib/hecks/query_specification/hop_path.rb +132 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/aggregate_lock.rb +45 -0
- data/lib/hecks/runtime/boot_gates.rb +41 -0
- data/lib/hecks/runtime/caller.rb +66 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
- data/lib/hecks/runtime/command_interpreter.rb +497 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +189 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +245 -0
- data/lib/hecks/runtime/dispatcher.rb +301 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +299 -0
- data/lib/hecks/runtime/errors.rb +122 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +156 -0
- data/lib/hecks/runtime/instance.rb +175 -0
- data/lib/hecks/runtime/interpreting.rb +90 -0
- data/lib/hecks/runtime/loader.rb +184 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +286 -0
- data/lib/hecks/runtime/reaction_invocation.rb +253 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +126 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
- data/lib/hecks/runtime/registry/verification.rb +246 -0
- data/lib/hecks/runtime/registry.rb +279 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +503 -0
- data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +130 -0
- data/lib/hecks/runtime/value/coercion.rb +565 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +107 -0
- data/lib/hecks/storehouse.rb +632 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +218 -0
- data/lib/hecks.rb +123 -6
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
- metadata +412 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require_relative "heki/snapshot"
|
|
4
|
+
require_relative "heki/journal"
|
|
5
|
+
require_relative "heki/saga_store"
|
|
6
|
+
require_relative "../../ports/persistence/append_only"
|
|
7
|
+
require_relative "../../ports/query/in_memory"
|
|
8
|
+
require_relative "in_memory_ordering"
|
|
9
|
+
require_relative "../../runtime/instance"
|
|
10
|
+
|
|
11
|
+
module Hecks
|
|
12
|
+
module Adapters
|
|
13
|
+
# The file-backed store: a compressed snapshot (heki/snapshot.rb)
|
|
14
|
+
# plus an append-only journal beside it (heki/journal.rb). What stays
|
|
15
|
+
# here is the repository surface — find/all/save/delete, and the
|
|
16
|
+
# entry append/project pair the persistence port drives.
|
|
17
|
+
class Heki
|
|
18
|
+
include Snapshot
|
|
19
|
+
include Journal
|
|
20
|
+
|
|
21
|
+
MAGIC = "HEKI"
|
|
22
|
+
HEADER_BYTES = 8
|
|
23
|
+
|
|
24
|
+
class Malformed < StandardError; end
|
|
25
|
+
|
|
26
|
+
attr_reader :aggregate, :path
|
|
27
|
+
|
|
28
|
+
def initialize(aggregate:, settings: {}, root: nil)
|
|
29
|
+
@aggregate = aggregate
|
|
30
|
+
@path = resolve_path(settings, root)
|
|
31
|
+
@journal_path = "#{@path}.journal"
|
|
32
|
+
@events = []
|
|
33
|
+
# THE OPTIONAL saga-persistence capability's own scoping (§2/§4)
|
|
34
|
+
# — falls back to the aggregate's own name for a directly-
|
|
35
|
+
# instantiated adapter (specs), same fallback shape Postgres's
|
|
36
|
+
# own @domain already uses.
|
|
37
|
+
@domain = (
|
|
38
|
+
if settings.key?(:domain)
|
|
39
|
+
settings[:domain]
|
|
40
|
+
elsif settings.key?("domain")
|
|
41
|
+
settings["domain"]
|
|
42
|
+
else
|
|
43
|
+
aggregate.name
|
|
44
|
+
end
|
|
45
|
+
).to_s
|
|
46
|
+
|
|
47
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def find(id)
|
|
51
|
+
record = store[id.to_s]
|
|
52
|
+
return nil unless record
|
|
53
|
+
|
|
54
|
+
instance(id.to_s, record)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def all(order_by: nil, direction: :asc)
|
|
58
|
+
records = store.sort_by { |id, _| id }.map { |id, record| instance(id, record) }
|
|
59
|
+
InMemoryOrdering.ordered(records, aggregate: @aggregate, order_by: order_by, direction: direction)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def count = store.size
|
|
63
|
+
|
|
64
|
+
# `registry: context[:registry]` — Memory's own `query` already
|
|
65
|
+
# threads this through; Heki's own never did, which made
|
|
66
|
+
# `none_in_state?` (Ports::Query::InMemory) unconditionally
|
|
67
|
+
# return `true` (its own graceful "no registry, no way to look
|
|
68
|
+
# the target up" default) for EVERY `none_in_state` where-clause
|
|
69
|
+
# against a Heki-backed aggregate — silently excluding nothing,
|
|
70
|
+
# always, no matter the actual target state.
|
|
71
|
+
def query(specification, args = {}, context: {})
|
|
72
|
+
Ports::Query::InMemory.execute(all, specification, args, registry: context[:registry])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def append(entry)
|
|
76
|
+
@entry_mirrors = entry.mirrors
|
|
77
|
+
append_entry(entry.operation, entry.id, entry.state)
|
|
78
|
+
entry
|
|
79
|
+
ensure
|
|
80
|
+
@entry_mirrors = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Reads fresh rather than trusting the memoized `store` — under
|
|
84
|
+
# `with_lock`, another process may have projected a snapshot since
|
|
85
|
+
# this one last read it, and mutating *its* stale copy would
|
|
86
|
+
# overwrite that write on disk rather than layer on top of it.
|
|
87
|
+
def project(entry)
|
|
88
|
+
current = read
|
|
89
|
+
entry.save? ? current[entry.id] = entry.state.dup : current.delete(entry.id)
|
|
90
|
+
write(current)
|
|
91
|
+
@store = current
|
|
92
|
+
entry
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def save(instance)
|
|
96
|
+
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
97
|
+
with_lock do
|
|
98
|
+
append(entry)
|
|
99
|
+
project(entry)
|
|
100
|
+
end
|
|
101
|
+
instance
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def delete(id)
|
|
105
|
+
return false unless find(id)
|
|
106
|
+
|
|
107
|
+
entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
|
|
108
|
+
with_lock do
|
|
109
|
+
append(entry)
|
|
110
|
+
project(entry)
|
|
111
|
+
end
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def record_event(event) = @events << event
|
|
116
|
+
|
|
117
|
+
def events = @events
|
|
118
|
+
|
|
119
|
+
# ── the OPTIONAL saga-persistence capability (§2) — Heki's own
|
|
120
|
+
# shape (a sibling snapshot+journal file pair, `SagaStore`,
|
|
121
|
+
# heki/saga_store.rb) rather than a table in a store this adapter
|
|
122
|
+
# doesn't have.
|
|
123
|
+
def save_saga(process_manager:, correlation:, state:, memory:, completed_compensations: [])
|
|
124
|
+
saga_store.save_saga(@domain, process_manager.to_s, correlation.to_s, state.to_s, memory, completed_compensations)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def delete_saga(process_manager:, correlation:)
|
|
128
|
+
saga_store.delete_saga(@domain, process_manager.to_s, correlation.to_s)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def each_saga(&block) = saga_store.each_saga(@domain, &block)
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def saga_store
|
|
136
|
+
@saga_store ||= SagaStore.new(File.dirname(@path))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def instance(id, record)
|
|
140
|
+
Runtime::Instance.new(
|
|
141
|
+
aggregate: @aggregate,
|
|
142
|
+
id: id,
|
|
143
|
+
state: record.transform_keys(&:to_sym)
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def store
|
|
148
|
+
@store ||= read
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def read
|
|
152
|
+
snapshot = read_snapshot
|
|
153
|
+
replay_journal(snapshot)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# `dir: :default` — a bare Symbol, the framework's own convention
|
|
157
|
+
# for "a DECLARED value that resolves by convention, never a silent
|
|
158
|
+
# fallback" — used to crash `File.join` outright
|
|
159
|
+
# (`TypeError: no implicit conversion of Symbol into String`):
|
|
160
|
+
# `resolve_path` only ever checked for a MISSING `dir` setting,
|
|
161
|
+
# never a Symbol one. Treated the same as no setting at all — falls
|
|
162
|
+
# back to the existing "data" default, not a new special case.
|
|
163
|
+
def resolve_path(settings, root)
|
|
164
|
+
declared =
|
|
165
|
+
if settings.key?(:dir)
|
|
166
|
+
settings[:dir]
|
|
167
|
+
elsif settings.key?("dir")
|
|
168
|
+
settings["dir"]
|
|
169
|
+
else
|
|
170
|
+
"data"
|
|
171
|
+
end
|
|
172
|
+
declared = "data" if declared == :default
|
|
173
|
+
dir = declared.start_with?("/") ? declared : File.join(root || Dir.pwd, declared)
|
|
174
|
+
|
|
175
|
+
File.join(dir, "#{@aggregate.storage_name}.heki")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Adapters
|
|
3
|
+
# THE `identity_resolution` PORT, FULFILLED BY THE Identity FRAMEWORK
|
|
4
|
+
# BLUEBOOK — same registry, same boot, so this is a dispatch against
|
|
5
|
+
# records already sitting in the store this adapter is handed, not a
|
|
6
|
+
# bridge to a second runtime. Same reasoning `GovernanceAuthorization`
|
|
7
|
+
# gives for itself, one port over.
|
|
8
|
+
module IdentityRegistry
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# `nil` for a pair nothing has linked, the first match otherwise —
|
|
12
|
+
# `ResolvedBy` is a lookup by the exact (issuer, subject) an
|
|
13
|
+
# authenticated token carries, not a listing, so more than one row
|
|
14
|
+
# would mean two links share a pair, which `Link`'s own natural
|
|
15
|
+
# key already prevents by construction.
|
|
16
|
+
def resolve(registry, issuer:, subject:)
|
|
17
|
+
rows = Runtime::Dispatcher.new(registry).query(
|
|
18
|
+
"Identity::ExternalIdentifier.ResolvedBy",
|
|
19
|
+
issuer: { value: issuer.to_s }, subject: { value: subject.to_s }
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
rows.first&.fetch(:identity)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require_relative "../../ports/query/in_memory"
|
|
2
|
+
require_relative "../../ports/query/ordering"
|
|
3
|
+
require_relative "../../query_specification/common/order_by"
|
|
4
|
+
require_relative "../../query_specification/field_path"
|
|
5
|
+
require_relative "../../runtime/errors"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Adapters
|
|
9
|
+
# SHARED BY every adapter that holds decoded Ruby records rather than
|
|
10
|
+
# running real SQL (Memory, Lambda, Heki) — the identical dotted-path
|
|
11
|
+
# value-object member-picking Postgres/Sqlite/D1's own
|
|
12
|
+
# order_expression does (numeric member wins, else the one-field
|
|
13
|
+
# convention "value"), just walked in Ruby instead of compiled to a
|
|
14
|
+
# JSONB/json_extract path, because there is no query engine
|
|
15
|
+
# underneath any of these three to hide that walk inside.
|
|
16
|
+
module InMemoryOrdering
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# order_by IS A RUNTIME VALUE (an HTTP query param, in the
|
|
20
|
+
# console's case), not framework-authored bluebook source — see
|
|
21
|
+
# postgres.rb's own all for the full reasoning. Whitelisted the
|
|
22
|
+
# identical way before FieldPath.dig ever runs.
|
|
23
|
+
def ordered(records, aggregate:, order_by:, direction:)
|
|
24
|
+
return records unless order_by
|
|
25
|
+
|
|
26
|
+
name = order_by.to_s.split(".").first
|
|
27
|
+
raise Runtime::WiringError, "#{aggregate.name} has no attribute #{order_by.inspect} to order by" unless aggregate.lifecycle&.field.to_s == name || aggregate.attribute(name)
|
|
28
|
+
|
|
29
|
+
path = sortable_path(aggregate, order_by)
|
|
30
|
+
spec = QuerySpecification::Common::OrderBy.new(field: order_by, direction: direction)
|
|
31
|
+
Ports::Query::Ordering.apply(records, spec, nil, identity: ->(record) { record.id.to_s }) do |record|
|
|
32
|
+
Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(record, path))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def sortable_path(aggregate, field)
|
|
37
|
+
name, *path = field.to_s.split(".")
|
|
38
|
+
return field.to_s unless path.empty?
|
|
39
|
+
|
|
40
|
+
attribute = aggregate.attribute(name)
|
|
41
|
+
return field.to_s if aggregate.lifecycle&.field.to_s == name || attribute.nil?
|
|
42
|
+
|
|
43
|
+
vo = aggregate.value_object(attribute.type)
|
|
44
|
+
return field.to_s unless vo
|
|
45
|
+
|
|
46
|
+
member = vo.attributes.find { |a| %w[Integer Float].include?(a.type) }&.name || "value"
|
|
47
|
+
"#{name}.#{member}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
class Lambda
|
|
6
|
+
# THE THIN AWS CLIENT — shared by this adapter's own read methods
|
|
7
|
+
# (Lambda#find/#all/#count/#query) and Runtime::RemoteDispatcher's
|
|
8
|
+
# write methods. One Lambda invoke, one JSON round trip, nothing
|
|
9
|
+
# domain-specific: neither caller needs to know an AWS SDK is
|
|
10
|
+
# involved at all.
|
|
11
|
+
#
|
|
12
|
+
# FUNCTION NAME IS COMPUTED, NOT PASSED — `"hecks-#{domain}"`,
|
|
13
|
+
# lowercased, matches bin/project_deploy's own `stack_name`
|
|
14
|
+
# exactly (bin/project_deploy: `stack_name = "hecks-#{domain_name}"`,
|
|
15
|
+
# `domain_name = File.basename(domain)`). `domain` here is the
|
|
16
|
+
# bluebook's OWN declared name (`Embryonaut`, not the directory);
|
|
17
|
+
# today's real corpus has directory name == declared name
|
|
18
|
+
# lowercased for every domain that deploys, so `.downcase` alone
|
|
19
|
+
# reproduces the same string bin/project_deploy computes from the
|
|
20
|
+
# directory — this is a real, load-bearing assumption, not a
|
|
21
|
+
# coincidence to lean on silently forever if that ever stops
|
|
22
|
+
# holding.
|
|
23
|
+
class Client
|
|
24
|
+
def initialize(domain:, region:)
|
|
25
|
+
require "aws-sdk-lambda"
|
|
26
|
+
@function_name = "hecks-#{domain.to_s.downcase}"
|
|
27
|
+
@client = Aws::Lambda::Client.new(region: region)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# THE WHOLE DOMAIN, EVERY TIME — matches dispatch::read's own
|
|
31
|
+
# rehydrate-the-full-journal design (Phase 1, rust/host). No
|
|
32
|
+
# caching here, deliberately not even per-request: Lambda#all
|
|
33
|
+
# used to memoize this across calls, which silently served
|
|
34
|
+
# stale reads for a warm web process's whole lifetime once a
|
|
35
|
+
# write happened elsewhere — see Lambda#instances's own comment
|
|
36
|
+
# on the real, live bug that caught it.
|
|
37
|
+
def read
|
|
38
|
+
invoke({ "read" => true })
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def dispatch(verb, args, role: nil)
|
|
42
|
+
payload = { "verb" => verb, "args" => args }
|
|
43
|
+
payload["role"] = role if role
|
|
44
|
+
invoke(payload)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def invoke(payload)
|
|
50
|
+
response = @client.invoke(function_name: @function_name, payload: JSON.generate(payload))
|
|
51
|
+
body = JSON.parse(response.payload.read)
|
|
52
|
+
|
|
53
|
+
if response.function_error
|
|
54
|
+
raise Runtime::WiringError,
|
|
55
|
+
"Lambda #{@function_name} (#{response.function_error}): #{body['errorMessage'] || body}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
body
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require_relative "lambda/client"
|
|
2
|
+
require_relative "../../ports/query/in_memory"
|
|
3
|
+
require_relative "../../ports/persistence/remote_runtime"
|
|
4
|
+
require_relative "in_memory_ordering"
|
|
5
|
+
require_relative "../../runtime/instance"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Adapters
|
|
9
|
+
# THE READ-SIDE HALF OF LAMBDA ROUTING — `persisted_by("Lambda")`'s
|
|
10
|
+
# own adapter, resolved through the SAME `Ports::Persistence`
|
|
11
|
+
# machinery `persisted_by("Postgres")`/`persisted_by("Memory")`
|
|
12
|
+
# already use (`Runtime::Registry#repository`, `RepositoryFactory
|
|
13
|
+
# .build`) — no new framework plumbing, just a new adapter class at
|
|
14
|
+
# the name the existing lookup already expects. See
|
|
15
|
+
# `Runtime::RemoteDispatcher` for the write-side half; the two
|
|
16
|
+
# compose through one shared `Client` per domain rather than each
|
|
17
|
+
# inventing its own AWS wiring.
|
|
18
|
+
#
|
|
19
|
+
# READ-ONLY, DELIBERATELY — `append`/`project` raise rather than
|
|
20
|
+
# silently no-op. A write reaching this class would mean
|
|
21
|
+
# `Runtime::CommandInterpreter` ran locally against a Lambda-routed
|
|
22
|
+
# domain, which is exactly the bypass `Runtime::RemoteDispatcher`
|
|
23
|
+
# exists to prevent (business-rule validation must happen in Rust
|
|
24
|
+
# for these domains, not be pre-computed here against an
|
|
25
|
+
# incomplete local view). A loud failure here is the correct
|
|
26
|
+
# outcome, not a bug to route around.
|
|
27
|
+
class Lambda
|
|
28
|
+
include Ports::Persistence::RemoteRuntime
|
|
29
|
+
|
|
30
|
+
attr_reader :aggregate
|
|
31
|
+
|
|
32
|
+
def initialize(aggregate:, settings: {}, root: nil)
|
|
33
|
+
@aggregate = aggregate
|
|
34
|
+
domain =
|
|
35
|
+
if settings.key?(:domain)
|
|
36
|
+
settings[:domain]
|
|
37
|
+
elsif settings.key?("domain")
|
|
38
|
+
settings["domain"]
|
|
39
|
+
else
|
|
40
|
+
aggregate.name
|
|
41
|
+
end
|
|
42
|
+
region =
|
|
43
|
+
if settings.key?(:region)
|
|
44
|
+
settings[:region]
|
|
45
|
+
elsif settings.key?("region")
|
|
46
|
+
settings["region"]
|
|
47
|
+
else
|
|
48
|
+
"us-east-1"
|
|
49
|
+
end
|
|
50
|
+
# TWO DIFFERENT "domain"s, deliberately not conflated: `domain`
|
|
51
|
+
# (this aggregate's OWN bluebook name — "Identity", "Governance")
|
|
52
|
+
# only ever prefixes the instances lookup, since that's how
|
|
53
|
+
# rust/host's own `Store::instances()` keys every record
|
|
54
|
+
# (`registry.rb`'s own `"#{a[:domain_name]}::#{a[:name]}#"`
|
|
55
|
+
# dump format, unchanged by which chapter attached it). The
|
|
56
|
+
# FUNCTION to actually call is a different question: Governance
|
|
57
|
+
# and Identity aggregates are compiled into the ATTACHING
|
|
58
|
+
# domain's own Lambda (one merged `Store` per target — Phase 0's
|
|
59
|
+
# framework-bluebook work), never a function of their own, so
|
|
60
|
+
# `settings[:domain]` is the wrong signal for `Client.new`.
|
|
61
|
+
# `root` is the boot's own directory (`Registry#root`, shared by
|
|
62
|
+
# EVERY bluebook in one registry regardless of which one
|
|
63
|
+
# attached it) — `File.basename(root)` reproduces the exact
|
|
64
|
+
# same string `bin/project_deploy`'s own `stack_name` computes
|
|
65
|
+
# from the domain PATH, so the two can never name two
|
|
66
|
+
# different functions for the same deploy... on a LOCAL boot,
|
|
67
|
+
# where `root` is a real project directory. Inside the deployed
|
|
68
|
+
# Lambda itself `root` is ALWAYS `/var/task` (every Lambda's own
|
|
69
|
+
# fixed code root, regardless of domain) — `File.basename` gives
|
|
70
|
+
# "task", not the domain name, and invokes the wrong function
|
|
71
|
+
# entirely. A real, live AccessDeniedException on
|
|
72
|
+
# "hecks-task" caught this: invisible through every earlier
|
|
73
|
+
# phase's own verify step, all run from a local boot, until
|
|
74
|
+
# WebFunction became the first Ruby process to EVER make this
|
|
75
|
+
# exact call from inside a deployed Lambda. `DOMAIN_NAME` (set
|
|
76
|
+
# by bin/project_deploy's own WebFunction Environment) is the
|
|
77
|
+
# real, unambiguous signal in that specific context; the
|
|
78
|
+
# root-basename heuristic stays as the local-boot fallback,
|
|
79
|
+
# unchanged.
|
|
80
|
+
function_domain = ENV["DOMAIN_NAME"] || (root ? File.basename(root) : domain)
|
|
81
|
+
@client = Client.new(domain: function_domain, region: region)
|
|
82
|
+
@prefix = "#{domain}::#{aggregate.hecks_name}#"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def find(id)
|
|
86
|
+
instances[id.to_s]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def all(order_by: nil, direction: :asc)
|
|
90
|
+
InMemoryOrdering.ordered(instances.values, aggregate: @aggregate, order_by: order_by, direction: direction)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def count = instances.size
|
|
94
|
+
|
|
95
|
+
def query(specification, args = {}, context: {})
|
|
96
|
+
Ports::Query::InMemory.execute(instances.values, specification, args)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# `entries`/`append`/`project` — the append-only contract
|
|
100
|
+
# `Ports::Persistence::AppendOnly` requires of every adapter
|
|
101
|
+
# (raises at construction if any are missing). Answered by
|
|
102
|
+
# `Ports::Persistence::RemoteRuntime`, included above: `entries` is
|
|
103
|
+
# `[]` unconditionally (Lambda's own Postgres is already the
|
|
104
|
+
# durable store — rust/host's rehydrate-and-replay journal, Phase
|
|
105
|
+
# 1 — there is no local write-ahead log for `recover!` to replay),
|
|
106
|
+
# `append`/`project` raise rather than silently no-op.
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# RE-FETCHED EVERY CALL, deliberately NOT memoized across them —
|
|
111
|
+
# this adapter itself is long-lived (one instance per aggregate,
|
|
112
|
+
# held by the registry `RUNTIME = Hecks.boot(...)` builds ONCE
|
|
113
|
+
# per Lambda web process — WebFunction's own top-level constant,
|
|
114
|
+
# reused warm across every HTTP request that process serves, not
|
|
115
|
+
# rebuilt per request the way a memoize-for-one-request comment
|
|
116
|
+
# here used to assume). A real, live bug caught this: a mutation
|
|
117
|
+
# dispatched fine (RemoteDispatcher always calls the dispatch
|
|
118
|
+
# Lambda fresh) and the very next `.all` on the SAME warm
|
|
119
|
+
# container kept returning the state from BEFORE that mutation,
|
|
120
|
+
# forever, until the container cold-started — memoizing here
|
|
121
|
+
# made every write invisible to every read on a warm container.
|
|
122
|
+
# Keyed by bare id (the part after "Domain::Aggregate#"), not the
|
|
123
|
+
# full "Domain::Aggregate#id" string — callers already know which
|
|
124
|
+
# aggregate they're asking this instance about.
|
|
125
|
+
def instances
|
|
126
|
+
@client.read.fetch("instances", {}).filter_map { |key, state|
|
|
127
|
+
next unless key.start_with?(@prefix)
|
|
128
|
+
|
|
129
|
+
[key.delete_prefix(@prefix), build_instance(key.delete_prefix(@prefix), state)]
|
|
130
|
+
}.to_h
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Lambda's own JSON response is already Ruby-decoded with STRING
|
|
134
|
+
# keys (plain `JSON.parse`, no `symbolize_names:`) — every other
|
|
135
|
+
# adapter's own `instance(row)` builder (Postgres, Sqlite) hands
|
|
136
|
+
# `Runtime::Instance.new` a SYMBOL-keyed state hash instead. A
|
|
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.
|
|
140
|
+
def build_instance(id, state)
|
|
141
|
+
Runtime::Instance.new(aggregate: @aggregate, id: id, state: JSON.parse(JSON.generate(state), symbolize_names: true))
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require_relative "../../ports/persistence/append_only"
|
|
2
|
+
require_relative "../../ports/query/in_memory"
|
|
3
|
+
require_relative "in_memory_ordering"
|
|
4
|
+
require_relative "../../runtime/instance"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
class Memory
|
|
9
|
+
# TENANT-CAPABLE TRIVIALLY — see Runtime::TenantCheck's own header
|
|
10
|
+
# for the full reasoning. `@records` is a plain instance variable;
|
|
11
|
+
# two `Runtime.boot` calls build two entirely separate Registry
|
|
12
|
+
# objects and, through them, two entirely separate Memory
|
|
13
|
+
# instances, so two tenant boots never share this adapter's state
|
|
14
|
+
# by construction — nothing here needs to know "tenant" exists.
|
|
15
|
+
def self.tenant_capable? = true
|
|
16
|
+
def persistence_capabilities = [:atomic_put]
|
|
17
|
+
|
|
18
|
+
attr_reader :aggregate
|
|
19
|
+
|
|
20
|
+
def initialize(aggregate:, settings: {}, root: nil)
|
|
21
|
+
@aggregate = aggregate
|
|
22
|
+
@records = {}
|
|
23
|
+
@events = []
|
|
24
|
+
@entries = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find(id) = @records[id.to_s]
|
|
28
|
+
def count = @records.size
|
|
29
|
+
|
|
30
|
+
def all(order_by: nil, direction: :asc)
|
|
31
|
+
InMemoryOrdering.ordered(@records.values, aggregate: @aggregate, order_by: order_by, direction: direction)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def query(specification, args = {}, context: {})
|
|
35
|
+
Ports::Query::InMemory.execute(all, specification, args, registry: context[:registry])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def append(entry)
|
|
39
|
+
@entries << entry
|
|
40
|
+
entry
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def project(entry)
|
|
44
|
+
if entry.save?
|
|
45
|
+
@records[entry.id] = Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: entry.state.dup)
|
|
46
|
+
else
|
|
47
|
+
@records.delete(entry.id)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def save(instance)
|
|
52
|
+
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
53
|
+
append(entry)
|
|
54
|
+
project(entry)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# One in-memory critical section in the only thread touching this plain
|
|
58
|
+
# Hash: classify and replace without a preliminary repository lookup.
|
|
59
|
+
# Durable append and projection remain ordered exactly as ordinary save.
|
|
60
|
+
def atomic_put(entry, insert_only: false)
|
|
61
|
+
exists = @records.key?(entry.id.to_s)
|
|
62
|
+
return :conflicted if insert_only && exists
|
|
63
|
+
|
|
64
|
+
status = exists ? :replaced : :inserted
|
|
65
|
+
append(entry)
|
|
66
|
+
project(entry)
|
|
67
|
+
status
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def delete(id)
|
|
71
|
+
entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
|
|
72
|
+
append(entry)
|
|
73
|
+
project(entry)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def record_event(event) = @events << event
|
|
77
|
+
|
|
78
|
+
def events = @events
|
|
79
|
+
|
|
80
|
+
def entries = @entries.dup
|
|
81
|
+
|
|
82
|
+
# Every other driven adapter (Postgres/PostgresEra/Sqlite/D1)
|
|
83
|
+
# already implements this — `Ports::Persistence::AppendOnly#reset!`
|
|
84
|
+
# forwards to it and only raises when the wrapped adapter doesn't
|
|
85
|
+
# respond to `reset!` at all, which was always this adapter's own
|
|
86
|
+
# gap, not a deliberate omission (nothing about "in memory" implies
|
|
87
|
+
# "cannot be cleared"). Existing callers that fully re-`Hecks.boot`
|
|
88
|
+
# a domain per test case don't need this — they get a brand new
|
|
89
|
+
# `Memory` instance, with brand new empty `@records`/`@events`/
|
|
90
|
+
# `@entries`, for free. This is for the other case: a caller that
|
|
91
|
+
# deliberately keeps ONE booted runtime across many cases (to skip
|
|
92
|
+
# `load_domain`'s own per-boot parse/verify cost) and wants each
|
|
93
|
+
# case to start from the same clean slate `Hecks.boot` would have
|
|
94
|
+
# given it, without paying for a fresh boot to get there.
|
|
95
|
+
def reset!
|
|
96
|
+
@records = {}
|
|
97
|
+
@events = []
|
|
98
|
+
@entries = []
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
# A GENERIC STAND-IN for a real Checkout-Session-style adapter — no
|
|
6
|
+
# network call, no real account, matching whatever real payment
|
|
7
|
+
# adapter a project pairs it with by shape alone
|
|
8
|
+
# (`create_session(event:, registration_id:, success_url:, cancel_url:)
|
|
9
|
+
# -> a URL string`), the same way `Memory` stands in for a real
|
|
10
|
+
# persistence adapter without knowing anything about the domain bound
|
|
11
|
+
# to it. Never reads `event`'s own fields — a real adapter needs the
|
|
12
|
+
# price/name inside it to build a session a payer actually sees;
|
|
13
|
+
# this only needs to look enough like one to swap in.
|
|
14
|
+
#
|
|
15
|
+
# The URL it returns carries the same `registration_id` a real
|
|
16
|
+
# webhook's metadata would, so a caller building its own signed
|
|
17
|
+
# webhook against it (a smoke test, CI) has something real to key
|
|
18
|
+
# off of — the real shape production has, minus the network hop.
|
|
19
|
+
class MockStripeAdapter
|
|
20
|
+
def initialize(aggregate: nil, settings: {}, root: nil); end
|
|
21
|
+
|
|
22
|
+
def create_session(event:, registration_id:, success_url:, cancel_url:)
|
|
23
|
+
separator = success_url.include?("?") ? "&" : "?"
|
|
24
|
+
"#{success_url}#{separator}mock_checkout=1&mock_registration_id=#{registration_id}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|