hecks 2.8.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 +4 -6
- 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 +17 -98
- 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 +5 -48
- 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 +31 -141
- 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 +14 -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 +25 -31
- 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 +16 -69
- 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 -194
- 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 -111
- 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 +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
- 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 +6 -21
- data/lib/hecks/bluebook/model_check.rb +100 -597
- 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 +4 -23
- data/lib/hecks/cli/document.rb +3 -21
- data/lib/hecks/cli/ir.rb +3 -5
- data/lib/hecks/cli/mcp.rb +2 -3
- data/lib/hecks/cli/mcp_door.rb +19 -114
- data/lib/hecks/cli/model_check.rb +18 -85
- data/lib/hecks/cli/project_cli.rb +7 -40
- data/lib/hecks/cli/project_diagrams.rb +0 -7
- data/lib/hecks/cli/run.rb +12 -58
- data/lib/hecks/cli/smoke_test.rb +2 -16
- data/lib/hecks/cli/stores.rb +1 -2
- data/lib/hecks/cli.rb +18 -24
- 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 +82 -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 +9 -90
- 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 +61 -64
- 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 +6 -25
- 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 +18 -124
- 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 +150 -678
- 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 +2 -15
- 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 +42 -134
- 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 +3 -6
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/gem_publisher.rb +2 -3
- data/lib/hecks/release/runner/npm_publisher.rb +6 -20
- data/lib/hecks/release/runner/preflight.rb +4 -19
- data/lib/hecks/release/runner/tagger.rb +2 -9
- data/lib/hecks/release/runner.rb +3 -32
- 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 +124 -306
- 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 +64 -219
- 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 +117 -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 +99 -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 +141 -568
- 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.rb +26 -81
- 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 +3 -2
|
@@ -9,36 +9,14 @@ require_relative "../../naming"
|
|
|
9
9
|
module Hecks
|
|
10
10
|
module Facade
|
|
11
11
|
module Surface
|
|
12
|
-
# One aggregate's door: creating verbs as module methods
|
|
13
|
-
#
|
|
14
|
-
# `persisted_by`-style binding collector a `.hecksagon` lands on, and
|
|
15
|
-
# the aggregate-scoped `port` a `.hecksagon` lands on beside it.
|
|
12
|
+
# One aggregate's door: creating verbs and queries as module methods, CRUD
|
|
13
|
+
# delegation, and the `.hecksagon` binding/port hooks it lands on.
|
|
16
14
|
module AggregateDoor
|
|
17
|
-
# Builds
|
|
18
|
-
#
|
|
19
|
-
# `project`/`docs`/`narrate`, and the `.hecksagon` binding hooks.
|
|
15
|
+
# Builds one aggregate's door module: creating-command methods, query methods,
|
|
16
|
+
# find/all/count/events, project/docs/narrate, and the port/binding hooks.
|
|
20
17
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# One method building one `door` module's ~20 singleton methods
|
|
24
|
-
# looks like it splits along each `define_singleton_method` call,
|
|
25
|
-
# but three of those blocks (`:port`, `:method_missing`,
|
|
26
|
-
# `:const_missing`) are a single cross-referencing essay on the
|
|
27
|
-
# stale-facade-across-boots hazard — each one's comment explicitly
|
|
28
|
-
# points at "below"/"above" as part of the same method. Splitting
|
|
29
|
-
# into helper methods wouldn't break anything at runtime (no
|
|
30
|
-
# shared mutable state beyond the closed-over args, which just
|
|
31
|
-
# become parameters), but it would sever that narrative across
|
|
32
|
-
# method boundaries for no functional gain.
|
|
33
|
-
#
|
|
34
|
-
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
|
|
35
|
-
# dispatcher the door's methods dispatch, query and read repositories through
|
|
36
|
-
# @param domain [String] the owning chapter's name, the first half of the
|
|
37
|
-
# aggregate's FQN (`"Pizzas"` in `"Pizzas::Pizza"`)
|
|
38
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate to build a door for
|
|
39
|
-
# @return [Module] a fresh, unnamed module carrying the door's singleton methods
|
|
40
|
-
# @raise [Bluebook::DSL::Malformed] never while building; the returned door's
|
|
41
|
-
# `port` raises it when called with no boot in progress
|
|
18
|
+
# Kept as one method: `:port`/`:method_missing`/`:const_missing` below
|
|
19
|
+
# cross-reference each other and would lose that thread if split up.
|
|
42
20
|
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
43
21
|
def aggregate_module(dispatcher, domain, aggregate)
|
|
44
22
|
fqn = "#{domain}::#{aggregate.hecks_name}"
|
|
@@ -50,16 +28,9 @@ module Hecks
|
|
|
50
28
|
warn "[hecks] #{aggregate.hecks_name}##{attribute.name} shadows a built-in — no reader defined"
|
|
51
29
|
end
|
|
52
30
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# convention for that ; `Naming.snake` alone would leave a
|
|
57
|
-
# creating command's bare name claiming the exact spelling a
|
|
58
|
-
# query of the same business name also wants (`Account`'s own
|
|
59
|
-
# "Open" — the creating command and a query listing open
|
|
60
|
-
# accounts, a real same-aggregate collision this corpus already
|
|
61
|
-
# has) — the suffix is what makes both nameable at all, not
|
|
62
|
-
# merely a style choice.
|
|
31
|
+
# `!` marks a creating command (Ruby's mutate-and-may-refuse convention).
|
|
32
|
+
# Without it, a query sharing the same business name (e.g. Account's
|
|
33
|
+
# "Open") would collide with the creating command's own bare name.
|
|
63
34
|
aggregate.commands.select(&:creates?).each do |command|
|
|
64
35
|
door.define_singleton_method("#{Naming.snake(command.hecks_name)}!") do |**args|
|
|
65
36
|
Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate,
|
|
@@ -67,12 +38,8 @@ module Hecks
|
|
|
67
38
|
end
|
|
68
39
|
end
|
|
69
40
|
|
|
70
|
-
# A query is a module method
|
|
71
|
-
#
|
|
72
|
-
# bare: a query reads and returns, nothing to warn a caller
|
|
73
|
-
# about the way `!` does for a command. Answers the raw row
|
|
74
|
-
# array `dispatcher.query` itself answers, the same shape
|
|
75
|
-
# `runtime.query("#{fqn}.Name")` already gave.
|
|
41
|
+
# A query is a bare module method (no `!`): it just reads and returns
|
|
42
|
+
# the same raw row array `dispatcher.query` itself answers.
|
|
76
43
|
aggregate.queries.each do |query|
|
|
77
44
|
door.define_singleton_method(Naming.snake(query.hecks_name)) do |**args|
|
|
78
45
|
dispatcher.query("#{fqn}.#{query.hecks_name}", **args)
|
|
@@ -82,16 +49,9 @@ module Hecks
|
|
|
82
49
|
door.define_singleton_method(:fqn) { fqn }
|
|
83
50
|
door.define_singleton_method(:ir) { aggregate }
|
|
84
51
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# Projections::IR)` is this aggregate's IR, not the chapter's.
|
|
89
|
-
#
|
|
90
|
-
# A chapter-scoped target refuses here rather than inventing an
|
|
91
|
-
# answer: `Projector.admits!` is what tells `Projections::IR`
|
|
92
|
-
# (`from: :any`) apart from `Projections::Shape`
|
|
93
|
-
# (`from: :chapter`), which would otherwise answer a confidently
|
|
94
|
-
# empty `{"aggregates" => []}` for an aggregate.
|
|
52
|
+
# Every construct emits its own IR, so an aggregate is a legitimate
|
|
53
|
+
# `project` target too. A chapter-scoped target refuses here via
|
|
54
|
+
# `Projector.admits!` rather than answering a confidently empty result.
|
|
95
55
|
door.define_singleton_method(:project) do |target, out: nil, **options|
|
|
96
56
|
key = Projector.key_for(target)
|
|
97
57
|
artifact = Projector.call(key, bluebook: aggregate, options: options)
|
|
@@ -104,16 +64,12 @@ module Hecks
|
|
|
104
64
|
aggregate.commands.map { |c| "#{Naming.snake(c.hecks_name)}!" }.sort
|
|
105
65
|
end
|
|
106
66
|
door.define_singleton_method(:queries) { aggregate.queries.map { |q| Naming.snake(q.hecks_name) }.sort }
|
|
107
|
-
#
|
|
108
|
-
# answers with, narrowed to this head. `commands` above already
|
|
109
|
-
# answers "what can I call"; this answers "and what does each one
|
|
110
|
-
# want, refuse, and guarantee", which is the rest of the question.
|
|
67
|
+
# This chapter's own `:docs` projection, narrowed to this aggregate.
|
|
111
68
|
door.define_singleton_method(:docs) do |**options|
|
|
112
69
|
Projector.call(:docs, bluebook: dispatcher.registry.bluebook(domain),
|
|
113
70
|
options: options.merge(aggregate: aggregate.hecks_name))
|
|
114
71
|
end
|
|
115
|
-
#
|
|
116
|
-
# takes, aimed at `:narrate` instead.
|
|
72
|
+
# Same narrowing as `:docs`, aimed at `:narrate`.
|
|
117
73
|
door.define_singleton_method(:narrate) do |**options|
|
|
118
74
|
Projector.call(:narrate, bluebook: dispatcher.registry.bluebook(domain),
|
|
119
75
|
options: options.merge(aggregate: aggregate.hecks_name))
|
|
@@ -132,44 +88,38 @@ module Hecks
|
|
|
132
88
|
end
|
|
133
89
|
end
|
|
134
90
|
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
# `
|
|
138
|
-
# `ConstShim`/`const_missing`, so `Pizzas::Pizza.port(...)` lands
|
|
139
|
-
# here instead once any boot has run before.
|
|
140
|
-
#
|
|
141
|
-
# Re-resolved, not the closed-over `aggregate` — this door can be a
|
|
142
|
-
# stale one, built by a boot from earlier in this same process,
|
|
143
|
-
# sitting on the `Pizzas`/`Pizza` constants only because nothing
|
|
144
|
-
# has re-installed them since. Attaching to this door's own
|
|
145
|
-
# `aggregate` would attach the port to a discarded aggregate from
|
|
146
|
-
# that old boot, invisible to the current one actually being
|
|
147
|
-
# loaded — silently, the exact way `method_missing` below already
|
|
148
|
-
# has to avoid it for a plain bind, via `HecksagonBuilder.collector`
|
|
149
|
-
# rather than anything this door closes over. `Hecks.current_registry`
|
|
150
|
-
# is the same "whichever boot is actually in progress" indirection,
|
|
151
|
-
# and `BindingProxy#port` already re-resolves through it the same
|
|
152
|
-
# way — this is that door's fallback twin, not a shortcut past it.
|
|
91
|
+
# Re-resolves via `Hecks.current_registry` rather than the closed-over
|
|
92
|
+
# `aggregate`: this door can be stale from an earlier boot, and its own
|
|
93
|
+
# `aggregate` would silently port a discarded one instead of the live one.
|
|
153
94
|
door.define_singleton_method(:port) do |name, &block|
|
|
154
95
|
current = Hecks.current_registry&.bluebook(domain)&.aggregate(aggregate.hecks_name) or
|
|
155
96
|
raise Bluebook::DSL::Malformed, "#{fqn}.port(#{name.inspect}) called outside a boot"
|
|
156
97
|
|
|
157
|
-
|
|
98
|
+
built = Bluebook::DSL::ConstShim.with(->(const) { const }) do
|
|
158
99
|
Bluebook::DSL::DomainPortBuilder.build(name, owner: current.hecks_name, &block)
|
|
159
100
|
end
|
|
160
|
-
|
|
101
|
+
|
|
102
|
+
# A `verb`-shaped port is a plain `Port`, the same struct `Hecks.port` registers —
|
|
103
|
+
# it belongs to no aggregate IR the way an operations-shaped `DomainPort` does, so
|
|
104
|
+
# it takes the registry's `add_port` directly. Mirrors `DSL::BindingProxy#port`,
|
|
105
|
+
# which handles the identical shape on a domain's first in-process boot (before
|
|
106
|
+
# this facade constant exists); this method is what a repeat boot reaches instead.
|
|
107
|
+
if built.is_a?(Bluebook::Port)
|
|
108
|
+
Hecks.current_registry.add_port(built)
|
|
109
|
+
return self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
built.operations.each do |operation|
|
|
161
113
|
operation.attributes.select(&:reference?).each { |attribute| attribute.type.declared_in = current }
|
|
162
114
|
end
|
|
163
|
-
current.add_port(
|
|
115
|
+
current.add_port(built)
|
|
164
116
|
self
|
|
165
117
|
end
|
|
166
118
|
|
|
167
119
|
door.define_singleton_method(:method_missing) do |verb, *args, **kwargs, &block|
|
|
168
|
-
# A bare call starts a Privacy marking chain
|
|
169
|
-
# `
|
|
170
|
-
#
|
|
171
|
-
# already a real, installed door (a second boot in-process)
|
|
172
|
-
# rather than a `.hecksagon`-parse-time `BindingProxy`.
|
|
120
|
+
# A bare call starts a Privacy marking chain (see
|
|
121
|
+
# `BindingProxy#method_missing`), reached here when the door is already
|
|
122
|
+
# installed (a second in-process boot) rather than at parse time.
|
|
173
123
|
return Bluebook::DSL::AttributePath.new(fqn, [verb.to_s]) if args.empty? && kwargs.empty? && !block
|
|
174
124
|
|
|
175
125
|
collector = Bluebook::DSL::HecksagonBuilder.collector
|
|
@@ -189,24 +139,9 @@ module Hecks
|
|
|
189
139
|
!Bluebook::DSL::HecksagonBuilder.collector.nil? || super(name, include_private)
|
|
190
140
|
end
|
|
191
141
|
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
# aggregate.hecks_name, ...)`, surface.rb's own `install`), not only
|
|
196
|
-
# nested under its chapter. So once any domain has booted once in this
|
|
197
|
-
# process, `Account::Debit` written while declaring some other
|
|
198
|
-
# bluebook — same domain or a different one — reaches this door's
|
|
199
|
-
# const_missing directly, never `Object.const_missing`/`ConstShim::
|
|
200
|
-
# Hook` at all: real modules resolve without ever calling that.
|
|
201
|
-
#
|
|
202
|
-
# `aggregate.hecks_name`, not the qualified `fqn` — a scoped
|
|
203
|
-
# reference has to read the same either way, whether or not a stale
|
|
204
|
-
# door happens to be sitting on this process from an earlier boot;
|
|
205
|
-
# qualifying it here would make `Account::Debit`'s own meaning
|
|
206
|
-
# depend on incidental process history, which is the exact
|
|
207
|
-
# instability S0b exists to remove (docs/dsl-work-slices.md — "two
|
|
208
|
-
# domains in one registry" is this file's own reason for being
|
|
209
|
-
# owned by that slice).
|
|
142
|
+
# Uses `aggregate.hecks_name`, not the qualified `fqn`: qualifying it here
|
|
143
|
+
# would make `Account::Debit`'s meaning depend on whether a stale door
|
|
144
|
+
# from an earlier boot happens to be sitting on this process.
|
|
210
145
|
door.define_singleton_method(:const_missing) do |name|
|
|
211
146
|
resolver = Bluebook::DSL::ConstShim.resolver
|
|
212
147
|
return resolver.call("#{aggregate.hecks_name}::#{name}") if resolver
|
|
@@ -5,9 +5,8 @@ require_relative "../../bluebook/dsl/hecksagon_builder"
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Facade
|
|
7
7
|
module Surface
|
|
8
|
-
# One chapter's module: vision and aggregate
|
|
9
|
-
#
|
|
10
|
-
# declaration hook for names the door does not carry.
|
|
8
|
+
# One chapter's module: vision and aggregate list on the singleton, one
|
|
9
|
+
# aggregate door per declared head, and a `const_missing` hook for undeclared names.
|
|
11
10
|
module Chapter
|
|
12
11
|
# Builds the anonymous module that stands for one booted chapter: `vision`,
|
|
13
12
|
# `aggregates`, `docs`, `narrate` and `project` as singleton methods, plus one
|
|
@@ -24,49 +23,22 @@ module Hecks
|
|
|
24
23
|
chapter.define_singleton_method(:vision) { bluebook.vision }
|
|
25
24
|
chapter.define_singleton_method(:aggregates) { bluebook.aggregates.map(&:name).sort }
|
|
26
25
|
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# from the domain — the same guarantee `bin/reference` gives the DSL
|
|
30
|
-
# reference by generating it from the Syntax chapter.
|
|
31
|
-
#
|
|
32
|
-
# A method rather than only a script because that is what gets it
|
|
33
|
-
# read: `QualityControl.docs` in a console, beside `vision` and
|
|
34
|
-
# `aggregates`, at the moment somebody is wondering what a verb
|
|
35
|
-
# wants. Projected on each call rather than memoised — it is a pure
|
|
36
|
-
# read of the IR, and a stale document is the entire thing this
|
|
37
|
-
# exists to prevent.
|
|
26
|
+
# The chapter's usage document, projected from its IR on each call so it
|
|
27
|
+
# cannot go stale.
|
|
38
28
|
chapter.define_singleton_method(:docs) do |**options|
|
|
39
29
|
Projector.call(:docs, bluebook: bluebook, options: options)
|
|
40
30
|
end
|
|
41
31
|
|
|
42
|
-
#
|
|
43
|
-
# beside `:docs`, for the reader who needs to confirm the domain is
|
|
44
|
-
# right rather than call it: `QualityControl.narrate` in a console.
|
|
32
|
+
# The chapter read back in english, via `Projector::NarrateProjector`.
|
|
45
33
|
chapter.define_singleton_method(:narrate) do |**options|
|
|
46
34
|
Projector.call(:narrate, bluebook: bluebook, options: options)
|
|
47
35
|
end
|
|
48
36
|
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# reach it from a booted domain — this module already closes
|
|
52
|
-
# over the one `Bluebook` every projector wants and simply
|
|
53
|
-
# never handed it out, so every bin/ projector rebuilt a
|
|
54
|
-
# Registry and reloaded the files to recover what was already
|
|
55
|
-
# sitting here.
|
|
37
|
+
# Projects the chapter's IR through a target; `out:` writes the artifact and
|
|
38
|
+
# the remaining options go to the projector (`audience:` for OIDC).
|
|
56
39
|
#
|
|
57
40
|
# Pizzas.project(Projections::OIDC)
|
|
58
41
|
# Pizzas.project(Projections::Shape, out: "shape.json")
|
|
59
|
-
#
|
|
60
|
-
# No `to_ir` companion: `project(Projections::IR)` already
|
|
61
|
-
# answers with the canonical serialized form, and handing out
|
|
62
|
-
# the live graph as well would be a second way to say the same
|
|
63
|
-
# thing. Anything genuinely needing the graph is a projector,
|
|
64
|
-
# and a projector is given it.
|
|
65
|
-
#
|
|
66
|
-
# `out:` writes and everything else is passed through to the
|
|
67
|
-
# target — so `options` stays the projector's own vocabulary
|
|
68
|
-
# (`audience:` for OIDC) rather than a fixed set this method
|
|
69
|
-
# would have to know about.
|
|
70
42
|
chapter.define_singleton_method(:project) do |target, out: nil, **options|
|
|
71
43
|
key = Projector.key_for(target)
|
|
72
44
|
artifact = Projector.call(key, bluebook: bluebook, options: options)
|
|
@@ -79,25 +51,9 @@ module Hecks
|
|
|
79
51
|
chapter.const_set(aggregate.hecks_name, aggregate_module(dispatcher, bluebook.name, aggregate))
|
|
80
52
|
end
|
|
81
53
|
|
|
82
|
-
# Inside a hecksagon
|
|
83
|
-
# `.
|
|
84
|
-
#
|
|
85
|
-
# can happen because the facade is installed once a boot finishes, not
|
|
86
|
-
# on every load. With a collector open, the name becomes a `BindingProxy`
|
|
87
|
-
# recording the same bind the aggregate module would ; without one, it
|
|
88
|
-
# is a genuine NameError.
|
|
89
|
-
#
|
|
90
|
-
# The other reader of a miss is S0b's own bridge (docs/dsl-work-
|
|
91
|
-
# slices.md, const_shim.rb's `ScopedConstant`): a bluebook still being
|
|
92
|
-
# declared that names `#{bluebook.name}::Something` — an event, a
|
|
93
|
-
# command reference, an `admits:` — reaches here, not
|
|
94
|
-
# `Object.const_missing`, the moment this chapter's own facade already
|
|
95
|
-
# exists (a previous boot in the same process, or this same chapter
|
|
96
|
-
# re-entering its own name). `ConstShim.active?` is exactly as true
|
|
97
|
-
# here as it is at the top level, mid-declaration, and consulting the
|
|
98
|
-
# same resolver is what makes a scoped reference resolve identically
|
|
99
|
-
# whether or not a facade happens to be built yet — a bridge consulted
|
|
100
|
-
# only at the top level works only before any facade exists.
|
|
54
|
+
# Inside a hecksagon a name is a declaration: with a collector open it becomes
|
|
55
|
+
# a `BindingProxy`. Otherwise a scoped reference from a bluebook mid-declaration
|
|
56
|
+
# goes to the `ConstShim` resolver, so it resolves whether or not a facade exists.
|
|
101
57
|
chapter.define_singleton_method(:const_missing) do |name|
|
|
102
58
|
collector = Bluebook::DSL::HecksagonBuilder.collector
|
|
103
59
|
return Bluebook::DSL::BindingProxy.new("#{bluebook.name}::#{name}", collector) if collector
|
data/lib/hecks/facade/surface.rb
CHANGED
|
@@ -3,26 +3,13 @@ require_relative "surface/aggregate_door"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Facade
|
|
6
|
-
#
|
|
6
|
+
# The door without classes: anonymous per-boot modules whose singleton methods
|
|
7
|
+
# dispatch by FQN, installed by `Loader.bind_runtime` and replaced whole on the next boot.
|
|
7
8
|
#
|
|
8
|
-
# `
|
|
9
|
-
#
|
|
10
|
-
# singleton methods close over the dispatcher and dispatch by FQN — the
|
|
11
|
-
# same shape `Router::NamespaceInstaller` proved. Nothing here is a domain
|
|
12
|
-
# class ; the IR is the only graph, and the door is a projection of it,
|
|
13
|
-
# installed by `Loader.bind_runtime` and re-installed whole on the next
|
|
14
|
-
# boot. How a chapter's module is built is surface/chapter.rb; how one
|
|
15
|
-
# aggregate's door is built is surface/aggregate_door.rb.
|
|
16
|
-
#
|
|
17
|
-
# The hexagon-binding door rides along : `Pizzas::Pizza.persisted_by("Heki")`
|
|
18
|
-
# in a `.hecksagon` file lands on the module's `method_missing`, which
|
|
19
|
-
# records an `Bind` into whatever `HecksagonBuilder.collector` is open
|
|
20
|
-
# at call time — so even a facade left over from a previous boot records
|
|
21
|
-
# into the current builder, and a chapter with no constant at all falls
|
|
22
|
-
# through to `ConstShim` → `BindingProxy`, which mints byte-identical binds.
|
|
9
|
+
# `persisted_by("Heki")` in a `.hecksagon` file lands on the module's `method_missing`,
|
|
10
|
+
# which records a `Bind` into the open `HecksagonBuilder.collector`.
|
|
23
11
|
module Surface
|
|
24
|
-
#
|
|
25
|
-
# of them gets no reader and says so — the same warning the class door gave.
|
|
12
|
+
# Names that would shadow the machinery a Handle runs on; a field with one gets no reader.
|
|
26
13
|
RESERVED = %i[id state events reload inspect to_h hash class].freeze
|
|
27
14
|
|
|
28
15
|
extend Chapter
|
|
@@ -34,14 +21,12 @@ module Hecks
|
|
|
34
21
|
# closing over `dispatcher`, so `Pizzas::Pizza` and the bare `Pizza` both open the
|
|
35
22
|
# door of the boot that ran last.
|
|
36
23
|
#
|
|
37
|
-
# An aggregate sharing its chapter's name gets no second constant
|
|
38
|
-
#
|
|
39
|
-
# is left alone with a warning (see `Namespace.install`).
|
|
24
|
+
# An aggregate sharing its chapter's name gets no second constant. A name that user
|
|
25
|
+
# code or the stdlib already owns is left alone with a warning (see `Namespace.install`).
|
|
40
26
|
#
|
|
41
27
|
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
|
|
42
28
|
# dispatcher every installed door dispatches and queries through
|
|
43
|
-
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the same `dispatcher
|
|
44
|
-
# so a boot can return the call's result
|
|
29
|
+
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the same `dispatcher`
|
|
45
30
|
# @raise [NameError] if a chapter or aggregate name is not a valid constant name
|
|
46
31
|
def install(dispatcher)
|
|
47
32
|
dispatcher.registry.bluebooks.each_value do |bluebook|
|
data/lib/hecks/facade.rb
CHANGED
|
@@ -1,39 +1,24 @@
|
|
|
1
1
|
module Hecks
|
|
2
|
-
# The class-free public surface
|
|
3
|
-
# facade/handle.rb for the record in hand. Installed per boot by
|
|
4
|
-
# `Runtime::Loader.bind_runtime`.
|
|
2
|
+
# The class-free public surface, installed per boot by `Runtime::Loader.bind_runtime`.
|
|
5
3
|
module Facade
|
|
6
4
|
end
|
|
7
5
|
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# Overwrites only what it itself installed (tracked in `GENERATED`), refuses —
|
|
11
|
-
# with a warning, never a raise — to clobber a constant belonging to user
|
|
12
|
-
# code or the stdlib. Which is why a chapter named `Set` never becomes a
|
|
13
|
-
# constant, and why `Registry` keeps its own table of chapters rather than
|
|
14
|
-
# trusting Ruby's. Each re-install replaces the previous boot's entry, so
|
|
15
|
-
# what `GENERATED` retains is one small facade module per name, not a graph
|
|
16
|
-
# per boot.
|
|
6
|
+
# Installs facade constants at top level, replacing only what it installed itself.
|
|
7
|
+
# A constant owned by user code or the stdlib is left alone with a warning.
|
|
17
8
|
module Namespace
|
|
18
|
-
#
|
|
19
|
-
# (`GENERATED[[container, name]] = value`). False positive for
|
|
20
|
-
# Style/MutableConstant.
|
|
9
|
+
# A live registry, mutated by `install`, so it cannot be frozen.
|
|
21
10
|
# rubocop:disable-next Style/MutableConstant
|
|
22
11
|
GENERATED = {}
|
|
23
12
|
|
|
24
13
|
module_function
|
|
25
14
|
|
|
26
|
-
# Sets `container::name` to `value`, replacing only a constant this module installed
|
|
27
|
-
# itself and leaving any other existing constant in place with a warning.
|
|
15
|
+
# Sets `container::name` to `value`, replacing only a constant this module installed.
|
|
28
16
|
#
|
|
29
|
-
# @param container [Module] the namespace to define the constant in
|
|
30
|
-
#
|
|
31
|
-
# @param
|
|
32
|
-
# @
|
|
33
|
-
# @
|
|
34
|
-
# defined under `name`, which this module does not own and did not replace
|
|
35
|
-
# @raise [NameError] if `name` is not a valid constant name (raised by
|
|
36
|
-
# `const_defined?`)
|
|
17
|
+
# @param container [Module] the namespace to define the constant in
|
|
18
|
+
# @param name [String, Symbol] the constant name
|
|
19
|
+
# @param value [Module] the facade module to install
|
|
20
|
+
# @return [Object] `value` when installed, else the foreign constant left in place
|
|
21
|
+
# @raise [NameError] if `name` is not a valid constant name
|
|
37
22
|
def install(container, name, value)
|
|
38
23
|
name = name.to_s
|
|
39
24
|
|
data/lib/hecks/forms/app.rb
CHANGED
|
@@ -12,18 +12,8 @@ require_relative "../facade/json_door"
|
|
|
12
12
|
|
|
13
13
|
module Hecks
|
|
14
14
|
module Forms
|
|
15
|
-
#
|
|
16
|
-
# renders the
|
|
17
|
-
# with no extension (or `.json`) dispatches the same ask through
|
|
18
|
-
# `Runtime::Dispatcher#query` and answers with its raw result — one
|
|
19
|
-
# route, two representations, exactly the "change the file format on
|
|
20
|
-
# the route" mechanism this was built around
|
|
21
|
-
# (docs/command-form-and-query-form-bluebook.md).
|
|
22
|
-
#
|
|
23
|
-
# A plain Rack app (`#call(env)`) — no Sinatra, no Rails. `rack` itself
|
|
24
|
-
# is a lazy dependency the same way `pg`/`oauth2`/`aws-sdk-lambda` are
|
|
25
|
-
# for their own adapters (see the Gemfile's own comment) : a project
|
|
26
|
-
# that never boots this file never needs it installed.
|
|
15
|
+
# A Rack app that content-negotiates one route: `/Banking/Account/Overdrawn.html`
|
|
16
|
+
# renders HTML, and the same path with no extension or `.json` answers JSON.
|
|
27
17
|
class App
|
|
28
18
|
# Builds the Rack app for a configured app name, exposing exactly the chapters its
|
|
29
19
|
# `Forms.configure` block declared.
|
|
@@ -99,18 +89,8 @@ module Hecks
|
|
|
99
89
|
raise RouteNotFound, "#{domain.inspect} is not exposed by this app — declared chapters: #{@exposed.join(', ')}"
|
|
100
90
|
end
|
|
101
91
|
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
# segment is just part of the identity. Splitting on the first "."
|
|
105
|
-
# instead truncates any identity value containing a dot (an email
|
|
106
|
-
# `identified_by { email.address }`, a decimal-ish reference — an
|
|
107
|
-
# aggregate's identity is free-form unless its value object declares
|
|
108
|
-
# a `pattern:`, see S3 in the same audit) at its own first dot, so
|
|
109
|
-
# `reference.value=c.1` 404s everywhere: detail page, JSON view, and
|
|
110
|
-
# its own index-table link. An identity that itself happens to end in
|
|
111
|
-
# exactly ".html" or ".json" is still ambiguous with a real format
|
|
112
|
-
# suffix — the same tension any extension-based content-negotiation
|
|
113
|
-
# scheme has, and not what H12 is about.
|
|
92
|
+
# Only a trailing ".html"/".json" is a format; other dots belong to the identity
|
|
93
|
+
# (an email, say). An identity ending in ".html" or ".json" stays ambiguous.
|
|
114
94
|
def split_format(segment)
|
|
115
95
|
segment = segment.to_s
|
|
116
96
|
return [Regexp.last_match(1), Regexp.last_match(2)] if segment =~ /\A(.*)\.(html|json)\z/
|
|
@@ -118,8 +98,6 @@ module Hecks
|
|
|
118
98
|
[segment, "json"]
|
|
119
99
|
end
|
|
120
100
|
|
|
121
|
-
# ---- home -------------------------------------------------------
|
|
122
|
-
|
|
123
101
|
def home(request)
|
|
124
102
|
format = request.params["format"] || "html"
|
|
125
103
|
chapters = @exposed.to_h { |name| [name, @registry.bluebook(name)] }.compact
|
|
@@ -128,8 +106,6 @@ module Hecks
|
|
|
128
106
|
html("Exposed domains", IndexRenderer.render(chapters))
|
|
129
107
|
end
|
|
130
108
|
|
|
131
|
-
# ---- /:domain/:aggregate.:format ---------------------------------
|
|
132
|
-
|
|
133
109
|
def aggregate_route(request, chapter, aggregate_name, format)
|
|
134
110
|
aggregate = find_aggregate(chapter, aggregate_name)
|
|
135
111
|
return respond(405, "text/plain", "GET only") unless request.get?
|
|
@@ -140,31 +116,17 @@ module Hecks
|
|
|
140
116
|
breadcrumbs: [[chapter.name, "/"], [aggregate.hecks_name, nil]])
|
|
141
117
|
else
|
|
142
118
|
instances = @registry.repository(chapter.name, aggregate).all
|
|
143
|
-
# id last
|
|
144
|
-
# to declare its own attribute literally named `id` has that
|
|
145
|
-
# attribute's own wrapped value sitting in `i.state[:id]`
|
|
146
|
-
# already, which silently clobbers the correct bare identity
|
|
147
|
-
# if `id:` is merged first.
|
|
119
|
+
# `id:` is merged last so an attribute named `id` in the state cannot clobber it.
|
|
148
120
|
json(200, instances.map { |i| i.state.merge(id: i.id) })
|
|
149
121
|
end
|
|
150
122
|
end
|
|
151
123
|
|
|
152
|
-
# ---- /:domain/:aggregate/:verb_or_id.:format ----------------------
|
|
153
|
-
|
|
154
124
|
def verb_or_record_route(request, chapter, aggregate_name, verb_or_id, format)
|
|
155
125
|
aggregate = find_aggregate(chapter, aggregate_name)
|
|
156
126
|
domain = chapter.name
|
|
157
127
|
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# aggregate's command/query names ("Close", "Overdrawn", ...).
|
|
161
|
-
# A GET for such an id must still be able to reach that record's
|
|
162
|
-
# own detail page when a record with that literal id actually
|
|
163
|
-
# exists — checking the verb first would mean a record unlucky
|
|
164
|
-
# enough to be named after a real verb could never be viewed.
|
|
165
|
-
# POST never means "view a record" at all (`record_route` only
|
|
166
|
-
# ever answers GET), so command submission there is unambiguous
|
|
167
|
-
# and matches the verb first.
|
|
128
|
+
# A record id is free-form and can equal a verb name ("Close"), so a GET checks for
|
|
129
|
+
# a record first. POST never views a record, so it matches the verb first.
|
|
168
130
|
if request.get? && (instance = @registry.repository(domain, aggregate).find(verb_or_id))
|
|
169
131
|
return record_route(request, domain, aggregate, verb_or_id, format, instance: instance)
|
|
170
132
|
end
|
|
@@ -204,8 +166,7 @@ module Hecks
|
|
|
204
166
|
def submit_command(request, domain, aggregate, command, action)
|
|
205
167
|
raw, envelope = submitted_command(request, aggregate, command)
|
|
206
168
|
result = @dispatcher.dispatch_flat("#{domain}::#{aggregate.hecks_name}.#{command.hecks_name}", envelope)
|
|
207
|
-
#
|
|
208
|
-
# a path segment here, not just interpolated raw.
|
|
169
|
+
# The id is free-form, so it is percent-encoded as a path segment.
|
|
209
170
|
redirect("/#{domain}/#{aggregate.hecks_name}/#{Escape.path(result.id)}.html")
|
|
210
171
|
rescue *Runtime::DOMAIN_REFUSALS, ArgumentError, TypeError, JSON::ParserError => e
|
|
211
172
|
status = e.is_a?(Runtime::NotFound) ? 404 : 422
|
|
@@ -218,8 +179,7 @@ module Hecks
|
|
|
218
179
|
|
|
219
180
|
_, envelope = submitted_command(request, aggregate, command)
|
|
220
181
|
result = @dispatcher.dispatch_flat("#{domain}::#{aggregate.hecks_name}.#{command.hecks_name}", envelope)
|
|
221
|
-
# id last
|
|
222
|
-
# sites in this file (see aggregate_route's own comment).
|
|
182
|
+
# `id:` last, as in `aggregate_route`.
|
|
223
183
|
json(201, result.state.merge(id: result.id))
|
|
224
184
|
rescue *Runtime::DOMAIN_REFUSALS, ArgumentError, TypeError, JSON::ParserError => e
|
|
225
185
|
status = e.is_a?(Runtime::NotFound) ? 404 : 422
|
|
@@ -255,30 +215,18 @@ module Hecks
|
|
|
255
215
|
results, error = run_query(domain, aggregate, query, fields, asked)
|
|
256
216
|
return json(422, { error: error.class.name.split("::").last, message: error.message }) if error
|
|
257
217
|
|
|
258
|
-
# id last
|
|
259
|
-
# sites in this file (see aggregate_route's own comment).
|
|
218
|
+
# `id:` last, as in `aggregate_route`.
|
|
260
219
|
json(200, results.map { |i| i.state.merge(id: i.id) })
|
|
261
220
|
end
|
|
262
221
|
|
|
263
|
-
# `Dispatcher#query` answers
|
|
264
|
-
#
|
|
265
|
-
# every path it can take — native pushdown or the in-memory
|
|
266
|
-
# fallback alike), not `Runtime::Instance`. Wrapped into `Record`
|
|
267
|
-
# here, once, so every renderer downstream reads one shape whether
|
|
268
|
-
# its records came from a query or straight off a repository.
|
|
222
|
+
# `Dispatcher#query` answers plain hashes, not `Runtime::Instance`; wrapping them
|
|
223
|
+
# in `Record` gives the renderers one shape.
|
|
269
224
|
def run_query(domain, aggregate, query, fields, asked)
|
|
270
225
|
args = Params.extract(fields, asked)
|
|
271
226
|
rows = @dispatcher.query("#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}", **args)
|
|
272
227
|
[rows.map { |row| Record.new(row[:id], row.except(:id)) }, nil]
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
# JSON (the honest fallback for a multi-attribute list element this
|
|
276
|
-
# prototype's textarea doesn't build a second widget for). A caller
|
|
277
|
-
# who types a non-JSON line into that field raises `JSON::ParserError`
|
|
278
|
-
# before dispatch ever sees it. It is rescued here, as both command
|
|
279
|
-
# submission paths rescue it (`submit_command`, `command_json`), so
|
|
280
|
-
# a malformed list-of-VO query shows the same 422 every other
|
|
281
|
-
# bad-input path shows rather than a 500.
|
|
228
|
+
# `Params.extract` parses a list-of-value-object line as JSON; rescuing
|
|
229
|
+
# `JSON::ParserError` turns a malformed line into a 422 rather than a 500.
|
|
282
230
|
rescue *Runtime::DOMAIN_REFUSALS, ArgumentError, TypeError, JSON::ParserError => e
|
|
283
231
|
[nil, e]
|
|
284
232
|
end
|
|
@@ -288,8 +236,7 @@ module Hecks
|
|
|
288
236
|
|
|
289
237
|
instance ||= @registry.repository(domain, aggregate).find(id)
|
|
290
238
|
return not_found(aggregate, id, format) unless instance
|
|
291
|
-
# id last
|
|
292
|
-
# sites in this file (see aggregate_route's own comment).
|
|
239
|
+
# `id:` last, as in `aggregate_route`.
|
|
293
240
|
return json(200, instance.state.merge(id: instance.id)) if format != "html"
|
|
294
241
|
|
|
295
242
|
html("#{domain}::#{aggregate.hecks_name} #{id}",
|
|
@@ -339,8 +286,6 @@ module Hecks
|
|
|
339
286
|
raw.merge("to" => raw["id"])
|
|
340
287
|
end
|
|
341
288
|
|
|
342
|
-
# ---- rendering ----------------------------------------------------
|
|
343
|
-
|
|
344
289
|
def html(title, body, breadcrumbs: [], status: 200)
|
|
345
290
|
respond(status, "text/html; charset=utf-8", Page.render(title: title, body: body, breadcrumbs: breadcrumbs))
|
|
346
291
|
end
|