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,366 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "../../hecks"
|
|
3
|
+
require_relative "mcp"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module CLI
|
|
7
|
+
# The MCP door onto the storehouse bus: hand-rolled JSON-RPC over stdio, one
|
|
8
|
+
# server per booted domain. All logic and the JSON shape answered with live in
|
|
9
|
+
# `Storehouse`; this is plumbing only. A `role:`/`actor_id:` a caller sends is
|
|
10
|
+
# asserted, not verified, unless Governance is attached.
|
|
11
|
+
#
|
|
12
|
+
# There is no authentication: `domain:` boots real Ruby confined to
|
|
13
|
+
# `Storehouse::BOOT_ROOT`, so this is a code execution surface for anyone who
|
|
14
|
+
# can write to stdin (ADR 0062). Spawning with `HECKS_DOOR_TOOLS=readers` and
|
|
15
|
+
# `HECKS_DOOR_DOMAINS=<dir>[:<dir>...]` narrows a door to read-only tools and
|
|
16
|
+
# named domains (`McpDoorScope`); this limits what one agent can reach and is
|
|
17
|
+
# not itself authentication.
|
|
18
|
+
module McpDoor
|
|
19
|
+
PROTOCOL_VERSION = "2024-11-05".freeze
|
|
20
|
+
|
|
21
|
+
SUMMARY_PROPERTY = {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "One line: why this call is happening. Required — it is what makes the audit trail legible later."
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
DOMAIN_PROPERTY = {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "The domain directory, containing a .hecksagon (e.g. \"examples/banking\"). The `domains` tool " \
|
|
29
|
+
"lists what's available."
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
ARGS_PROPERTY = {
|
|
33
|
+
type: "object",
|
|
34
|
+
description: "The command/query's declared arguments, nested value-object literals included " \
|
|
35
|
+
"(e.g. {\"reference\":{\"value\":\"BUG#1\"}})."
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
SOURCE_PROPERTY = {
|
|
39
|
+
type: "string",
|
|
40
|
+
enum: Hecks::Storehouse::SOURCE_TAGS,
|
|
41
|
+
description: "Optional: who is calling, for the audit log `follow` reads back."
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
ROLE_PROPERTY = {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Bind a caller identity for the call's duration, so a role-gated command's authorization is " \
|
|
47
|
+
"checked rather than merely documented. Required by `dispatch` for any command that declares a " \
|
|
48
|
+
"role — omitting it refuses the call rather than running it unchecked. Also required if " \
|
|
49
|
+
"`actor_id` is given."
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
ACTOR_ID_PROPERTY = {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Optional, alongside `role`: WHO holds that role — once the domain attaches Governance, checked " \
|
|
55
|
+
"against a real Governance::RoleAssignment instead of a bare string match. Requires `role`."
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
TOOLS = [
|
|
59
|
+
{
|
|
60
|
+
name: "dispatch",
|
|
61
|
+
description: "Issue a command against a booted domain — or, with `steps`, several in one call. `command` is " \
|
|
62
|
+
"the short or qualified verb name (the catalog/describe tools show what's available). Set " \
|
|
63
|
+
"`dry_run: true` to check whether it WOULD succeed without committing anything. Answers the " \
|
|
64
|
+
"record's id, its resulting state, and the events it announced — or a structured refusal, " \
|
|
65
|
+
"never a crash.",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: {
|
|
69
|
+
domain: DOMAIN_PROPERTY,
|
|
70
|
+
command: { type: "string", description: "The command's short or qualified name, e.g. \"open_account\". " \
|
|
71
|
+
"Ignored when `steps` is given." },
|
|
72
|
+
args: ARGS_PROPERTY,
|
|
73
|
+
summary: SUMMARY_PROPERTY,
|
|
74
|
+
source: SOURCE_PROPERTY,
|
|
75
|
+
role: ROLE_PROPERTY,
|
|
76
|
+
actor_id: ACTOR_ID_PROPERTY,
|
|
77
|
+
dry_run: { type: "boolean", description: "Preview only — check the command would succeed, commit nothing. " \
|
|
78
|
+
"Ignored when `steps` is given." },
|
|
79
|
+
steps: {
|
|
80
|
+
type: "array",
|
|
81
|
+
description: "Run several commands in one call, in order. Each item: {command, args}. When given, " \
|
|
82
|
+
"`command`/`args`/`dry_run` above are ignored.",
|
|
83
|
+
items: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: { command: { type: "string" }, args: ARGS_PROPERTY },
|
|
86
|
+
required: %w[command]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
required: %w[domain summary]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "query",
|
|
95
|
+
description: "Ask a declared question of a booted domain. `question` is the short or qualified query name. " \
|
|
96
|
+
"Answers the row array the query itself declares.",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
domain: DOMAIN_PROPERTY,
|
|
101
|
+
question: { type: "string", description: "The query's short or qualified name." },
|
|
102
|
+
args: ARGS_PROPERTY,
|
|
103
|
+
summary: SUMMARY_PROPERTY,
|
|
104
|
+
source: SOURCE_PROPERTY,
|
|
105
|
+
role: ROLE_PROPERTY,
|
|
106
|
+
actor_id: ACTOR_ID_PROPERTY
|
|
107
|
+
},
|
|
108
|
+
required: %w[domain question summary]
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "events",
|
|
113
|
+
description: "What actually HAPPENED, with payloads, to one aggregate/record — events THIS DOOR witnessed " \
|
|
114
|
+
"(every real dispatch ever routed through it, sourced from its own audit log — not a full " \
|
|
115
|
+
"domain-wide event-sourcing replay). Distinct from `state` (what's stored now) and `history` " \
|
|
116
|
+
"(append-only operation snapshots, no payload). Narrow with `aggregate` and (requires " \
|
|
117
|
+
"`aggregate`) `id`; `limit` keeps only the most recent.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
domain: DOMAIN_PROPERTY,
|
|
122
|
+
aggregate: { type: "string", description: "Narrow to one aggregate, e.g. \"Account\"." },
|
|
123
|
+
id: { type: "string", description: "Narrow to one record's own events. Requires `aggregate`." },
|
|
124
|
+
limit: { type: "integer", description: "Keep only the most recent N events. Omit for all of them." }
|
|
125
|
+
},
|
|
126
|
+
required: %w[domain]
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "state",
|
|
131
|
+
description: "Read what an aggregate actually has stored, no verb involved. Pass `id` for one record; " \
|
|
132
|
+
"omit it to list every record the aggregate currently holds.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {
|
|
136
|
+
domain: DOMAIN_PROPERTY,
|
|
137
|
+
aggregate: { type: "string", description: "The aggregate's name, e.g. \"Account\"." },
|
|
138
|
+
id: { type: "string", description: "A specific record's id. Omit to list every record." },
|
|
139
|
+
summary: SUMMARY_PROPERTY
|
|
140
|
+
},
|
|
141
|
+
required: %w[domain aggregate summary]
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "catalog",
|
|
146
|
+
description: "Zoom level one: every aggregate a domain declares, and every command/query name each " \
|
|
147
|
+
"answers to. No summary needed — this changes nothing and commits nothing to any log.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: { domain: DOMAIN_PROPERTY },
|
|
151
|
+
required: %w[domain]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "describe",
|
|
156
|
+
description: "Zoom level two: one aggregate's full contract — every command's arguments, the states it " \
|
|
157
|
+
"may be issued from, and every way it can refuse. Omit `aggregate` for the whole domain.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
domain: DOMAIN_PROPERTY,
|
|
162
|
+
aggregate: { type: "string", description: "Narrow to one aggregate. Omit for the whole domain." }
|
|
163
|
+
},
|
|
164
|
+
required: %w[domain]
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "validate",
|
|
169
|
+
description: "Zoom level three: is this domain's wiring sound — every bind names a declared aggregate, " \
|
|
170
|
+
"every adapter satisfies its port, the default adapter is usable. {valid:false, error:…} " \
|
|
171
|
+
"on a real wiring defect, never a bare crash. Set `deep: true` to also model-check the IR " \
|
|
172
|
+
"for dead lifecycle transitions, unreachable saga states, and dispatches to nowhere.",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
domain: DOMAIN_PROPERTY,
|
|
177
|
+
deep: { type: "boolean", description: "Also run the static model checker (lifecycle/saga/policy IR)." }
|
|
178
|
+
},
|
|
179
|
+
required: %w[domain]
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "domains",
|
|
184
|
+
description: "Zoom level zero: every domain directory found under a root (default \"examples\"), so a " \
|
|
185
|
+
"caller who doesn't already know a path can find one.",
|
|
186
|
+
inputSchema: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: { under: { type: "string", description: "Root directory to scan. Defaults to \"examples\"." } }
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "history",
|
|
193
|
+
description: "The full write history for every aggregate in a domain — every operation an append-only " \
|
|
194
|
+
"adapter ever recorded, not just current state. An aggregate on a non-append-only adapter " \
|
|
195
|
+
"answers an empty history honestly.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: { domain: DOMAIN_PROPERTY },
|
|
199
|
+
required: %w[domain]
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "behaviors",
|
|
204
|
+
description: "Run a domain's hand-curated `.behaviors` example tests and report pass/fail/error per test. " \
|
|
205
|
+
"`target` is a single `.behaviors` file or a directory to sweep.",
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: { target: { type: "string", description: "A .behaviors file, or a directory to sweep." } },
|
|
209
|
+
required: %w[target]
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "follow",
|
|
214
|
+
description: "Tail this door's own audit log for a domain — the last `limit` dispatch/query/state/dry-run " \
|
|
215
|
+
"calls made through it, each carrying its summary, source, and outcome.",
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
domain: DOMAIN_PROPERTY,
|
|
220
|
+
limit: { type: "integer", description: "How many recent entries to return. Defaults to 20." }
|
|
221
|
+
},
|
|
222
|
+
required: %w[domain]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
].freeze
|
|
226
|
+
|
|
227
|
+
NOTES = [
|
|
228
|
+
"Identity is self-asserted: role:/actor_id: are claims made by the caller and are not verified.",
|
|
229
|
+
"state, events, history, follow, describe, catalog and query run with no role check at all.",
|
|
230
|
+
"domain: boots real Ruby under #{Storehouse::BOOT_ROOT}; anyone who can write to stdin can run it."
|
|
231
|
+
].freeze
|
|
232
|
+
|
|
233
|
+
module_function
|
|
234
|
+
|
|
235
|
+
# Read once, so a bad setting refuses to start the door before anything is
|
|
236
|
+
# written to stdout.
|
|
237
|
+
def scope
|
|
238
|
+
@scope ||= McpDoorScope.start!(server: Mcp::SERVER)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# The standing notes, then what this door's scope says about `domain:`.
|
|
242
|
+
def warning_notes
|
|
243
|
+
NOTES[0, 2] + (scope.restricted? ? scope.notes : [NOTES[2]])
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def serve
|
|
247
|
+
McpStdioGuard.warn!(server: Mcp::SERVER, notes: warning_notes)
|
|
248
|
+
$stdout.sync = true
|
|
249
|
+
|
|
250
|
+
$stdin.each_line do |line|
|
|
251
|
+
line = line.strip
|
|
252
|
+
next if line.empty?
|
|
253
|
+
|
|
254
|
+
begin
|
|
255
|
+
request = JSON.parse(line)
|
|
256
|
+
rescue JSON::ParserError => e
|
|
257
|
+
send_error(nil, -32_700, "Parse error: #{e.message}")
|
|
258
|
+
next
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
begin
|
|
262
|
+
handle(request)
|
|
263
|
+
rescue StandardError => e
|
|
264
|
+
send_error(request["id"], -32_603, "Internal error: #{e.class}: #{e.message}")
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a reader
|
|
270
|
+
# door's `HECKS_DOOR_DOMAINS`, before anything boots.
|
|
271
|
+
def boot(domain)
|
|
272
|
+
Hecks.boot(Storehouse.confine!(scope.admit_domain!(domain), "domain"), install_facade: false)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# One `when` per tool, so the shared `rescue` catches every branch's escape
|
|
276
|
+
# once; a domain refusal is already `{ok: false, ...}` from `Storehouse`.
|
|
277
|
+
def call_tool(name, args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
278
|
+
case name
|
|
279
|
+
when "dispatch"
|
|
280
|
+
runtime = boot(args["domain"])
|
|
281
|
+
if args["steps"]
|
|
282
|
+
Storehouse.dispatch_batch(runtime: runtime, steps: args["steps"], summary: args["summary"],
|
|
283
|
+
source: args["source"], role: args["role"], actor_id: args["actor_id"])
|
|
284
|
+
else
|
|
285
|
+
Storehouse.dispatch(runtime: runtime, command: args["command"], summary: args["summary"],
|
|
286
|
+
args: args["args"] || {}, source: args["source"],
|
|
287
|
+
dry_run: args["dry_run"] == true, role: args["role"], actor_id: args["actor_id"])
|
|
288
|
+
end
|
|
289
|
+
when "query"
|
|
290
|
+
Storehouse.query(runtime: boot(args["domain"]), question: args["question"],
|
|
291
|
+
summary: args["summary"], args: args["args"] || {}, source: args["source"],
|
|
292
|
+
role: args["role"], actor_id: args["actor_id"])
|
|
293
|
+
when "events"
|
|
294
|
+
Storehouse.events(runtime: boot(args["domain"]), aggregate: args["aggregate"], id: args["id"],
|
|
295
|
+
limit: args["limit"])
|
|
296
|
+
when "state"
|
|
297
|
+
Storehouse.state(runtime: boot(args["domain"]), aggregate: args["aggregate"],
|
|
298
|
+
summary: args["summary"], id: args["id"])
|
|
299
|
+
when "catalog"
|
|
300
|
+
Storehouse.catalog(runtime: boot(args["domain"]))
|
|
301
|
+
when "describe"
|
|
302
|
+
Storehouse.describe(runtime: boot(args["domain"]), aggregate: args["aggregate"])
|
|
303
|
+
when "validate"
|
|
304
|
+
Storehouse.validate(domain: scope.admit_domain!(args["domain"]), deep: args["deep"] == true)
|
|
305
|
+
when "domains"
|
|
306
|
+
Storehouse.domains(under: args["under"] || "examples")
|
|
307
|
+
when "history"
|
|
308
|
+
Storehouse.history(runtime: boot(args["domain"]))
|
|
309
|
+
when "behaviors"
|
|
310
|
+
Storehouse.behaviors(target: args["target"])
|
|
311
|
+
when "follow"
|
|
312
|
+
Storehouse.follow(runtime: boot(args["domain"]), limit: args["limit"] || 20)
|
|
313
|
+
else
|
|
314
|
+
{ ok: false, error: "no such tool: #{name.inspect} — known: #{TOOLS.map { |t| t[:name] }.join(', ')}" }
|
|
315
|
+
end
|
|
316
|
+
rescue StandardError => e
|
|
317
|
+
# A defect, not a refusal: reaching here means a bad domain path or an
|
|
318
|
+
# argument shape `Facade::JsonDoor` could not symbolize.
|
|
319
|
+
{ ok: false, error: "#{e.class}: #{e.message}" }
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def answer_tool(name, args)
|
|
323
|
+
return scope.refusal(name) unless scope.permits_tool?(name)
|
|
324
|
+
|
|
325
|
+
call_tool(name, args)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def tool_result(payload)
|
|
329
|
+
{ content: [{ type: "text", text: JSON.pretty_generate(payload) }], isError: payload[:ok] == false }
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def send_response(id, result)
|
|
333
|
+
puts JSON.generate({ jsonrpc: "2.0", id: id, result: result })
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def send_error(id, code, message)
|
|
337
|
+
puts JSON.generate({ jsonrpc: "2.0", id: id, error: { code: code, message: message } })
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def handle(request)
|
|
341
|
+
id = request["id"]
|
|
342
|
+
method = request["method"]
|
|
343
|
+
params = request["params"] || {}
|
|
344
|
+
|
|
345
|
+
case method
|
|
346
|
+
when "initialize"
|
|
347
|
+
send_response(id, {
|
|
348
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
349
|
+
capabilities: { tools: {} },
|
|
350
|
+
serverInfo: { name: Mcp::SERVER, version: "1.1.0" }
|
|
351
|
+
})
|
|
352
|
+
when "notifications/initialized"
|
|
353
|
+
nil # a notification — no id, no response
|
|
354
|
+
when "tools/list"
|
|
355
|
+
send_response(id, { tools: TOOLS.select { |tool| scope.permits_tool?(tool[:name]) } })
|
|
356
|
+
when "tools/call"
|
|
357
|
+
send_response(id, tool_result(answer_tool(params["name"], params["arguments"] || {})))
|
|
358
|
+
when "ping"
|
|
359
|
+
send_response(id, {})
|
|
360
|
+
else
|
|
361
|
+
send_error(id, -32_601, "Method not found: #{method}") unless id.nil?
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
require_relative "../../hecks"
|
|
2
|
+
require_relative "../bluebook/model_check"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module CLI
|
|
6
|
+
# The command behind `bin/model_check` and `hecks model_check`: static analysis
|
|
7
|
+
# over the IR, printed per domain, exiting non-zero when any error-severity
|
|
8
|
+
# finding is left — unreachable lifecycle states, dead transitions, unreached
|
|
9
|
+
# saga states, dispatches to a nonexistent command, handlers for an event
|
|
10
|
+
# nothing emits. The runtime checks none of these; a dead transition never
|
|
11
|
+
# fires, in silence.
|
|
12
|
+
#
|
|
13
|
+
# Given domain paths it checks just those, cross-domain; given none it sweeps
|
|
14
|
+
# the repository's corpus and the language's own chapters, which needs `root:`.
|
|
15
|
+
module ModelCheck
|
|
16
|
+
# The directory the framework's own `.port` and `.adapter` files live under.
|
|
17
|
+
HECKS_DIR = File.expand_path("..", __dir__)
|
|
18
|
+
|
|
19
|
+
# How one run is configured: `strict` and `profile` from the command line,
|
|
20
|
+
# `rust_dir` from the root (nil without one).
|
|
21
|
+
Options = Struct.new(:strict, :profile, :rust_dir)
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Runs the model check `argv` describes and exits with its verdict.
|
|
26
|
+
#
|
|
27
|
+
# @param argv [Array<String>] `--strict`, `--profile NAME` and domain paths
|
|
28
|
+
# @param program [String] the name the usage message calls this command by
|
|
29
|
+
# @param root [String, nil] the repository root, for the corpus sweep and the
|
|
30
|
+
# Rust-target check; nil when there is no checkout
|
|
31
|
+
# @return [void]
|
|
32
|
+
# @raise [SystemExit] always: 0 when clean, 1 with findings or a usage error,
|
|
33
|
+
# 2 for an unknown profile
|
|
34
|
+
def call(argv, program:, root: nil)
|
|
35
|
+
argv = argv.dup
|
|
36
|
+
strict = !argv.delete("--strict").nil?
|
|
37
|
+
profile = take_profile(argv)
|
|
38
|
+
unless profile.nil? || Bluebook::ModelCheck::PROFILES.include?(profile)
|
|
39
|
+
warn "unknown profile #{profile.inspect} (known: #{Bluebook::ModelCheck::PROFILES.join(', ')})"
|
|
40
|
+
exit 2
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
options = Options.new(strict, profile, root && File.join(root, "rust"))
|
|
44
|
+
ok = if argv.any? then check_domains(argv, options)
|
|
45
|
+
elsif root then check_corpus(root, options)
|
|
46
|
+
else abort "usage: #{program} [--strict] [--profile client] <domain> [<domain> …]"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
puts
|
|
50
|
+
puts ok ? "No dead states, no unreachable protocol steps." : "THE MODEL HAS FINDINGS."
|
|
51
|
+
exit(ok ? 0 : 1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def take_profile(argv)
|
|
55
|
+
inline = argv.find { |arg| arg.start_with?("--profile=") }
|
|
56
|
+
index = argv.index("--profile")
|
|
57
|
+
return unless inline || index
|
|
58
|
+
|
|
59
|
+
name = inline ? inline.delete_prefix("--profile=") : argv[index + 1].to_s
|
|
60
|
+
argv.delete(inline) if inline
|
|
61
|
+
argv.slice!(index, 2) if index
|
|
62
|
+
name.to_sym
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Examines every named domain against one shared set of known domains and
|
|
66
|
+
# emitted events, so a cross-domain reaction between two of them is checked.
|
|
67
|
+
def check_domains(domain_args, options)
|
|
68
|
+
booted = domain_args.map do |domain_arg|
|
|
69
|
+
[File.basename(domain_arg.chomp("/")), boot(bluebook_dir(domain_arg.chomp("/")) || domain_arg)]
|
|
70
|
+
end
|
|
71
|
+
examine_all(booted, options)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Two passes over the same boots, because a single boot's own registry cannot
|
|
75
|
+
# answer whether a target domain exists anywhere in the corpus.
|
|
76
|
+
def check_corpus(root, options)
|
|
77
|
+
require_relative "../corpus"
|
|
78
|
+
targets = Corpus.model_check_members(root: root).map { |member| [member.stem, Corpus.source_of(member)] }
|
|
79
|
+
booted = targets.map { |name, source| [name, boot(source)] }
|
|
80
|
+
ok = examine_all(booted, options)
|
|
81
|
+
examine_language(options) && ok
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# `root:` is the source's parent, so a member declaring
|
|
85
|
+
# `uses_embryonaut_bluebook` can vendor from it.
|
|
86
|
+
def boot(source)
|
|
87
|
+
root = File.directory?(source) ? File.dirname(source) : nil
|
|
88
|
+
registry = Runtime::Registry.new(root: root)
|
|
89
|
+
Hecks.with_registry(registry) { load_source(source) }
|
|
90
|
+
registry
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @api private
|
|
94
|
+
def load_source(source)
|
|
95
|
+
Kernel.load(File.join(HECKS_DIR, "ports/persistence.port"))
|
|
96
|
+
Kernel.load(File.join(HECKS_DIR, "ports/extraction.port"))
|
|
97
|
+
Kernel.load(File.join(HECKS_DIR, "adapters/driven/memory.adapter"))
|
|
98
|
+
Kernel.load(File.join(HECKS_DIR, "adapters/driven/prism.adapter"))
|
|
99
|
+
folder = Adapters::Folder.new
|
|
100
|
+
if File.directory?(source)
|
|
101
|
+
folder.load_bluebooks(source)
|
|
102
|
+
else
|
|
103
|
+
folder.load_bluebooks(File.dirname(source), [File.basename(source)])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A port attaches to its aggregate from the hecksagon, not the bluebook, so a
|
|
107
|
+
# deaf-policy check that skipped it would miss a policy reacting to an event
|
|
108
|
+
# nothing emits. Recording a bind builds IR only; no adapter resolves here.
|
|
109
|
+
hecksagons = if File.directory?(source)
|
|
110
|
+
Dir.glob(File.join(source, "*.hecksagon"))
|
|
111
|
+
else
|
|
112
|
+
[source.sub(/\.bluebook\z/, ".hecksagon")]
|
|
113
|
+
end
|
|
114
|
+
hecksagons.each { |hecksagon| Kernel.load(hecksagon) if File.exist?(hecksagon) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @api private
|
|
118
|
+
def examine_all(booted, options)
|
|
119
|
+
known_domains = booted.flat_map { |_, registry| registry.bluebooks.keys + registry.hecksagons.keys }.to_set
|
|
120
|
+
global_emitted_events = booted.flat_map do |_, registry|
|
|
121
|
+
registry.bluebooks.values.flat_map { |chapter| Bluebook::ModelCheck.emitted_events(chapter) }
|
|
122
|
+
end.to_set
|
|
123
|
+
|
|
124
|
+
booted.reduce(true) do |all_ok, (name, registry)|
|
|
125
|
+
domain_passes?(name, registry, options,
|
|
126
|
+
known_domains: known_domains, global_emitted_events: global_emitted_events) && all_ok
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def domain_passes?(name, registry, options, known_domains:, global_emitted_events:)
|
|
131
|
+
puts "── #{name}"
|
|
132
|
+
|
|
133
|
+
rust_target = rust_target?(name, options.rust_dir)
|
|
134
|
+
chapters = registry.bluebooks.values
|
|
135
|
+
findings = chapters.flat_map do |chapter|
|
|
136
|
+
Bluebook::ModelCheck.call(chapter, hecksagon: registry.hecksagon(chapter.name), known_domains: known_domains,
|
|
137
|
+
global_emitted_events: global_emitted_events,
|
|
138
|
+
rust_target: rust_target,
|
|
139
|
+
strict: options.strict, profile: options.profile)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
allowed = Bluebook::ModelCheck::ALLOWED_FINDINGS.fetch(name, [])
|
|
143
|
+
matched, findings = findings.partition { |f| allowed.include?([f.kind, f.subject]) }
|
|
144
|
+
matched.each { |f| puts " ALLOWLISTED #{f}" }
|
|
145
|
+
|
|
146
|
+
stale = allowed - matched.map { |f| [f.kind, f.subject] }
|
|
147
|
+
unless stale.empty?
|
|
148
|
+
puts " STALE ALLOWLIST ENTRIES (no longer found — remove them): #{stale.inspect}"
|
|
149
|
+
return false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
passes?(findings, " clean — #{chapters.size} chapter(s), no dead states, no unreachable protocol steps")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Checked through `MetaValidator.grammar_registry`, the judged graph its
|
|
156
|
+
# conformance specs use, because any one grammar file booted alone is a
|
|
157
|
+
# fraction of the language.
|
|
158
|
+
def examine_language(options)
|
|
159
|
+
ok = true
|
|
160
|
+
["Bluebook", "World"].each do |name|
|
|
161
|
+
chapter = Bluebook::MetaValidator.grammar_registry.bluebook(name)
|
|
162
|
+
next unless chapter
|
|
163
|
+
|
|
164
|
+
puts "── #{name} (the language itself)"
|
|
165
|
+
findings = Bluebook::ModelCheck.call(chapter, strict: options.strict, profile: options.profile)
|
|
166
|
+
ok = passes?(findings, " clean — no dead states, no unreachable protocol steps") && ok
|
|
167
|
+
end
|
|
168
|
+
ok
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @api private
|
|
172
|
+
def passes?(findings, clean_line)
|
|
173
|
+
if findings.empty?
|
|
174
|
+
puts clean_line
|
|
175
|
+
return true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
errors, warnings = findings.partition { |f| f.severity == :error }
|
|
179
|
+
puts " #{errors.size} error(s), #{warnings.size} warning(s)"
|
|
180
|
+
findings.each { |f| puts " #{f}" }
|
|
181
|
+
errors.empty?
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# The repository-only fuzzing tooling is loaded lazily, only when there is a
|
|
185
|
+
# `rust/` to read.
|
|
186
|
+
def rust_target?(name, rust_dir)
|
|
187
|
+
return false unless rust_dir
|
|
188
|
+
|
|
189
|
+
require_relative "../fuzzing/target_capabilities"
|
|
190
|
+
Fuzzing::TargetCapabilities.rust_feature?(name, rust_dir)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# The same rule `Corpus.bluebook_dir` applies.
|
|
194
|
+
def bluebook_dir(domain_path)
|
|
195
|
+
[File.join(domain_path, "bluebook"), domain_path].each do |dir|
|
|
196
|
+
files = Dir[File.join(dir, "*.bluebook")]
|
|
197
|
+
return File.dirname(files.first) unless files.empty?
|
|
198
|
+
end
|
|
199
|
+
nil
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require_relative "../../hecks"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module CLI
|
|
6
|
+
# The command behind `bin/project_cli` and `hecks project_cli`: writes a
|
|
7
|
+
# command-line launcher beside each domain, named after its bluebook, so
|
|
8
|
+
# `Hecks.bluebook "QualityControl"` in `qa/` becomes `qa/quality_control`.
|
|
9
|
+
#
|
|
10
|
+
# Each launcher is a dozen lines pinning one directory and handing over to
|
|
11
|
+
# `Facade::CliRunner`; the surface itself is projected at the moment it runs,
|
|
12
|
+
# so editing a chapter changes behaviour without regenerating anything.
|
|
13
|
+
module ProjectCli
|
|
14
|
+
# Build output and other trees under a root that hold no domain a caller means.
|
|
15
|
+
IGNORED = %r{\A(rust|deploy|tmp|coverage)/}
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# `remove_stale_bin` deletes `root/bin/<name>`, where a launcher for the same
|
|
20
|
+
# chapter would otherwise linger as a second front door.
|
|
21
|
+
def call(argv, program:, root:, remove_stale_bin: true)
|
|
22
|
+
wanted = argv.empty? ? domains(root) : argv.map { |path| path.delete_prefix("#{root}/").chomp("/") }
|
|
23
|
+
|
|
24
|
+
wanted.each do |path|
|
|
25
|
+
name = bluebook_name(root, path) or next
|
|
26
|
+
snake = Naming.snake(name)
|
|
27
|
+
file = File.join(root, path, snake)
|
|
28
|
+
|
|
29
|
+
File.write(file, launcher(path, name, program))
|
|
30
|
+
FileUtils.chmod("+x", file)
|
|
31
|
+
FileUtils.rm_f(File.join(root, "bin", snake)) if remove_stale_bin
|
|
32
|
+
|
|
33
|
+
puts " #{path}/#{snake} -> #{name}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def domains(root)
|
|
38
|
+
folder = Adapters::Folder.new
|
|
39
|
+
Dir.glob(File.join(root, "**/*.hecksagon"))
|
|
40
|
+
.map { |path| folder.domain_root(File.dirname(path)) }
|
|
41
|
+
.compact
|
|
42
|
+
.map { |path| path.delete_prefix("#{root}/") }
|
|
43
|
+
.grep_v(IGNORED)
|
|
44
|
+
.uniq.sort
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Reads the name from `Hecks.bluebook "…"` rather than the directory, so a
|
|
48
|
+
# domain using `formerly_known_as` gets a launcher under its current name.
|
|
49
|
+
def bluebook_name(root, path)
|
|
50
|
+
Hecks.boot(File.join(root, path), install_facade: false).registry.bluebooks.keys.first
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
warn " #{path}: cannot boot — #{e.message.lines.first.strip}"
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def launcher(path, name, program)
|
|
57
|
+
snake = Naming.snake(name)
|
|
58
|
+
up = "../" * path.count("/").succ
|
|
59
|
+
|
|
60
|
+
<<~RUBY
|
|
61
|
+
#!/usr/bin/env ruby
|
|
62
|
+
|
|
63
|
+
# #{name}'s command line — GENERATED by #{program}. Do not edit.
|
|
64
|
+
#
|
|
65
|
+
# A launcher, not a CLI: the verbs, their arguments and their refusals are
|
|
66
|
+
# projected from the bluebook beside it every time this runs, so a change
|
|
67
|
+
# to the chapter shows up here without re-minting anything.
|
|
68
|
+
#
|
|
69
|
+
# #{path}/#{snake} every verb, and every question
|
|
70
|
+
# #{path}/#{snake} <verb> --help what it wants, and how it refuses
|
|
71
|
+
|
|
72
|
+
$LOAD_PATH.unshift File.expand_path("#{up}lib", __dir__)
|
|
73
|
+
|
|
74
|
+
require "hecks"
|
|
75
|
+
|
|
76
|
+
runtime = begin
|
|
77
|
+
Hecks.boot(__dir__, install_facade: false)
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
abort "cannot open #{name}: \#{e.message.lines.first.strip}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
text, status = Hecks::Facade::CliRunner.call(
|
|
83
|
+
runtime: runtime, argv: ARGV, program: "#{path}/#{snake}"
|
|
84
|
+
)
|
|
85
|
+
status.zero? ? puts(text) : abort(text)
|
|
86
|
+
RUBY
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|