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
data/lib/hecks/forms/params.rb
CHANGED
|
@@ -4,38 +4,19 @@ require_relative "field_renderer"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Forms
|
|
7
|
-
#
|
|
8
|
-
# (`{
|
|
9
|
-
# off a POST form body or a GET query string — Rack hands back the same
|
|
10
|
-
# flat shape for both as long as nothing uses `[]` bracket names) and the
|
|
11
|
-
# nested, typed hash `Dispatcher#dispatch`/`#query` actually take
|
|
12
|
-
# (`{amount: {cents: 1050, currency: "USD"}}`).
|
|
7
|
+
# Converts between flat dotted web params (`{"amount.cents"=>"1050"}`) and the nested
|
|
8
|
+
# hash of typed values a dispatch takes (`{amount: {cents: 1050}}`).
|
|
13
9
|
#
|
|
14
|
-
# `extract` needs the Field tree
|
|
15
|
-
#
|
|
16
|
-
# requires an actual `Integer`/`Float`, not a String that merely looks
|
|
17
|
-
# like one (`given.is_a?(expected)`, no coercion attempted there); a web
|
|
18
|
-
# form can only ever hand back strings, so the cast has to happen here,
|
|
19
|
-
# once, using the same shape `FieldShape` already resolved for
|
|
20
|
-
# rendering the input in the first place — one reading of the IR, not
|
|
21
|
-
# two that could disagree.
|
|
10
|
+
# `extract` needs the Field tree because the runtime's numeric check rejects
|
|
11
|
+
# numeric-looking Strings, so the cast happens here.
|
|
22
12
|
module Params
|
|
23
13
|
# Casts a flat, all-strings web payload into the nested, typed arguments a command or
|
|
24
14
|
# query takes, guided by the field tree the form was rendered from.
|
|
25
15
|
#
|
|
26
|
-
#
|
|
27
|
-
# deep `FieldShape` nested it to get there — a single-attribute value
|
|
28
|
-
# object unwraps to a leaf sitting at the top of the fields array with
|
|
29
|
-
# a two-segment path (`"reference.value"`), the exact same shape a
|
|
30
|
-
# `:group`'s own child carries. So this collects every leaf as
|
|
31
|
-
# (full path -> value) first, and nests by the path's own segments
|
|
32
|
-
# last — one nesting rule, blind to how a field arrived at its path.
|
|
33
|
-
#
|
|
34
|
-
# @param fields [Array<Forms::Field>] the resolved field tree the payload was posted
|
|
35
|
-
# against
|
|
16
|
+
# @param fields [Array<Forms::Field>] the resolved field tree the payload was posted against
|
|
36
17
|
# @param raw [Hash{String => String}] the flat web payload, keyed by dotted field path
|
|
37
|
-
# @return [Hash{Symbol => Object}] nested, typed arguments
|
|
38
|
-
#
|
|
18
|
+
# @return [Hash{Symbol => Object}] nested, typed arguments ready to splat into a dispatch;
|
|
19
|
+
# a blank optional field is left out
|
|
39
20
|
# @raise [ArgumentError] if a number field's text is not numeric, or two fields collide
|
|
40
21
|
# at one path (see `nest`)
|
|
41
22
|
# @raise [TypeError] if a required number field is missing from `raw`
|
|
@@ -68,23 +49,8 @@ module Hecks
|
|
|
68
49
|
# Nests values keyed by dotted path into a Hash keyed by path segment, refusing two
|
|
69
50
|
# paths that cannot share one result.
|
|
70
51
|
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
# and the parent of a nested group — the two can never coexist in
|
|
74
|
-
# the same result hash. Depending on which pair `each_with_object`
|
|
75
|
-
# reaches first, an unguarded walk fails in one of two ways: a scalar
|
|
76
|
-
# planted first leaves `acc[segment] ||= {}` seeing a truthy non-Hash
|
|
77
|
-
# and reusing it as `node`, so the next `node[leaf] = value` blows up
|
|
78
|
-
# with a raw `TypeError` from calling `String#[]=` with a Symbol key;
|
|
79
|
-
# a scalar planted after the nested group instead sails through
|
|
80
|
-
# `node[leaf] = value` and silently clobbers the entire nested hash
|
|
81
|
-
# with the scalar, losing every sibling under it with no error at
|
|
82
|
-
# all. Both directions are checked explicitly here so either order
|
|
83
|
-
# raises the same clear `ArgumentError` instead of a confusing crash
|
|
84
|
-
# or silent data loss — this is the family of error every
|
|
85
|
-
# command/query submission path in app.rb already rescues into a 422
|
|
86
|
-
# (`ArgumentError` sits right alongside the domain refusals in every
|
|
87
|
-
# one of those rescue clauses).
|
|
52
|
+
# "price" and "price.cents" cannot coexist. Both orders are checked so a collision
|
|
53
|
+
# raises ArgumentError (rescued into a 422) rather than a TypeError or silent data loss.
|
|
88
54
|
#
|
|
89
55
|
# @param pairs [Hash{String => Object}] values keyed by dotted path, such as
|
|
90
56
|
# `{"amount.cents" => 1050}`
|
|
@@ -121,11 +87,8 @@ module Hecks
|
|
|
121
87
|
|
|
122
88
|
# Reads a `:list` field's textarea into an Array, one element per non-blank line.
|
|
123
89
|
#
|
|
124
|
-
# One line
|
|
125
|
-
#
|
|
126
|
-
# is read as JSON on that one line — the honest fallback documented in
|
|
127
|
-
# docs/command-form-and-query-form-bluebook.md rather than a second
|
|
128
|
-
# widget this prototype doesn't build yet.
|
|
90
|
+
# One textarea line per element; a multi-attribute value object element is read as
|
|
91
|
+
# JSON on its line.
|
|
129
92
|
#
|
|
130
93
|
# @param field [Forms::Field] a `:list` field whose first child describes one element
|
|
131
94
|
# @param raw [Hash{String => String}] the flat web payload, keyed by dotted field path
|
|
@@ -191,13 +154,8 @@ module Hecks
|
|
|
191
154
|
# Flattens held values back into the dotted-path, all-strings pairs a query string
|
|
192
155
|
# carries — the reverse of `extract`.
|
|
193
156
|
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
# "shareable link" and its filter form stay two renderings of the
|
|
197
|
-
# same data rather than two formats that can drift. Reads with
|
|
198
|
-
# `FieldRenderer.dig` — the identical full-path lookup a re-rendered
|
|
199
|
-
# input's own value comes from — for the same reason `extract` above
|
|
200
|
-
# nests by full path rather than by tree shape.
|
|
157
|
+
# Reads with `FieldRenderer.dig`, the same full-path lookup a re-rendered input uses,
|
|
158
|
+
# so a shareable link and its filter form cannot drift.
|
|
201
159
|
#
|
|
202
160
|
# @param fields [Array<Forms::Field>] the field tree naming which paths to read
|
|
203
161
|
# @param values [Hash{String, Symbol => Object}] held values, flat or nested; see
|
|
@@ -221,11 +179,7 @@ module Hecks
|
|
|
221
179
|
# Lists every dotted path a field tree submits under, whether or not anything was
|
|
222
180
|
# entered for it.
|
|
223
181
|
#
|
|
224
|
-
#
|
|
225
|
-
# values — what `command_form_renderer.rb`'s inspect panel wants
|
|
226
|
-
# ("which fields does this command take"), where `flatten` above wants
|
|
227
|
-
# "what does this one submission look like" and returns nothing for a
|
|
228
|
-
# field nothing was entered for.
|
|
182
|
+
# Unlike `flatten`, this does not depend on values, so unset fields are listed too.
|
|
229
183
|
#
|
|
230
184
|
# @param fields [Array<Forms::Field>] the field tree to walk
|
|
231
185
|
# @param into [Array<String>] accumulator the paths are appended to in place
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Forms
|
|
3
|
-
# bin/present's
|
|
4
|
-
#
|
|
5
|
-
# boot. Two spellings the previous inline version got wrong:
|
|
6
|
-
#
|
|
7
|
-
# --port=8080 the equals form — the old `ARGV.each_cons(2)` scan
|
|
8
|
-
# only ever recognized "--port", "8080" as two
|
|
9
|
-
# separate argv entries, so this spelling matched
|
|
10
|
-
# nothing and silently fell through to the default.
|
|
11
|
-
# -p abc a non-numeric value — the old code did `.to_i` on
|
|
12
|
-
# whatever followed unconditionally, so a typo
|
|
13
|
-
# quietly became port 0 (Rackup/WEBrick's actual
|
|
14
|
-
# behavior for `Port: 0` is to bind an ephemeral
|
|
15
|
-
# port — arguably useful on purpose elsewhere, but
|
|
16
|
-
# never what a mistyped `-p abc` meant to ask for).
|
|
17
|
-
#
|
|
18
|
-
# Returns `[port, nil]` on a clean parse (falling back to `default`
|
|
19
|
-
# when neither spelling appears at all) or `[nil, message]` when an
|
|
20
|
-
# explicit port was given but isn't a real port number — the caller
|
|
21
|
-
# decides what to do with a refusal (bin/present aborts on it).
|
|
3
|
+
# Reads bin/present's `-p PORT` / `--port PORT` / `--port=PORT` argument.
|
|
4
|
+
# Returns `[port, nil]` or `[nil, message]`; a non-numeric port is refused, not read as 0.
|
|
22
5
|
module PortArgument
|
|
23
6
|
module_function
|
|
24
7
|
|
|
@@ -9,32 +9,10 @@ require_relative "record_table"
|
|
|
9
9
|
|
|
10
10
|
module Hecks
|
|
11
11
|
module Forms
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# bluebook.md): a canonical, shareable link (queries are GETs — every
|
|
15
|
-
# one of them is already a URL, so this says so up front rather than
|
|
16
|
-
# hiding that behind a form); a row of ready-made links for any
|
|
17
|
-
# closed-set parameter, since a caller filtering by an enum should not
|
|
18
|
-
# have to fill in a form to get a link they could just click; a filter
|
|
19
|
-
# form for everything else, which — being a GET form — produces
|
|
20
|
-
# exactly the same kind of link on submit; and the results table once
|
|
21
|
-
# a request actually supplies parameters.
|
|
12
|
+
# The GET view of a query: canonical link, quick links, filter form and results.
|
|
13
|
+
# See docs/command-form-and-query-form-bluebook.md.
|
|
22
14
|
module QueryFormRenderer
|
|
23
|
-
# Renders one query's whole GET view
|
|
24
|
-
# form, any error, results, and the inspect panel.
|
|
25
|
-
#
|
|
26
|
-
# @param registry [Runtime::Registry] the booted registry, for resolving reference
|
|
27
|
-
# fields' target aggregates
|
|
28
|
-
# @param domain [String] the owning chapter's name
|
|
29
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the query belongs to
|
|
30
|
-
# @param query [Bluebook::Query] the query being rendered
|
|
31
|
-
# @param action [String] the form's `action` URL and the canonical link's base
|
|
32
|
-
# @param params [Hash] the request's own query params, pre-filling the filter form
|
|
33
|
-
# @param results [Array<Hash>, nil] the query's own answer rows; `nil` renders no
|
|
34
|
-
# results section
|
|
35
|
-
# @param error [StandardError, nil] a raised error to show in the error banner;
|
|
36
|
-
# `nil` renders none
|
|
37
|
-
# @return [String] the page body markup
|
|
15
|
+
# Renders one query's whole GET view as the page body.
|
|
38
16
|
def self.render(registry:, domain:, aggregate:, query:, action:, params: {}, results: nil, error: nil)
|
|
39
17
|
fields = query.attributes.map { |a| FieldShape.resolve(a, aggregate: aggregate) }
|
|
40
18
|
reference_options = ReferenceOptions.collect(registry, domain, fields)
|
|
@@ -50,12 +28,6 @@ module Hecks
|
|
|
50
28
|
HTML
|
|
51
29
|
end
|
|
52
30
|
|
|
53
|
-
# Renders the query's title, description and badges.
|
|
54
|
-
#
|
|
55
|
-
# @param domain [String] the owning chapter's name
|
|
56
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the query belongs to
|
|
57
|
-
# @param query [Bluebook::Query] the query being rendered
|
|
58
|
-
# @return [String] the header markup
|
|
59
31
|
def self.header(domain, aggregate, query)
|
|
60
32
|
<<~HTML
|
|
61
33
|
<h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}")}</h1>
|
|
@@ -64,22 +36,12 @@ module Hecks
|
|
|
64
36
|
HTML
|
|
65
37
|
end
|
|
66
38
|
|
|
67
|
-
# Renders the query's declared badges — today, just its `limit` when it declares one.
|
|
68
|
-
#
|
|
69
|
-
# @param query [Bluebook::Query] the query being rendered
|
|
70
|
-
# @return [String] the badge markup; `""` when the query declares no `limit`
|
|
71
39
|
def self.badges(query)
|
|
72
40
|
parts = []
|
|
73
41
|
parts << %(<span class="badge">limit #{Escape.html(query.limit.to_h[:value])}</span>) if query.limit
|
|
74
42
|
parts.join
|
|
75
43
|
end
|
|
76
44
|
|
|
77
|
-
# Renders the query's plain-GET canonical link template, one `path={path}` per
|
|
78
|
-
# parameter.
|
|
79
|
-
#
|
|
80
|
-
# @param action [String] the query's base URL
|
|
81
|
-
# @param fields [Array<Forms::Field>] the resolved field tree naming the parameters
|
|
82
|
-
# @return [String] the help text and the templated link markup
|
|
83
45
|
def self.canonical_link(action, fields)
|
|
84
46
|
paths = Params.paths(fields)
|
|
85
47
|
template = paths.empty? ? action : "#{action}?#{paths.map { |path| "#{path}={#{path}}" }.join('&')}"
|
|
@@ -89,18 +51,7 @@ module Hecks
|
|
|
89
51
|
HTML
|
|
90
52
|
end
|
|
91
53
|
|
|
92
|
-
#
|
|
93
|
-
# clickable links — no form to fill in for "show me the suspended
|
|
94
|
-
# ones". Capped at the first closed-set field on purpose: a second one
|
|
95
|
-
# would mean a cross product of links, which reads as noise rather
|
|
96
|
-
# than help. A query with more than one is still fully reachable
|
|
97
|
-
# through the filter form below.
|
|
98
|
-
#
|
|
99
|
-
# @param action [String] the query's base URL
|
|
100
|
-
# @param fields [Array<Forms::Field>] the resolved field tree to search for a
|
|
101
|
-
# `:select` or `:radio` field
|
|
102
|
-
# @return [String] one clickable link per option of the first closed-set field
|
|
103
|
-
# found; `""` when no field is `:select` or `:radio`
|
|
54
|
+
# Capped at the first closed-set field: a second would mean a cross product of links.
|
|
104
55
|
def self.quick_links(action, fields)
|
|
105
56
|
field = fields.find { |f| %i[select radio].include?(f.kind) }
|
|
106
57
|
return "" unless field
|
|
@@ -112,14 +63,6 @@ module Hecks
|
|
|
112
63
|
%(<div class="example-links">#{links.join}</div>)
|
|
113
64
|
end
|
|
114
65
|
|
|
115
|
-
# Renders the GET filter form covering every declared parameter.
|
|
116
|
-
#
|
|
117
|
-
# @param action [String] the form's `action` URL
|
|
118
|
-
# @param fields [Array<Forms::Field>] the resolved field tree to render
|
|
119
|
-
# @param params [Hash] the request's own query params, pre-filling each field
|
|
120
|
-
# @param reference_options [Hash{String => Array<Array(String, String)>, nil}] each
|
|
121
|
-
# `:reference` field's own dropdown options, as `ReferenceOptions.collect` returns
|
|
122
|
-
# @return [String] the form markup; `""` when the query declares no parameters
|
|
123
66
|
def self.filter_form(action, fields, params, reference_options)
|
|
124
67
|
return "" if fields.empty?
|
|
125
68
|
|
|
@@ -131,10 +74,6 @@ module Hecks
|
|
|
131
74
|
HTML
|
|
132
75
|
end
|
|
133
76
|
|
|
134
|
-
# Renders a raised error as a dismissable-looking alert banner.
|
|
135
|
-
#
|
|
136
|
-
# @param error [StandardError, nil] the error to show
|
|
137
|
-
# @return [String] the banner markup; `""` when `error` is `nil`
|
|
138
77
|
def self.error_banner(error)
|
|
139
78
|
return "" unless error
|
|
140
79
|
|
|
@@ -142,26 +81,12 @@ module Hecks
|
|
|
142
81
|
"#{Escape.html(error.message)}</p></div>"
|
|
143
82
|
end
|
|
144
83
|
|
|
145
|
-
# Renders the results heading and table, once a request actually supplied
|
|
146
|
-
# parameters.
|
|
147
|
-
#
|
|
148
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate whose fields shape the table
|
|
149
|
-
# @param results [Array<Hash>, nil] the query's own answer rows
|
|
150
|
-
# @param domain [String] the owning chapter's name, for building record links
|
|
151
|
-
# @return [String] the results section markup; `""` when `results` is `nil`
|
|
152
84
|
def self.results_section(aggregate, results, domain)
|
|
153
85
|
return "" unless results
|
|
154
86
|
|
|
155
87
|
"<h2>Results (#{results.size})</h2>#{RecordTable.render(aggregate, results, domain: domain)}"
|
|
156
88
|
end
|
|
157
89
|
|
|
158
|
-
# Renders a collapsed panel with the query's own parameter list and raw IR JSON.
|
|
159
|
-
#
|
|
160
|
-
# @param domain [String] the owning chapter's name
|
|
161
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the query belongs to
|
|
162
|
-
# @param query [Bluebook::Query] the query being rendered
|
|
163
|
-
# @param fields [Array<Forms::Field>] the resolved field tree, for the parameter list
|
|
164
|
-
# @return [String] the `<details>` panel markup
|
|
165
90
|
def self.inspect_panel(domain, aggregate, query, fields)
|
|
166
91
|
verb = "#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}"
|
|
167
92
|
paths = Params.paths(fields)
|
|
@@ -4,19 +4,10 @@ require_relative "record_table"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Forms
|
|
7
|
-
# The
|
|
8
|
-
#
|
|
9
|
-
# state plus what it can legally do next. Neither is a `query` — they
|
|
10
|
-
# read the repository directly, the same way `AggregateDoor#all`/`#find`
|
|
11
|
-
# do (facade/surface/aggregate_door.rb) — so they exist for every
|
|
12
|
-
# aggregate whether or not its bluebook declared a query at all.
|
|
7
|
+
# The index and show pages every aggregate gets, whether or not it declares a query.
|
|
8
|
+
# They read the repository directly, like `AggregateDoor#all`/`#find`.
|
|
13
9
|
module RecordRenderer
|
|
14
|
-
# Renders the
|
|
15
|
-
#
|
|
16
|
-
# @param registry [Runtime::Registry] the booted registry holding the domain
|
|
17
|
-
# @param domain [String, Symbol] the domain name
|
|
18
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate whose records are listed
|
|
19
|
-
# @return [String] the index page's rendered HTML
|
|
10
|
+
# Renders the page listing every record of one aggregate.
|
|
20
11
|
def self.index(registry:, domain:, aggregate:)
|
|
21
12
|
instances = registry.repository(domain, aggregate).all
|
|
22
13
|
<<~HTML
|
|
@@ -29,12 +20,6 @@ module Hecks
|
|
|
29
20
|
HTML
|
|
30
21
|
end
|
|
31
22
|
|
|
32
|
-
# Renders the "+ Command" buttons for every creating command.
|
|
33
|
-
#
|
|
34
|
-
# @param domain [String, Symbol] the domain name
|
|
35
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate whose creating commands
|
|
36
|
-
# are linked
|
|
37
|
-
# @return [String] HTML action buttons, or `""` if the aggregate declares none
|
|
38
23
|
def self.creating_links(domain, aggregate)
|
|
39
24
|
creators = aggregate.commands.select(&:creates?)
|
|
40
25
|
return "" if creators.empty?
|
|
@@ -46,15 +31,8 @@ module Hecks
|
|
|
46
31
|
%(<div class="actions">#{links.join}</div>)
|
|
47
32
|
end
|
|
48
33
|
|
|
49
|
-
# Renders one record's state
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# @param registry [Runtime::Registry] the booted registry holding the domain
|
|
53
|
-
# @param domain [String, Symbol] the domain name
|
|
54
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
|
|
55
|
-
# @param id [String, Object] the record's identity
|
|
56
|
-
# @return [String, nil] the record page's rendered HTML, or nil if no record
|
|
57
|
-
# has that id
|
|
34
|
+
# Renders one record's state plus the commands and queries it can dispatch next.
|
|
35
|
+
# Returns nil when no record has that id.
|
|
58
36
|
def self.show(registry:, domain:, aggregate:, id:)
|
|
59
37
|
instance = registry.repository(domain, aggregate).find(id)
|
|
60
38
|
return nil unless instance
|
|
@@ -71,10 +49,6 @@ module Hecks
|
|
|
71
49
|
HTML
|
|
72
50
|
end
|
|
73
51
|
|
|
74
|
-
# Renders a record's state as an HTML field table.
|
|
75
|
-
#
|
|
76
|
-
# @param state [Hash{Symbol => Object}] the record's stored attribute values
|
|
77
|
-
# @return [String] an HTML table of the state's fields, values escaped
|
|
78
52
|
def self.state_table(state)
|
|
79
53
|
rows = state.map do |key, value|
|
|
80
54
|
"<tr><th>#{Escape.html(Humanize.label(key.to_s))}</th><td>#{Escape.html(render_value(value))}</td></tr>"
|
|
@@ -82,13 +56,7 @@ module Hecks
|
|
|
82
56
|
%(<div class="table-scroll"><table><tbody>#{rows.join}</tbody></table></div>)
|
|
83
57
|
end
|
|
84
58
|
|
|
85
|
-
#
|
|
86
|
-
# wherever its attribute is a value object, not a plain Hash (see
|
|
87
|
-
# record_table.rb's own note on the same read).
|
|
88
|
-
#
|
|
89
|
-
# @param value [Object] a stored field's value, possibly a `Runtime::Value`
|
|
90
|
-
# @return [String] the value rendered as text: a Hash as `"key: value, ..."`
|
|
91
|
-
# pairs, an Array `;`-joined, anything else its own `to_s`
|
|
59
|
+
# A stored field is a `Runtime::Value` wherever its attribute is a value object.
|
|
92
60
|
def self.render_value(value)
|
|
93
61
|
case (value = Runtime::Value.materialize(value))
|
|
94
62
|
when Hash then value.map { |k, v| "#{k}: #{render_value(v)}" }.join(", ")
|
|
@@ -97,27 +65,13 @@ module Hecks
|
|
|
97
65
|
end
|
|
98
66
|
end
|
|
99
67
|
|
|
100
|
-
#
|
|
101
|
-
# not apply from the record's current state — the same rule
|
|
102
|
-
# `Rules#admissible_transition` enforces at dispatch, read here so a
|
|
103
|
-
# link that would only refuse is never offered in the first place.
|
|
104
|
-
#
|
|
105
|
-
# @param domain [String, Symbol] the domain name
|
|
106
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
|
|
107
|
-
# @param id [String, Object] the record's identity
|
|
108
|
-
# @param current_state [String, nil] the record's current lifecycle state, or
|
|
109
|
-
# nil if the aggregate has no lifecycle field or it is unset
|
|
110
|
-
# @return [String] an HTML list of admissible command links, or a message
|
|
111
|
-
# when no command currently applies
|
|
68
|
+
# Omits lifecycle transitions that dispatch would refuse from the current state.
|
|
112
69
|
def self.command_links(domain, aggregate, id, current_state)
|
|
113
70
|
commands = aggregate.commands.reject(&:creates?).select { |cmd| applies?(aggregate, cmd, current_state) }
|
|
114
71
|
return "<p><em>No commands act on an existing #{Escape.html(aggregate.hecks_name)}.</em></p>" if commands.empty?
|
|
115
72
|
|
|
116
73
|
items = commands.map do |cmd|
|
|
117
|
-
#
|
|
118
|
-
# value (a raw `&` here would smuggle a second bogus query
|
|
119
|
-
# parameter), then the assembled href is attribute-escaped as
|
|
120
|
-
# usual.
|
|
74
|
+
# The id is free-form, so it is percent-encoded as a query value.
|
|
121
75
|
href = "/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html?to=#{Escape.url(id)}"
|
|
122
76
|
%(<li><a href="#{Escape.attr(href)}"><span>#{Escape.html(cmd.hecks_name)}</span>) \
|
|
123
77
|
"<span class=\"kind\">#{Escape.html(cmd.goal.to_s)}</span></a></li>"
|
|
@@ -125,14 +79,6 @@ module Hecks
|
|
|
125
79
|
%(<ul class="verb-list">#{items.join}</ul>)
|
|
126
80
|
end
|
|
127
81
|
|
|
128
|
-
# Whether the aggregate's lifecycle admits dispatching `command` from
|
|
129
|
-
# `current_state`.
|
|
130
|
-
#
|
|
131
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the command belongs to
|
|
132
|
-
# @param command [Bluebook::Command] the command to check
|
|
133
|
-
# @param current_state [String, nil] the record's current lifecycle state
|
|
134
|
-
# @return [Boolean] true if the aggregate declares no lifecycle transitions for
|
|
135
|
-
# this command, or one of them admits `current_state`
|
|
136
82
|
def self.applies?(aggregate, command, current_state)
|
|
137
83
|
transitions = aggregate.lifecycle&.transitions_for(command.hecks_name) || []
|
|
138
84
|
return true if transitions.empty?
|
|
@@ -140,12 +86,6 @@ module Hecks
|
|
|
140
86
|
transitions.any? { |t| t.from.nil? || Array(t.from).map(&:to_s).include?(current_state.to_s) }
|
|
141
87
|
end
|
|
142
88
|
|
|
143
|
-
# Renders the "Queries" link list for an aggregate's own queries.
|
|
144
|
-
#
|
|
145
|
-
# @param domain [String, Symbol] the domain name
|
|
146
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate whose queries are linked
|
|
147
|
-
# @return [String] an HTML section listing every query, or `""` if the
|
|
148
|
-
# aggregate declares none
|
|
149
89
|
def self.query_links(domain, aggregate)
|
|
150
90
|
return "" if aggregate.queries.empty?
|
|
151
91
|
|
|
@@ -156,13 +96,6 @@ module Hecks
|
|
|
156
96
|
%(<h2>Queries</h2><ul class="verb-list">#{items.join}</ul>)
|
|
157
97
|
end
|
|
158
98
|
|
|
159
|
-
# Renders the full command-and-query index for an aggregate, independent of
|
|
160
|
-
# any one record's state.
|
|
161
|
-
#
|
|
162
|
-
# @param domain [String, Symbol] the domain name
|
|
163
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate whose verbs are listed
|
|
164
|
-
# @return [String] an HTML section listing every non-creating command and every
|
|
165
|
-
# query, or `""` if the aggregate declares neither
|
|
166
99
|
def self.verb_list(domain, aggregate)
|
|
167
100
|
commands = aggregate.commands.reject(&:creates?)
|
|
168
101
|
return "" if commands.empty? && aggregate.queries.empty?
|
|
@@ -3,42 +3,20 @@ require_relative "field_shape"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Forms
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# so `repository.all`'s own records pass straight through; a query's
|
|
9
|
-
# answer does not (`QueryInterpreter#call` flattens `{id:}.merge(state)`
|
|
10
|
-
# into one hash with no method to call — see
|
|
11
|
-
# docs/command-form-and-query-form-bluebook.md's note on why), so
|
|
12
|
-
# app.rb wraps those results in one of these before they ever reach a
|
|
13
|
-
# renderer, and every renderer here only has to know one shape.
|
|
6
|
+
# A record as every renderer here reads it: `id` plus its state hash.
|
|
7
|
+
# Query results are flat hashes, so they are wrapped in one before rendering.
|
|
14
8
|
Record = Struct.new(:id, :state)
|
|
15
9
|
|
|
16
|
-
#
|
|
17
|
-
# Runtime::Instance qualifies without wrapping) -> an HTML table.
|
|
18
|
-
# Shared by the aggregate index page (record_renderer.rb) and a
|
|
19
|
-
# query's own results (query_form_renderer.rb) — the same records
|
|
20
|
-
# read the same columns either way.
|
|
10
|
+
# Records (a Record or anything answering `#id`/`#state`) as an HTML table.
|
|
21
11
|
module RecordTable
|
|
22
|
-
#
|
|
23
|
-
# attributes, capped at seven.
|
|
24
|
-
#
|
|
25
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate whose records will be shown
|
|
26
|
-
# @return [Array<Symbol>] up to 7 unique field names, in that order; excludes
|
|
27
|
-
# reference and list attributes
|
|
12
|
+
# Identity, lifecycle state, then scalar attributes, capped at seven columns.
|
|
28
13
|
def self.columns(aggregate)
|
|
29
14
|
lifecycle = aggregate.lifecycle&.field
|
|
30
15
|
scalars = aggregate.attributes.reject { |a| a.reference? || a.list? }.map(&:name)
|
|
31
16
|
[*aggregate.identity_heads, lifecycle, *scalars].compact.uniq.first(7)
|
|
32
17
|
end
|
|
33
18
|
|
|
34
|
-
# Renders
|
|
35
|
-
#
|
|
36
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the records belong to
|
|
37
|
-
# @param instances [Array<Runtime::Instance, Forms::Record>] the records to render;
|
|
38
|
-
# anything answering `#id`/`#state` works
|
|
39
|
-
# @param domain [String] the owning chapter's name, for building each row's link
|
|
40
|
-
# @return [String] the table markup; `"<p><em>No records.</em></p>"` when
|
|
41
|
-
# `instances` is empty
|
|
19
|
+
# Renders records as an HTML table, one row per record.
|
|
42
20
|
def self.render(aggregate, instances, domain:)
|
|
43
21
|
cols = columns(aggregate)
|
|
44
22
|
head = (["id"] + cols).map { |name| "<th>#{Escape.html(Humanize.label(name.to_s))}</th>" }.join
|
|
@@ -53,36 +31,15 @@ module Hecks
|
|
|
53
31
|
HTML
|
|
54
32
|
end
|
|
55
33
|
|
|
56
|
-
# Renders one record's own table row, linked to its show page.
|
|
57
|
-
#
|
|
58
|
-
# @param instance [Runtime::Instance, Forms::Record] the record to render
|
|
59
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
|
|
60
|
-
# @param cols [Array<Symbol>] the column field names, as `columns` returns them
|
|
61
|
-
# @param domain [String] the owning chapter's name, for building the row's link
|
|
62
|
-
# @return [String] the `<tr>` markup
|
|
63
34
|
def self.row(instance, aggregate, cols, domain)
|
|
64
35
|
cells = cols.map { |name| "<td>#{Escape.html(cell(instance, name))}</td>" }.join
|
|
65
|
-
#
|
|
66
|
-
# segment, HTML-escaped as the link text, and the assembled href
|
|
67
|
-
# is itself attribute-escaped (belt-and-suspenders — nothing else
|
|
68
|
-
# in `href` is untrusted, but this matches the convention used
|
|
69
|
-
# everywhere else an href is built from parts).
|
|
36
|
+
# The id is free-form, so it is percent-encoded in the path.
|
|
70
37
|
href = "/#{domain}/#{aggregate.hecks_name}/#{Escape.path(instance.id)}.html"
|
|
71
38
|
"<tr><td><a href=\"#{Escape.attr(href)}\">#{Escape.html(instance.id)}</a></td>#{cells}</tr>"
|
|
72
39
|
end
|
|
73
40
|
|
|
74
|
-
# Reads one field's value for display, unwrapped to a single cell value.
|
|
75
|
-
#
|
|
76
|
-
# @param instance [Runtime::Instance, Forms::Record] the record to read
|
|
77
|
-
# @param name [Symbol] the field name to read
|
|
78
|
-
# @return [Object] the field's own value; for a Hash-shaped value object, its
|
|
79
|
-
# first member's value; `""` for `nil`
|
|
80
41
|
def self.cell(instance, name)
|
|
81
|
-
# `state` holds `Runtime::Value` wherever an attribute is a value
|
|
82
|
-
# object, not a plain Hash — `.materialize` is the runtime's own
|
|
83
|
-
# unwrap (value.rb), the same one a `to_h` read anywhere else in
|
|
84
|
-
# this codebase goes through, so a table cell agrees with every
|
|
85
|
-
# other reader about what a stored field actually contains.
|
|
42
|
+
# `state` holds a `Runtime::Value` wherever an attribute is a value object.
|
|
86
43
|
value = Runtime::Value.materialize(instance.state[name])
|
|
87
44
|
case value
|
|
88
45
|
when Hash then value.values.first
|
|
@@ -1,33 +1,15 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Forms
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# Shared by CommandFormRenderer and QueryFormRenderer alike: a
|
|
6
|
-
# command's own reference field and a query's own reference parameter
|
|
7
|
-
# resolve against the same repository the same way, and neither word
|
|
8
|
-
# owns this more than the other.
|
|
3
|
+
# The `<select>` options for every `:reference` field in a Field tree, keyed by path.
|
|
4
|
+
# Shared by the command and query form renderers.
|
|
9
5
|
module ReferenceOptions
|
|
10
|
-
#
|
|
11
|
-
# tree.
|
|
12
|
-
#
|
|
13
|
-
# @param registry [Runtime::Registry] the booted registry to read repositories from
|
|
14
|
-
# @param domain [String] the owning chapter's name
|
|
15
|
-
# @param fields [Array<Forms::Field>] the resolved field tree to search
|
|
16
|
-
# @return [Hash{String => Array<Array(String, String)>, nil}] one entry per
|
|
17
|
-
# `:reference` field found, keyed by its `path`; `nil` when the field's target
|
|
18
|
-
# could not be resolved or its options could not be read
|
|
6
|
+
# Returns `{path => [[id, id], ...]}`; nil for a field whose options cannot be read.
|
|
19
7
|
def self.collect(registry, domain, fields)
|
|
20
8
|
targets = {}
|
|
21
9
|
walk(fields) { |field| targets[field.path] = field.target_aggregate if field.kind == :reference }
|
|
22
10
|
targets.transform_values { |aggregate| aggregate && options_for(registry, domain, aggregate) }
|
|
23
11
|
end
|
|
24
12
|
|
|
25
|
-
# Visits every field in a tree, depth-first, including each `:list`/`:group`
|
|
26
|
-
# field's own children.
|
|
27
|
-
#
|
|
28
|
-
# @param fields [Array<Forms::Field>] the field tree to walk
|
|
29
|
-
# @yieldparam field [Forms::Field] each field visited, parent before children
|
|
30
|
-
# @return [void]
|
|
31
13
|
def self.walk(fields, &block)
|
|
32
14
|
fields.each do |field|
|
|
33
15
|
block.call(field)
|
|
@@ -35,15 +17,6 @@ module Hecks
|
|
|
35
17
|
end
|
|
36
18
|
end
|
|
37
19
|
|
|
38
|
-
# Reads up to 200 records' ids as `<select>` options, for one reference field's
|
|
39
|
-
# target aggregate.
|
|
40
|
-
#
|
|
41
|
-
# @param registry [Runtime::Registry] the booted registry to read the repository from
|
|
42
|
-
# @param domain [String] the owning chapter's name
|
|
43
|
-
# @param aggregate [Bluebook::Aggregate] the referenced aggregate to list
|
|
44
|
-
# @return [Array<Array(String, String)>, nil] up to 200 `[id, id]` pairs; `nil` when
|
|
45
|
-
# the repository cannot be resolved (a wiring gap degrades to a plain text id
|
|
46
|
-
# input, not a 500)
|
|
47
20
|
def self.options_for(registry, domain, aggregate)
|
|
48
21
|
registry.repository(domain, aggregate).all.first(200).map { |instance| [instance.id, instance.id] }
|
|
49
22
|
rescue StandardError
|
|
@@ -1,53 +1,26 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Forms
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# form), `ui_schema.rb` (the console app's table/detail-panel
|
|
6
|
-
# renderer, a separate app, not this repo), and
|
|
7
|
-
# `adapters/driven/sql_query_builder.rb` (a query's own ORDER BY/
|
|
8
|
-
# where compiler) each grew their own copy of "is this VO money-
|
|
9
|
-
# shaped," "does it have exactly one attribute, worth unwrapping,"
|
|
10
|
-
# and "which member of it is numeric" — the same three questions,
|
|
11
|
-
# answered identically by definition (a VO's shape doesn't change
|
|
12
|
-
# depending on who's asking), so a fix to one copy was never
|
|
13
|
-
# guaranteed to reach the others.
|
|
3
|
+
# Shared value-object shape classification, so callers such as
|
|
4
|
+
# field_shape.rb, ui_schema.rb and sql_query_builder.rb agree on it.
|
|
14
5
|
module ValueObjectShape
|
|
15
6
|
module_function
|
|
16
7
|
|
|
17
|
-
# `
|
|
18
|
-
# every money-shaped VO in the corpus
|
|
19
|
-
# the two-attribute convention the language itself never enforces
|
|
20
|
-
# but every real chapter follows.
|
|
21
|
-
# @param value_object [Bluebook::ValueObject] the value object to classify
|
|
22
|
-
# @return [Boolean] true if its attributes are exactly `cents` and `currency`
|
|
8
|
+
# True if the VO's attributes are exactly `cents` and `currency`, the
|
|
9
|
+
# convention every money-shaped VO in the corpus follows.
|
|
23
10
|
def money?(value_object)
|
|
24
11
|
value_object.attributes.map { |a| a.name.to_s }.sort == %w[cents currency]
|
|
25
12
|
end
|
|
26
13
|
|
|
27
|
-
# A VO with exactly one attribute
|
|
28
|
-
#
|
|
29
|
-
# [[feedback_name_the_scalar_field]]'s own reasoning, shared here
|
|
30
|
-
# rather than re-decided per caller. Returns the sole attribute,
|
|
31
|
-
# or nil for anything else.
|
|
32
|
-
#
|
|
33
|
-
# @param value_object [Bluebook::ValueObject] the value object to inspect
|
|
34
|
-
# @return [Bluebook::Attribute, nil] its one declared attribute, or nil if
|
|
35
|
-
# it declares zero or more than one
|
|
14
|
+
# A VO with exactly one attribute names a scalar, not a genuine group.
|
|
15
|
+
# Returns that attribute, or nil for anything else.
|
|
36
16
|
def sole_attribute(value_object)
|
|
37
17
|
return nil unless value_object.attributes.size == 1
|
|
38
18
|
|
|
39
19
|
value_object.attributes.first
|
|
40
20
|
end
|
|
41
21
|
|
|
42
|
-
# The first Integer/Float member, or nil — what a numeric
|
|
43
|
-
#
|
|
44
|
-
# money-shaped (money's own two members are handled by `money?`
|
|
45
|
-
# instead, since which one governs ordering is a money-specific
|
|
46
|
-
# decision, not a general "pick the first number" one).
|
|
47
|
-
#
|
|
48
|
-
# @param value_object [Bluebook::ValueObject] the value object to inspect
|
|
49
|
-
# @return [Bluebook::Attribute, nil] its first Integer- or Float-typed
|
|
50
|
-
# attribute, or nil if it declares none
|
|
22
|
+
# The first Integer/Float member, or nil — what a numeric comparison or
|
|
23
|
+
# ORDER BY compiles against when the VO isn't money-shaped.
|
|
51
24
|
def numeric_member(value_object)
|
|
52
25
|
value_object.attributes.find { |a| %w[Integer Float].include?(a.type.to_s) }
|
|
53
26
|
end
|