hecks 2.7.0 → 2.9.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/exe/hecks +11 -0
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +18 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +32 -142
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
- data/lib/hecks/bluebook/model_check.rb +102 -600
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +84 -0
- data/lib/hecks/cli/document.rb +86 -0
- data/lib/hecks/cli/ir.rb +45 -0
- data/lib/hecks/cli/mcp.rb +31 -0
- data/lib/hecks/cli/mcp_door.rb +366 -0
- data/lib/hecks/cli/model_check.rb +203 -0
- data/lib/hecks/cli/project_cli.rb +90 -0
- data/lib/hecks/cli/project_diagrams.rb +36 -0
- data/lib/hecks/cli/run.rb +173 -0
- data/lib/hecks/cli/smoke_test.rb +45 -0
- data/lib/hecks/cli/stores.rb +53 -0
- data/lib/hecks/cli.rb +160 -0
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +95 -0
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +62 -65
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +151 -679
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +9 -16
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +46 -138
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +93 -0
- data/lib/hecks/release/runner/commands.rb +66 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +43 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +75 -0
- data/lib/hecks/release/runner/preflight.rb +104 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +80 -0
- data/lib/hecks/release/runner.rb +215 -0
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +125 -307
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +65 -220
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +103 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +146 -567
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +32 -82
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +30 -59
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -4,39 +4,28 @@ module Hecks
|
|
|
4
4
|
module Ports
|
|
5
5
|
module Persistence
|
|
6
6
|
class PostgresDump
|
|
7
|
-
# A Postgres connection URL, split so a password reaches
|
|
8
|
-
#
|
|
9
|
-
# any user of the machine can read it from the process list.
|
|
7
|
+
# A Postgres connection URL, split so a password reaches child-process tools
|
|
8
|
+
# through their environment, never on a command line the process list exposes.
|
|
10
9
|
class Connection
|
|
11
|
-
# @param url [String] a `postgres://` or `postgresql://` URL
|
|
12
|
-
# @raise [PostgresDump::Error] if `url` is not a Postgres URL
|
|
13
10
|
def initialize(url)
|
|
14
11
|
@uri = URI.parse(url)
|
|
15
12
|
raise Error, "not a postgres URL" unless %w[postgres postgresql].include?(@uri.scheme)
|
|
16
13
|
end
|
|
17
14
|
|
|
18
|
-
# Names the database the URL points at.
|
|
19
|
-
#
|
|
20
|
-
# @return [String] the database name, empty when the URL names none
|
|
15
|
+
# Names the database the URL points at, empty when the URL names none.
|
|
21
16
|
def database
|
|
22
17
|
@uri.path.to_s.delete_prefix("/")
|
|
23
18
|
end
|
|
24
19
|
|
|
25
|
-
# Points the same server and credentials at another database.
|
|
26
|
-
#
|
|
27
|
-
# @param name [String] the database to connect to instead
|
|
28
|
-
# @return [Connection] a new connection; this one is unchanged
|
|
20
|
+
# Points the same server and credentials at another database; this one is unchanged.
|
|
29
21
|
def with_database(name)
|
|
30
22
|
copy = @uri.dup
|
|
31
23
|
copy.path = "/#{name}"
|
|
32
24
|
self.class.new(copy.to_s)
|
|
33
25
|
end
|
|
34
26
|
|
|
35
|
-
# Lists the connection settings the URL carries, in `PG.connect` keyword form
|
|
36
|
-
#
|
|
37
|
-
# @return [Hash{Symbol => String, Integer}] `host`, `port`, `user`, `password`,
|
|
38
|
-
# `dbname` and `sslmode`, each only when the URL gives one; the user and password
|
|
39
|
-
# are percent-decoded
|
|
27
|
+
# Lists the connection settings the URL carries, in `PG.connect` keyword form; the
|
|
28
|
+
# user and password come back percent-decoded.
|
|
40
29
|
def settings
|
|
41
30
|
{
|
|
42
31
|
host: (@uri.host unless @uri.host.to_s.empty?), port: @uri.port,
|
|
@@ -46,16 +35,11 @@ module Hecks
|
|
|
46
35
|
end
|
|
47
36
|
|
|
48
37
|
# Builds the libpq environment variables for a child process.
|
|
49
|
-
#
|
|
50
|
-
# @return [Hash{String => String}] `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`,
|
|
51
|
-
# `PGDATABASE` and `PGSSLMODE`, each only when `settings` has the matching key
|
|
52
38
|
def env
|
|
53
39
|
settings.to_h { |key, value| ["PG#{key.to_s.sub('dbname', 'database').upcase}", value.to_s] }
|
|
54
40
|
end
|
|
55
41
|
|
|
56
|
-
# Opens a connection with the `pg` gem.
|
|
57
|
-
#
|
|
58
|
-
# @return [PG::Connection] an open connection; the caller closes it
|
|
42
|
+
# Opens a connection with the `pg` gem; the caller closes it.
|
|
59
43
|
def connect
|
|
60
44
|
require "pg"
|
|
61
45
|
PG.connect(**settings)
|
|
@@ -5,42 +5,16 @@ require_relative "postgres_dump/connection"
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Ports
|
|
7
7
|
module Persistence
|
|
8
|
-
# Dumps one schema of a Postgres database and proves the dump restores
|
|
9
|
-
#
|
|
10
|
-
# ## What the proof is
|
|
11
|
-
#
|
|
12
|
-
# A dump nobody has restored is a hope. After `pg_dump` writes the file,
|
|
13
|
-
# the dump is restored into a scratch database (on the source server, or on a
|
|
14
|
-
# separate `verify_url` server such as the one the dump will be loaded
|
|
15
|
-
# into) and every table's row count is compared with the source. That shows
|
|
16
|
-
# the dump is complete and loadable. It does not re-derive current state
|
|
17
|
-
# from an event journal, so a caller who needs that proves it separately.
|
|
18
|
-
#
|
|
19
|
-
# ## Credentials
|
|
20
|
-
#
|
|
21
|
-
# URLs are split by `Connection`: the password travels in the child
|
|
22
|
-
# process's environment, never in `argv`.
|
|
23
|
-
#
|
|
24
|
-
# ## Requirements
|
|
25
|
-
#
|
|
26
|
-
# The `pg` gem, and `pg_dump` and `pg_restore` on `PATH` at a version no older
|
|
27
|
-
# than the server's. Neither the gem nor the tools are a dependency of
|
|
28
|
-
# hecks; nothing here loads until a dump is asked for.
|
|
8
|
+
# Dumps one schema of a Postgres database and proves the dump restores, by
|
|
9
|
+
# restoring it into a scratch database and comparing every table's row count.
|
|
29
10
|
class PostgresDump
|
|
30
11
|
# Raised for a refusal or a failed tool.
|
|
31
12
|
class Error < StandardError; end
|
|
32
13
|
|
|
33
|
-
# What a verified dump holds.
|
|
34
|
-
#
|
|
35
|
-
# @!attribute [r] tables
|
|
36
|
-
# @return [Hash{String => Integer}] row count of every table in the schema, by name
|
|
14
|
+
# What a verified dump holds: `tables` is each table's row count, by name.
|
|
37
15
|
Result = Struct.new(:tables, keyword_init: true)
|
|
38
16
|
|
|
39
|
-
#
|
|
40
|
-
# @param schema [String] the schema to dump, a plain identifier
|
|
41
|
-
# @param verify_url [String, nil] the server to restore into for the proof; the source
|
|
42
|
-
# server when nil
|
|
43
|
-
# @raise [Error] if `schema` is not a plain identifier or a URL is not a Postgres URL
|
|
17
|
+
# Splits `url` and `verify_url` into connections and validates `schema`.
|
|
44
18
|
def initialize(url:, schema:, verify_url: nil)
|
|
45
19
|
raise Error, "schema #{schema.inspect} is not a plain identifier" unless schema.to_s.match?(/\A[a-z_][a-z0-9_]*\z/i)
|
|
46
20
|
|
|
@@ -50,11 +24,6 @@ module Hecks
|
|
|
50
24
|
end
|
|
51
25
|
|
|
52
26
|
# Writes a custom-format dump of the schema and proves it restores.
|
|
53
|
-
#
|
|
54
|
-
# @param dest [String] the file to write
|
|
55
|
-
# @return [Result] the verified row counts
|
|
56
|
-
# @raise [Error] if the schema has no tables, a tool fails, the database refuses, or
|
|
57
|
-
# the restored counts differ from the source's
|
|
58
27
|
def call(dest)
|
|
59
28
|
require "pg"
|
|
60
29
|
raise Error, "schema #{@schema} has no tables in #{@source.database}" if counting(@source) { |db| counts(db) }.empty?
|
|
@@ -3,30 +3,8 @@ require_relative "../../runtime/registry"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Ports
|
|
5
5
|
module Persistence
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Heki) or over the network (D1), doesn't matter, the point is it
|
|
9
|
-
# does real local interpretation and has real entries to replay.
|
|
10
|
-
# This names the second, different shape: not "storage reached
|
|
11
|
-
# remotely," but the real interpreter itself living behind a call
|
|
12
|
-
# boundary — `append`/`project` don't do partial local work and
|
|
13
|
-
# then fail, they raise unconditionally, because there is no local
|
|
14
|
-
# write-ahead log to have; `entries` is always `[]` for the same
|
|
15
|
-
# reason (`AppendOnly#recover!` replaying zero entries here is a
|
|
16
|
-
# correct no-op, not a lie).
|
|
17
|
-
#
|
|
18
|
-
# `Adapters::Lambda` is the first adapter to `include` this, not
|
|
19
|
-
# the only one meant to — any future adapter for some other
|
|
20
|
-
# remote-runtime target (a different compute platform, a gRPC
|
|
21
|
-
# stub, whatever) gets this shape for free by including one module
|
|
22
|
-
# instead of re-deriving the same raise-on-write/always-empty-
|
|
23
|
-
# entries boilerplate from scratch. And because it's a real,
|
|
24
|
-
# checkable module rather than an adapter's own ad hoc
|
|
25
|
-
# implementation, a caller can ask "is this adapter a remote-
|
|
26
|
-
# runtime delegate?" as a genuine capability check
|
|
27
|
-
# (`registry.adapter_class(name) <= RemoteRuntime`,
|
|
28
|
-
# `Runtime::RemoteDispatcher`'s own use) instead of comparing
|
|
29
|
-
# adapter names by string.
|
|
6
|
+
# Adapter shape for an interpreter behind a call boundary: no local log, so writes raise.
|
|
7
|
+
# Detect it with `registry.adapter_class(name) <= RemoteRuntime`, not by adapter name.
|
|
30
8
|
module RemoteRuntime
|
|
31
9
|
# Refuses every local write; `project` is an alias and refuses the same way.
|
|
32
10
|
#
|
|
@@ -4,48 +4,29 @@ require_relative "codec_boundary"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Ports
|
|
6
6
|
module Persistence
|
|
7
|
-
# Turns a declared adapter binding
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# Every adapter it builds is guarded (`CodecBoundary.guard!`) before
|
|
11
|
-
# anything else touches it — `recover!` included — so no adapter a
|
|
12
|
-
# runtime reaches can build an `Instance` from undecoded state.
|
|
7
|
+
# Turns a declared adapter binding into a concrete repository, guarding every
|
|
8
|
+
# adapter it builds (`CodecBoundary.guard!`) before `recover!` or anything else touches it.
|
|
13
9
|
module RepositoryFactory
|
|
14
10
|
module_function
|
|
15
11
|
|
|
16
12
|
# Instantiates the adapter a bind names, guards it, and wraps it as a repository.
|
|
17
|
-
#
|
|
18
|
-
# @param registry [Runtime::Registry] the registry supplying the adapter class, the
|
|
19
|
-
# domain's world settings, its resolved eras and the root path
|
|
20
|
-
# @param domain [String, Symbol] name of the domain the aggregate belongs to
|
|
21
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate to persist
|
|
22
|
-
# @param bind [Bluebook::Bind] the bind naming the adapter, as `BindingPolicy.resolve`
|
|
23
|
-
# or the projection port chose it
|
|
24
|
-
# @param recover [Boolean] true to replay the journal through `project` before returning
|
|
25
|
-
# @param settings_verb [String] the verb whose world settings configure the adapter;
|
|
26
|
-
# `"persisted_by"` by default, `Ports::Projection::VERB` for a projection
|
|
27
|
-
# @return [Persistence::AppendOnly] the repository over the guarded adapter
|
|
28
|
-
# @raise [Runtime::WiringError] if the bind's verb or settings fail the registry's
|
|
29
|
-
# checks, no `Hecks::Adapters` constant matches the adapter name, or the adapter
|
|
30
|
-
# lacks `append`, `project` or `entries`
|
|
31
13
|
def build(registry, domain, aggregate, bind, recover: true, settings_verb: VERB)
|
|
32
14
|
registry.check_verb(bind)
|
|
33
15
|
settings = registry.binding_settings(domain, settings_verb, bind.adapter)
|
|
34
16
|
.reject { |key, _| key.to_sym == :role }
|
|
35
17
|
registry.check_settings(bind, settings)
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# check: a lineage adapter journals per domain, writes into its
|
|
39
|
-
# own era's partition, and refuses to write at all once that era
|
|
40
|
-
# is superseded (`PostgresEra#append`, BUG#24) — none of which a
|
|
41
|
-
# world's settings carry.
|
|
18
|
+
# domain, resolved era and superseding era ride along outside the declared
|
|
19
|
+
# settings: a lineage adapter needs them to journal per domain, per-era.
|
|
42
20
|
adapter = registry.adapter_class(bind.adapter)
|
|
43
21
|
.new(aggregate: aggregate,
|
|
44
22
|
settings: settings.merge(domain: domain.to_s, era: registry.resolved_eras[domain.to_s],
|
|
45
23
|
superseded_by: registry.superseded_eras[domain.to_s]),
|
|
46
24
|
root: registry.root)
|
|
47
25
|
repository = AppendOnly.new(CodecBoundary.guard!(adapter))
|
|
48
|
-
|
|
26
|
+
# `:atomic_append` (PostgresEra) means `append` already committed the
|
|
27
|
+
# projected state in the same transaction as the journal row, so a
|
|
28
|
+
# boot-time replay through `project` has nothing left to fix up.
|
|
29
|
+
recover && !repository.capabilities.include?(:atomic_append) ? repository.recover! : repository
|
|
49
30
|
end
|
|
50
31
|
end
|
|
51
32
|
end
|
|
@@ -4,58 +4,11 @@ require_relative "../../runtime/value"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Ports
|
|
6
6
|
module Persistence
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# answer every adapter converges on, because adapters left to decode
|
|
10
|
-
# their own way disagree: symbolizing the top level only (a Heki head,
|
|
11
|
-
# a journal reader), symbolizing deep (a SQL or Lambda head), or never
|
|
12
|
-
# serializing at all (Memory's shallow `state.dup`).
|
|
13
|
-
# spec/ports/persistence_legacy_decode_spec.rb pins that bytes written
|
|
14
|
-
# without the codec still decode to the one canonical shape. Every
|
|
15
|
-
# adapter writes through `encode` and reads through `decode`
|
|
16
|
-
# (Memory through `copy`; PR A3), and `CodecBoundary` — installed on every
|
|
17
|
-
# adapter `RepositoryFactory.build` makes — refuses an `Instance`
|
|
18
|
-
# built inside an adapter call from state `decoded?` rejects.
|
|
7
|
+
# Canonical spelling of an aggregate's state across the store boundary;
|
|
8
|
+
# every adapter's `encode`, `decode` and `copy` route through here.
|
|
19
9
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# - `encode` — canonical and JSON-ready: string keys at every depth,
|
|
23
|
-
# `Runtime::Value`s materialized, only JSON scalars at the leaves.
|
|
24
|
-
# Needs no IR (JSON has one spelling), but takes it for symmetry.
|
|
25
|
-
# - `decode` — walks the aggregate's IR: attributes, value objects
|
|
26
|
-
# (their fields, recursively), `list_of` value objects and entities
|
|
27
|
-
# (entity fields, nested entities, the entity's own lifecycle),
|
|
28
|
-
# references, the lifecycle field, and projected fields. A declared
|
|
29
|
-
# key becomes a symbol at every depth, whichever spelling arrived.
|
|
30
|
-
# - `copy` — `decode(encode(state))`: what a durable adapter would hand
|
|
31
|
-
# back, for Memory, with no JSON text in between.
|
|
32
|
-
#
|
|
33
|
-
# ## What decode never does
|
|
34
|
-
#
|
|
35
|
-
# It never invents a key. A declared field absent from the stored
|
|
36
|
-
# state stays absent — not a present nil — because the runtime reads
|
|
37
|
-
# absence as "this record predates the field": `Instance.
|
|
38
|
-
# hydrate_with_defaults` fills a declared `default:` only when the key
|
|
39
|
-
# is missing (spec/runtime/hydrate_defaults_spec.rb), Era translation
|
|
40
|
-
# backfills only `unless state.key?` (era/lineage.rb#translate), and a
|
|
41
|
-
# required declared-but-absent field reads as a named refusal rather
|
|
42
|
-
# than nil (spec/runtime/attribute_absence_spec.rb). A present nil
|
|
43
|
-
# would silently suppress all three. For the same reason it never
|
|
44
|
-
# drops a key, nil or not: a stored nil stays a stored nil.
|
|
45
|
-
#
|
|
46
|
-
# It never touches an undeclared key's value — a retired field, or a
|
|
47
|
-
# member a value object no longer declares, is exactly what an Era
|
|
48
|
-
# translation (rename/move/drop) still has to read. Its key keeps its
|
|
49
|
-
# spelling below the top level; at the top level every key is a
|
|
50
|
-
# symbol, declared or not, because every adapter has always
|
|
51
|
-
# symbolized the top level and `Lineage#translate` reads retired
|
|
52
|
-
# top-level names as symbols.
|
|
53
|
-
#
|
|
54
|
-
# ## When both spellings arrive
|
|
55
|
-
#
|
|
56
|
-
# When a hash carries both spellings of one declared key, the symbol
|
|
57
|
-
# spelling wins: it can only have been written by Ruby after the
|
|
58
|
-
# string one was read.
|
|
10
|
+
# `decode` never invents or drops a declared key: absence must stay absence, since
|
|
11
|
+
# defaults, era translation and required-field checks all key off it.
|
|
59
12
|
module StateCodec
|
|
60
13
|
JSON_SCALARS = [String, Integer, Float, TrueClass, FalseClass, NilClass].freeze
|
|
61
14
|
|
|
@@ -95,14 +48,8 @@ module Hecks
|
|
|
95
48
|
|
|
96
49
|
# Maps every declared top-level field name to its attribute.
|
|
97
50
|
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# fields: bare scalars with no attribute of their own). The same
|
|
101
|
-
# three sources, the same precedence — an attribute that happens to
|
|
102
|
-
# share the lifecycle's or a projected field's name keeps its type.
|
|
103
|
-
# An entity (no `projected_fields` of its own) gets its entity
|
|
104
|
-
# field set — `decoded?` is asked about any `Instance`, and an
|
|
105
|
-
# entity is "structurally interchangeable with an aggregate".
|
|
51
|
+
# An attribute that shares a name with the lifecycle field or a projected field keeps
|
|
52
|
+
# its declared type; an entity (no `projected_fields` of its own) gets its entity fields.
|
|
106
53
|
#
|
|
107
54
|
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct to walk
|
|
108
55
|
# @return [Hash{Symbol => Bluebook::Attribute, nil}] a new Hash of field name to
|
|
@@ -117,12 +64,8 @@ module Hecks
|
|
|
117
64
|
|
|
118
65
|
# Checks whether state already has the shape `decode` produces.
|
|
119
66
|
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# hash carrying both spellings of a declared key. Allocates
|
|
123
|
-
# nothing; `CodecBoundary` asks it of every `Instance` an adapter
|
|
124
|
-
# builds. A `Runtime::Value` (hydrated state, a save's own entry)
|
|
125
|
-
# already is the declared shape, so it answers true.
|
|
67
|
+
# `CodecBoundary` asks this of every `Instance` an adapter builds; a `Runtime::Value`
|
|
68
|
+
# already is the declared shape, so it answers true without allocating.
|
|
126
69
|
#
|
|
127
70
|
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct the state
|
|
128
71
|
# belongs to
|
|
@@ -134,8 +77,6 @@ module Hecks
|
|
|
134
77
|
hash_decoded?(aggregate, declared_top_level(aggregate), state, top: true)
|
|
135
78
|
end
|
|
136
79
|
|
|
137
|
-
# ── encode ──────────────────────────────────────────────────────
|
|
138
|
-
|
|
139
80
|
# Encodes one value, recursing through Hashes, Arrays and `Runtime::Value`s.
|
|
140
81
|
#
|
|
141
82
|
# @param value [Object] any state value; a `Runtime::Value` is encoded as its `to_h`
|
|
@@ -155,21 +96,15 @@ module Hecks
|
|
|
155
96
|
end
|
|
156
97
|
end
|
|
157
98
|
|
|
158
|
-
# ── decode ──────────────────────────────────────────────────────
|
|
159
|
-
|
|
160
99
|
# Decodes one Hash level, symbolizing its declared keys and recursing into their values.
|
|
100
|
+
# Key order is kept; a string key is skipped when the symbol spelling is also present.
|
|
161
101
|
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct, through
|
|
167
|
-
# which nested value objects and entities resolve
|
|
168
|
-
# @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at
|
|
169
|
-
# this level
|
|
102
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct nested
|
|
103
|
+
# value objects and entities resolve through
|
|
104
|
+
# @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at this level
|
|
170
105
|
# @param raw [Hash] the stored Hash for this level
|
|
171
|
-
# @param symbolize_undeclared [Boolean] true to symbolize undeclared keys too
|
|
172
|
-
#
|
|
106
|
+
# @param symbolize_undeclared [Boolean] true to symbolize undeclared keys too (the top
|
|
107
|
+
# level does); false to leave their spelling alone
|
|
173
108
|
# @return [Hash] a new Hash in `raw`'s key order
|
|
174
109
|
def decode_hash(aggregate, fields, raw, symbolize_undeclared: false)
|
|
175
110
|
raw.each_with_object({}) do |(key, value), out|
|
|
@@ -186,12 +121,8 @@ module Hecks
|
|
|
186
121
|
|
|
187
122
|
# Decodes the stored value of one declared field.
|
|
188
123
|
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
# bare id or a list of them), and a scalar pass through untouched; a
|
|
192
|
-
# value object or entity recurses only when the stored value really
|
|
193
|
-
# is the Hash/Array its declaration says — anything else (a legacy
|
|
194
|
-
# bare scalar for a one-field value object) is left for hydration.
|
|
124
|
+
# A value object or entity recurses only when the stored value matches its declared
|
|
125
|
+
# Hash/Array shape; anything else (a legacy bare scalar) is left for hydration.
|
|
195
126
|
#
|
|
196
127
|
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct, through
|
|
197
128
|
# which the field's type resolves
|
|
@@ -235,11 +166,8 @@ module Hecks
|
|
|
235
166
|
# Maps an entity's (or aggregate's) attribute names, plus its lifecycle field, to
|
|
236
167
|
# their attributes.
|
|
237
168
|
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# Its value objects and nested entities resolve through the root
|
|
241
|
-
# aggregate, the same way `EntityListCoercion#hydrate_entity_list`
|
|
242
|
-
# resolves them.
|
|
169
|
+
# Nested value objects and entities resolve through the root aggregate, the same way
|
|
170
|
+
# `EntityListCoercion#hydrate_entity_list` resolves them.
|
|
243
171
|
#
|
|
244
172
|
# @param entity [Bluebook::Entity, Bluebook::Aggregate] the construct to walk
|
|
245
173
|
# @return [Hash{Symbol => Bluebook::Attribute, nil}] a new Hash of field name to
|
|
@@ -251,23 +179,15 @@ module Hecks
|
|
|
251
179
|
fields
|
|
252
180
|
end
|
|
253
181
|
|
|
254
|
-
# ── decoded? ────────────────────────────────────────────────────
|
|
255
|
-
|
|
256
182
|
# Checks one Hash level for keys `decode` would respell.
|
|
183
|
+
# The mirror of `decode_hash`: an undeclared nested key keeps its own spelling.
|
|
257
184
|
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
# it has, exactly as `decode` keeps it.
|
|
262
|
-
#
|
|
263
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct, through
|
|
264
|
-
# which nested types resolve
|
|
265
|
-
# @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at
|
|
266
|
-
# this level
|
|
185
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct nested
|
|
186
|
+
# types resolve through
|
|
187
|
+
# @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at this level
|
|
267
188
|
# @param hash [Hash] the Hash to inspect
|
|
268
189
|
# @param top [Boolean] true for the top level, where every key must be a Symbol
|
|
269
|
-
# @return [Boolean] true when this level and every declared composite below it is
|
|
270
|
-
# decoded
|
|
190
|
+
# @return [Boolean] true when this level and every declared composite below it is decoded
|
|
271
191
|
def hash_decoded?(aggregate, fields, hash, top: false)
|
|
272
192
|
hash.all? do |key, value|
|
|
273
193
|
if key.is_a?(Symbol)
|
|
@@ -23,29 +23,22 @@ Hecks::Ports::Persistence.autoload(:PostgresDump, File.join(__dir__, "persistenc
|
|
|
23
23
|
|
|
24
24
|
module Hecks
|
|
25
25
|
module Ports
|
|
26
|
-
# Reopened once
|
|
27
|
-
#
|
|
28
|
-
# which adapter authoritatively binds an aggregate and building the
|
|
29
|
-
# repository that reads/writes it.
|
|
26
|
+
# Reopened once BindingPolicy/RepositoryFactory are loaded, adding the port's
|
|
27
|
+
# call surface for resolving binds and building repositories.
|
|
30
28
|
module Persistence
|
|
31
29
|
module_function
|
|
32
30
|
|
|
33
|
-
# Builds the repository that reads and writes one aggregate's authoritative store
|
|
34
|
-
#
|
|
35
|
-
# The public persistence port owns only authoritative aggregate heads. The repository
|
|
36
|
-
# comes back already recovered: every journal entry has been re-projected.
|
|
31
|
+
# Builds the repository that reads and writes one aggregate's authoritative store,
|
|
32
|
+
# already recovered from its journal.
|
|
37
33
|
#
|
|
38
34
|
# @param registry [Runtime::Registry] the booted registry holding the domain's
|
|
39
35
|
# hecksagon, world and adapters
|
|
40
36
|
# @param domain [String, Symbol] name of the domain the aggregate belongs to
|
|
41
37
|
# @param aggregate [Bluebook::Aggregate] the aggregate to persist
|
|
42
38
|
# @return [Persistence::AppendOnly] repository over the aggregate's authoritative
|
|
43
|
-
# adapter, or
|
|
44
|
-
# @raise [Runtime::WiringError] if the aggregate
|
|
45
|
-
#
|
|
46
|
-
# unknown, answers a different verb, is given a setting it does not declare, has no
|
|
47
|
-
# Ruby implementation, or lacks a method its port's `answers` list or the
|
|
48
|
-
# append-only contract (`append`, `project`, `entries`) requires
|
|
39
|
+
# adapter, or a `Memory` adapter when the domain declares no hecksagon
|
|
40
|
+
# @raise [Runtime::WiringError] if the aggregate's authoritative bind is missing,
|
|
41
|
+
# ambiguous, or the bound adapter cannot satisfy the append-only contract
|
|
49
42
|
def repository(registry, domain, aggregate)
|
|
50
43
|
authoritative = BindingPolicy.resolve(registry, domain, aggregate)
|
|
51
44
|
RepositoryFactory.build(registry, domain, aggregate, authoritative)
|
|
@@ -4,40 +4,18 @@ require_relative "../runtime/registry"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Ports
|
|
7
|
-
#
|
|
8
|
-
# authoritative entries and never participate in command decisions.
|
|
7
|
+
# Read-side port: rebuildable stores fed from authoritative entries, never read by commands.
|
|
9
8
|
module Projection
|
|
10
9
|
NAME = "projection".freeze
|
|
11
10
|
VERB = "projected_by".freeze
|
|
12
11
|
|
|
13
12
|
module_function
|
|
14
13
|
|
|
15
|
-
# Lists the `projected_by` binds a domain's hecksagon declares for one aggregate.
|
|
16
|
-
#
|
|
17
|
-
# @param registry [Runtime::Registry] the booted registry to search
|
|
18
|
-
# @param domain [String, Symbol] name of the domain the aggregate belongs to
|
|
19
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate to find projection binds for
|
|
20
|
-
# @return [Array<Bluebook::Bind>] the binds naming the aggregate, or failing that the
|
|
21
|
-
# hecksagon's aggregate-less `projected_by` binds; `[]` if there are none or the
|
|
22
|
-
# domain declares no hecksagon
|
|
23
14
|
def binds_for(registry, domain, aggregate)
|
|
24
15
|
registry.hecksagon(domain)&.binds_for(aggregate.hecks_name, VERB) || []
|
|
25
16
|
end
|
|
26
17
|
|
|
27
|
-
# Builds a worker
|
|
28
|
-
#
|
|
29
|
-
# @param registry [Runtime::Registry] the booted registry to resolve binds against
|
|
30
|
-
# @param domain [String, Symbol] name of the domain the aggregate belongs to
|
|
31
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate being projected
|
|
32
|
-
# @param policy [Symbol, String] `:refresh` (reset the store and rebuild it) or
|
|
33
|
-
# `:strict` (refuse on divergent history) — see `Worker::VALID_POLICIES`
|
|
34
|
-
# @return [Ports::Projection::Worker, nil] a worker for the aggregate's first declared
|
|
35
|
-
# bind, or nil if it has no projection bind
|
|
36
|
-
# @raise [Runtime::WiringError] if the aggregate's authoritative persistence bind does
|
|
37
|
-
# not resolve (see `Persistence.repository`), or the projection's adapter is unknown,
|
|
38
|
-
# answers a different verb, is given a setting it does not declare, has no Ruby
|
|
39
|
-
# implementation, or lacks a method the append-only contract requires
|
|
40
|
-
# @raise [ArgumentError] if `policy` is not one of `Worker::VALID_POLICIES`
|
|
18
|
+
# Builds a worker for the aggregate's first `projected_by` bind; nil when it has none.
|
|
41
19
|
def worker(registry, domain, aggregate, policy: :refresh)
|
|
42
20
|
bind = binds_for(registry, domain, aggregate).first
|
|
43
21
|
return unless bind
|
|
@@ -48,38 +26,15 @@ module Hecks
|
|
|
48
26
|
Worker.new(authoritative, target, policy: policy)
|
|
49
27
|
end
|
|
50
28
|
|
|
51
|
-
# Catches one projection
|
|
52
|
-
#
|
|
53
|
-
# Constructed via Projection.worker; #catch_up! runs from a
|
|
54
|
-
# separate process or scheduler, never from the command-side write
|
|
55
|
-
# path.
|
|
29
|
+
# Catches one projection store up to its authoritative journal; run from a separate
|
|
30
|
+
# process or scheduler, never from the command-side write path.
|
|
56
31
|
class Worker
|
|
57
|
-
# @return [Persistence::AppendOnly] the projection store this worker catches up
|
|
58
32
|
attr_reader :projection
|
|
59
33
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# "lenient append" policy on record anywhere. This list is the
|
|
63
|
-
# contract for what a valid policy is: `catch_up!` enforces
|
|
64
|
-
# history agreement only for the exact symbol `:strict`, so any
|
|
65
|
-
# other unrecognised value — a typo like `:strikt` — would fall
|
|
66
|
-
# through the `consistent?` check below and append onto
|
|
67
|
-
# divergent history without a word. A caller-supplied String
|
|
68
|
-
# `"strict"` is accepted on purpose, normalised by `policy.to_sym`
|
|
69
|
-
# in `initialize` before it is checked against this list, not by
|
|
70
|
-
# the accident of duck typing.
|
|
71
|
-
# Refusing loudly at construction, once, for anything outside
|
|
72
|
-
# this list turns a silent no-op into an immediate, named error
|
|
73
|
-
# — the "refuse rather than silently skip" reading of L1, since
|
|
74
|
-
# `:strict` really is meant to be the only enforcing contract.
|
|
34
|
+
# Only `:strict` enforces history agreement, so an unknown policy (a typo) would append
|
|
35
|
+
# onto divergent history silently; construction refuses anything else.
|
|
75
36
|
VALID_POLICIES = %i[refresh strict].freeze
|
|
76
37
|
|
|
77
|
-
# @param authoritative [Persistence::AppendOnly] the authoritative repository whose
|
|
78
|
-
# journal the projection is caught up to
|
|
79
|
-
# @param projection [Persistence::AppendOnly] the projection store being caught up
|
|
80
|
-
# @param policy [Symbol, String] one of `VALID_POLICIES`, as a Symbol or its String
|
|
81
|
-
# spelling
|
|
82
|
-
# @raise [ArgumentError] if `policy` is not one of `VALID_POLICIES`
|
|
83
38
|
def initialize(authoritative, projection, policy: :refresh)
|
|
84
39
|
@authoritative = authoritative
|
|
85
40
|
@projection = projection
|
|
@@ -91,45 +46,55 @@ module Hecks
|
|
|
91
46
|
end
|
|
92
47
|
end
|
|
93
48
|
|
|
94
|
-
# Appends and projects every authoritative entry the
|
|
49
|
+
# Appends and projects every authoritative entry the store lacks.
|
|
95
50
|
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
# replayed.
|
|
103
|
-
#
|
|
104
|
-
# @return [Persistence::AppendOnly] the projection store, now caught up
|
|
105
|
-
# @raise [Runtime::WiringError] under `:strict`, if the projection's history is not a
|
|
106
|
-
# prefix of the authoritative journal; under `:refresh`, if the projection's
|
|
107
|
-
# adapter cannot `reset!`
|
|
51
|
+
# `:refresh` never reads journal history — it rebuilds from the aggregate's own current
|
|
52
|
+
# records, which the authoritative table already holds directly, so it stays correct
|
|
53
|
+
# past a compacted journal. `:strict` verifies its own entries are a real prefix of the
|
|
54
|
+
# authoritative journal, which genuinely needs that history; it refuses rather than
|
|
55
|
+
# silently misreading a compacted journal's surviving rows as if they started at
|
|
56
|
+
# position zero.
|
|
108
57
|
def catch_up!
|
|
109
|
-
|
|
58
|
+
return refresh! if @policy == :refresh
|
|
59
|
+
|
|
60
|
+
compacted_through = @authoritative.respond_to?(:compacted_through) ? @authoritative.compacted_through : 0
|
|
110
61
|
present = @projection.entries
|
|
111
|
-
if
|
|
112
|
-
|
|
113
|
-
|
|
62
|
+
if present.length < compacted_through
|
|
63
|
+
raise Runtime::WiringError,
|
|
64
|
+
":strict catch-up needs journal history the authoritative store has already " \
|
|
65
|
+
"compacted (through sequence #{compacted_through}, this projection has only " \
|
|
66
|
+
"consumed #{present.length}) — use :refresh instead"
|
|
114
67
|
end
|
|
115
|
-
|
|
116
|
-
|
|
68
|
+
|
|
69
|
+
entries = Queue.new(@authoritative).entries
|
|
70
|
+
present_tail = present.drop(compacted_through)
|
|
71
|
+
unless consistent?(entries, present_tail)
|
|
72
|
+
raise Runtime::WiringError, "projection history does not match its authoritative history"
|
|
117
73
|
end
|
|
118
|
-
|
|
74
|
+
|
|
75
|
+
entries.drop(present_tail.length).each do |entry|
|
|
119
76
|
@projection.append(entry)
|
|
120
77
|
@projection.project(entry)
|
|
121
78
|
end
|
|
122
79
|
@projection
|
|
123
80
|
end
|
|
124
81
|
|
|
125
|
-
# Counts the entries the projection store holds, which is how far it has caught up.
|
|
126
|
-
#
|
|
127
|
-
# @return [Integer] number of journal entries already appended to the projection
|
|
128
|
-
# store; 0 for a store that has never caught up
|
|
129
82
|
def checkpoint = @projection.entries.length
|
|
130
83
|
|
|
131
84
|
private
|
|
132
85
|
|
|
86
|
+
# Resets the store and rebuilds it from the aggregate's own current records — one
|
|
87
|
+
# synthetic save per live record, never a replay of deleted or superseded history.
|
|
88
|
+
def refresh!
|
|
89
|
+
@projection.reset!
|
|
90
|
+
@authoritative.all.each do |instance|
|
|
91
|
+
entry = Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
92
|
+
@projection.append(entry)
|
|
93
|
+
@projection.project(entry)
|
|
94
|
+
end
|
|
95
|
+
@projection
|
|
96
|
+
end
|
|
97
|
+
|
|
133
98
|
def same_entry?(left, right) = left.operation == right.operation && left.id == right.id && left.state == right.state
|
|
134
99
|
|
|
135
100
|
def consistent?(entries, present)
|
|
@@ -139,18 +104,11 @@ module Hecks
|
|
|
139
104
|
end
|
|
140
105
|
end
|
|
141
106
|
|
|
142
|
-
# The authoritative
|
|
143
|
-
#
|
|
144
|
-
# worker's durable checkpoint, so delivery is at-least-once and safe to replay.
|
|
107
|
+
# The authoritative journal doubles as the queue: entries are committed before a worker
|
|
108
|
+
# sees them, so delivery is at-least-once and safe to replay.
|
|
145
109
|
class Queue
|
|
146
|
-
# @param authoritative [Persistence::AppendOnly] the authoritative repository to read
|
|
147
|
-
# entries from
|
|
148
110
|
def initialize(authoritative) = @authoritative = authoritative
|
|
149
111
|
|
|
150
|
-
# Reads the whole authoritative journal, the work a projection worker replays from.
|
|
151
|
-
#
|
|
152
|
-
# @return [Array<Persistence::Entry>] every committed entry, oldest first; `[]` for
|
|
153
|
-
# an empty journal
|
|
154
112
|
def entries = @authoritative.entries
|
|
155
113
|
end
|
|
156
114
|
end
|