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
data/lib/hecks/facade/handle.rb
CHANGED
|
@@ -4,18 +4,8 @@ require_relative "../ports/authorization"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Facade
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# One shared class, not one minted per aggregate. Rather than subclassing
|
|
11
|
-
# per head and defining a reader per field, this wraps a
|
|
12
|
-
# `Runtime::Instance` state hash, answers readers through
|
|
13
|
-
# `method_missing` and verbs through per-handle singleton methods, and
|
|
14
|
-
# closes over the dispatcher and the aggregate's IR — so a boot mints no
|
|
15
|
-
# classes at all, and two boots in one process each hand out handles bound
|
|
16
|
-
# to their own dispatcher.
|
|
17
|
-
#
|
|
18
|
-
# A non-creating verb is a method returning self, so commands chain :
|
|
7
|
+
# A record in hand: what `Pizza.create_pizza!(...)` and `Pizza.find(id)`
|
|
8
|
+
# give back. One shared class per aggregate; verbs are per-handle singleton methods.
|
|
19
9
|
#
|
|
20
10
|
# Pizza.create_pizza!(...).add_topping!(...).purchase!(...)
|
|
21
11
|
class Handle
|
|
@@ -45,22 +35,10 @@ module Hecks
|
|
|
45
35
|
# or a referenced record's id); `nil` when the field is unset or not in state
|
|
46
36
|
def [](key) = redacted(key.to_sym)
|
|
47
37
|
|
|
48
|
-
# Answers the record's state as a
|
|
49
|
-
#
|
|
50
|
-
# `id: @id` last, not first — an aggregate is free to declare its own
|
|
51
|
-
# attribute literally named `id` (BurningManPrep's `Item`, `attribute
|
|
52
|
-
# :id, ItemId`, is real corpus now: `identified_by :id` reads
|
|
53
|
-
# that attribute for identity). When it does, `@state[:id]` holds the
|
|
54
|
-
# full wrapped value object, not the bare identity string — merging
|
|
55
|
-
# `@state` on top of `{ id: @id }` would let that wrapped VO silently
|
|
56
|
-
# clobber the correct bare `@id`, so every caller of `to_h` (the JSON
|
|
57
|
-
# door's own `/api/:coll` listing, in particular) would get an object
|
|
58
|
-
# where a plain identity string belongs. `@id` merged last always wins,
|
|
59
|
-
# so `to_h[:id]` is always the true bare identity, regardless of
|
|
60
|
-
# whether the aggregate also happens to declare a same-named field.
|
|
38
|
+
# Answers the record's state as a Hash, with `:id` merged in last so it
|
|
39
|
+
# always wins over a same-named `id` attribute (BurningManPrep::Item).
|
|
61
40
|
#
|
|
62
|
-
# @return [Hash{Symbol => Object}]
|
|
63
|
-
# name, plus `:id` holding the identity String
|
|
41
|
+
# @return [Hash{Symbol => Object}] every state field by attribute name, plus `:id`
|
|
64
42
|
def to_h = @state.to_h { |key, _| [key, redacted(key)] }.merge(id: @id)
|
|
65
43
|
|
|
66
44
|
# Names the aggregate this record belongs to, in the form every dispatch verb and
|
|
@@ -90,14 +68,11 @@ module Hecks
|
|
|
90
68
|
self
|
|
91
69
|
end
|
|
92
70
|
|
|
93
|
-
# Equality is (
|
|
94
|
-
#
|
|
95
|
-
# share an id. With one shared class for every aggregate,
|
|
96
|
-
# `other.is_a?(self.class)` cannot tell them apart ; the fqn says it in data.
|
|
71
|
+
# Equality is (fqn, id): two handles to the same record are equal, and
|
|
72
|
+
# `other.is_a?(self.class)` can't tell aggregates apart since they share one class.
|
|
97
73
|
#
|
|
98
74
|
# @param other [Object] anything; only another `Handle` can be equal
|
|
99
|
-
# @return [Boolean] true when `other` is a `Handle` with the same `fqn` and `id
|
|
100
|
-
# whatever state either one holds
|
|
75
|
+
# @return [Boolean] true when `other` is a `Handle` with the same `fqn` and `id`
|
|
101
76
|
def ==(other) = other.is_a?(Handle) && other.fqn == fqn && other.id == @id
|
|
102
77
|
alias eql? ==
|
|
103
78
|
def hash = [Handle, fqn, @id].hash
|
|
@@ -108,18 +83,13 @@ module Hecks
|
|
|
108
83
|
end
|
|
109
84
|
alias to_s inspect
|
|
110
85
|
|
|
111
|
-
# Answers a field reader: `pizza.name` reads `name` out of state.
|
|
112
|
-
#
|
|
113
|
-
# A declared field not yet written arrives here too (nil, the way a
|
|
114
|
-
# defined reader answers). Verbs are not handled here — see
|
|
115
|
-
# `define_verb_methods` for why.
|
|
86
|
+
# Answers a field reader: `pizza.name` reads `name` out of state. A
|
|
87
|
+
# declared field not yet written arrives here too, as `nil`.
|
|
116
88
|
#
|
|
117
89
|
# @param name [Symbol] the method called, read as an attribute or lifecycle field name
|
|
118
90
|
# @param args [Array<Object>] ignored by a reader; passed on to `super` otherwise
|
|
119
|
-
# @param kwargs [Hash{Symbol => Object}] ignored by a reader; passed on to `super`
|
|
120
|
-
#
|
|
121
|
-
# @return [Object, nil] the field's value; `nil` for a declared field with nothing
|
|
122
|
-
# written yet
|
|
91
|
+
# @param kwargs [Hash{Symbol => Object}] ignored by a reader; passed on to `super` otherwise
|
|
92
|
+
# @return [Object, nil] the field's value; `nil` when nothing is written yet
|
|
123
93
|
# @raise [NoMethodError] if `name` is neither a key in state nor a declared field
|
|
124
94
|
def method_missing(name, *args, **kwargs, &)
|
|
125
95
|
return redacted(name) if @state.key?(name) || reader?(name)
|
|
@@ -135,28 +105,9 @@ module Hecks
|
|
|
135
105
|
|
|
136
106
|
def repository = @dispatcher.registry.repository(@domain, @ir)
|
|
137
107
|
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
# Privacy framework member (lib/hecks/framework/bluebook/
|
|
142
|
-
# privacy.bluebook): a marking's presence is what makes a read
|
|
143
|
-
# redacted, not a separate flag this class carries itself.
|
|
144
|
-
#
|
|
145
|
-
# ALWAYS THE STRONG CHECK, never the weak string-only fallback
|
|
146
|
-
# `CommandRules::Authorization#refuse_role_mismatch` allows an
|
|
147
|
-
# unidentified caller — a read gate gone wrong is a leak, not a
|
|
148
|
-
# refused command, so an ambient caller with no `actor_id` (or no
|
|
149
|
-
# caller at all) is masked here, full stop, rather than waved
|
|
150
|
-
# through the way a self-asserted `role` string is for a command.
|
|
151
|
-
#
|
|
152
|
-
# ONE LEVEL OF NESTING ONLY — `attendee.medications` masks inside
|
|
153
|
-
# the returned `Runtime::Value` via its own `#with`; a marking two
|
|
154
|
-
# levels deep is not supported and is left unmasked rather than
|
|
155
|
-
# silently mishandled, since nothing in this corpus needs it yet.
|
|
156
|
-
#
|
|
157
|
-
# @param field [Symbol] the state key being read
|
|
158
|
-
# @return [Object, nil] `@state[field]`, or a copy with the marked leaf replaced by
|
|
159
|
-
# the literal String `"[redacted]"` when the caller is not authorized to see it
|
|
108
|
+
# Masks any Privacy::Marking leaf the caller isn't granted to read, and
|
|
109
|
+
# masks an unidentified caller outright (a leak is worse than a refusal
|
|
110
|
+
# commands allow). Nesting is masked one level deep only.
|
|
160
111
|
def redacted(field)
|
|
161
112
|
raw = @state[field]
|
|
162
113
|
rows = marked_paths.select { |row| row[:attribute_path][:value].to_s.split(".", 2).first == field.to_s }
|
|
@@ -177,12 +128,8 @@ module Hecks
|
|
|
177
128
|
raw
|
|
178
129
|
end
|
|
179
130
|
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
# cheaply rather than paying for a query dispatch every read on a domain that never
|
|
183
|
-
# attached Privacy.
|
|
184
|
-
#
|
|
185
|
-
# @return [Array<Hash>] `Privacy::Marking.ForDomain`'s own rows for this `fqn`
|
|
131
|
+
# Cached per handle so an unattached-Privacy domain never pays for a query
|
|
132
|
+
# dispatch on every read.
|
|
186
133
|
def marked_paths
|
|
187
134
|
return @marked_paths if defined?(@marked_paths)
|
|
188
135
|
return @marked_paths = [] unless @dispatcher.registry.bluebook("Privacy")
|
|
@@ -190,13 +137,8 @@ module Hecks
|
|
|
190
137
|
@marked_paths = @dispatcher.query("Privacy::Marking.ForDomain", domain: fqn)
|
|
191
138
|
end
|
|
192
139
|
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
# with no authorization provider attached, never the weak fallback a command's own
|
|
196
|
-
# role check allows (see `redacted`'s own header for why).
|
|
197
|
-
#
|
|
198
|
-
# @param role [String] the marking's own `readable_by`
|
|
199
|
-
# @return [Boolean] true only for an identified caller holding a live grant
|
|
140
|
+
# False outright for an unidentified caller or an unattached domain, never
|
|
141
|
+
# the weak fallback a command's own role check allows.
|
|
200
142
|
def authorized_for?(role)
|
|
201
143
|
caller = Runtime::Caller.current
|
|
202
144
|
return false unless caller&.actor_id
|
|
@@ -210,20 +152,9 @@ module Hecks
|
|
|
210
152
|
!@ir.attribute(name).nil? || @ir.lifecycle&.field&.to_sym == name
|
|
211
153
|
end
|
|
212
154
|
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
# answering the name — and every object already answers `freeze` and
|
|
217
|
-
# `send` (Kernel/Object), among others. A verb whose snake-cased name
|
|
218
|
-
# collided with one of those — `Account::Freeze` -> `freeze`,
|
|
219
|
-
# `ExternalTransfer::Send` -> `send` in the banking corpus, both real —
|
|
220
|
-
# would silently run the Kernel method instead of dispatching: no
|
|
221
|
-
# error, no refusal, the call just does the wrong thing. Defining a
|
|
222
|
-
# real singleton method per verb closes that; the `!` suffix (every
|
|
223
|
-
# command, door and Handle alike) closes it a second, permanent way —
|
|
224
|
-
# `freeze!`/`send!` name nothing Kernel/Object already answers to,
|
|
225
|
-
# so this exact class of collision cannot recur no matter what a
|
|
226
|
-
# future domain names a command.
|
|
155
|
+
# Real singleton methods, not method_missing: a verb named `freeze` or
|
|
156
|
+
# `send` (both real, e.g. Account::Freeze, ExternalTransfer::Send) would
|
|
157
|
+
# otherwise silently hit the Kernel method instead of dispatching.
|
|
227
158
|
def define_verb_methods
|
|
228
159
|
@ir.commands.reject(&:creates?).each do |command|
|
|
229
160
|
define_singleton_method("#{Naming.snake(command.hecks_name)}!") do |**args|
|
|
@@ -232,42 +163,24 @@ module Hecks
|
|
|
232
163
|
end
|
|
233
164
|
end
|
|
234
165
|
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
# identity payload from `identity_heads` instead reads every head, one
|
|
239
|
-
# or many alike, straight out of state that already carries them.
|
|
166
|
+
# `@ir.identified_by` is only the single-head shorthand (nil for a
|
|
167
|
+
# composite identity, e.g. SafeDepositBox's branch_code/box_number);
|
|
168
|
+
# this reads every head through `identity_heads` instead.
|
|
240
169
|
def run(command, **args)
|
|
241
170
|
@state = @dispatcher.dispatch("#{fqn}.#{command.hecks_name}", to: @id, with: args).instance.state
|
|
242
171
|
self
|
|
243
172
|
end
|
|
244
173
|
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
# chaining composes for free from here : `payment.disputed_by_customer.name`
|
|
252
|
-
# is two ordinary calls, each individually lazy, which is exactly why
|
|
253
|
-
# this is a named accessor per reference rather than a `through:`
|
|
254
|
-
# option — that shape was considered and rejected in the same design
|
|
255
|
-
# note for hiding how many lookups actually happened behind one call.
|
|
174
|
+
# Lazy hop: `transfer.source_account` resolves to the actual record only
|
|
175
|
+
# when called; nothing loads until then, so chains like
|
|
176
|
+
# `payment.disputed_by_customer.name` stay lazy at every step.
|
|
177
|
+
#
|
|
178
|
+
# Defined before verb methods so a same-named verb wins the name on the
|
|
179
|
+
# rare collision; `initialize` calls this first for that reason.
|
|
256
180
|
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
# defines second and last.
|
|
261
|
-
# No derivation left (ADR 0025, "References"): `reference_to`
|
|
262
|
-
# itself mints the bare attribute name now — `:account`, never
|
|
263
|
-
# `:account_id` — so the accessor is spelled exactly like the
|
|
264
|
-
# attribute it reads, with no `_id`-strip or `as:`-suffix rule to
|
|
265
|
-
# apply first. `piece.account` (a method, defined here) and
|
|
266
|
-
# `piece[:account]` (`Handle#[]`, bracket access reading the raw
|
|
267
|
-
# id straight off `@instance`) never collide despite sharing a
|
|
268
|
-
# name — Ruby dispatches the two completely differently — so no
|
|
269
|
-
# renamed accessor (a "studio_studio"-style double suffix) is needed
|
|
270
|
-
# to keep them apart.
|
|
181
|
+
# The accessor is named like the attribute itself, never `<name>_id`
|
|
182
|
+
# (ADR 0025) — `piece.account` and `piece[:account]` (bracket access,
|
|
183
|
+
# the raw id) never collide despite sharing a name.
|
|
271
184
|
def define_reference_accessors
|
|
272
185
|
@ir.attributes.select(&:reference?).each do |attribute|
|
|
273
186
|
target = attribute.type.resolve
|
|
@@ -7,73 +7,23 @@ require_relative "../runtime/value"
|
|
|
7
7
|
|
|
8
8
|
module Hecks
|
|
9
9
|
module Facade
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# `Handle`/`Surface` are Ruby sugar over the dispatcher for a Ruby caller
|
|
13
|
-
# holding real objects — a symbol verb name, a `**kwargs` payload, a
|
|
14
|
-
# `Handle` back in hand. A REST-ish JSON API is a caller holding strings
|
|
15
|
-
# instead: a URL segment naming a collection, a URL segment naming a
|
|
16
|
-
# record or a verb, a parsed request body whose every key arrived as a
|
|
17
|
-
# String because that is all JSON ever gives. Every app that wants to put
|
|
18
|
-
# a JSON API in front of a booted domain has to do that translation —
|
|
19
|
-
# name to door module, string to symbol, nested `Runtime::Value` back to
|
|
20
|
-
# plain data. This is that translation written once, generic, reading the
|
|
21
|
-
# same IR the rest of the facade already reads rather than each app
|
|
22
|
-
# re-deriving "how do I find an aggregate by name" against its own routes.
|
|
23
|
-
#
|
|
24
|
-
# ## No HTTP lives here
|
|
25
|
-
#
|
|
26
|
-
# Same discipline `Router` and `Surface` already
|
|
27
|
-
# hold: this module never sees a request object, never picks a status
|
|
28
|
-
# code, never calls `halt`. Every method here takes plain Ruby values in
|
|
29
|
-
# — a raw JSON String is the one exception, see `.parse` below, every
|
|
30
|
-
# other input is already-parsed data — and returns plain Ruby values
|
|
31
|
-
# out, or raises. Turning a raw request body into that input, and
|
|
32
|
-
# turning a raised exception into an HTTP status, both stay the calling
|
|
33
|
-
# app's job, exactly the way they already are for `Router#dispatch`.
|
|
34
|
-
#
|
|
35
|
-
# ## One refusal class for every miss
|
|
36
|
-
#
|
|
37
|
-
# Every "that doesn't exist" case raises `Runtime::NotFound` — the same
|
|
38
|
-
# class every time, not a new one per caller. `Runtime::NotFound` already
|
|
39
|
-
# sits in `Runtime::DOMAIN_REFUSALS` (runtime/errors.rb) — the family a
|
|
40
|
-
# booted app already has, or trivially can have, one generic `error`
|
|
41
|
-
# handler for, mapping the whole family to a status code without a
|
|
42
|
-
# special case per refusal. A bespoke `JsonDoor::CollectionNotFound` (or
|
|
43
|
-
# three of those, one per flavor of "not found") would just force every
|
|
44
|
-
# app that adopts this door to widen its rescue clause to match it — the
|
|
45
|
-
# opposite of what a shared refusal family is for. So "no such
|
|
46
|
-
# aggregate", "no creating command", "no such command", and "no record
|
|
47
|
-
# with that id" all raise the one class, distinguished only by message.
|
|
10
|
+
# The JSON door: translates URL segments and parsed JSON bodies to and from the facade.
|
|
11
|
+
# No HTTP lives here; every miss raises `Runtime::NotFound`, distinguished by message.
|
|
48
12
|
module JsonDoor
|
|
49
13
|
module_function
|
|
50
14
|
|
|
51
15
|
# Resolves a domain name and an aggregate name, as two URL segments carry them, to
|
|
52
16
|
# that aggregate's door.
|
|
53
17
|
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# `Facade::Handle`'s own reference accessors reach with
|
|
57
|
-
# `Object.const_get` (see handle.rb's `define_reference_accessors`).
|
|
18
|
+
# The name is checked against the current boot's IR before Ruby's constant table, so
|
|
19
|
+
# a stale same-named constant from an earlier boot is never returned.
|
|
58
20
|
#
|
|
59
|
-
#
|
|
60
|
-
# constant table directly — a name that names nothing in this
|
|
61
|
-
# registry should refuse before ever asking Ruby whether some
|
|
62
|
-
# same-named constant happens to exist (possibly a stale one, left
|
|
63
|
-
# over from an earlier boot in this same process — the exact hazard
|
|
64
|
-
# `AggregateDoor#port`'s own comment describes at length). Only once
|
|
65
|
-
# the IR confirms the aggregate is real does this read the constant
|
|
66
|
-
# the current boot's `Surface.install` actually minted for it.
|
|
67
|
-
#
|
|
68
|
-
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
|
|
69
|
-
# dispatcher whose registry decides whether the aggregate exists
|
|
21
|
+
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
|
|
70
22
|
# @param domain [String, Symbol] the chapter name, such as `"Banking"`
|
|
71
23
|
# @param name [String, Symbol] the aggregate's declared name, such as `"Customer"`
|
|
72
24
|
# @return [Module] the aggregate door installed at `domain::name`
|
|
73
|
-
# @raise [Runtime::NotFound] if the
|
|
74
|
-
#
|
|
75
|
-
# @raise [NameError] if the IR has the aggregate but no facade constant is
|
|
76
|
-
# installed for it, as after a boot with `install_facade: false`
|
|
25
|
+
# @raise [Runtime::NotFound] if the chapter or its aggregate is not declared
|
|
26
|
+
# @raise [NameError] if the aggregate has no facade constant (`install_facade: false`)
|
|
77
27
|
def aggregate(dispatcher, domain, name)
|
|
78
28
|
ir = dispatcher.registry.bluebook(domain)&.aggregate(name)
|
|
79
29
|
raise Runtime::NotFound, "#{domain} declares no aggregate named #{name.inspect}" unless ir
|
|
@@ -81,21 +31,11 @@ module Hecks
|
|
|
81
31
|
Object.const_get("#{domain}::#{ir.hecks_name}")
|
|
82
32
|
end
|
|
83
33
|
|
|
84
|
-
# Names the door method that creates a record of this aggregate
|
|
85
|
-
#
|
|
86
|
-
# The one command a POST to a bare collection URL means — "make one of
|
|
87
|
-
# these". A creating command is one that declares no `references`
|
|
88
|
-
# (`Command#creates?`), and the first one the aggregate declares is the
|
|
89
|
-
# one named here; this just names it the same
|
|
90
|
-
# snake_case-plus-bang a Ruby caller would already ask the door for
|
|
91
|
-
# (`Naming.snake`, the identical call `AggregateDoor` itself makes
|
|
92
|
-
# when it defines that singleton method in the first place — `!`
|
|
93
|
-
# because every command carries it, door and Handle alike).
|
|
34
|
+
# Names the door method that creates a record of this aggregate: the first command
|
|
35
|
+
# it declares that `creates?`, spelled as a Ruby caller would (`"create_pizza!"`).
|
|
94
36
|
#
|
|
95
|
-
# @param klass [Module] an aggregate door, as `aggregate` returns
|
|
96
|
-
#
|
|
97
|
-
# @return [String] the method name to `public_send` to the door, such as
|
|
98
|
-
# `"create_pizza!"`
|
|
37
|
+
# @param klass [Module] an aggregate door, as `aggregate` returns
|
|
38
|
+
# @return [String] the method name to `public_send` to the door
|
|
99
39
|
# @raise [Runtime::NotFound] if the aggregate declares no creating command
|
|
100
40
|
def creating_command(klass)
|
|
101
41
|
creating = klass.ir.commands.find(&:creates?)
|
|
@@ -107,28 +47,14 @@ module Hecks
|
|
|
107
47
|
# Confirms that a command name arriving as text is one a `Handle` of this aggregate
|
|
108
48
|
# answers, before a caller `public_send`s it.
|
|
109
49
|
#
|
|
110
|
-
# A
|
|
111
|
-
#
|
|
112
|
-
# `AggregateDoor`'s own door-level list and includes the one creating
|
|
113
|
-
# command too (`aggregate_door.rb`'s `commands` singleton method maps
|
|
114
|
-
# every `ir.commands`, full stop). A `Handle` only ever defines
|
|
115
|
-
# singleton methods for the non-creating ones
|
|
116
|
-
# (`Handle#define_verb_methods`, `@ir.commands.reject(&:creates?)`) —
|
|
117
|
-
# the creating command lives on the aggregate class itself, dispatched
|
|
118
|
-
# through `.creating_command` above, not through a `Handle` in hand.
|
|
119
|
-
# Accepting a creating-command name here would let it past this gate
|
|
120
|
-
# clean, only to blow up as a raw `NoMethodError` the moment a caller
|
|
121
|
-
# tries `handle.public_send(name, **args)`, instead of the 404 this
|
|
122
|
-
# method promises. Filtering `reject(&:creates?)` here, the same filter
|
|
123
|
-
# `Handle` itself applies, is what keeps "accepted here" and
|
|
124
|
-
# "dispatchable there" the same set.
|
|
50
|
+
# A `Handle` defines only the non-creating commands, so the creating command is
|
|
51
|
+
# rejected here rather than failing later as a raw `NoMethodError`.
|
|
125
52
|
#
|
|
126
53
|
# @param klass [Module] an aggregate door, as `aggregate` returns
|
|
127
54
|
# @param name [String, Symbol] the wanted method name with its bang, such as
|
|
128
55
|
# `"add_topping!"`
|
|
129
56
|
# @return [String] `name` as a String, unchanged, when a `Handle` answers it
|
|
130
|
-
# @raise [Runtime::NotFound] if no non-creating command has that method name
|
|
131
|
-
# including when `name` is the creating command's or lacks the `!`
|
|
57
|
+
# @raise [Runtime::NotFound] if no non-creating command has that method name
|
|
132
58
|
def validate_command!(klass, name)
|
|
133
59
|
wanted = name.to_s
|
|
134
60
|
dispatchable = klass.ir.commands.reject(&:creates?).map { |command| "#{Naming.snake(command.hecks_name)}!" }
|
|
@@ -137,14 +63,7 @@ module Hecks
|
|
|
137
63
|
raise Runtime::NotFound, "#{klass.ir.hecks_name} declares no command named #{wanted.inspect}"
|
|
138
64
|
end
|
|
139
65
|
|
|
140
|
-
# Fetches one record by id,
|
|
141
|
-
#
|
|
142
|
-
# `klass.find` already answers nil-on-miss — the right shape for a
|
|
143
|
-
# Ruby caller that means to check for itself. A JSON caller asking for
|
|
144
|
-
# one record by id off a URL means to have it, or answer 404 — this is
|
|
145
|
-
# that stricter wrapper, raising the same `Runtime::NotFound` the rest
|
|
146
|
-
# of this door raises rather than handing back nil for the caller to
|
|
147
|
-
# remember to check.
|
|
66
|
+
# Fetches one record by id, raising instead of answering nil on a miss.
|
|
148
67
|
#
|
|
149
68
|
# @param klass [Module] an aggregate door, as `aggregate` returns
|
|
150
69
|
# @param id [String] the record's identity, as the URL carried it
|
|
@@ -156,11 +75,6 @@ module Hecks
|
|
|
156
75
|
|
|
157
76
|
# Converts every Hash key to a Symbol, at every depth of a parsed JSON body.
|
|
158
77
|
#
|
|
159
|
-
# JSON only ever hands back String keys. A command's args, and every
|
|
160
|
-
# nested value-object literal inside them, need symbol keys before
|
|
161
|
-
# `Handle`/`Dispatcher` will accept them at all — this is that
|
|
162
|
-
# recursive conversion, blind to how deep a body nests.
|
|
163
|
-
#
|
|
164
78
|
# @param value [Hash, Array, Object] parsed JSON: a Hash or Array is walked, any
|
|
165
79
|
# other value is a leaf
|
|
166
80
|
# @return [Hash{Symbol => Object}, Array, Object] a new structure of the same shape
|
|
@@ -174,72 +88,43 @@ module Hecks
|
|
|
174
88
|
end
|
|
175
89
|
|
|
176
90
|
# Unwraps a record, a query row, or any value holding `Runtime::Value`s into plain
|
|
177
|
-
# Hashes, Arrays and scalars.
|
|
91
|
+
# Hashes, Arrays and scalars a JSON encoder can walk.
|
|
178
92
|
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
# down to plain Ruby a JSON encoder can walk without knowing what a
|
|
182
|
-
# `Runtime::Value` is.
|
|
183
|
-
#
|
|
184
|
-
# `Runtime::Value.materialize` (runtime/value.rb) already does the
|
|
185
|
-
# actual recursion — its `Hash` branch calls itself on every value,
|
|
186
|
-
# its `Value` branch unwraps through `#to_h`, which itself calls
|
|
187
|
-
# `materialize` on every field, so a value object three levels deep
|
|
188
|
-
# unwraps three levels deep for free. This is not a second unwrapper
|
|
189
|
-
# sitting next to it: `materialize` covers `Hash`/`Array`/`Value`
|
|
190
|
-
# already, and a `Handle` is none of those three, so the one thing
|
|
191
|
-
# this adds is `#to_h`'ing a `Handle` first so `materialize`'s own
|
|
192
|
-
# `Hash` case can take it from there.
|
|
93
|
+
# Delegates to `Runtime::Value.materialize`; the only addition is `#to_h` on a
|
|
94
|
+
# `Handle`, which `materialize` does not recognize.
|
|
193
95
|
#
|
|
194
96
|
# @param value [Facade::Handle, Runtime::Value, Hash, Array, Object] what to unwrap;
|
|
195
97
|
# a `Handle` is read through its `to_h`, so its `:id` comes along
|
|
196
98
|
# @return [Hash, Array, Object] the same data with every `Runtime::Value` replaced
|
|
197
|
-
# by a Hash of its fields;
|
|
198
|
-
# as it came
|
|
99
|
+
# by a Hash of its fields; any other value is returned as it came
|
|
199
100
|
def materialize(value)
|
|
200
101
|
value = value.to_h if value.is_a?(Handle)
|
|
201
102
|
Runtime::Value.materialize(value)
|
|
202
103
|
end
|
|
203
104
|
|
|
204
105
|
# Turns a command's JSON body, raw or already parsed, into the `to:`/`with:`
|
|
205
|
-
# envelope a dispatcher takes.
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
# @param receiver [Symbol, nil] the kind of receiver the command takes: `:aggregate`,
|
|
214
|
-
# `:entity`, or `nil` for none (see `CommandRequest.normalize`)
|
|
215
|
-
# @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] the
|
|
216
|
-
# flat key, or pair of keys, a body without `to` may name its receiver under;
|
|
217
|
-
# `nil` accepts none
|
|
218
|
-
# @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` whenever `receiver`
|
|
219
|
-
# is not `nil`
|
|
106
|
+
# envelope a dispatcher takes. The result splats into `Dispatcher#dispatch`.
|
|
107
|
+
#
|
|
108
|
+
# @param body [String, Hash] raw JSON text, or the Hash it parses to
|
|
109
|
+
# @param receiver [Symbol, nil] `:aggregate`, `:entity`, or `nil` for none
|
|
110
|
+
# (see `CommandRequest.normalize`)
|
|
111
|
+
# @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] the flat key,
|
|
112
|
+
# or pair of keys, a body without `to` may name its receiver under; `nil` accepts none
|
|
113
|
+
# @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` unless `receiver` is `nil`
|
|
220
114
|
# @raise [JSON::ParserError] if `body` is a String that is not valid JSON
|
|
221
|
-
# @raise [Runtime::TypeMismatch] if the body is not a JSON object
|
|
222
|
-
# missing, malformed, or mixed with loose keys beside an explicit `with:`
|
|
115
|
+
# @raise [Runtime::TypeMismatch] if the body is not a JSON object or its routing is malformed
|
|
223
116
|
# @raise [ArgumentError] if `receiver` is not `nil`, `:aggregate` or `:entity`
|
|
224
117
|
def command_request(body, receiver:, legacy_receiver: nil)
|
|
225
118
|
input = body.is_a?(String) ? parse(body) : body
|
|
226
119
|
CommandRequest.normalize(input, receiver: receiver, legacy_receiver: legacy_receiver)
|
|
227
120
|
end
|
|
228
121
|
|
|
229
|
-
# Parses a raw request body, leaving keys as Strings.
|
|
230
|
-
#
|
|
231
|
-
# The one place a raw JSON string is legitimate input for this door —
|
|
232
|
-
# a POST body, still text at the point a generic, HTTP-blind layer can
|
|
233
|
-
# see it. `JSON::ParserError` already names "this wasn't JSON" exactly
|
|
234
|
-
# right ; wrapping it in a bespoke `JsonDoor`-specific class would
|
|
235
|
-
# only be a second name for the same fact, so it propagates exactly as
|
|
236
|
-
# `JSON.parse` raises it — a calling app catches it the same standard
|
|
237
|
-
# way it would catch any other malformed-input error, no new class to
|
|
238
|
-
# learn.
|
|
122
|
+
# Parses a raw request body, leaving keys as Strings. `JSON::ParserError` propagates
|
|
123
|
+
# unwrapped.
|
|
239
124
|
#
|
|
240
125
|
# @param raw_json [String] JSON text, such as a POST body
|
|
241
126
|
# @return [Hash{String => Object}, Array, String, Numeric, Boolean, nil] whatever
|
|
242
|
-
# the text encodes
|
|
127
|
+
# the text encodes
|
|
243
128
|
# @raise [JSON::ParserError] if the text is not valid JSON
|
|
244
129
|
def parse(raw_json) = JSON.parse(raw_json)
|
|
245
130
|
end
|