hecks 2.7.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/hecks +11 -0
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +18 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +32 -142
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
- data/lib/hecks/bluebook/model_check.rb +102 -600
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +84 -0
- data/lib/hecks/cli/document.rb +86 -0
- data/lib/hecks/cli/ir.rb +45 -0
- data/lib/hecks/cli/mcp.rb +31 -0
- data/lib/hecks/cli/mcp_door.rb +366 -0
- data/lib/hecks/cli/model_check.rb +203 -0
- data/lib/hecks/cli/project_cli.rb +90 -0
- data/lib/hecks/cli/project_diagrams.rb +36 -0
- data/lib/hecks/cli/run.rb +173 -0
- data/lib/hecks/cli/smoke_test.rb +45 -0
- data/lib/hecks/cli/stores.rb +53 -0
- data/lib/hecks/cli.rb +160 -0
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +95 -0
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +62 -65
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +151 -679
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +9 -16
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +46 -138
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +93 -0
- data/lib/hecks/release/runner/commands.rb +66 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +43 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +75 -0
- data/lib/hecks/release/runner/preflight.rb +104 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +80 -0
- data/lib/hecks/release/runner.rb +215 -0
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +125 -307
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +65 -220
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +103 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +146 -567
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +32 -82
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +30 -59
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
require_relative "runner/commands"
|
|
2
|
+
require_relative "runner/console"
|
|
3
|
+
require_relative "runner/git"
|
|
4
|
+
require_relative "runner/preflight"
|
|
5
|
+
require_relative "runner/published"
|
|
6
|
+
require_relative "runner/tagger"
|
|
7
|
+
require_relative "runner/gem_publisher"
|
|
8
|
+
require_relative "runner/npm_publisher"
|
|
9
|
+
require_relative "runner/ci_publisher"
|
|
10
|
+
|
|
11
|
+
module Hecks
|
|
12
|
+
module Release
|
|
13
|
+
# Performs the release once the release PR has merged: tags the merge
|
|
14
|
+
# commit, publishes the gem, and gets the JS client published to npm.
|
|
15
|
+
#
|
|
16
|
+
# Idempotent: it asks the registries what is already published and skips
|
|
17
|
+
# it, so a run that died between the gem and the package resumes at the package.
|
|
18
|
+
class Runner
|
|
19
|
+
# Which steps to run and how.
|
|
20
|
+
class Options
|
|
21
|
+
# @return [Boolean] run every check and build, but tag, push and publish nothing
|
|
22
|
+
attr_reader :dry_run
|
|
23
|
+
|
|
24
|
+
# @param dry_run [Boolean] run every check and build, but tag, push and publish nothing
|
|
25
|
+
# @param gem_only [Boolean] publish the gem, not the package
|
|
26
|
+
# @param npm_only [Boolean] do the npm step only, not the gem: wait for CI's publish, or
|
|
27
|
+
# with `npm_local` publish from here
|
|
28
|
+
# @param npm_local [Boolean] publish the package from this machine, not by CI
|
|
29
|
+
# @param no_wait [Boolean] do not wait for CI's publish to reach npm
|
|
30
|
+
# @param yes [Boolean] answer every confirmation yes
|
|
31
|
+
# @raise [ArgumentError] if the flags contradict each other
|
|
32
|
+
def initialize(dry_run: false, gem_only: false, npm_only: false, npm_local: false, no_wait: false, yes: false)
|
|
33
|
+
check_flags(gem_only, npm_only, npm_local, no_wait)
|
|
34
|
+
|
|
35
|
+
@dry_run = dry_run
|
|
36
|
+
@gem_only = gem_only
|
|
37
|
+
@npm_only = npm_only
|
|
38
|
+
@npm_local = npm_local
|
|
39
|
+
@no_wait = no_wait
|
|
40
|
+
@yes = yes
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Says whether confirmations are answered automatically.
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean] true under `--yes`
|
|
46
|
+
def yes?
|
|
47
|
+
@yes
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Says whether the gem step is in scope.
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean] true unless `--npm-only`
|
|
53
|
+
def gem?
|
|
54
|
+
!@npm_only
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Says whether the npm step is in scope.
|
|
58
|
+
#
|
|
59
|
+
# @return [Boolean] true unless `--gem-only`
|
|
60
|
+
def npm?
|
|
61
|
+
!@gem_only
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Says whether the package is published from this machine.
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true under `--npm-local`
|
|
67
|
+
def npm_local?
|
|
68
|
+
@npm_local
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Says whether to wait for CI's publish to reach npm.
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] true unless `--no-wait`
|
|
74
|
+
def wait?
|
|
75
|
+
!@no_wait
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def check_flags(gem_only, npm_only, npm_local, no_wait)
|
|
81
|
+
raise ArgumentError, "--gem-only and --npm-only cannot be combined" if gem_only && npm_only
|
|
82
|
+
raise ArgumentError, "--gem-only and --npm-local cannot be combined" if gem_only && npm_local
|
|
83
|
+
return unless no_wait && npm_local
|
|
84
|
+
|
|
85
|
+
raise ArgumentError, "--no-wait applies to CI's publish, so it cannot be combined with --npm-local"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @param root [String] the repository root
|
|
90
|
+
# @param options [Options] which steps to run and how
|
|
91
|
+
# @param commands [#capture, #run!] starts every process the release runs
|
|
92
|
+
# @param input [IO] where confirmations are read from
|
|
93
|
+
# @param out [IO] where progress is written
|
|
94
|
+
# @param err [IO] where refusals and failures are written
|
|
95
|
+
# @param pause [#call] sleeps for a number of seconds while waiting for CI
|
|
96
|
+
# @param now [#call] answers the current time in seconds, monotonically, while waiting for CI
|
|
97
|
+
def initialize(root:, options: Options.new, commands: Commands.new, input: $stdin, out: $stdout, err: $stderr,
|
|
98
|
+
pause: ->(seconds) { sleep(seconds) }, now: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
|
|
99
|
+
@root = root
|
|
100
|
+
@options = options
|
|
101
|
+
@commands = commands
|
|
102
|
+
@console = Console.new(input: input, out: out, err: err, assume_yes: options.yes?)
|
|
103
|
+
@git = Git.new(root: root, commands: commands)
|
|
104
|
+
@published = Published.new(root: root, commands: commands)
|
|
105
|
+
@ci = CiPublisher.new(root: root, published: @published, console: @console, pause: pause, now: now)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Runs the release.
|
|
109
|
+
#
|
|
110
|
+
# @return [Integer] 0 when the release finished or had nothing to do, 1 when a check refused,
|
|
111
|
+
# a step failed, CI's publish did not arrive in time or a confirmation was declined
|
|
112
|
+
def call
|
|
113
|
+
facts = Preflight.new(root: @root, commands: @commands, git: @git, tools: tools).check!
|
|
114
|
+
pending = pending_steps(facts.version)
|
|
115
|
+
require_workflow(pending)
|
|
116
|
+
return declined unless Tagger.new(git: @git, console: @console, dry_run: @options.dry_run).ensure!(facts)
|
|
117
|
+
|
|
118
|
+
publish(facts, pending)
|
|
119
|
+
rescue Refusal, CommandFailed => e
|
|
120
|
+
@console.warn(e.message)
|
|
121
|
+
1
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def tools
|
|
127
|
+
list = %w[git curl]
|
|
128
|
+
list << "gem" if @options.gem?
|
|
129
|
+
list << "npm" if @options.npm?
|
|
130
|
+
list << "op" if !@options.dry_run && (@options.gem? || @options.npm_local?)
|
|
131
|
+
list
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def pending_steps(version)
|
|
135
|
+
[
|
|
136
|
+
(:gem if @options.gem? && !done?("hecks #{version} is already on rubygems.org", @published.gem?(version))),
|
|
137
|
+
(:npm if @options.npm? && !done?("@hecks/client #{version} is already on npm", @published.npm?(version)))
|
|
138
|
+
].compact
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def done?(message, published)
|
|
142
|
+
@console.say("#{message}; skipping.") if published
|
|
143
|
+
published
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def ci_publishes?(pending)
|
|
147
|
+
pending.include?(:npm) && !@options.npm_local?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def require_workflow(pending)
|
|
151
|
+
return unless ci_publishes?(pending) && !@ci.workflow?
|
|
152
|
+
|
|
153
|
+
raise Refusal, "#{CiPublisher::WORKFLOW} is not in this checkout, so CI cannot publish @hecks/client; " \
|
|
154
|
+
"publish it from here with --npm-local (the first publish, before trusted publishing is set up)"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def publish(facts, pending)
|
|
158
|
+
if pending.empty?
|
|
159
|
+
@console.say("Nothing to publish for #{facts.version}.")
|
|
160
|
+
return 0
|
|
161
|
+
end
|
|
162
|
+
return declined unless @options.dry_run || publish_confirmed?(facts.version, pending)
|
|
163
|
+
|
|
164
|
+
run_steps(facts.version, pending)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def publish_confirmed?(version, pending)
|
|
168
|
+
targets = []
|
|
169
|
+
targets << "hecks #{version} to rubygems.org" if pending.include?(:gem)
|
|
170
|
+
targets << "@hecks/client #{version} to npm" if pending.include?(:npm) && @options.npm_local?
|
|
171
|
+
return true if targets.empty?
|
|
172
|
+
|
|
173
|
+
suffix = ci_publishes?(pending) ? " (CI then publishes @hecks/client from the tag)" : ""
|
|
174
|
+
@console.confirm?("Publish #{targets.join(' and ')}#{suffix}? This cannot be undone.")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def run_steps(version, pending)
|
|
178
|
+
dry_run = @options.dry_run
|
|
179
|
+
publish_gem(version, dry_run) if pending.include?(:gem)
|
|
180
|
+
return 1 if pending.include?(:npm) && !publish_npm!(version, dry_run, ci_publishes?(pending))
|
|
181
|
+
|
|
182
|
+
@console.say(dry_run ? "Dry run complete; nothing was tagged, pushed or published." : "Released hecks #{version}.")
|
|
183
|
+
0
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def publish_gem(version, dry_run)
|
|
187
|
+
GemPublisher.new(root: @root, commands: @commands, console: @console).publish!(version, dry_run: dry_run)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def publish_npm!(version, dry_run, via_ci)
|
|
191
|
+
return wait_for_ci!(version, dry_run) if via_ci
|
|
192
|
+
|
|
193
|
+
NpmPublisher.new(root: @root, commands: @commands, console: @console).publish!(version, dry_run: dry_run)
|
|
194
|
+
true
|
|
195
|
+
rescue CommandFailed => e
|
|
196
|
+
@console.warn("npm publish failed: #{e.message}")
|
|
197
|
+
unless dry_run
|
|
198
|
+
@console.warn("Finish the release with: bin/release --npm-only --npm-local (published steps are not repeated)")
|
|
199
|
+
end
|
|
200
|
+
raise
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def wait_for_ci!(version, dry_run)
|
|
204
|
+
arrived = @ci.wait!(version, dry_run: dry_run, wait: @options.wait?)
|
|
205
|
+
@console.warn(CiPublisher.timeout_hint(version)) unless arrived
|
|
206
|
+
arrived
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def declined
|
|
210
|
+
@console.warn("Aborted; nothing was published.")
|
|
211
|
+
1
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
data/lib/hecks/rendering.rb
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
|
-
# How a value reads inside a refusal
|
|
5
|
-
# answer and its wording is contract — pinned byte-for-byte by the corpus.
|
|
4
|
+
# How a value reads inside a refusal; the wording is contract, pinned by the corpus.
|
|
6
5
|
#
|
|
7
|
-
# `inspect` and JSON
|
|
8
|
-
# a hash is `{"cents"=>100}` against `{"cents":100}`, an array is `["a", "b"]`
|
|
9
|
-
# against `["a","b"]`. Each such spelling is invisible until a composite
|
|
10
|
-
# actually reaches a message, which is why both known cases were found by
|
|
11
|
-
# something deliberately passing the wrong shape rather than by review — the
|
|
12
|
-
# first by hand in spec/corpus/banking.json, the second by bin/fuzz, at a
|
|
13
|
-
# different site, after the first was patched where it was found.
|
|
14
|
-
#
|
|
15
|
-
# So it lives here rather than in whichever file noticed it, and the house style
|
|
16
|
-
# is JSON because that is what every other refusal in the corpus already prints.
|
|
6
|
+
# `inspect` and JSON disagree on composites, so refusals use JSON throughout.
|
|
17
7
|
module Rendering
|
|
18
8
|
module_function
|
|
19
9
|
|
|
@@ -28,20 +18,8 @@ module Hecks
|
|
|
28
18
|
when nil then "nil"
|
|
29
19
|
when Hash, Array then JSON.generate(value)
|
|
30
20
|
else
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# `current`, ...) had no case here, falling straight through to
|
|
34
|
-
# Ruby's own #inspect and leaking a raw object pointer
|
|
35
|
-
# ("#<Hecks::Runtime::Value:0x...>") into an otherwise
|
|
36
|
-
# correct domain refusal. Duck-typed on `respond_to?(:to_h)`
|
|
37
|
-
# rather than naming `Runtime::Value` directly — `Runtime::Value`
|
|
38
|
-
# itself requires this file (`runtime/value.rb`'s own
|
|
39
|
-
# `require_relative "../rendering"`), so naming it here would be
|
|
40
|
-
# circular. A single-field wrapper (the overwhelming common case
|
|
41
|
-
# — an amount, an id, a lifecycle field) unwraps to its bare
|
|
42
|
-
# scalar, described recursively; a genuinely multi-field value
|
|
43
|
-
# renders as its fields' JSON, same as a bare Hash already does
|
|
44
|
-
# two lines up.
|
|
21
|
+
# Duck-typed on `to_h`: naming Runtime::Value here would be circular, since
|
|
22
|
+
# it requires this file. A one-field wrapper unwraps to its bare scalar.
|
|
45
23
|
if value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
|
|
46
24
|
fields = value.to_h
|
|
47
25
|
fields.size == 1 ? describe(fields.values.first) : JSON.generate(fields)
|
|
@@ -3,22 +3,13 @@ require_relative "../facade/handle"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
class Router
|
|
6
|
-
# Installs optional Ruby syntax over an already-loaded router
|
|
7
|
-
# resolution stays in Router
|
|
8
|
-
# method calls.
|
|
6
|
+
# Installs optional Ruby syntax over an already-loaded router; FQN
|
|
7
|
+
# resolution stays in Router, this only adapts it to constants and methods.
|
|
9
8
|
class NamespaceInstaller
|
|
10
|
-
# Returned by
|
|
11
|
-
#
|
|
12
|
-
# to the router, pinned to this specific FQN version rather than
|
|
13
|
-
# the router's default resolution.
|
|
9
|
+
# Returned by `.options(version:)`; forwards command/query verbs to the
|
|
10
|
+
# router, pinned to this FQN version rather than the router's default.
|
|
14
11
|
class OptionsProxy
|
|
15
|
-
#
|
|
16
|
-
# @param realm [String, nil] realm segment of the FQN this proxy pins
|
|
17
|
-
# @param domain [String] domain segment of the FQN this proxy pins
|
|
18
|
-
# @param aggregate [String, nil] aggregate segment, or nil for a domain-level query
|
|
19
|
-
# @param options [Hash{Symbol => Object}] the `.options(...)` call's keywords; only
|
|
20
|
-
# `:version` is accepted
|
|
21
|
-
# @raise [ArgumentError] if `options` has a key other than `:version`
|
|
12
|
+
# Only the `:version` keyword is accepted; any other key raises `ArgumentError`.
|
|
22
13
|
def initialize(router:, realm:, domain:, aggregate:, options:)
|
|
23
14
|
unknown = options.keys - [:version]
|
|
24
15
|
raise ArgumentError, "unknown router options: #{unknown.join(', ')}" unless unknown.empty?
|
|
@@ -52,16 +43,12 @@ module Hecks
|
|
|
52
43
|
end
|
|
53
44
|
end
|
|
54
45
|
|
|
55
|
-
# @param router [Router] router whose current routes get Ruby constants and methods
|
|
56
46
|
def initialize(router)
|
|
57
47
|
@router = router
|
|
58
48
|
end
|
|
59
49
|
|
|
60
|
-
# Installs every current-version route as a namespace constant/method,
|
|
61
|
-
#
|
|
62
|
-
# short `Aggregate.verb` shortcuts.
|
|
63
|
-
#
|
|
64
|
-
# @return [self]
|
|
50
|
+
# Installs every current-version route as a namespace constant/method, plus
|
|
51
|
+
# the aggregate CRUD door and short `Aggregate.verb` shortcuts.
|
|
65
52
|
def install!
|
|
66
53
|
current_entries.each { |entry| install_namespace_entry(entry) }
|
|
67
54
|
install_shortcuts!
|
|
@@ -90,18 +77,8 @@ module Hecks
|
|
|
90
77
|
end
|
|
91
78
|
end
|
|
92
79
|
|
|
93
|
-
# `.find`/`.all`/`.count`/`.events`/`.repository
|
|
94
|
-
#
|
|
95
|
-
# .boot`, missing here until now: `install_namespace_entry` above
|
|
96
|
-
# installs one method per declared verb, so an aggregate with no
|
|
97
|
-
# commands or queries of its own shape (or simply never asked for
|
|
98
|
-
# a `find`-shaped query) had no way to look up one record by id
|
|
99
|
-
# through the router surface at all — real gap, hit live building
|
|
100
|
-
# a `List` aggregate meant to be read this way. Grouped by
|
|
101
|
-
# (realm, domain, aggregate) rather than installed per-verb,
|
|
102
|
-
# because unlike a command or query this is the same five methods
|
|
103
|
-
# regardless of which verb happened to trigger this aggregate's
|
|
104
|
-
# own namespace module into existing.
|
|
80
|
+
# `.find`/`.all`/`.count`/`.events`/`.repository`, matching `Hecks.boot`'s
|
|
81
|
+
# door; grouped by (realm, domain, aggregate) since the five don't vary by verb.
|
|
105
82
|
def install_aggregate_doors!
|
|
106
83
|
current_entries.reject { |entry| entry.fqn.aggregate.nil? }
|
|
107
84
|
.group_by { |entry| [entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate] }
|
|
@@ -138,10 +115,9 @@ module Hecks
|
|
|
138
115
|
current_entries.reject { |entry| entry.fqn.aggregate.nil? }
|
|
139
116
|
.group_by { |entry| [entry.fqn.aggregate, entry.fqn.verb] }
|
|
140
117
|
.each do |(aggregate, verb), candidates|
|
|
141
|
-
# Bounded chapters wrap in their own module (`Domain::Aggregate`)
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
# get the shortcut — they are not BCs.
|
|
118
|
+
# Bounded chapters wrap in their own module (`Domain::Aggregate`) so two
|
|
119
|
+
# BCs can both declare `Person` without colliding on `Object::Person`.
|
|
120
|
+
# Folder-spread files of the same chapter aren't BCs, so they still get it.
|
|
145
121
|
next if bounded_chapter?(candidates)
|
|
146
122
|
|
|
147
123
|
shortcut_target(aggregate).define_singleton_method(verb) do |**args|
|
data/lib/hecks/router.rb
CHANGED
|
@@ -16,8 +16,7 @@ module Hecks
|
|
|
16
16
|
# @return [Router] the built router
|
|
17
17
|
def self.load(root) = new(Bluebook::ProjectLoader.load(root))
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
# `Router.load` API remains useful for tests and embedded hosts.
|
|
19
|
+
# Installs one project router as `.default` for ordinary application calls.
|
|
21
20
|
#
|
|
22
21
|
# @param root [String] project root to discover bluebooks under
|
|
23
22
|
# @return [Router] the booted router, also stored as `.default`
|
|
@@ -75,14 +74,12 @@ module Hecks
|
|
|
75
74
|
# Dispatches a command to its resolved aggregate.
|
|
76
75
|
#
|
|
77
76
|
# @param address [String] a fully-qualified command address, realm included
|
|
78
|
-
# @param args [Hash] command facts, plus
|
|
79
|
-
# and `:saga_correlation` keys
|
|
77
|
+
# @param args [Hash] command facts, plus optional `:to`, `:with`, `:saga_correlation`
|
|
80
78
|
# @return [Runtime::Dispatcher::Result] the dispatch result
|
|
81
79
|
# @raise [UnknownAddress] if `address` has no realm, or names no known route
|
|
82
80
|
# @raise [WrongVerbKind] if `address` names a query
|
|
83
81
|
# @raise [Runtime::UnknownVerb] if the resolved verb names something undeclared
|
|
84
|
-
# @raise [StandardError] any
|
|
85
|
-
# the call
|
|
82
|
+
# @raise [StandardError] any `Runtime::DOMAIN_REFUSALS` class when the domain refuses
|
|
86
83
|
# @raise [Runtime::StaleWrite] if concurrent writers beat this one through every retry
|
|
87
84
|
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
88
85
|
def dispatch(address, **args)
|
|
@@ -97,13 +94,12 @@ module Hecks
|
|
|
97
94
|
# @param address [String] a fully-qualified query address, realm included
|
|
98
95
|
# @param args [Hash{Symbol => Object}] the query's declared arguments
|
|
99
96
|
# @return [Array<Hash>] one row Hash per match; see `Runtime::Dispatcher#query`
|
|
100
|
-
# for the exact shape per address kind
|
|
101
97
|
# @raise [UnknownAddress] if `address` has no realm, or names no known route
|
|
102
98
|
# @raise [WrongVerbKind] if `address` names a command
|
|
103
99
|
# @raise [Runtime::UnknownVerb] if the resolved verb names something undeclared
|
|
104
100
|
# @raise [Runtime::NotFound] if a read model's root reference names no record
|
|
105
101
|
# @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its declared type
|
|
106
|
-
# @raise [KeyError] if a rooted read model
|
|
102
|
+
# @raise [KeyError] if a rooted read model lacks its reference argument
|
|
107
103
|
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
108
104
|
def query(address, **args)
|
|
109
105
|
entry = resolve(address)
|
|
@@ -1,29 +1,10 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Runtime
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# `:optimistic_concurrency` (Heki, Memory today; see
|
|
6
|
-
# `CommandInterpreter#call`/`EntityInterpreter#call`, which choose
|
|
7
|
-
# between this and Postgres's CAS+retry purely off
|
|
8
|
-
# `repository.capabilities`).
|
|
3
|
+
# A process-wide, striped mutex registry, keyed by `[domain, aggregate, id]`.
|
|
4
|
+
# Serializes hydrate-through-save for adapters without `:optimistic_concurrency`.
|
|
9
5
|
#
|
|
10
|
-
#
|
|
11
|
-
# process
|
|
12
|
-
# states the confirmed fact this relies on — two `Runtime.boot` calls
|
|
13
|
-
# get two entirely separate adapter instances; there is never a second
|
|
14
|
-
# process writing the same Heki file or the same Memory Hash, only
|
|
15
|
-
# possibly other threads within this one process. A `Mutex` held for
|
|
16
|
-
# the full hydrate-through-save critical section closes the identical
|
|
17
|
-
# lost-update gap CAS closes for Postgres, with no schema, no version
|
|
18
|
-
# column, and no retry loop — the second thread simply doesn't start
|
|
19
|
-
# its own hydrate until the first thread's save has landed.
|
|
20
|
-
#
|
|
21
|
-
# **Striped, not one global lock**: keyed by `[domain, aggregate.hecks_name,
|
|
22
|
-
# id]`, so two dispatches against two different records never block
|
|
23
|
-
# each other. The registry Hash itself is guarded by its own top-level
|
|
24
|
-
# Mutex only for the moment a new per-key Mutex is created — two
|
|
25
|
-
# threads locking different keys for the first time never wait on one
|
|
26
|
-
# another beyond that brief creation window.
|
|
6
|
+
# A lock suffices because those adapters (Heki, Memory) hold process-local data:
|
|
7
|
+
# only threads of this process can write the same record.
|
|
27
8
|
module AggregateLock
|
|
28
9
|
@registry_lock = Mutex.new
|
|
29
10
|
@locks = {}
|
|
@@ -31,19 +12,13 @@ module Hecks
|
|
|
31
12
|
class << self
|
|
32
13
|
# Returns the Mutex striped to one record, creating it on first use.
|
|
33
14
|
#
|
|
34
|
-
#
|
|
35
|
-
# `id: nil` — identity could not be resolved yet (see
|
|
36
|
-
# `Identity.best_effort`) — locks by aggregate type alone, coarser
|
|
37
|
-
# (every record of this aggregate serializes against every other)
|
|
38
|
-
# but still correct: it can only ever make dispatch more
|
|
39
|
-
# conservative than a resolved id would.
|
|
15
|
+
# AggregateLock.for(domain, aggregate, id).synchronize { ... }
|
|
40
16
|
#
|
|
41
17
|
# @param domain [String] the domain the aggregate belongs to
|
|
42
18
|
# @param aggregate [Bluebook::Aggregate] the aggregate being dispatched
|
|
43
|
-
# @param id [String, nil] the
|
|
44
|
-
#
|
|
45
|
-
# @return [Mutex] the mutex for this `[domain, aggregate, id]` key
|
|
46
|
-
# hydrate-through-save critical section
|
|
19
|
+
# @param id [String, nil] the record id; nil locks by aggregate type alone, which
|
|
20
|
+
# is coarser but still correct
|
|
21
|
+
# @return [Mutex] the mutex for this `[domain, aggregate, id]` key
|
|
47
22
|
def for(domain, aggregate, id = nil)
|
|
48
23
|
key = id.nil? ? [domain.to_s, aggregate.hecks_name] : [domain.to_s, aggregate.hecks_name, id.to_s]
|
|
49
24
|
@registry_lock.synchronize { @locks[key] ||= Mutex.new }
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Runtime
|
|
3
|
-
# A boot's own
|
|
4
|
-
#
|
|
5
|
-
# call, never a module-level singleton: a process that boots more than
|
|
6
|
-
# one registry in its lifetime (every spec suite does) must never let
|
|
7
|
-
# one boot's capability profile leak into the next boot's gate list.
|
|
3
|
+
# A boot's own set of phase-tagged, conditionally-present gates (ADR 0031).
|
|
4
|
+
# A gate is anything responding to `.call(registry, directory)`; run in registration order.
|
|
8
5
|
#
|
|
9
|
-
#
|
|
10
|
-
# module method handed over as a `Method` object (`EraCheck.method
|
|
11
|
-
# (:check_lineage!)`) needs no wrapper; a bare block does. Phases run
|
|
12
|
-
# in the order `run!` is called, gates within a phase in registration
|
|
13
|
-
# order — today exactly one gate per phase, so ordering among
|
|
14
|
-
# same-phase gates has never been exercised.
|
|
15
|
-
#
|
|
16
|
-
# This is deliberately not the same registry `Hecks::Projector` uses
|
|
17
|
-
# (ADR 0027) — that one is a process-wide, static IR-in/artifact-out
|
|
18
|
-
# registry with no bindings and no live-state mutation; this one is
|
|
19
|
-
# per-boot and gates real I/O (a Postgres mint, a saga-store read).
|
|
20
|
-
# Sharing a primitive between them is deferred until a third consumer
|
|
21
|
-
# actually wants it (0031's own Rejected Alternatives).
|
|
6
|
+
# Instance-scoped, one per `Loader.boot`, so one boot's gates never leak into the next.
|
|
22
7
|
class BootGates
|
|
23
8
|
# @return [void]
|
|
24
9
|
def initialize
|
data/lib/hecks/runtime/caller.rb
CHANGED
|
@@ -1,38 +1,14 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Runtime
|
|
3
|
-
# Who is dispatching, bound for the duration of a block
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# time (single-threaded by construction), this happens at dispatch
|
|
7
|
-
# time, which a future per-request caller (docs/rails-integration.md's
|
|
8
|
-
# still-unbuilt `WebDoor`) needs to bind safely under concurrency.
|
|
9
|
-
#
|
|
10
|
-
# A child `Thread.new` spawned mid-block does not inherit this — plain
|
|
11
|
-
# Ruby thread-local semantics — but nothing in this codebase spawns a
|
|
12
|
-
# worker thread mid-dispatch today, so this is a fact worth naming
|
|
13
|
-
# rather than a gap worth solving.
|
|
3
|
+
# Who is dispatching, bound for the duration of a block.
|
|
4
|
+
# `Thread.current`-backed, so per-request callers bind safely under concurrency;
|
|
5
|
+
# a child thread spawned mid-block does not inherit it.
|
|
14
6
|
module Caller
|
|
15
|
-
#
|
|
16
|
-
# here — a caller that states only a role (every caller before this)
|
|
17
|
-
# is checked the way it always was, string equality against the
|
|
18
|
-
# command's own `role`. A caller that also names who it is lets
|
|
19
|
-
# `CommandRules::Authorization` check a real Governance
|
|
20
|
-
# `RoleAssignment` instead, once the command's domain has Governance
|
|
21
|
-
# attached — see that module's own header for the full split.
|
|
7
|
+
# The bound caller: a role, plus optional `actor_id`, `as_of` and `scope`.
|
|
22
8
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# inside the interpreter (see `Ports::Clock`'s own header), so
|
|
27
|
-
# there is no other place to source it from. `scope` stays here
|
|
28
|
-
# rather than becoming a command-level DSL construct on purpose —
|
|
29
|
-
# a scope check that lived in the bluebook would put an
|
|
30
|
-
# authorization concern inside the domain declaration itself; this
|
|
31
|
-
# keeps it an application-boundary fact instead, the same shape
|
|
32
|
-
# `role`/`actor_id` already are. The tradeoff: a caller states what
|
|
33
|
-
# scope it is acting in, and `holds_role?` verifies a live grant
|
|
34
|
-
# exists for that scope — it does not independently confirm the
|
|
35
|
-
# scope matches whatever the command's own target data belongs to.
|
|
9
|
+
# All but `role` are self-asserted by the caller, not derived from the command;
|
|
10
|
+
# `scope` is deliberately not a DSL construct, and `holds_role?` checks only that a grant
|
|
11
|
+
# exists for it, not that it matches the command's target data.
|
|
36
12
|
Current = Struct.new(:role, :actor_id, :as_of, :scope, keyword_init: true)
|
|
37
13
|
|
|
38
14
|
module_function
|
|
@@ -43,27 +19,17 @@ module Hecks
|
|
|
43
19
|
# block is on the stack for this thread
|
|
44
20
|
def current = Thread.current[:hecks_caller]
|
|
45
21
|
|
|
46
|
-
# Binds the ambient caller for the duration of the block, restoring
|
|
47
|
-
#
|
|
22
|
+
# Binds the ambient caller for the duration of the block, restoring the previous
|
|
23
|
+
# one even if the block raises.
|
|
48
24
|
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
# @param
|
|
55
|
-
#
|
|
56
|
-
# @
|
|
57
|
-
# `CommandRules::Authorization` check a real Governance `RoleAssignment`
|
|
58
|
-
# instead of the bare role-string comparison
|
|
59
|
-
# @param as_of [Integer, nil] Unix epoch seconds the caller asserts as "now",
|
|
60
|
-
# filled at the door from `Ports::Clock.now`; nil leaves a `RoleAssignment`'s
|
|
61
|
-
# `starts_at` unchecked
|
|
62
|
-
# @param scope [String, nil] the scope the caller states it is acting in,
|
|
63
|
-
# checked against the matching `RoleAssignment`'s own `scope`; nil skips
|
|
64
|
-
# that check
|
|
65
|
-
# @yield the code that should see `role`/`actor_id`/`as_of`/`scope` as the
|
|
66
|
-
# ambient caller
|
|
25
|
+
# @param role [String] the role the caller holds, checked against the command's `role`
|
|
26
|
+
# @param actor_id [String, nil] who the caller is; when given, `CommandRules::Authorization`
|
|
27
|
+
# checks a real Governance `RoleAssignment`
|
|
28
|
+
# @param as_of [Integer, nil] Unix epoch seconds taken as "now"; nil leaves a
|
|
29
|
+
# `RoleAssignment`'s `starts_at` unchecked
|
|
30
|
+
# @param scope [String, nil] the scope acted in, checked against the `RoleAssignment`'s
|
|
31
|
+
# `scope`; nil skips that check
|
|
32
|
+
# @yield the code that sees these values as the ambient caller
|
|
67
33
|
# @return [Object] the block's result
|
|
68
34
|
def as(role:, actor_id: nil, as_of: nil, scope: nil)
|
|
69
35
|
previous = Thread.current[:hecks_caller]
|
|
@@ -75,14 +41,8 @@ module Hecks
|
|
|
75
41
|
Thread.current[:hecks_caller] = previous
|
|
76
42
|
end
|
|
77
43
|
|
|
78
|
-
# Clears the ambient caller for the duration of the block, restoring
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
# A reaction is the system acting, not the caller who happened to be
|
|
82
|
-
# on the stack when the triggering command ran — `Dispatcher#reenter`
|
|
83
|
-
# clears the ambient caller around a reaction's own dispatch so a
|
|
84
|
-
# triggering caller's role can neither satisfy nor block a reaction
|
|
85
|
-
# command it has nothing to do with.
|
|
44
|
+
# Clears the ambient caller for the duration of the block, restoring the previous one.
|
|
45
|
+
# A reaction is the system acting, so `Dispatcher#reenter` clears the caller around it.
|
|
86
46
|
#
|
|
87
47
|
# @yield the code that should see no ambient caller bound
|
|
88
48
|
# @return [Object] the block's result
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Runtime
|
|
3
|
-
# Which ports a boot can
|
|
4
|
-
#
|
|
5
|
-
# on ; `registry.adapters` names every adapter wired to implement one — the
|
|
6
|
-
# same `adapter.port == port.name` match `Ports::Extraction` and
|
|
7
|
-
# `Ports::IdentityGeneration` already make for themselves, one at a time,
|
|
8
|
-
# each time they resolve. This is that same question asked once, for
|
|
9
|
-
# every port at once, so a gap in the wiring is something a caller can ask
|
|
10
|
-
# about rather than something a live dispatch discovers by refusing.
|
|
11
|
-
#
|
|
12
|
-
# `cycles` answers `[]`, always, and honestly: nothing in this port model
|
|
13
|
-
# lets one port depend on another — a port names a verb and a signal, an
|
|
14
|
-
# adapter names the port it implements, and neither can point at a third
|
|
15
|
-
# port. There is no edge for a cycle to be made of.
|
|
3
|
+
# Which ports a boot can fulfill: each declared port matched to the adapters wired to it,
|
|
4
|
+
# so a wiring gap can be asked about before a dispatch refuses.
|
|
16
5
|
class CapabilityGraph
|
|
17
6
|
# @param registry [Runtime::Registry] the booted registry to read ports and adapters from
|
|
18
7
|
# @return [void]
|
|
@@ -20,9 +9,7 @@ module Hecks
|
|
|
20
9
|
@registry = registry
|
|
21
10
|
end
|
|
22
11
|
|
|
23
|
-
#
|
|
24
|
-
# declares — including the ports nothing implements, so a caller can
|
|
25
|
-
# tell "declared, unfulfilled" from "never declared at all".
|
|
12
|
+
# Maps each declared port's name to the names of its adapters, unfulfilled ports included.
|
|
26
13
|
#
|
|
27
14
|
# @return [Hash{String => Array<String>}] each declared port's name mapped to the names
|
|
28
15
|
# of every adapter bound to it (empty when none are)
|
|
@@ -30,17 +17,14 @@ module Hecks
|
|
|
30
17
|
@fulfillments ||= @registry.ports.each_key.to_h { |name| [name, adapters_for(name)] }
|
|
31
18
|
end
|
|
32
19
|
|
|
33
|
-
# The ports with zero adapters bound
|
|
34
|
-
# would otherwise only surface at the moment something tries to dispatch
|
|
35
|
-
# through one.
|
|
20
|
+
# The ports with zero adapters bound.
|
|
36
21
|
#
|
|
37
22
|
# @return [Array<String>] the names of every declared port with no adapter bound to it
|
|
38
23
|
def unfulfilled
|
|
39
24
|
fulfillments.select { |_, adapters| adapters.empty? }.keys
|
|
40
25
|
end
|
|
41
26
|
|
|
42
|
-
# Reports the port-dependency cycles
|
|
43
|
-
# header for why no port model here can express one.
|
|
27
|
+
# Reports the port-dependency cycles; always none, as a port cannot depend on another port.
|
|
44
28
|
#
|
|
45
29
|
# @return [Array] always empty
|
|
46
30
|
def cycles = []
|