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
|
@@ -2,65 +2,25 @@ require_relative "../naming"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Projector
|
|
5
|
-
#
|
|
5
|
+
# Projects a bluebook as its own command-line surface: the verb tree, argument spec
|
|
6
|
+
# and usage text. Nothing executes here; the generic runner (`bin/run`) parses against it.
|
|
6
7
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# already stated in the chapter. A hand-written CLI restates all of it and
|
|
10
|
-
# then drifts — and the first thing to drift is the help text, which is the
|
|
11
|
-
# only part anybody reads.
|
|
12
|
-
#
|
|
13
|
-
# ## What is projected
|
|
14
|
-
#
|
|
15
|
-
# This answers the surface only — the verb tree, the argument spec, the
|
|
16
|
-
# usage text — and nothing executes here. One small generic runner
|
|
17
|
-
# (`bin/run`) boots a domain, asks for this, parses against it and
|
|
18
|
-
# dispatches.
|
|
19
|
-
#
|
|
20
|
-
# ## Why project rather than generate
|
|
21
|
-
#
|
|
22
|
-
# The alternative was generating an executable per domain, which is what
|
|
23
|
-
# `bin/project_rust` does for a whole runtime and would be the more
|
|
24
|
-
# spectacular version of this. It was not taken: a generated program is a
|
|
25
|
-
# second copy of the dispatch logic, and it needs regenerating on every
|
|
26
|
-
# bluebook edit — a second tax beside the era gate, paid for a file nobody
|
|
27
|
-
# reads. Projecting the surface keeps one dispatcher and a help text that
|
|
28
|
-
# cannot be stale, because it is computed at the moment it is printed.
|
|
29
|
-
#
|
|
30
|
-
# ## The typing is the point
|
|
31
|
-
#
|
|
32
|
-
# A CLI hands everything over as a String. `sequence.value=99` has to
|
|
33
|
-
# become the Integer 99 or the runtime refuses it, and the only honest
|
|
34
|
-
# place to learn that is the value object's own declared field type. A CLI
|
|
35
|
-
# that guessed — "it looks like a number" — would send 99 for a version
|
|
36
|
-
# string of "99" and be wrong in a way nobody could see.
|
|
8
|
+
# Argument types come from the declared field types, never from guessing at the
|
|
9
|
+
# string: `sequence.value=99` must become 99, and a version "99" must stay a String.
|
|
37
10
|
module CliProjector
|
|
38
11
|
module_function
|
|
39
12
|
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
# A chapter may legally declare a command and a query of one name: the
|
|
43
|
-
# language namespaces them and `Banking::Account.Open` is both, in the
|
|
44
|
-
# corpus, today. A single flat list of subcommands has to pick one, and
|
|
45
|
-
# picking silently is how `Ticket.Filed` sat undetected in this
|
|
46
|
-
# repository for a day.
|
|
13
|
+
# Projects the verb and question tables and the usage text.
|
|
47
14
|
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# shell-safe where a `?` suffix would be eaten by globbing, and it makes
|
|
51
|
-
# the collision impossible rather than detected. It also reads as what it
|
|
52
|
-
# is: everything under `ask` changes nothing.
|
|
15
|
+
# Commands and queries are separate namespaces because a chapter may declare both
|
|
16
|
+
# under one name; questions are asked with `ask`.
|
|
53
17
|
#
|
|
54
18
|
# @param bluebook [Bluebook::Chapter] the booted domain to project
|
|
55
|
-
# @param options [Hash{Symbol => Object}] `:program` (
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# (short and full spelling both keying the full name); `:usage` is the
|
|
61
|
-
# pre-rendered help text
|
|
62
|
-
# @raise [Bluebook::DSL::Malformed] if two verbs project to the same command-line
|
|
63
|
-
# name
|
|
19
|
+
# @param options [Hash{Symbol => Object}] `:program` (default `"bin/run"`) for the usage
|
|
20
|
+
# text; `:verb` and `:ask` select one verb's `--help` text
|
|
21
|
+
# @return [Hash{Symbol => Object}] `:verbs`, `:questions`, `:names` (alias tables) and
|
|
22
|
+
# `:usage` (pre-rendered help text)
|
|
23
|
+
# @raise [Bluebook::DSL::Malformed] if two verbs project to the same command-line name
|
|
64
24
|
def call(bluebook:, options: {})
|
|
65
25
|
verbs = {}
|
|
66
26
|
questions = {}
|
|
@@ -78,46 +38,19 @@ module Hecks
|
|
|
78
38
|
end
|
|
79
39
|
end
|
|
80
40
|
|
|
81
|
-
#
|
|
82
|
-
# rather than a tasteful omission. The runtime has always dispatched
|
|
83
|
-
# a port operation by exactly the same name as a command — the
|
|
84
|
-
# projection simply never listed one, so `run_specs` and `file`
|
|
85
|
-
# answered "no such verb" while working perfectly through Ruby.
|
|
86
|
-
#
|
|
87
|
-
# It matters most for the caller with no other door. An agent that
|
|
88
|
-
# may not shell out reaches this domain only through the projected
|
|
89
|
-
# CLI, and a port it cannot see is a capability it does not have.
|
|
41
|
+
# Port operations dispatch by the same name as a command, so they are verbs too.
|
|
90
42
|
aggregate.ports.each do |port|
|
|
91
43
|
port.operations.each { |o| claim(verbs, name_for(aggregate, o), port_spec(bluebook, aggregate, port, o)) }
|
|
92
44
|
end
|
|
93
45
|
end
|
|
94
46
|
|
|
95
|
-
# A report
|
|
96
|
-
#
|
|
97
|
-
# the projection simply never listed one — so the composed reads worked
|
|
98
|
-
# from Ruby and did not exist for anybody whose only door is the command
|
|
99
|
-
# line.
|
|
100
|
-
#
|
|
101
|
-
# It matters most for exactly what a report is for. Every other question
|
|
102
|
-
# here answers with rows and leaves the arithmetic to the reader; a
|
|
103
|
-
# `group_by` report is the one that counts. An agent that cannot reach
|
|
104
|
-
# it can list bugs all day and never answer "how are we doing".
|
|
105
|
-
#
|
|
106
|
-
# **One dot, not two** — a report belongs to the chapter rather than to any
|
|
107
|
-
# aggregate (that is what rootless means), so it is addressed
|
|
108
|
-
# `QualityControl.BugsByStatus` where a query is
|
|
109
|
-
# `QualityControl::Bug.Queue`. `Dispatcher#query` splits on precisely
|
|
110
|
-
# that difference.
|
|
47
|
+
# A report belongs to the chapter, not an aggregate, so it is addressed
|
|
48
|
+
# `Chapter.Report` (one dot) where a query is `Chapter::Aggregate.Query`.
|
|
111
49
|
bluebook.read_models.each do |model|
|
|
112
50
|
claim(questions, Naming.snake(model.hecks_name), report_spec(bluebook, model))
|
|
113
51
|
end
|
|
114
52
|
|
|
115
|
-
# The
|
|
116
|
-
# create_pizza` rather than `pizzas order.create_pizza` — the
|
|
117
|
-
# aggregate is worth typing only when two of them declare the same
|
|
118
|
-
# verb, and in a one-aggregate domain it never is. Both spellings are
|
|
119
|
-
# always accepted; `names` maps every accepted one to its canonical
|
|
120
|
-
# key, and the display name is the shortest that is unambiguous.
|
|
53
|
+
# The display name is the shortest unambiguous one; both spellings are accepted.
|
|
121
54
|
shorten(verbs)
|
|
122
55
|
shorten(questions)
|
|
123
56
|
|
|
@@ -126,13 +59,10 @@ module Hecks
|
|
|
126
59
|
usage: usage(bluebook, verbs, questions, options) }
|
|
127
60
|
end
|
|
128
61
|
|
|
129
|
-
#
|
|
130
|
-
# aggregates declaring `Close` keep
|
|
131
|
-
# which is the honest answer — a CLI that picked one would be choosing
|
|
132
|
-
# for the caller.
|
|
62
|
+
# Sets `:short` on each spec: the last segment when exactly one verb ends in it,
|
|
63
|
+
# else the full name (two aggregates both declaring `Close` keep both spellings).
|
|
133
64
|
#
|
|
134
|
-
# @param specs [Hash{String => Hash}] the verb or question map
|
|
135
|
-
# mutated in place: each spec hash gains a `:short` key
|
|
65
|
+
# @param specs [Hash{String => Hash}] the verb or question map, mutated in place
|
|
136
66
|
# @return [void]
|
|
137
67
|
def shorten(specs)
|
|
138
68
|
tails = specs.keys.group_by { |name| name.split(".").last }
|
|
@@ -142,12 +72,7 @@ module Hecks
|
|
|
142
72
|
end
|
|
143
73
|
end
|
|
144
74
|
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
# @param specs [Hash{String => Hash}] a verb or question map whose specs already
|
|
148
|
-
# carry `:short` (set by `shorten`)
|
|
149
|
-
# @return [Hash{String => String}] every accepted spelling — the full name and
|
|
150
|
-
# its `:short` form — mapped to the full name
|
|
75
|
+
# Maps every accepted spelling (full name and `:short`) to the full name.
|
|
151
76
|
def aliases(specs)
|
|
152
77
|
specs.each_with_object({}) do |(name, spec), map|
|
|
153
78
|
map[name] = name
|
|
@@ -155,17 +80,9 @@ module Hecks
|
|
|
155
80
|
end
|
|
156
81
|
end
|
|
157
82
|
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# subcommands. Refused here rather than silently resolving to whichever
|
|
161
|
-
# was walked first, which is how `Ticket.Filed` (a command) and
|
|
162
|
-
# `Ticket.Filed` (a query) sat undetected until something flattened them.
|
|
83
|
+
# Stores `spec` under `name`, refusing a second claim rather than silently
|
|
84
|
+
# keeping whichever verb was walked first.
|
|
163
85
|
#
|
|
164
|
-
# @param verbs [Hash{String => Hash}] the verb or question map being built,
|
|
165
|
-
# mutated in place
|
|
166
|
-
# @param name [String] the command-line name this spec claims
|
|
167
|
-
# @param spec [Hash{Symbol => Object}] the spec hash to store under `name`
|
|
168
|
-
# @return [void]
|
|
169
86
|
# @raise [Bluebook::DSL::Malformed] if `name` is already claimed by another verb
|
|
170
87
|
def claim(verbs, name, spec)
|
|
171
88
|
if verbs.key?(name)
|
|
@@ -177,15 +94,7 @@ module Hecks
|
|
|
177
94
|
verbs[name] = spec
|
|
178
95
|
end
|
|
179
96
|
|
|
180
|
-
#
|
|
181
|
-
# snake-cased.
|
|
182
|
-
#
|
|
183
|
-
# @param aggregate [Bluebook::Aggregate] the verb's owning aggregate
|
|
184
|
-
# @param verb [Bluebook::Command, Bluebook::Query, Bluebook::PortOperation]
|
|
185
|
-
# the command, query or port operation being named
|
|
186
|
-
# @param entity [Bluebook::Entity, nil] the entity the verb is declared on, or `nil`
|
|
187
|
-
# for an aggregate-level verb
|
|
188
|
-
# @return [String] the dotted, snake-cased command-line name
|
|
97
|
+
# The dotted command-line name, `aggregate[.entity].verb`, snake-cased.
|
|
189
98
|
def name_for(aggregate, verb, entity = nil)
|
|
190
99
|
parts = [Naming.snake(aggregate.hecks_name)]
|
|
191
100
|
parts << Naming.snake(entity.hecks_name) if entity
|
|
@@ -193,39 +102,15 @@ module Hecks
|
|
|
193
102
|
parts.join(".")
|
|
194
103
|
end
|
|
195
104
|
|
|
196
|
-
#
|
|
197
|
-
# for display in help text.
|
|
198
|
-
#
|
|
199
|
-
# @param bluebook [Bluebook::Chapter] the verb's owning chapter
|
|
200
|
-
# @param aggregate [Bluebook::Aggregate] the verb's owning aggregate
|
|
201
|
-
# @param verb [Bluebook::Command, Bluebook::Query, Bluebook::PortOperation]
|
|
202
|
-
# the command, query or port operation being named
|
|
203
|
-
# @param entity [Bluebook::Entity, nil] the entity the verb is declared on, or `nil`
|
|
204
|
-
# for an aggregate-level verb
|
|
205
|
-
# @return [String] the fully-qualified name, as the language spells it
|
|
105
|
+
# The verb's language name, `Chapter::Aggregate[.Entity].Verb`, for help text.
|
|
206
106
|
def fqn(bluebook, aggregate, verb, entity = nil)
|
|
207
107
|
[bluebook.name, "::", aggregate.hecks_name, ".",
|
|
208
108
|
entity ? "#{entity.hecks_name}." : "", verb.hecks_name].join
|
|
209
109
|
end
|
|
210
110
|
|
|
211
|
-
# The
|
|
212
|
-
# `
|
|
213
|
-
# aggregate
|
|
214
|
-
# `:entity` or `nil`, because a port is declared on an aggregate, never
|
|
215
|
-
# an entity), and this is exactly the same "how do I name the record"
|
|
216
|
-
# question a command with a receiver already answers, so it is answered
|
|
217
|
-
# once, here, rather than duplicated.
|
|
218
|
-
#
|
|
219
|
-
# `nil` — a creating command — takes none: there is no existing record
|
|
220
|
-
# yet for `to=` to name.
|
|
221
|
-
#
|
|
222
|
-
# @param receiver [Symbol, nil] `:entity`, `:aggregate`, or `nil` for a creating
|
|
223
|
-
# command that names no existing record
|
|
224
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
|
|
225
|
-
# @param entity [Bluebook::Entity, nil] the entity to name, required when
|
|
226
|
-
# `receiver` is `:entity`
|
|
227
|
-
# @return [Array<Hash{Symbol => Object}>] the `to`/`to.aggregate`+`to.entity` option
|
|
228
|
-
# specs this receiver needs, or `[]` for `nil`
|
|
111
|
+
# The options that name the record a verb acts on, before its own arguments.
|
|
112
|
+
# `nil` (a creating command) takes none: there is no record yet.
|
|
113
|
+
# Ports always pass `:aggregate`, since a port is declared on an aggregate.
|
|
229
114
|
def receiver_options(receiver, aggregate, entity)
|
|
230
115
|
case receiver
|
|
231
116
|
when :entity
|
|
@@ -243,20 +128,6 @@ module Hecks
|
|
|
243
128
|
end
|
|
244
129
|
end
|
|
245
130
|
|
|
246
|
-
# ── one verb ──────────────────────────────────────────────────────
|
|
247
|
-
|
|
248
|
-
# Projects one command into the spec `verb_help` and `CliRunner` both read.
|
|
249
|
-
#
|
|
250
|
-
# @param bluebook [Bluebook::Chapter] the command's owning chapter
|
|
251
|
-
# @param aggregate [Bluebook::Aggregate] the command's owning aggregate
|
|
252
|
-
# @param entity [Bluebook::Entity, nil] the entity the command is declared on, or
|
|
253
|
-
# `nil` for an aggregate-level command
|
|
254
|
-
# @param command [Bluebook::Command] the command being projected
|
|
255
|
-
# @return [Hash{Symbol => Object}] `:verb` (fully-qualified name), `:kind` (`:command`),
|
|
256
|
-
# `:summary`, `:role` (String, nil if the command declares none), `:role_gated`
|
|
257
|
-
# (Boolean), `:creates` (Boolean), `:receiver` (`:entity`, `:aggregate` or `nil`),
|
|
258
|
-
# `:legacy_receiver` (`:id` or `nil`), `:legacy_arguments`, `:refusals`
|
|
259
|
-
# (see `refusals`) and `:arguments` (see `options_for`)
|
|
260
131
|
def command_spec(bluebook, aggregate, entity, command)
|
|
261
132
|
holder = entity || aggregate
|
|
262
133
|
arguments = command.attributes.flat_map { |a| options_for(a, holder, aggregate) }
|
|
@@ -266,16 +137,9 @@ module Hecks
|
|
|
266
137
|
(command.creates? ? nil : :aggregate)
|
|
267
138
|
end
|
|
268
139
|
|
|
269
|
-
# The receiver
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
# projected option list makes the human-facing request complete while
|
|
273
|
-
# CommandRequest can remove them before it builds with: from the
|
|
274
|
-
# command's declared facts.
|
|
275
|
-
#
|
|
276
|
-
# Existing aggregate scripts may still spell the receiver id=... .
|
|
277
|
-
# That alias is deliberately hidden from help and recorded separately
|
|
278
|
-
# as legacy_arguments; new help and examples teach only to=... .
|
|
140
|
+
# The receiver paths stay in the projected option list so the request is complete;
|
|
141
|
+
# CommandRequest strips them before building `with:`. `id=...` is still accepted
|
|
142
|
+
# for aggregate receivers but hidden from help, which teaches only `to=...`.
|
|
279
143
|
arguments = receiver_options(receiver, aggregate, entity) + arguments
|
|
280
144
|
legacy_arguments = receiver == :aggregate ? [{ path: "id", type: "String", required: true }] : []
|
|
281
145
|
|
|
@@ -287,52 +151,18 @@ module Hecks
|
|
|
287
151
|
refusals: refusals(command, holder), arguments: arguments }
|
|
288
152
|
end
|
|
289
153
|
|
|
290
|
-
# A port operation reads as a verb but reports as a boundary
|
|
291
|
-
#
|
|
292
|
-
# `creates: false` because it makes no record, and `refusals: []`
|
|
293
|
-
# because it has none in the sense every other verb means: a command's
|
|
294
|
-
# refusals are sentences the chapter will say back to you, and an
|
|
295
|
-
# outbound operation's failure is somebody else's sentence, unknowable
|
|
296
|
-
# from here.
|
|
297
|
-
#
|
|
298
|
-
# The summary names both endings, which is the one thing a caller most
|
|
299
|
-
# needs and cannot infer. `run_specs` looks like it either works or
|
|
300
|
-
# errors; what it actually does is answer `SpecsCompleted` even when the
|
|
301
|
-
# suite is red, and refuse only when rspec could not run. Somebody
|
|
302
|
-
# reading `--help` should not have to open the hecksagon to find that
|
|
303
|
-
# out.
|
|
304
|
-
#
|
|
305
|
-
# @param bluebook [Bluebook::Chapter] the port's owning chapter
|
|
306
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the port is declared on
|
|
307
|
-
# @param port [Bluebook::DomainPort] the port the operation is declared on
|
|
308
|
-
# @param operation [Bluebook::PortOperation] the operation being projected
|
|
309
|
-
# @return [Hash{Symbol => Object}] `:verb` (the `Aggregate.port.operation` name),
|
|
310
|
-
# `:kind` (`:command`), `:creates` (`false`), `:receiver` (`:aggregate`),
|
|
311
|
-
# `:refusals` (`[]`), `:role` (String, who calls whom through the port),
|
|
312
|
-
# `:role_gated` (`false`), `:summary` (see `port_summary`) and `:arguments`
|
|
313
|
-
# (see `options_for`)
|
|
154
|
+
# A port operation reads as a verb but reports as a boundary: it creates no
|
|
155
|
+
# record, and an outbound failure is the other side's sentence, so `refusals` is empty.
|
|
314
156
|
def port_spec(bluebook, aggregate, port, operation)
|
|
315
157
|
arguments = receiver_options(:aggregate, aggregate, nil) +
|
|
316
158
|
operation.attributes.flat_map { |a| options_for(a, aggregate, aggregate) }
|
|
317
159
|
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
# `Dispatcher#dispatch` splits a verb into head and sub and looks the
|
|
321
|
-
# head up as a port, so a port operation is addressed
|
|
322
|
-
# `Aggregate.Port.Operation` — three parts where a command has two.
|
|
323
|
-
# But nobody should have to type `sweep.toolchain.run_specs`: which
|
|
324
|
-
# port a verb goes out through is wiring, and the caller's business is
|
|
325
|
-
# what they want done. So the projection spells the verb in full and
|
|
326
|
-
# names it short, which is the same split `shorten` already makes.
|
|
160
|
+
# `Dispatcher#dispatch` looks the head up as a port, so the wire name is
|
|
161
|
+
# `Aggregate.Port.Operation`; the short name (see `shorten`) hides the port.
|
|
327
162
|
{ verb: [fqn(bluebook, aggregate, operation).sub(/\.[^.]+\z/, ""), port.name, operation.hecks_name].join("."),
|
|
328
163
|
kind: :command, creates: false, receiver: :aggregate, refusals: [],
|
|
329
|
-
# `role:`
|
|
330
|
-
#
|
|
331
|
-
# "issued by" line. A port operation never reaches
|
|
332
|
-
# `CommandRules::Authorization#refuse_role_mismatch` (only
|
|
333
|
-
# `CommandInterpreter`/`EntityInterpreter` call it, never the port
|
|
334
|
-
# dispatch path), so `role_gated: false` always, unlike
|
|
335
|
-
# `command_spec` where the same key name means a real one.
|
|
164
|
+
# `role:` is help text only; port dispatch never reaches the role check,
|
|
165
|
+
# so `role_gated` is always false here.
|
|
336
166
|
role: if operation.outbound?
|
|
337
167
|
"#{aggregate.hecks_name} asking #{port.name}"
|
|
338
168
|
else
|
|
@@ -342,26 +172,16 @@ module Hecks
|
|
|
342
172
|
summary: port_summary(port, operation), arguments: arguments }
|
|
343
173
|
end
|
|
344
174
|
|
|
345
|
-
# Names both endings a port operation
|
|
346
|
-
#
|
|
347
|
-
#
|
|
348
|
-
# @param port [Bluebook::DomainPort] the operation's owning port
|
|
349
|
-
# @param operation [Bluebook::PortOperation] the operation being summarized
|
|
350
|
-
# @return [String] one line, worded for `--help`
|
|
175
|
+
# Names both endings of a port operation, since `--help` is where a caller
|
|
176
|
+
# learns that e.g. a spec run answers `SpecsCompleted` even when the suite is red.
|
|
351
177
|
def port_summary(port, operation)
|
|
352
178
|
return "#{port.name} reports it; emits #{operation.emits.join(', ')}" unless operation.outbound?
|
|
353
179
|
|
|
354
180
|
"Ask #{port.name} — answers #{operation.answers}, refuses #{operation.refuses}"
|
|
355
181
|
end
|
|
356
182
|
|
|
357
|
-
# A rootless report takes nothing; a rooted one takes the id of the
|
|
358
|
-
#
|
|
359
|
-
#
|
|
360
|
-
# @param bluebook [Bluebook::Chapter] the report's owning chapter
|
|
361
|
-
# @param model [Bluebook::ReadModel] the report being projected
|
|
362
|
-
# @return [Hash{Symbol => Object}] `:verb` (`Chapter.ReportName`), `:kind`
|
|
363
|
-
# (`:query`), `:summary` and `:arguments` (`[]`, or one required id option
|
|
364
|
-
# named after the model's `reference_name` when it declares a `reference_target`)
|
|
183
|
+
# A rootless report takes nothing; a rooted one takes the id of the record it
|
|
184
|
+
# is a view of, under the name the model gave that reference.
|
|
365
185
|
def report_spec(bluebook, model)
|
|
366
186
|
arguments =
|
|
367
187
|
if model.reference_target
|
|
@@ -375,15 +195,6 @@ module Hecks
|
|
|
375
195
|
summary: model.description, arguments: arguments }
|
|
376
196
|
end
|
|
377
197
|
|
|
378
|
-
# Projects one query into the spec `verb_help` and `CliRunner` both read.
|
|
379
|
-
#
|
|
380
|
-
# @param bluebook [Bluebook::Chapter] the query's owning chapter
|
|
381
|
-
# @param aggregate [Bluebook::Aggregate] the query's owning aggregate
|
|
382
|
-
# @param entity [Bluebook::Entity, nil] the entity the query is declared on, or
|
|
383
|
-
# `nil` for an aggregate-level query
|
|
384
|
-
# @param query [Bluebook::Query] the query being projected
|
|
385
|
-
# @return [Hash{Symbol => Object}] `:verb` (fully-qualified name), `:kind`
|
|
386
|
-
# (`:query`), `:summary` and `:arguments` (see `options_for`)
|
|
387
198
|
def query_spec(bluebook, aggregate, entity, query)
|
|
388
199
|
arguments = Array(query.to_h[:attributes]).flat_map do |declared|
|
|
389
200
|
attribute = query.attributes.find { |a| a.name.to_s == declared[:name].to_s }
|
|
@@ -394,35 +205,16 @@ module Hecks
|
|
|
394
205
|
summary: query.description, arguments: arguments }
|
|
395
206
|
end
|
|
396
207
|
|
|
397
|
-
#
|
|
398
|
-
|
|
399
|
-
#
|
|
400
|
-
# `CommitRef` is `--commit.value`, because that is the shape the runtime
|
|
401
|
-
# wants and a flat `--commit` would have to guess which field it meant.
|
|
402
|
-
# Single-field value objects — almost all of them — read fine either way,
|
|
403
|
-
# and the runner accepts the short form for exactly those.
|
|
404
|
-
# Recursive, and it has to be. A value object may hold another one —
|
|
405
|
-
# pizzas' `Pizza` holds a `Price` and a `Size` — so stopping after one
|
|
406
|
-
# level produced `pizza.price_cents=1500` and sent the string "1500"
|
|
407
|
-
# where `{ cents: 1500 }` belonged.
|
|
408
|
-
#
|
|
409
|
-
# The runtime took it. `qa/FINDINGS.md` #2 is exactly that gap —
|
|
410
|
-
# `Value::Coercion.build` does not validate nested value objects — so a
|
|
411
|
-
# one-level CLI is not merely inconvenient, it is a machine for writing
|
|
412
|
-
# malformed records into a real store, which is what it did on its first
|
|
413
|
-
# run against the pizzas database.
|
|
208
|
+
# Flattens an attribute into one option per leaf field. A value object becomes
|
|
209
|
+
# dotted options (`--commit.value`), recursing into nested value objects so
|
|
210
|
+
# `{ cents: 1500 }` is never sent as the string "1500".
|
|
414
211
|
#
|
|
415
212
|
# @param attribute [Bluebook::Attribute] the field being projected
|
|
416
|
-
# @param holder [Bluebook::Aggregate, Bluebook::Entity, Class]
|
|
417
|
-
# `attribute` is declared on; a `Bluebook::ValueObject` subclass on a recursive call
|
|
213
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity, Class] what declares `attribute`
|
|
418
214
|
# @param aggregate [Bluebook::Aggregate] the top-level aggregate, kept across recursion
|
|
419
|
-
#
|
|
420
|
-
# @param
|
|
421
|
-
# @
|
|
422
|
-
# optional; `nil` defers to `attribute.optional?`
|
|
423
|
-
# @return [Array<Hash{Symbol => Object}>] one option spec per leaf field, each with
|
|
424
|
-
# `:path`, `:type`, `:required`, and optionally `:note`, `:enum`, `:pattern`,
|
|
425
|
-
# `:default`, `:list`
|
|
215
|
+
# @param prefix [String, nil] the dotted path built so far
|
|
216
|
+
# @param optional [Boolean, nil] whether an enclosing field already makes this one optional
|
|
217
|
+
# @return [Array<Hash{Symbol => Object}>] one option spec per leaf field
|
|
426
218
|
def options_for(attribute, holder, aggregate, prefix = nil, optional = nil)
|
|
427
219
|
path = [prefix, attribute.name].compact.join(".")
|
|
428
220
|
optional ||= attribute.optional?
|
|
@@ -431,19 +223,8 @@ module Hecks
|
|
|
431
223
|
value_object = value_object_for(attribute, holder, aggregate)
|
|
432
224
|
return [scalar_option(path, attribute, optional)] unless value_object
|
|
433
225
|
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
# Without this a `list_of(Tag)` projected exactly like a single Tag:
|
|
437
|
-
# one option, `tags.value`, indistinguishable from a scalar. So the
|
|
438
|
-
# help said to pass one, `CliDoor#bury` overwrote the leaf each time,
|
|
439
|
-
# and passing two tags stored the second and lost the first without
|
|
440
|
-
# saying anything. A missing argument is refused loudly; a forgotten
|
|
441
|
-
# one is not, which makes it the more expensive of the two by far.
|
|
442
|
-
#
|
|
443
|
-
# The flag is carried on the leaf rather than kept beside the
|
|
444
|
-
# attribute because the leaf is all `CliDoor` ever sees — it is handed
|
|
445
|
-
# a path and a spec, and reuniting them with the attribute that
|
|
446
|
-
# produced them would be a lookup that exists only to answer this.
|
|
226
|
+
# The list flag rides on each leaf: without it a repeated flag overwrote the
|
|
227
|
+
# leaf silently, and CliDoor only ever sees a path and a spec.
|
|
447
228
|
fields = value_object.attributes.flat_map do |field|
|
|
448
229
|
nested = value_object_for(field, value_object, aggregate)
|
|
449
230
|
next options_for(field, value_object, aggregate, path, optional) if nested
|
|
@@ -457,27 +238,11 @@ module Hecks
|
|
|
457
238
|
fields.map { |option| option.merge(list: true, note: [option[:note], "repeatable"].compact.join("; ")) }
|
|
458
239
|
end
|
|
459
240
|
|
|
460
|
-
# Projects a `reference_to` attribute as the id of the record it points at.
|
|
461
|
-
#
|
|
462
|
-
# @param attribute [Bluebook::Attribute] a `reference?` attribute
|
|
463
|
-
# @return [Hash{Symbol => Object}] `:path`, `:type` (`"String"`), `:required` and
|
|
464
|
-
# `:note`
|
|
465
241
|
def reference_option(attribute)
|
|
466
242
|
{ path: attribute.name.to_s, type: "String", required: !attribute.optional?,
|
|
467
243
|
note: "id of a #{attribute.type.target_name}" }
|
|
468
244
|
end
|
|
469
245
|
|
|
470
|
-
# Projects one leaf field — a plain attribute, or one field of a flattened
|
|
471
|
-
# value object.
|
|
472
|
-
#
|
|
473
|
-
# @param path [String] the dotted option path this field is reached at
|
|
474
|
-
# @param field [Bluebook::Attribute] the field being projected
|
|
475
|
-
# @param optional [Boolean] whether this option may be omitted
|
|
476
|
-
# @param enum [Array<Object>] the closed set of values this field admits, `[]` for
|
|
477
|
-
# an open field
|
|
478
|
-
# @return [Hash{Symbol => Object}] `:path`, `:type`, `:required`, plus `:enum` when
|
|
479
|
-
# `enum` is non-empty, `:pattern` when `field` declares one, and `:default` when
|
|
480
|
-
# `field` declares a non-nil one
|
|
481
246
|
def scalar_option(path, field, optional, enum: [])
|
|
482
247
|
option = { path: path, type: field.type.to_s, required: !optional }
|
|
483
248
|
option[:enum] = enum unless enum.empty?
|
|
@@ -486,30 +251,15 @@ module Hecks
|
|
|
486
251
|
option
|
|
487
252
|
end
|
|
488
253
|
|
|
489
|
-
# The values a `one_of` value object's field is closed to, for
|
|
490
|
-
# `:enum`.
|
|
491
|
-
#
|
|
492
|
-
# @param value_object [Class] a `Bluebook::ValueObject` subclass
|
|
493
|
-
# @param field [Bluebook::Attribute] the value object's field being projected
|
|
494
|
-
# @return [Array<Object>] the distinct values every declared member gives this
|
|
495
|
-
# field, or `[]` when the value object declares no closed set
|
|
254
|
+
# The values a `one_of` value object's field is closed to, or `[]` for an open one.
|
|
496
255
|
def closed_members(value_object, field)
|
|
497
256
|
return [] unless value_object.closed_set?
|
|
498
257
|
|
|
499
258
|
value_object.members.filter_map { |member| member[field.name] }.uniq
|
|
500
259
|
end
|
|
501
260
|
|
|
502
|
-
#
|
|
503
|
-
# `
|
|
504
|
-
# field).
|
|
505
|
-
#
|
|
506
|
-
# @param attribute [Bluebook::Attribute] the field whose type is being resolved
|
|
507
|
-
# @param holder [Bluebook::Aggregate, Bluebook::Entity, Class, nil] the construct
|
|
508
|
-
# `attribute` is declared on, searched first
|
|
509
|
-
# @param aggregate [Bluebook::Aggregate, nil] the top-level aggregate, searched when
|
|
510
|
-
# `holder` does not declare the value object itself
|
|
511
|
-
# @return [Class, nil] the matching `Bluebook::ValueObject` subclass, or `nil` if
|
|
512
|
-
# `attribute`'s type names no value object in scope
|
|
261
|
+
# The `Bluebook::ValueObject` subclass an attribute's type names, searching
|
|
262
|
+
# `holder` then `aggregate`, or `nil` for a plain scalar.
|
|
513
263
|
def value_object_for(attribute, holder, aggregate)
|
|
514
264
|
[holder, aggregate].compact.each do |scope|
|
|
515
265
|
next unless scope.respond_to?(:value_objects)
|
|
@@ -520,15 +270,8 @@ module Hecks
|
|
|
520
270
|
nil
|
|
521
271
|
end
|
|
522
272
|
|
|
523
|
-
# Every way
|
|
524
|
-
#
|
|
525
|
-
#
|
|
526
|
-
# @param command [Bluebook::Command] the command whose refusals are being projected
|
|
527
|
-
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the aggregate or entity the
|
|
528
|
-
# command's lifecycle guard, if any, is checked against
|
|
529
|
-
# @return [Array<String>] one sentence per way the command can refuse: a lifecycle
|
|
530
|
-
# state mismatch, a missing referenced record per `reference_to` attribute, and
|
|
531
|
-
# each declared `given`'s description, in that order
|
|
273
|
+
# Every way the verb can say no, in the chapter's own words: a lifecycle state
|
|
274
|
+
# mismatch, a missing referenced record per `reference_to`, then each `given`.
|
|
532
275
|
def refusals(command, holder)
|
|
533
276
|
out = []
|
|
534
277
|
lifecycle = holder.lifecycle
|
|
@@ -540,25 +283,14 @@ module Hecks
|
|
|
540
283
|
out + command.givens.map(&:description)
|
|
541
284
|
end
|
|
542
285
|
|
|
543
|
-
# ── the help ──────────────────────────────────────────────────────
|
|
544
|
-
|
|
545
286
|
# Renders the full verb/question table, or one verb's `--help` text when
|
|
546
287
|
# `options[:verb]` names one.
|
|
547
|
-
#
|
|
548
|
-
# @param bluebook [Bluebook::Chapter] the projected chapter, for its name and vision
|
|
549
|
-
# line
|
|
550
|
-
# @param verbs [Hash{String => Hash}] the projected, `shorten`ed command map
|
|
551
|
-
# @param questions [Hash{String => Hash}] the projected, `shorten`ed query map
|
|
552
|
-
# @param options [Hash{Symbol => Object}] `:program` (String), `:verb` (String, nil)
|
|
553
|
-
# and `:ask` (Boolean) — see `call`
|
|
554
|
-
# @return [String] the rendered help text
|
|
555
288
|
def usage(bluebook, verbs, questions, options)
|
|
556
289
|
program = options[:program] || "bin/run"
|
|
557
290
|
only = options[:verb]
|
|
558
291
|
|
|
559
|
-
#
|
|
560
|
-
#
|
|
561
|
-
# shares its name, which banking has and which is how this was found.
|
|
292
|
+
# `options[:ask]` picks the namespace when both hold the name; without it a
|
|
293
|
+
# question's `--help` would print the command sharing its name.
|
|
562
294
|
if only
|
|
563
295
|
pool = options[:ask] ? questions : verbs
|
|
564
296
|
key = aliases(pool)[only] || (options[:ask] ? nil : aliases(questions)[only])
|
|
@@ -582,40 +314,19 @@ module Hecks
|
|
|
582
314
|
out.join("\n")
|
|
583
315
|
end
|
|
584
316
|
|
|
585
|
-
#
|
|
586
|
-
# caller both forms exist.
|
|
587
|
-
#
|
|
588
|
-
# @param verbs [Hash{String => Hash}] the projected, `shorten`ed command map
|
|
589
|
-
# @return [String] `"<short> is also <full>"`, or `""` if every verb's short form
|
|
590
|
-
# is its full name (a one-aggregate domain, where no name ever collides)
|
|
317
|
+
# One verb whose full spelling differs from its short one, or `""` when none does.
|
|
591
318
|
def example_qualified(verbs)
|
|
592
319
|
name, spec = verbs.find { |key, value| key != value[:short] } || verbs.first
|
|
593
320
|
name ? "#{spec[:short]} is also #{name}" : ""
|
|
594
321
|
end
|
|
595
322
|
|
|
596
|
-
#
|
|
597
|
-
# the list is worth reading. A verb table wants the first sentence of
|
|
598
|
-
# that argument; `--help` still prints the whole thing.
|
|
599
|
-
#
|
|
600
|
-
# @param text [String, nil] the full description, or `nil`
|
|
601
|
-
# @return [String] the text up to and including its first `.`, or `""` for `nil`
|
|
323
|
+
# The verb table wants a query description's first sentence; `--help` prints all.
|
|
602
324
|
def first_sentence(text)
|
|
603
325
|
text.to_s.split(/(?<=\.)\s/).first.to_s
|
|
604
326
|
end
|
|
605
327
|
|
|
606
|
-
# Four
|
|
607
|
-
#
|
|
608
|
-
# others' content (only the output order is fixed, and stays fixed
|
|
609
|
-
# below), so each is its own method returning the lines it
|
|
610
|
-
# contributes — `[]` when it contributes none — concatenated in the
|
|
611
|
-
# same order the original inline version built them in.
|
|
612
|
-
#
|
|
613
|
-
# @param program [String] how the caller was invoked, echoed in the invocation line
|
|
614
|
-
# @param name [String] the verb's short display name
|
|
615
|
-
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec (see
|
|
616
|
-
# `command_spec`, `port_spec`, `report_spec` or `query_spec`)
|
|
617
|
-
# @param ask [Boolean] whether this is a question, reached through `ask`
|
|
618
|
-
# @return [String] the verb's full `--help` text
|
|
328
|
+
# Four independent blocks, concatenated in fixed order: meta, invocation,
|
|
329
|
+
# arguments, refusals.
|
|
619
330
|
def verb_help(program, name, spec, ask: false)
|
|
620
331
|
out = verb_help_meta_lines(name, spec)
|
|
621
332
|
out.concat(verb_help_invocation_lines(program, name, spec, ask))
|
|
@@ -624,12 +335,6 @@ module Hecks
|
|
|
624
335
|
out.join("\n")
|
|
625
336
|
end
|
|
626
337
|
|
|
627
|
-
# Renders the name/summary heading, plus the dispatched or read verb and issuing
|
|
628
|
-
# role when the spec declares them.
|
|
629
|
-
#
|
|
630
|
-
# @param name [String] the verb's short display name
|
|
631
|
-
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec
|
|
632
|
-
# @return [Array<String>] the meta block's lines
|
|
633
338
|
def verb_help_meta_lines(name, spec)
|
|
634
339
|
out = ["#{name} — #{spec[:summary]}", ""]
|
|
635
340
|
out << "dispatches #{spec[:verb]}" if spec[:kind] == :command
|
|
@@ -638,26 +343,11 @@ module Hecks
|
|
|
638
343
|
out
|
|
639
344
|
end
|
|
640
345
|
|
|
641
|
-
# Renders the one-line example invocation, with every argument's path as a
|
|
642
|
-
# placeholder.
|
|
643
|
-
#
|
|
644
|
-
# @param program [String] how the caller was invoked
|
|
645
|
-
# @param name [String] the verb's short display name
|
|
646
|
-
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
|
|
647
|
-
# for `:arguments`
|
|
648
|
-
# @param ask [Boolean] whether to show the `ask` form of the invocation
|
|
649
|
-
# @return [Array<String>] the invocation block's lines
|
|
650
346
|
def verb_help_invocation_lines(program, name, spec, ask)
|
|
651
347
|
invocation = ask ? "#{program} ask #{name}" : "#{program} #{name}"
|
|
652
348
|
["", " #{invocation}#{spec[:arguments].map { |a| " #{a[:path]}=…" }.join}", ""]
|
|
653
349
|
end
|
|
654
350
|
|
|
655
|
-
# Renders one line per declared argument: its type, admitted values, pattern,
|
|
656
|
-
# default and whether it is optional.
|
|
657
|
-
#
|
|
658
|
-
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
|
|
659
|
-
# for `:arguments`
|
|
660
|
-
# @return [Array<String>] the argument block's lines, `[]` if the verb takes none
|
|
661
351
|
def verb_help_argument_lines(spec)
|
|
662
352
|
return [] if spec[:arguments].empty?
|
|
663
353
|
|
|
@@ -675,11 +365,6 @@ module Hecks
|
|
|
675
365
|
lines << ""
|
|
676
366
|
end
|
|
677
367
|
|
|
678
|
-
# Renders the "refused when:" block, one line per way the verb can say no.
|
|
679
|
-
#
|
|
680
|
-
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
|
|
681
|
-
# for `:refusals`
|
|
682
|
-
# @return [Array<String>] the refusal block's lines, `[]` if the verb declares none
|
|
683
368
|
def verb_help_refusal_lines(spec)
|
|
684
369
|
return [] if Array(spec[:refusals]).empty?
|
|
685
370
|
|