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/naming.rb
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
module Hecks
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# splitting, and the command/event reference rewrites — collected here so
|
|
5
|
-
# two readers never invent two different spellings for the same
|
|
6
|
-
# derivation.
|
|
2
|
+
# Derived-name and identity-joining rules: casing, pluralisation, dotted-path
|
|
3
|
+
# and verb splitting, command/event reference rewrites.
|
|
7
4
|
module Naming
|
|
8
|
-
# What separates the parts of a derived identity.
|
|
9
|
-
#
|
|
10
|
-
# An identity of several parts is their join, and the join has to be spelled the
|
|
11
|
-
# same everywhere or two readers name two different records off one declaration.
|
|
12
|
-
# It was spelled three ways at once here — "::" for an aggregate under a
|
|
13
|
-
# chapter, "." for everything under an aggregate, "#" for the three that keyed
|
|
14
|
-
# off position — and each was written where it happened to be needed. Once the
|
|
15
|
-
# runtime derived the same identity, the runtime made a fourth, and a reference
|
|
16
|
-
# that resolved by string comparison found nothing.
|
|
5
|
+
# What separates the parts of a derived identity. Every reader must spell the
|
|
6
|
+
# join the same way, or string-compared references resolve to nothing.
|
|
17
7
|
IDENTITY_JOIN = ":".freeze
|
|
18
8
|
|
|
19
9
|
module_function
|
|
@@ -35,9 +25,8 @@ module Hecks
|
|
|
35
25
|
type.to_s.split("::").last.to_s
|
|
36
26
|
end
|
|
37
27
|
|
|
38
|
-
# snake_case -> PascalCase.
|
|
39
|
-
#
|
|
40
|
-
# the IR contract: the same bluebook must always produce the same name.
|
|
28
|
+
# snake_case -> PascalCase. Names a closed-set value object synthesised from an
|
|
29
|
+
# inline attribute; the derivation is part of the IR contract.
|
|
41
30
|
#
|
|
42
31
|
# @param text [String, Symbol, #to_s] a snake_case (or already Pascal) name
|
|
43
32
|
# @return [String] the PascalCase form
|
|
@@ -56,15 +45,8 @@ module Hecks
|
|
|
56
45
|
.downcase
|
|
57
46
|
end
|
|
58
47
|
|
|
59
|
-
# An identifier as a
|
|
60
|
-
# `
|
|
61
|
-
# limit", `Back office` -> "Back office". The same two word-boundary
|
|
62
|
-
# splits `snake` uses, with a space instead of an underscore — plus
|
|
63
|
-
# the one thing `snake` cannot give back: an all-caps run stays an
|
|
64
|
-
# acronym ("ATM", "KYC") instead of being lowercased into a word
|
|
65
|
-
# nobody says ("Atm"). First word capitalized, the rest lowercased,
|
|
66
|
-
# so a headword reads as sentence case whatever casing it was
|
|
67
|
-
# declared in.
|
|
48
|
+
# An identifier as a sentence-cased phrase: `ATMCard` -> "ATM card",
|
|
49
|
+
# `daily_limit` -> "Daily limit". All-caps runs stay acronyms.
|
|
68
50
|
#
|
|
69
51
|
# @param text [String, Symbol, #to_s] a Pascal-, camel-, or snake-case identifier
|
|
70
52
|
# @return [String] the space-separated, sentence-cased phrase
|
|
@@ -81,9 +63,7 @@ module Hecks
|
|
|
81
63
|
end.join(" ")
|
|
82
64
|
end
|
|
83
65
|
|
|
84
|
-
# Joins items into
|
|
85
|
-
# every English sentence a projection writes wants; lived in
|
|
86
|
-
# `NarrateProjector` alone until a second projection needed it.
|
|
66
|
+
# Joins items into an Oxford-comma list: "A, B, and C" / "A or B".
|
|
87
67
|
#
|
|
88
68
|
# @param items [Array<#to_s>] the items to join
|
|
89
69
|
# @param conj [String] the word before the last item, such as `"and"` or `"or"`
|
|
@@ -98,9 +78,8 @@ module Hecks
|
|
|
98
78
|
end
|
|
99
79
|
end
|
|
100
80
|
|
|
101
|
-
# Picks the article for `word` by its first letter
|
|
102
|
-
#
|
|
103
|
-
# gives: a construct name is a plain word, never "hour" or "university".
|
|
81
|
+
# Picks the article for `word` by its first letter. Construct names are plain
|
|
82
|
+
# words, never "hour" or "university", so the vowel-letter heuristic holds.
|
|
104
83
|
#
|
|
105
84
|
# @param word [String, Symbol, #to_s] the word the article precedes
|
|
106
85
|
# @return [String] `"an"` if `word` starts with a vowel letter, `"a"` otherwise
|
|
@@ -108,15 +87,8 @@ module Hecks
|
|
|
108
87
|
%w[a e i o u].include?(word.to_s[0].to_s.downcase) ? "an" : "a"
|
|
109
88
|
end
|
|
110
89
|
|
|
111
|
-
# The name a collection of something takes
|
|
112
|
-
#
|
|
113
|
-
# There were two of these and one was wrong. A read model's gathered heads
|
|
114
|
-
# derived their name with a bare `"#{snake(target)}s"`, so the meta-domain's
|
|
115
|
-
# own whole-bluebook read model handed back `querys`, `entitys`, `policys`
|
|
116
|
-
# and `dispatchs` — and every check was green, because the checks compared
|
|
117
|
-
# the wrong rule against itself. Agreement is not correctness; it never was.
|
|
118
|
-
#
|
|
119
|
-
# So: one pluraliser, three rules, and every collection name flows through it.
|
|
90
|
+
# The name a collection of something takes: "y" -> "ies", sibilants take "es",
|
|
91
|
+
# everything else "s". Every collection name must flow through this one rule.
|
|
120
92
|
#
|
|
121
93
|
# @param text [String, Symbol, #to_s] a singular name
|
|
122
94
|
# @return [String] the pluralised name
|
|
@@ -128,13 +100,9 @@ module Hecks
|
|
|
128
100
|
"#{word}s"
|
|
129
101
|
end
|
|
130
102
|
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
# name wrong reads as a typo forever ; this only ever recovers a name someone
|
|
135
|
-
# already wrote as a real aggregate, so "ies -> y, trailing s dropped" is the
|
|
136
|
-
# whole rule — enough for `has_many Invoices` to resolve to the aggregate
|
|
137
|
-
# actually named Invoice.
|
|
103
|
+
# Recovers the singular aggregate name from a `has_many` plural. Deliberately
|
|
104
|
+
# crude ("ies" -> "y", sibilant "es" and trailing "s" dropped): it only has to
|
|
105
|
+
# find a name someone already wrote.
|
|
138
106
|
#
|
|
139
107
|
# @param text [String, Symbol, #to_s] a plural name
|
|
140
108
|
# @return [String] the singularised name
|
|
@@ -142,15 +110,8 @@ module Hecks
|
|
|
142
110
|
word = text.to_s
|
|
143
111
|
return "#{word[0..-4]}y" if word.length > 3 && word.end_with?("ies")
|
|
144
112
|
|
|
145
|
-
# `plural`'s
|
|
146
|
-
#
|
|
147
|
-
# itself would have suffixed with "es" comes back missing its
|
|
148
|
-
# own trailing letter ("Boxes" -> "Boxe", not "Box") once this
|
|
149
|
-
# only ever knew how to drop a bare "s". Checked before the
|
|
150
|
-
# bare-"s" rule below: stripping "es" first and confirming what
|
|
151
|
-
# is left actually ends in one of those five shapes is what
|
|
152
|
-
# keeps an ordinary "-es" word (e.g. "Invoices" -> "Invoice")
|
|
153
|
-
# from also losing a letter it never doubled.
|
|
113
|
+
# Undo `plural`'s "es" after s/x/z/ch/sh before the bare-"s" rule, so "Boxes"
|
|
114
|
+
# gives "Box" while "Invoices" still gives "Invoice".
|
|
154
115
|
return word[0..-3] if word.length > 3 && word.end_with?("es") && word[0..-3].match?(/(s|x|z|ch|sh)\z/)
|
|
155
116
|
|
|
156
117
|
return word[0..-2] if word.length > 1 && word.end_with?("s")
|
|
@@ -199,29 +160,9 @@ module Hecks
|
|
|
199
160
|
|
|
200
161
|
# Splits a domain-qualified verb into its domain, aggregate, and command parts.
|
|
201
162
|
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
# construction — every caller here has already prefixed the domain
|
|
206
|
-
# itself (`PolicyInterpreter#deliver`, `SagaInterpreter#qualified`,
|
|
207
|
-
# `Router#dispatch`'s own rebuilt string) before this ever runs. A
|
|
208
|
-
# third `::` segment can still show up past that boundary: a bare
|
|
209
|
-
# `ScopedConstant` naming a port operation (`command_ref`'s own
|
|
210
|
-
# comment — `Aggregate::Port::Operation`, three colon-joined
|
|
211
|
-
# segments with no `.` of its own) only gets its last `::` rewritten
|
|
212
|
-
# to `.` there, at DSL-build time, because nothing at that point
|
|
213
|
-
# knows yet whether the constant names a port operation or a
|
|
214
|
-
# domain-qualified command (`Domain::Aggregate::Command`, the other
|
|
215
|
-
# shape `command_ref` documents) — both are textually identical.
|
|
216
|
-
# Here, past the already-resolved domain boundary, any leftover
|
|
217
|
-
# `::` is unambiguous: it is that same rewrite artifact, and folding
|
|
218
|
-
# it into the dot-joined tail recovers exactly the
|
|
219
|
-
# `Aggregate::Port.Operation` shape a working port dispatch already
|
|
220
|
-
# expects (`spec/port_operation_interpreter_spec.rb`'s own
|
|
221
|
-
# `"Payments::Payment.PaymentGateway.Receive"`). `Outbox::Fanout
|
|
222
|
-
# .kind_for` and `ReactionInvocation#resolve_target` both already
|
|
223
|
-
# assume this contract on their own end; this is what actually
|
|
224
|
-
# delivers it to them.
|
|
163
|
+
# A `::` left after the domain and aggregate is the last-`::`-to-`.` rewrite
|
|
164
|
+
# `command_ref` leaves on a port operation; it folds into the dotted command
|
|
165
|
+
# tail (`Aggregate::Port.Operation`).
|
|
225
166
|
#
|
|
226
167
|
# @param verb [String, Symbol, #to_s] a `Domain::Aggregate.command` (or
|
|
227
168
|
# `.query`) path
|
|
@@ -239,26 +180,11 @@ module Hecks
|
|
|
239
180
|
[domain, aggregate, command]
|
|
240
181
|
end
|
|
241
182
|
|
|
242
|
-
# Rewrites a bare command constant's
|
|
243
|
-
#
|
|
183
|
+
# Rewrites a bare command constant's last `::` into `.`, matching a command's
|
|
184
|
+
# FQN separator (`Banking::Account::Debit` -> `Banking::Account.Debit`).
|
|
244
185
|
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
# text (ADR 0025, "events and reactions" — command references become
|
|
248
|
-
# first-class). Ruby's `::` joins every segment the same way a
|
|
249
|
-
# constant path always does, but a command's own `hecks_fqn` joins
|
|
250
|
-
# its aggregate with `.` (`Construct#hecks_separator`'s default,
|
|
251
|
-
# only an aggregate overrides it to `::`) — so only the last `::`
|
|
252
|
-
# becomes a `.`; everything before it (the chapter, when a domain is
|
|
253
|
-
# spelled at all: `Banking::Account::Debit`) stays `::`-joined.
|
|
254
|
-
#
|
|
255
|
-
# A string passes through unchanged, on purpose — legacy era text
|
|
256
|
-
# (S0a's own shadow-parsed spelling) already mixes `::` (domain) and
|
|
257
|
-
# `.` (command) correctly on its own, e.g. `"Banking::Account.Debit"`,
|
|
258
|
-
# and re-splitting that by content rather than by type would corrupt
|
|
259
|
-
# it (its own last `::` sits between the domain and the aggregate,
|
|
260
|
-
# not the aggregate and the command). Only an actual constant object
|
|
261
|
-
# — never seen holding a `.` of its own — needs the rewrite at all.
|
|
186
|
+
# A String or Symbol passes through: era text already mixes `::` and `.`
|
|
187
|
+
# correctly, and re-splitting it by content would corrupt it.
|
|
262
188
|
#
|
|
263
189
|
# @param value [Symbol, String, Module] the command, as a bare constant (a
|
|
264
190
|
# `ScopedConstant` module `ConstShim` resolves) or already-dotted text
|
|
@@ -271,18 +197,8 @@ module Hecks
|
|
|
271
197
|
path.empty? ? text : "#{path}.#{command}"
|
|
272
198
|
end
|
|
273
199
|
|
|
274
|
-
# The event-side twin of `command_ref
|
|
275
|
-
# event
|
|
276
|
-
#
|
|
277
|
-
# `emits Account::AccountFrozen` / `on Account::AccountFrozen` — the
|
|
278
|
-
# event-side twin of `command_ref`, above (ADR 0025, S6 — "events
|
|
279
|
-
# first-class"). Identical transform (a bare `ScopedConstant`'s last
|
|
280
|
-
# `::` becomes `.`, a String passes through unchanged for legacy
|
|
281
|
-
# `shadow_parse` text and for corpus sites this pass didn't migrate —
|
|
282
|
-
# see that method's own comment for why both rules exist), given its
|
|
283
|
-
# own name because the two references mean different things even
|
|
284
|
-
# though the rewrite is byte-identical: an event name is not a
|
|
285
|
-
# command name that happens to share a format.
|
|
200
|
+
# The event-side twin of `command_ref`. Same rewrite, separate name, because an
|
|
201
|
+
# event reference is not a command reference that happens to share a format.
|
|
286
202
|
#
|
|
287
203
|
# @param value [Symbol, String, Module] the event, as a bare constant (a
|
|
288
204
|
# `ScopedConstant` module `ConstShim` resolves) or already-dotted text
|
|
@@ -291,32 +207,8 @@ module Hecks
|
|
|
291
207
|
|
|
292
208
|
# Strips a process manager's event reference down to its bare event name.
|
|
293
209
|
#
|
|
294
|
-
# `
|
|
295
|
-
#
|
|
296
|
-
# — a process manager's own event references (ADR 0025, S6),
|
|
297
|
-
# deliberately not `event_ref` — found live, not assumed, wiring a
|
|
298
|
-
# real migrated corpus site into `bin/model_check` for the first
|
|
299
|
-
# time (2026-08-28): `SagaInterpreter#begin_saga`/`#advance_saga`
|
|
300
|
-
# match `pm.starts_on`/`pm.handler_for` against `event.name`, which
|
|
301
|
-
# `CommandRules::Emission#emit` stamps bare — a command's own
|
|
302
|
-
# `emits AccountDebited` never carries its aggregate's name at all
|
|
303
|
-
# (unlike a policy's cross-aggregate `on`, matched instead by
|
|
304
|
-
# `Naming.demodulise(event.aggregate)` split apart from the bare
|
|
305
|
-
# name — `PolicyInterpreter#policies_for`). Handing a saga's own
|
|
306
|
-
# matcher the dotted `event_ref` form ("Account.AccountDebited")
|
|
307
|
-
# would silently name an event no command in the domain ever
|
|
308
|
-
# actually emits — caught by `bin/model_check`'s own `deaf_handler`/
|
|
309
|
-
# `deaf_trigger` findings the moment a real qualified corpus site
|
|
310
|
-
# existed to trip them, not by any unit test in isolation.
|
|
311
|
-
#
|
|
312
|
-
# A qualifier is still worth writing (`Account::`) — the same
|
|
313
|
-
# provenance a reader gets from `trigger Account::Debit` — it is
|
|
314
|
-
# only not worth keeping: `demodulise` drops everything but the
|
|
315
|
-
# final segment, so `Account::AccountDebited` and a bare
|
|
316
|
-
# `AccountDebited` resolve to the identical stored string. A String
|
|
317
|
-
# passes through unchanged either way, exactly like `command_ref`'s
|
|
318
|
-
# own legacy branch — this corpus never spelled one dotted to begin
|
|
319
|
-
# with, so there is nothing here to strip.
|
|
210
|
+
# Not `event_ref`: saga matching compares against `event.name`, which is stamped
|
|
211
|
+
# bare, so a dotted "Account.AccountDebited" would name an event nothing emits.
|
|
320
212
|
#
|
|
321
213
|
# @param value [Symbol, String, Module] the event, as a bare constant (a
|
|
322
214
|
# `ScopedConstant` module `ConstShim` resolves) or already-dotted text
|
|
@@ -2,22 +2,9 @@ require_relative "../runtime/registry"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Ports
|
|
5
|
-
# Who can sign in
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# provider, never this registry's own data). Resolved the same way
|
|
9
|
-
# every other port here resolves its adapter: one adapter
|
|
10
|
-
# registry-wide answers this, since an app has no reason to want a
|
|
11
|
-
# different "who's allowed in" source per aggregate.
|
|
12
|
-
#
|
|
13
|
-
# Every domain shapes this differently — who its own "a person who
|
|
14
|
-
# can sign in" aggregate is, what admits them, what grants them
|
|
15
|
-
# access — so this port is pure delegation, same as every sibling
|
|
16
|
-
# port. The one piece of this that is generic (a live Governance
|
|
17
|
-
# grant should win over whatever an aggregate's own role field
|
|
18
|
-
# says) lives on `Ports::Authorization#live_role_for` instead —
|
|
19
|
-
# a domain's adapter here calls that directly, rather than this
|
|
20
|
-
# port re-deriving Governance-query logic another port already owns.
|
|
5
|
+
# Who can sign in and what role they hold: the domain-specific half of sign-in.
|
|
6
|
+
# Pure delegation to the one adapter bound registry-wide; adapters that need a
|
|
7
|
+
# live Governance grant call `Ports::Authorization#live_role_for`.
|
|
21
8
|
module AccessControl
|
|
22
9
|
NAME = "access_control".freeze
|
|
23
10
|
|
|
@@ -39,16 +26,13 @@ module Hecks
|
|
|
39
26
|
|
|
40
27
|
# Admits a new person, in whatever way this domain's adapter defines admission.
|
|
41
28
|
#
|
|
42
|
-
# The keywords carry the names of a verified sign-in (`Ports::Authentication.verify`
|
|
43
|
-
#
|
|
44
|
-
# two together, so their shapes here are adapter-defined.
|
|
29
|
+
# The keywords carry the names of a verified sign-in (`Ports::Authentication.verify`),
|
|
30
|
+
# but nothing here wires the two together, so their shapes are adapter-defined.
|
|
45
31
|
#
|
|
46
32
|
# @param registry [Runtime::Registry] the booted registry to resolve the adapter against
|
|
47
33
|
# @param email [Object] adapter-defined, forwarded unchanged; the person's email
|
|
48
|
-
# @param issuer [Object] adapter-defined, forwarded unchanged; the OIDC issuer
|
|
49
|
-
#
|
|
50
|
-
# @param subject [Object] adapter-defined, forwarded unchanged; the OIDC subject the
|
|
51
|
-
# issuer vouches for
|
|
34
|
+
# @param issuer [Object] adapter-defined, forwarded unchanged; the OIDC issuer
|
|
35
|
+
# @param subject [Object] adapter-defined, forwarded unchanged; the OIDC subject
|
|
52
36
|
# @return [Object] adapter-defined representation of the newly admitted person
|
|
53
37
|
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
54
38
|
# (see `adapter`)
|
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Ports
|
|
3
3
|
module Agent
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# standing in for it in a spec) is held to the identical shape.
|
|
7
|
-
# An adapter's whole job ends at "here is what came back, already
|
|
8
|
-
# JSON"; whether that hash is usable is decided once, here, not
|
|
9
|
-
# re-decided per adapter.
|
|
10
|
-
#
|
|
11
|
-
# Every failure raises `ValidationError` — never lets a bare
|
|
12
|
-
# `NoMethodError` on nil, or a `KeyError`, reach a caller looking
|
|
13
|
-
# for an agent-shaped failure. `bin/interview` catches exactly one
|
|
14
|
-
# class for "the model answered badly," not a grab-bag of Ruby's
|
|
15
|
-
# own.
|
|
4
|
+
# Turns an adapter's raw reply hash into structs, so every adapter is held to
|
|
5
|
+
# one shape. Every failure raises `ValidationError`, never a bare `NoMethodError`.
|
|
16
6
|
module Answers
|
|
17
7
|
VERB_PATTERN = /\A[A-Za-z]+::[A-Za-z]+\.[A-Za-z]+\z/
|
|
18
8
|
|
|
@@ -77,8 +67,6 @@ module Hecks
|
|
|
77
67
|
end
|
|
78
68
|
end
|
|
79
69
|
|
|
80
|
-
# ── shared checks, each named for what it refuses ───────────────
|
|
81
|
-
|
|
82
70
|
# Reads the array of rows an answer holds under one key, refusing any other shape.
|
|
83
71
|
#
|
|
84
72
|
# @param raw [Object] the adapter's reply; only a Hash is accepted
|
|
@@ -111,12 +99,8 @@ module Hecks
|
|
|
111
99
|
|
|
112
100
|
# Reads a proposal row's verb, refusing one that is not fully qualified.
|
|
113
101
|
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
# language's own grammar already refuses by, reused rather than
|
|
117
|
-
# reinvented. A verb naming a real category that turns out to
|
|
118
|
-
# describe the wrong fact is not this port's business: that one
|
|
119
|
-
# dispatches, and `Interview::Session#offer` is what says no.
|
|
102
|
+
# Only the shape is checked; whether the verb fits the fact is
|
|
103
|
+
# `Interview::Session#offer`'s call.
|
|
120
104
|
#
|
|
121
105
|
# @param row [Hash{String => Object}] one proposal row of the adapter's answer
|
|
122
106
|
# @return [String] the row's `"verb"`, shaped `Chapter::Aggregate.Command`
|
|
@@ -157,9 +141,7 @@ module Hecks
|
|
|
157
141
|
|
|
158
142
|
# Normalises a proposal row's arguments into symbol-keyed, all-String rows.
|
|
159
143
|
#
|
|
160
|
-
#
|
|
161
|
-
# reference's `field` is legitimately blank (a reference is an
|
|
162
|
-
# id), so only `name` is required here.
|
|
144
|
+
# Only `name` is required: a reference's `field` is legitimately blank.
|
|
163
145
|
#
|
|
164
146
|
# @param row [Hash{String => Object}] one proposal row of the adapter's answer
|
|
165
147
|
# @return [Array<Hash{Symbol => String}>] one `{name:, field:, value:}` Hash per
|
data/lib/hecks/ports/agent.rb
CHANGED
|
@@ -2,103 +2,28 @@ require_relative "../runtime/registry"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Ports
|
|
5
|
-
# The interviewer
|
|
6
|
-
#
|
|
7
|
-
# rather than structure, or suggests a name.
|
|
8
|
-
#
|
|
9
|
-
# ## Why a singleton port
|
|
10
|
-
#
|
|
11
|
-
# Resolved the same zero/one/many way every other port here is
|
|
12
|
-
# (`Ports::IdentityGeneration`'s own singleton adapter — one
|
|
13
|
-
# interviewer per process, never per-aggregate), because a second
|
|
14
|
-
# adapter answering this port is exactly as unchoosable as a second
|
|
15
|
-
# one minting identities.
|
|
16
|
-
#
|
|
17
|
-
# ## Why this exists
|
|
18
|
-
#
|
|
19
|
-
# Built last, on purpose, and still optional. `.claude/skills/
|
|
20
|
-
# interview/SKILL.md` conducts a whole session with none of this —
|
|
21
|
-
# Claude Code reads `bin/interview state` itself, asks the human
|
|
22
|
-
# directly with real conversation context, and calls `bin/interview
|
|
23
|
-
# ask`/`propose`/`accept` itself. This port exists for what that
|
|
24
|
-
# cannot reach: a headless run (`bin/interview suggest`, no model in
|
|
25
|
-
# the room otherwise), a batch `critique` over an already-written
|
|
26
|
-
# chapter, and — the actual point of building it at all — a
|
|
27
|
-
# scripted double (`spec/fixtures/scripted_agent.rb`) that makes the
|
|
28
|
-
# whole loop testable with no real model involved.
|
|
29
|
-
#
|
|
30
|
-
# ## What this port is not
|
|
31
|
-
#
|
|
32
|
-
# It does not judge whether a declaration is well-formed — that is
|
|
33
|
-
# the meta-domain's own job, answered by a real dispatch refusing or
|
|
34
|
-
# not (`Interview::Session#offer`). This port only ever produces
|
|
35
|
-
# shape: a question, a proposed declaration, a judgement about
|
|
36
|
-
# taste, a name. A proposal naming a category the language does not
|
|
37
|
-
# declare is an adapter fault, refused here, in `Answers`; a
|
|
38
|
-
# proposal naming a real category but describing the wrong domain
|
|
39
|
-
# fact is dispatched anyway, so the language gets to say why. That
|
|
40
|
-
# line is the whole design.
|
|
41
|
-
#
|
|
42
|
-
# ## Where parsing happens
|
|
43
|
-
#
|
|
44
|
-
# Parsing belongs to this file, not the adapter. An adapter answers
|
|
45
|
-
# with a plain, already-JSON-shaped Hash (a real model's parsed
|
|
46
|
-
# reply, or a spec double's own hand-built one) — never a Struct —
|
|
47
|
-
# so every adapter is validated identically here rather than trusting
|
|
48
|
-
# each one to refuse the same way. Two adapters that parsed
|
|
49
|
-
# differently would mean the same malformed answer passing through
|
|
50
|
-
# one and refusing through the other.
|
|
5
|
+
# The interviewer: asks questions, reads prose into proposed declarations, critiques a
|
|
6
|
+
# model on taste, and suggests names. One adapter per process.
|
|
51
7
|
module Agent
|
|
52
8
|
NAME = "agent".freeze
|
|
53
9
|
|
|
54
|
-
# The answer
|
|
55
|
-
# category the language does not declare, a severity outside the
|
|
56
|
-
# two that exist. A foreign failure (JSON::ParserError, a missing
|
|
57
|
-
# key) wrapped into something this port owns, the same shape
|
|
58
|
-
# `Ports::Authentication::ValidationError` already is, so no
|
|
59
|
-
# caller ever rescues a raw parser error leaking out of an
|
|
60
|
-
# adapter.
|
|
10
|
+
# The adapter's answer arrived but was unusable; retrying the same prompt will not help.
|
|
61
11
|
class ValidationError < StandardError
|
|
62
12
|
end
|
|
63
13
|
|
|
64
|
-
# Nothing answered
|
|
65
|
-
# died, the call timed out. Separate from ValidationError on
|
|
66
|
-
# purpose: unavailable means retry or fall back to asking the
|
|
67
|
-
# human directly; malformed means the prompt is wrong, not the
|
|
68
|
-
# transport, and retrying identically will not fix it.
|
|
14
|
+
# Nothing answered (missing binary, dead subprocess, timeout); retry or ask the human.
|
|
69
15
|
class Unavailable < StandardError
|
|
70
16
|
end
|
|
71
17
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
#
|
|
75
|
-
# already is one: a `keyword_init` Struct raises on a key nobody
|
|
76
|
-
# declared, which turns "the model invented a field" into a
|
|
77
|
-
# failure at the boundary rather than a nil three calls in.
|
|
78
|
-
|
|
79
|
-
# One question. `because` is the reasoning quoted back so a human
|
|
80
|
-
# can see it, not decoration — the same "show your work" the
|
|
81
|
-
# language's own refusal messages already practice.
|
|
18
|
+
# keyword_init Structs raise on an undeclared key, so an invented field fails at the edge.
|
|
19
|
+
|
|
20
|
+
# One question; `because` is the reasoning shown to the human.
|
|
82
21
|
Question = Struct.new(:text, :because, keyword_init: true)
|
|
83
22
|
|
|
84
|
-
#
|
|
85
|
-
# `Interview::Proposal`'s own `Argument` rows — `{name:, field:,
|
|
86
|
-
# value:}`, the exact triple `bin/interview propose --arg
|
|
87
|
-
# name:field:value` already takes and `Interview::Lowering`
|
|
88
|
-
# already knows how to lower. Nothing here decides how to address
|
|
89
|
-
# a record or whether a value is bare or wrapped — that was
|
|
90
|
-
# already the proposal-writer's job in `interview.bluebook`'s own
|
|
91
|
-
# design (see `Argument`'s own comment there); this only adds the
|
|
92
|
-
# prose reasoning a human typed does not carry on its own.
|
|
23
|
+
# `arguments` are `{name:, field:, value:}` rows, as `bin/interview propose --arg` takes.
|
|
93
24
|
Proposal = Struct.new(:verb, :arguments, :rationale, keyword_init: true)
|
|
94
25
|
|
|
95
|
-
#
|
|
96
|
-
# same fields, same severities, so a mechanical finding
|
|
97
|
-
# (`Session#gaps`) and a judgement (this) print in one list and
|
|
98
|
-
# sort together. The kind vocabulary is not shared — ModelCheck's
|
|
99
|
-
# eleven kinds are structural facts about a graph; these are
|
|
100
|
-
# opinions about a model, closed here on purpose (an open kind
|
|
101
|
-
# field is an open prompt, and an open prompt drifts).
|
|
26
|
+
# Kinds are closed: an open kind is an open prompt, and an open prompt drifts.
|
|
102
27
|
CRITIQUE_KINDS = %i[
|
|
103
28
|
anemic_aggregate wrong_boundary crud_verb leaky_value_object
|
|
104
29
|
missing_lifecycle missing_invariant ubiquitous_language
|
|
@@ -106,132 +31,82 @@ module Hecks
|
|
|
106
31
|
].freeze
|
|
107
32
|
SEVERITIES = %i[error warning].freeze
|
|
108
33
|
|
|
34
|
+
# Same shape as `Bluebook::ModelCheck::Finding`, so gaps and critiques sort together.
|
|
109
35
|
Finding = Struct.new(:kind, :severity, :subject, :message, keyword_init: true) do
|
|
110
36
|
def to_s = "#{severity.to_s.upcase.ljust(7)} #{kind.to_s.ljust(20)} #{subject} — #{message}"
|
|
111
37
|
end
|
|
112
38
|
|
|
113
|
-
#
|
|
114
|
-
# they were passed over — free at the point the model is choosing
|
|
115
|
-
# anyway, and the part a human actually learns from.
|
|
39
|
+
# `rejected` holds the near-misses and why each was passed over.
|
|
116
40
|
Suggestion = Struct.new(:name, :because, :rejected, keyword_init: true)
|
|
117
41
|
|
|
42
|
+
# Adapters return plain Hashes; validating here makes every adapter refuse the same way.
|
|
118
43
|
module_function
|
|
119
44
|
|
|
120
|
-
# Asks the adapter for the next
|
|
121
|
-
#
|
|
122
|
-
# `state` is the whole picture — normally
|
|
123
|
-
# `Interview::Session#declaration` plus `#gaps`, so an adapter
|
|
124
|
-
# needs no memory of its own between calls; that is what lets a
|
|
125
|
-
# headless run be a series of one-shot processes, same as
|
|
126
|
-
# `bin/interview` itself already is.
|
|
45
|
+
# Asks the adapter for the next question.
|
|
127
46
|
#
|
|
128
47
|
# @param registry [Runtime::Registry] the booted registry to resolve the adapter against
|
|
129
|
-
# @param state [Hash]
|
|
130
|
-
#
|
|
131
|
-
# @
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# @raise [
|
|
136
|
-
# `"questions"` array whose rows each carry a non-blank `"text"` and `"because"`
|
|
137
|
-
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer at all (missing
|
|
138
|
-
# binary, failed subprocess, timeout, or an exhausted scripted queue)
|
|
139
|
-
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
140
|
-
# (see `adapter`)
|
|
48
|
+
# @param state [Hash] declaration plus gaps; adapters keep no memory between calls
|
|
49
|
+
# @param asked [Array<Object>] questions already asked, so the adapter does not repeat one
|
|
50
|
+
# @return [Array<Ports::Agent::Question>]
|
|
51
|
+
# @raise [Ports::Agent::ValidationError] unless the answer is a Hash whose `"questions"`
|
|
52
|
+
# rows each carry a non-blank `"text"` and `"because"`
|
|
53
|
+
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer
|
|
54
|
+
# @raise [Runtime::WiringError] unless exactly one adapter implements the port
|
|
141
55
|
def ask(registry, state:, asked: [])
|
|
142
56
|
Answers.questions(adapter(registry).ask(state: state, asked: asked))
|
|
143
57
|
end
|
|
144
58
|
|
|
145
|
-
# Turns a
|
|
146
|
-
#
|
|
147
|
-
# Returns `[]` when the sentence
|
|
148
|
-
# carried no declaration at all (a clarifying question back from
|
|
149
|
-
# the human, say) — a legitimate answer, not a failure.
|
|
59
|
+
# Turns a sentence into proposed declarations; `[]` when it named none.
|
|
150
60
|
#
|
|
151
61
|
# @param registry [Runtime::Registry] the booted registry to resolve the adapter against
|
|
152
62
|
# @param prose [String] a human's plain-English sentence
|
|
153
|
-
# @param state [Hash]
|
|
154
|
-
#
|
|
155
|
-
# @
|
|
156
|
-
#
|
|
157
|
-
# @raise [Ports::Agent::
|
|
158
|
-
#
|
|
159
|
-
# `"rationale"` is blank, or one of its argument rows has no `"name"`
|
|
160
|
-
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer at all (missing
|
|
161
|
-
# binary, failed subprocess, timeout, or an exhausted scripted queue)
|
|
162
|
-
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
163
|
-
# (see `adapter`)
|
|
63
|
+
# @param state [Hash] declaration plus gaps
|
|
64
|
+
# @return [Array<Ports::Agent::Proposal>]
|
|
65
|
+
# @raise [Ports::Agent::ValidationError] unless the answer is a Hash whose `"proposals"`
|
|
66
|
+
# rows have a `Chapter::Aggregate.Command` verb, a rationale and named arguments
|
|
67
|
+
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer
|
|
68
|
+
# @raise [Runtime::WiringError] unless exactly one adapter implements the port
|
|
164
69
|
def interpret(registry, prose:, state:)
|
|
165
70
|
Answers.proposals(adapter(registry).interpret(prose: prose, state: state))
|
|
166
71
|
end
|
|
167
72
|
|
|
168
|
-
# Asks the adapter to judge a declared model on taste
|
|
169
|
-
#
|
|
170
|
-
# What is wrong with this as a model — handed everything already
|
|
171
|
-
# known (the language's own refusals, `Session#gaps`'s mechanical
|
|
172
|
-
# findings) so it spends its judgement on what neither of those
|
|
173
|
-
# can see, rather than restating them.
|
|
73
|
+
# Asks the adapter to judge a declared model on taste.
|
|
174
74
|
#
|
|
175
75
|
# @param registry [Runtime::Registry] the booted registry to resolve the adapter against
|
|
176
|
-
# @param declared [Hash] the chapter as declared so far
|
|
177
|
-
#
|
|
178
|
-
# @param
|
|
179
|
-
#
|
|
180
|
-
# @
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
# @raise [Ports::Agent::ValidationError] if the answer is not a Hash holding a
|
|
185
|
-
# `"findings"` array, a row's `"kind"` is outside `CRITIQUE_KINDS`, its `"severity"`
|
|
186
|
-
# is outside `SEVERITIES`, or its `"subject"` or `"message"` is blank
|
|
187
|
-
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer at all (missing
|
|
188
|
-
# binary, failed subprocess, timeout, or an exhausted scripted queue)
|
|
189
|
-
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
190
|
-
# (see `adapter`)
|
|
76
|
+
# @param declared [Hash] the chapter as declared so far
|
|
77
|
+
# @param refusals [Array<Object>] refusals the language already raised
|
|
78
|
+
# @param findings [Array<Object>] `Session#gaps` findings; both are passed so the
|
|
79
|
+
# adapter spends its judgement on what neither can see
|
|
80
|
+
# @return [Array<Ports::Agent::Finding>]
|
|
81
|
+
# @raise [Ports::Agent::ValidationError] if a kind is outside `CRITIQUE_KINDS` or a
|
|
82
|
+
# severity outside `SEVERITIES`
|
|
83
|
+
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer
|
|
191
84
|
def critique(registry, declared:, refusals: [], findings: [])
|
|
192
85
|
Answers.findings(adapter(registry).critique(declared: declared, refusals: refusals, findings: findings))
|
|
193
86
|
end
|
|
194
87
|
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# Vocabulary help. `near` is what the chapter already calls
|
|
198
|
-
# things, so a suggestion cannot collide with a name in use.
|
|
88
|
+
# Suggests names for a construct.
|
|
199
89
|
#
|
|
200
|
-
#
|
|
201
|
-
# operation, but `Module#name` already exists and is load-bearing
|
|
202
|
-
# everywhere (backtraces, RSpec's own description building,
|
|
203
|
-
# `inspect`): a module-function called `name` shadows it the
|
|
204
|
-
# instant it's defined, and `SomeModule.name` (no args) then
|
|
205
|
-
# raises `ArgumentError: missing keywords` the next time anything
|
|
206
|
-
# — not this file, something else entirely — asks the module its
|
|
207
|
-
# own name. Measured, not theoretical: this exact collision broke
|
|
208
|
-
# RSpec's own failure-message formatting the first time it was
|
|
209
|
-
# named `name` here.
|
|
90
|
+
# Not called `name`: a module function of that name shadows `Module#name`.
|
|
210
91
|
#
|
|
211
92
|
# @param registry [Runtime::Registry] the booted registry to resolve the adapter against
|
|
212
93
|
# @param meaning [String] what the new name needs to mean
|
|
213
|
-
# @param kind [String] the
|
|
214
|
-
#
|
|
215
|
-
# @
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
# @raise [Ports::Agent::ValidationError] if the answer is not a Hash holding a
|
|
220
|
-
# `"names"` array whose rows each carry a non-blank `"name"` and `"because"`
|
|
221
|
-
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer at all (missing
|
|
222
|
-
# binary, failed subprocess, timeout, or an exhausted scripted queue)
|
|
223
|
-
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
224
|
-
# (see `adapter`)
|
|
94
|
+
# @param kind [String] the construct being named, such as `"event"`
|
|
95
|
+
# @param near [Array<String>] names already in use; a suggestion must not collide
|
|
96
|
+
# @return [Array<Ports::Agent::Suggestion>]
|
|
97
|
+
# @raise [Ports::Agent::ValidationError] unless the answer is a Hash whose `"names"`
|
|
98
|
+
# rows each carry a non-blank `"name"` and `"because"`
|
|
99
|
+
# @raise [Ports::Agent::Unavailable] if the adapter cannot answer
|
|
225
100
|
def suggest_name(registry, meaning:, kind:, near: [])
|
|
226
101
|
Answers.suggestions(adapter(registry).suggest_name(meaning: meaning, kind: kind, near: near))
|
|
227
102
|
end
|
|
228
103
|
|
|
229
|
-
# Finds the single adapter bound to this port
|
|
104
|
+
# Finds the single adapter bound to this port.
|
|
230
105
|
#
|
|
231
106
|
# @param registry [Runtime::Registry] the booted registry to search
|
|
232
107
|
# @return [Module] the adapter module or class implementing this port
|
|
233
|
-
# @raise [Runtime::WiringError] if
|
|
234
|
-
#
|
|
108
|
+
# @raise [Runtime::WiringError] if none, or more than one, implements the port, or the
|
|
109
|
+
# one that does has no Ruby implementation under `Hecks::Adapters`
|
|
235
110
|
def adapter(registry)
|
|
236
111
|
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
237
112
|
|