hecks 0.2.0 → 0.3.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 +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +403 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +148 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +131 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +391 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +285 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +36 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +96 -0
- data/lib/hecks/behaviors/expectations.rb +280 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
- data/lib/hecks/bluebook/assembly/marks.rb +227 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +78 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +68 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
- data/lib/hecks/bluebook/expression/projection.json +170 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +630 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +452 -0
- data/lib/hecks/bluebook/model_check.rb +318 -0
- data/lib/hecks/bluebook/pattern_subset.rb +120 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +86 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +58 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +389 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +116 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +154 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +300 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +170 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +53 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +132 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +115 -0
- data/lib/hecks/forms/record_table.rb +63 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
- data/lib/hecks/fuzzing/properties.rb +1146 -0
- data/lib/hecks/fuzzing/replay.rb +635 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
- data/lib/hecks/fuzzing/value_generator.rb +177 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +291 -0
- data/lib/hecks/grammar/expression.bluebook +417 -0
- data/lib/hecks/grammar/expression_operators.json +1152 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +437 -0
- data/lib/hecks/language/bluebook/entity.bluebook +316 -0
- data/lib/hecks/language/bluebook/policy.bluebook +172 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +313 -0
- data/lib/hecks/language/port.bluebook +92 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +121 -0
- data/lib/hecks/ports/access_control.port +4 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +4 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +4 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +4 -0
- data/lib/hecks/ports/authorization.rb +59 -0
- data/lib/hecks/ports/clock.port +4 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +4 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +4 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +4 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +4 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +102 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +18 -0
- data/lib/hecks/ports/persistence/lineage.rb +277 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +76 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +413 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +31 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +437 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +141 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +392 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +80 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +89 -0
- data/lib/hecks/query_specification/hop_path.rb +130 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/caller.rb +50 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
- data/lib/hecks/runtime/command_interpreter.rb +413 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +181 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +231 -0
- data/lib/hecks/runtime/dispatcher.rb +286 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +228 -0
- data/lib/hecks/runtime/era_check.rb +136 -0
- data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/runtime/era_guard.rb +193 -0
- data/lib/hecks/runtime/era_tamper.rb +61 -0
- data/lib/hecks/runtime/errors.rb +100 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +128 -0
- data/lib/hecks/runtime/instance.rb +120 -0
- data/lib/hecks/runtime/interpreting.rb +69 -0
- data/lib/hecks/runtime/loader.rb +143 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +254 -0
- data/lib/hecks/runtime/reaction_invocation.rb +245 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +124 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
- data/lib/hecks/runtime/registry/verification.rb +158 -0
- data/lib/hecks/runtime/registry.rb +218 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +301 -0
- data/lib/hecks/runtime/storage_shape.rb +120 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +112 -0
- data/lib/hecks/runtime/value/coercion.rb +500 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +95 -0
- data/lib/hecks/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/translation/audit/layer_one.rb +37 -0
- data/lib/hecks/translation/audit/layer_two.rb +77 -0
- data/lib/hecks/translation/audit/unfed_report.rb +42 -0
- data/lib/hecks/translation/audit.rb +70 -0
- data/lib/hecks/translation/reattest.rb +72 -0
- data/lib/hecks/translation/rule_compiler.rb +120 -0
- data/lib/hecks/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/translation/scaffold.rb +30 -0
- data/lib/hecks/translation.rb +11 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +213 -0
- data/lib/hecks.rb +118 -6
- metadata +401 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
require_relative "docs_projector"
|
|
2
|
+
require_relative "../forms/field_shape"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Projector
|
|
6
|
+
# A BLUEBOOK, PROJECTED AS PROSE AN SME CAN READ BACK AND CONFIRM.
|
|
7
|
+
#
|
|
8
|
+
# WHAT THIS IS FOR. `DocsProjector` already answers "what can I call and
|
|
9
|
+
# what does it want" for the person implementing against a domain —
|
|
10
|
+
# tables of arguments, shapes, refusal reasons. That is the wrong
|
|
11
|
+
# register for the person who can actually say whether the domain is
|
|
12
|
+
# RIGHT: the subject-matter expert who knows what an account is and has
|
|
13
|
+
# never read a markdown table in their life. This projects the same IR
|
|
14
|
+
# as sentences instead — "Debit — take money out. Issued by a Teller. It
|
|
15
|
+
# only goes through if the balance covers it." — so a domain can be
|
|
16
|
+
# read back to the person who can validate it without them learning the
|
|
17
|
+
# DSL first.
|
|
18
|
+
#
|
|
19
|
+
# SAME SOURCE, SAME GUARANTEE `DocsProjector` gives: nothing here is
|
|
20
|
+
# invented. Every sentence quotes a `description`, `goal`, or `given`
|
|
21
|
+
# already declared in the chapter; where a chapter says nothing, this
|
|
22
|
+
# says nothing rather than manufacturing a sentence out of an
|
|
23
|
+
# identifier. Registered as `:narrate` beside `:docs`, same call shape
|
|
24
|
+
# (`Projector.call(:narrate, bluebook: ...)`), same aggregate-scoping
|
|
25
|
+
# via `options[:aggregate]`.
|
|
26
|
+
#
|
|
27
|
+
# WHAT IT DOES NOT DO: replace `DocsProjector`. A shape table still says
|
|
28
|
+
# "id of a Customer" more precisely than any sentence would, and an
|
|
29
|
+
# implementer still wants that. This is the other document the same IR
|
|
30
|
+
# is owed — one written for the reader who is being asked "is this
|
|
31
|
+
# right?", not "how do I call it?"
|
|
32
|
+
module NarrateProjector
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# `options[:heading]` sets the top heading level, exactly as
|
|
36
|
+
# `DocsProjector` does — so this, too, can be spliced into a larger
|
|
37
|
+
# document rather than always starting at H1.
|
|
38
|
+
def call(bluebook:, options: {})
|
|
39
|
+
depth = (options[:heading] || 1).to_i
|
|
40
|
+
only = options[:aggregate]
|
|
41
|
+
|
|
42
|
+
sections = []
|
|
43
|
+
sections << chapter_intro(bluebook, depth) unless only
|
|
44
|
+
Array(DocsProjector.aggregates(bluebook, only)).each do |aggregate|
|
|
45
|
+
sections << aggregate_narrative(aggregate, only ? depth : depth + 1)
|
|
46
|
+
end
|
|
47
|
+
sections << reactions_narrative(bluebook, depth + 1) unless only
|
|
48
|
+
"#{sections.compact.join("\n\n").rstrip}\n"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# ── the chapter ───────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def chapter_intro(bluebook, depth)
|
|
54
|
+
parts = [DocsProjector.h(depth, bluebook.name)]
|
|
55
|
+
parts << bluebook.vision if bluebook.vision
|
|
56
|
+
|
|
57
|
+
meta = []
|
|
58
|
+
meta << "This is a #{bluebook.classification} domain." if bluebook.classification
|
|
59
|
+
meta << "It was formerly known as `#{bluebook.formerly_known_as}`." if bluebook.formerly_known_as
|
|
60
|
+
parts << meta.join(" ") unless meta.empty?
|
|
61
|
+
|
|
62
|
+
names = bluebook.aggregates.map(&:hecks_name)
|
|
63
|
+
parts << "It's told through #{to_sentence_list(names)}." unless names.empty?
|
|
64
|
+
parts.join("\n\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ── one aggregate ─────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
def aggregate_narrative(aggregate, depth)
|
|
70
|
+
parts = [DocsProjector.h(depth, aggregate.hecks_name)]
|
|
71
|
+
parts << aggregate.description if aggregate.description
|
|
72
|
+
parts << identity_sentence(aggregate)
|
|
73
|
+
|
|
74
|
+
refs = aggregate.attributes.select(&:reference?)
|
|
75
|
+
parts << "Each one is linked to #{to_sentence_list(refs.map { |r| "#{a_or_an(r.type.target_name)} #{r.type.target_name}" })}." unless refs.empty?
|
|
76
|
+
|
|
77
|
+
parts << lifecycle_narrative(aggregate)
|
|
78
|
+
parts << verbs_narrative(aggregate, depth + 1)
|
|
79
|
+
parts << queries_narrative(aggregate.queries, aggregate.hecks_name, depth + 1)
|
|
80
|
+
|
|
81
|
+
aggregate.entities.each { |entity| parts << entity_narrative(aggregate, entity, depth + 1) }
|
|
82
|
+
parts.compact.join("\n\n")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def identity_sentence(holder)
|
|
86
|
+
return nil if holder.identity_heads.empty?
|
|
87
|
+
|
|
88
|
+
"Every #{holder.hecks_name} is identified by its #{to_sentence_list(holder.identity_heads.map { |h| "`#{h}`" })}."
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def entity_narrative(aggregate, entity, depth)
|
|
92
|
+
parts = [DocsProjector.h(depth, "#{entity.hecks_name} (within #{aggregate.hecks_name})")]
|
|
93
|
+
parts << entity.description if entity.description
|
|
94
|
+
parts << "Reached through its #{aggregate.hecks_name} — you address it by the #{aggregate.hecks_name}'s " \
|
|
95
|
+
"id together with its own `#{entity.identity_heads.join('`, `')}`."
|
|
96
|
+
|
|
97
|
+
parts << lifecycle_narrative(entity)
|
|
98
|
+
parts << verbs_narrative(entity, depth + 1)
|
|
99
|
+
parts << queries_narrative(entity.queries, entity.hecks_name, depth + 1)
|
|
100
|
+
parts.compact.join("\n\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ── the machine ───────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
def lifecycle_narrative(holder)
|
|
106
|
+
lifecycle = holder.lifecycle or return nil
|
|
107
|
+
|
|
108
|
+
sentences = ["It carries a `#{lifecycle.field}`, starting out at `#{lifecycle.default}`."]
|
|
109
|
+
lifecycle.transitions.each do |name, transition|
|
|
110
|
+
froms = Array(transition.from).map { |f| "`#{f}`" }
|
|
111
|
+
sentences << "**#{name}** moves it from #{to_sentence_list(froms, conj: 'or')} to `#{transition.target}`."
|
|
112
|
+
end
|
|
113
|
+
sentences << "A verb not listed here can be issued from any state."
|
|
114
|
+
sentences.join(" ")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ── the verbs ─────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
def verbs_narrative(holder, depth)
|
|
120
|
+
return nil if holder.commands.empty?
|
|
121
|
+
|
|
122
|
+
header = DocsProjector.h(depth, "What can happen to #{a_or_an(holder.hecks_name)} #{holder.hecks_name}")
|
|
123
|
+
body = holder.commands.map { |command| command_paragraph(command, holder) }.join("\n\n")
|
|
124
|
+
"#{header}\n\n#{body}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def command_paragraph(command, holder)
|
|
128
|
+
# THE GOAL, VERBATIM — same rule `DocsProjector` holds to: quoted
|
|
129
|
+
# exactly as declared, not recased to fit mid-sentence, because the
|
|
130
|
+
# promise this whole projector makes is that a sentence here is a
|
|
131
|
+
# sentence the chapter actually wrote.
|
|
132
|
+
sentences = ["**#{command.hecks_name}**#{command.goal ? " — #{command.goal}." : '.'}"]
|
|
133
|
+
sentences << "Issued by #{a_or_an(command.role)} #{command.role}." if command.role
|
|
134
|
+
# `acts_on.nil?`, NOT `creates?` — `creates?` answers true for every
|
|
135
|
+
# verb an ENTITY declares (it never references itself; see
|
|
136
|
+
# `Command#acts_on`'s own comment), so reading it directly here would
|
|
137
|
+
# tell an SME that `LedgerEntry.Amend` brings a new ledger entry into
|
|
138
|
+
# being, which is exactly backwards.
|
|
139
|
+
sentences << "This is how a new #{holder.hecks_name} comes into being." if command.acts_on.nil?
|
|
140
|
+
|
|
141
|
+
arguments = command.attributes.reject(&:reference?)
|
|
142
|
+
sentences << "It takes #{to_sentence_list(arguments.map { |a| Forms::Humanize.label(a.name.to_s).downcase })}." unless arguments.empty?
|
|
143
|
+
|
|
144
|
+
refs = command.attributes.select(&:reference?)
|
|
145
|
+
sentences << "It's aimed at one existing #{to_sentence_list(refs.map { |r| r.type.target_name })}, by id." unless refs.empty?
|
|
146
|
+
|
|
147
|
+
conditions = conditions_of(command, holder)
|
|
148
|
+
sentences << "It only goes through if #{conditions.join('; ')}." unless conditions.empty?
|
|
149
|
+
|
|
150
|
+
guarantees = command.ensures.map(&:description)
|
|
151
|
+
sentences << "When it succeeds: #{guarantees.join('; ')}." unless guarantees.empty?
|
|
152
|
+
|
|
153
|
+
sentences << "It records `#{command.emits.join('`, `')}` as a fact." unless command.emits.empty?
|
|
154
|
+
|
|
155
|
+
sentences.join(" ")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# EVERY REQUIRED CONDITION, STATED AS SOMETHING THAT MUST BE TRUE —
|
|
159
|
+
# the same three sources `DocsProjector#refusals_of` reads (the
|
|
160
|
+
# lifecycle edge, a reference's existence, and the command's own
|
|
161
|
+
# `given`s), but kept positive rather than phrased as a refusal
|
|
162
|
+
# reason. "Refused unless not X" is a sentence a reader has to
|
|
163
|
+
# invert in their head; "only goes through if X" is not.
|
|
164
|
+
def conditions_of(command, holder)
|
|
165
|
+
conditions = []
|
|
166
|
+
|
|
167
|
+
lifecycle = holder.lifecycle
|
|
168
|
+
if lifecycle
|
|
169
|
+
froms = lifecycle.transitions.filter_map { |name, transition|
|
|
170
|
+
Array(transition.from) if name.to_s == command.hecks_name
|
|
171
|
+
}.flatten.uniq
|
|
172
|
+
conditions << "its `#{lifecycle.field}` is currently #{to_sentence_list(froms.map { |f| "`#{f}`" }, conj: 'or')}" unless froms.empty?
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
conditions + command.givens.map(&:description)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# ── the reads ─────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
def queries_narrative(queries, holder_name, depth)
|
|
181
|
+
return nil if queries.empty?
|
|
182
|
+
|
|
183
|
+
header = DocsProjector.h(depth, "Questions you can ask about #{a_or_an(holder_name)} #{holder_name}")
|
|
184
|
+
lines = queries.map do |query|
|
|
185
|
+
shape = query.to_h
|
|
186
|
+
takes = Array(shape[:attributes]).map { |a| Forms::Humanize.label(a[:name].to_s).downcase }
|
|
187
|
+
# `w[:value]` ALREADY WEARS ITS OWN QUOTES OR COLON — it is a
|
|
188
|
+
# `Literal.render`ed string (see lib/hecks/literal.rb), not a raw
|
|
189
|
+
# Ruby value, so wrapping it in `.inspect` here would quote an
|
|
190
|
+
# already-quoted string a second time.
|
|
191
|
+
filters = Array(shape[:wheres]).map { |w| "`#{w[:field]}` #{op_words(w[:op])} #{w[:value]}" }
|
|
192
|
+
|
|
193
|
+
sentence = "- **#{query.hecks_name}**"
|
|
194
|
+
sentence << " (given #{to_sentence_list(takes)})" unless takes.empty?
|
|
195
|
+
sentence << " — #{query.description}" if query.description
|
|
196
|
+
sentence << "." unless sentence.end_with?(".")
|
|
197
|
+
sentence << " Only where #{to_sentence_list(filters)}." unless filters.empty?
|
|
198
|
+
sentence
|
|
199
|
+
end
|
|
200
|
+
"#{header}\n\n#{lines.join("\n")}"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def op_words(op)
|
|
204
|
+
{ eq: "is", lt: "under", lte: "at most", gt: "over", gte: "at least" }[op.to_s.to_sym] || op.to_s
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# ── what happens on its own ───────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
def reactions_narrative(bluebook, depth)
|
|
210
|
+
return nil if bluebook.policies.empty? && bluebook.process_managers.empty?
|
|
211
|
+
|
|
212
|
+
parts = [DocsProjector.h(depth, "Reactions")]
|
|
213
|
+
bluebook.policies.each do |policy|
|
|
214
|
+
elsewhere = policy.target_domain ? " in #{policy.target_domain}" : ""
|
|
215
|
+
parts << "Whenever `#{policy.on_event}` happens, `#{policy.trigger_command}` fires on its own#{elsewhere} — nobody has to ask for it."
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
bluebook.process_managers.each do |saga|
|
|
219
|
+
shape = saga.to_h
|
|
220
|
+
parts << "**#{shape[:name]}** is a saga: it starts when `#{shape[:starts_on]}` happens and ends when " \
|
|
221
|
+
"`#{shape[:ends_on]}` happens, with each run tracked by its `#{shape[:correlates_by]}`. Along the " \
|
|
222
|
+
"way it moves through #{Array(shape[:states]).map { |s| "`#{s}`" }.join(' → ')}."
|
|
223
|
+
end
|
|
224
|
+
parts.join("\n\n")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# ── small sentence carpentry ──────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
def to_sentence_list(items, conj: "and")
|
|
230
|
+
case items.size
|
|
231
|
+
when 0 then ""
|
|
232
|
+
when 1 then items[0].to_s
|
|
233
|
+
when 2 then "#{items[0]} #{conj} #{items[1]}"
|
|
234
|
+
else "#{items[0..-2].join(', ')}, #{conj} #{items[-1]}"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def a_or_an(word)
|
|
239
|
+
%w[a e i o u].include?(word.to_s[0].to_s.downcase) ? "an" : "a"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Projector
|
|
3
|
+
# WHAT MAKES A MODULE A PROJECTION TARGET. `Projector.register` has
|
|
4
|
+
# always accepted anything answering `call(bluebook:, options:)` —
|
|
5
|
+
# this only removes the second step, so a target declares its own key
|
|
6
|
+
# beside its own implementation instead of being registered from
|
|
7
|
+
# somewhere else that has to be kept in sync with it.
|
|
8
|
+
#
|
|
9
|
+
# module Hecks::Projections::OIDC
|
|
10
|
+
# extend Hecks::Projector::Target
|
|
11
|
+
# projects_as :oidc
|
|
12
|
+
#
|
|
13
|
+
# module_function
|
|
14
|
+
#
|
|
15
|
+
# def call(bluebook:, options: {}) = { ... }
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# NAMED `Target`, NOT `Projection`, on purpose. "Projection" already
|
|
19
|
+
# means two other things in this codebase: `Ports::Projection` is
|
|
20
|
+
# read-model catch-up (events folded into view state), and `bin/project`
|
|
21
|
+
# forces that catch-up by hand. Neither has anything to do with
|
|
22
|
+
# "canonical IR in, external artifact out". A third meaning under the
|
|
23
|
+
# same word would make the two impossible to grep apart — and from a
|
|
24
|
+
# domain's point of view `project(X)` really does read as "project TO
|
|
25
|
+
# a target", so the narrower word is also the more accurate one.
|
|
26
|
+
module Target
|
|
27
|
+
# Registering at declaration time means `require`ing a target is
|
|
28
|
+
# the whole of installing it — there is no separate manifest that
|
|
29
|
+
# can silently disagree about which targets exist.
|
|
30
|
+
#
|
|
31
|
+
# `requires:` NAMES A CAPABILITY, not a shape word.
|
|
32
|
+
#
|
|
33
|
+
# This began as `from: :chapter` / `from: :any` — two hand-kept
|
|
34
|
+
# symbols, admitted by duck-typing on `.aggregates`, which is a
|
|
35
|
+
# guess at what "is a chapter" means. The capabilities were already
|
|
36
|
+
# real by then: `Hecks::IR` is the ability to emit IR (its own
|
|
37
|
+
# header says so), and `Behaviour::Chapter` is the ability to answer
|
|
38
|
+
# as a chapter. So a projection names the module it needs, and
|
|
39
|
+
# admission is a genuine check rather than a proxy for one.
|
|
40
|
+
#
|
|
41
|
+
# projects_as :ir, requires: Hecks::IR
|
|
42
|
+
# projects_as :vocabulary # chapter, the default
|
|
43
|
+
#
|
|
44
|
+
# It also composes: a projection needing two capabilities names
|
|
45
|
+
# both, instead of a third symbol being invented for the pair.
|
|
46
|
+
#
|
|
47
|
+
# THE FAIL-QUIET THIS CLOSES, unchanged in substance: every
|
|
48
|
+
# construct emits its own IR, so handing a projector an AGGREGATE
|
|
49
|
+
# instead of a chapter is the natural thing to try. `bluebook:` was
|
|
50
|
+
# only ever a PARAMETER NAME, never a contract. `:oidc` failed
|
|
51
|
+
# loudly (no `aggregates` method), but `:shape` returned
|
|
52
|
+
# `{"name" => "Order", "aggregates" => []}` — well-formed,
|
|
53
|
+
# confident, and wrong.
|
|
54
|
+
# `declares:` NAMES AN AGGREGATE THE CHAPTER MUST HAVE.
|
|
55
|
+
#
|
|
56
|
+
# A capability says what a construct can DO; this says what it must
|
|
57
|
+
# CARRY. `:vocabulary` needs a chapter declaring a Vocabulary
|
|
58
|
+
# aggregate, `:parser_table` one declaring Syntax — and both used
|
|
59
|
+
# to state that as a `raise` in their own body, which is a
|
|
60
|
+
# requirement written as behaviour instead of declared. Stated
|
|
61
|
+
# here, the registry refuses before the projection runs and the
|
|
62
|
+
# projection stops carrying a guard about its own admission.
|
|
63
|
+
# `emits:` SAYS WHAT KIND OF ARTIFACT COMES BACK.
|
|
64
|
+
#
|
|
65
|
+
# `:artifact` (the default) is one thing — a Hash, or a String.
|
|
66
|
+
# `:files` is a TREE: a Hash of relative path => contents, which is
|
|
67
|
+
# what a reference-page or codegen projection produces.
|
|
68
|
+
#
|
|
69
|
+
# Declared rather than sniffed, deliberately. Inferring a tree from
|
|
70
|
+
# an ordinary Hash is guesswork — `{"name" => "Pizzas"}` is
|
|
71
|
+
# indistinguishable from a one-file tree — so `write` asks what the
|
|
72
|
+
# projection said rather than inspecting what it returned.
|
|
73
|
+
def projects_as(key, requires: nil, declares: nil, emits: :artifact)
|
|
74
|
+
@projection_emits = emits
|
|
75
|
+
@projection_key = key.to_sym
|
|
76
|
+
@projection_declares = Array(declares)
|
|
77
|
+
@projection_requires = Array(requires)
|
|
78
|
+
Projector.register(@projection_key, self)
|
|
79
|
+
@projection_key
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def projection_key = @projection_key
|
|
83
|
+
|
|
84
|
+
# Empty means "a chapter" — resolved HERE rather than as a default
|
|
85
|
+
# argument, because Behaviour::Chapter is not loaded yet when this
|
|
86
|
+
# file is.
|
|
87
|
+
def projection_declares = @projection_declares || []
|
|
88
|
+
|
|
89
|
+
def projection_emits = @projection_emits || :artifact
|
|
90
|
+
|
|
91
|
+
def projection_requires
|
|
92
|
+
req = @projection_requires
|
|
93
|
+
req.nil? || req.empty? ? [Bluebook::Behaviour::Chapter] : req
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# A named registry of "canonical IR in, external artifact out" tools —
|
|
3
|
+
# §30 of docs/HECKS_IMPLEMENTATION_PLAN.md. Before this existed, every
|
|
4
|
+
# such tool was its own thing with its own call shape: `Exporter`
|
|
5
|
+
# (registry-wide, consumed directly by bin/ir, bin/project_rust, and
|
|
6
|
+
# translation/audit's approval digest — untouched by this file, still
|
|
7
|
+
# exactly what those three read), and `RustProjection::Projector`
|
|
8
|
+
# (`rust/project.rb`, a whole separate Ruby program under a
|
|
9
|
+
# confusingly-identical module name) are the two that already exist.
|
|
10
|
+
# Neither is registered here — retrofitting either is real, separate
|
|
11
|
+
# work (Rust's generator is a whole second toolchain; a UL/OIDC
|
|
12
|
+
# projector doesn't exist yet at all) — but `:ir` is, as a genuine,
|
|
13
|
+
# working, golden-tested example of the shape every future projector
|
|
14
|
+
# (`:rust`, `:ul`, `:openid`, ...) is meant to follow.
|
|
15
|
+
#
|
|
16
|
+
# The unit is ONE bluebook's IR, not a whole booted registry — matching
|
|
17
|
+
# every real projection target (Rust/UL/OIDC all project one domain at
|
|
18
|
+
# a time), and deliberately narrower than `Exporter.call`'s own
|
|
19
|
+
# multi-domain shape.
|
|
20
|
+
# THREE KINDS OF "PROJECT", TOLD APART BY WHAT THEY NEED AS INPUT.
|
|
21
|
+
# Only the first belongs in this registry.
|
|
22
|
+
#
|
|
23
|
+
# A PROJECTION takes a chapter's DECLARATION and answers something
|
|
24
|
+
# that DESCRIBES the domain: its IR, its storage shape, an OIDC scope
|
|
25
|
+
# manifest, the parser's keyword table, the reference pages. Inert,
|
|
26
|
+
# derived, and runnable against any chapter that carries what it
|
|
27
|
+
# declares it needs. These are what `register` holds.
|
|
28
|
+
#
|
|
29
|
+
# AN EXPORT takes a declaration AND its BINDINGS and answers
|
|
30
|
+
# something that IS the domain, running elsewhere — rust/project.rb's
|
|
31
|
+
# generated crate, the WASM artifact, the SAM template
|
|
32
|
+
# bin/project_deploy renders. It needs the `.world`/`.hecksagon` a
|
|
33
|
+
# projection never looks at, because a running system has to know how
|
|
34
|
+
# it is wired. That is the whole reason bin/project_deploy cannot use
|
|
35
|
+
# this protocol: `call(bluebook:, options:)` has no channel for it.
|
|
36
|
+
#
|
|
37
|
+
# A STATE PROJECTION takes RECORDS — a domain after dispatch — and is
|
|
38
|
+
# a read-model question wearing the same word.
|
|
39
|
+
# `bin/expression_projection` is the one of these: its operators are
|
|
40
|
+
# not declared anywhere, they are what exists after
|
|
41
|
+
# `Grammar.expression` replays expression_operators.json's ledger of
|
|
42
|
+
# dispatches. Converting it into this registry would be a category
|
|
43
|
+
# error, however much its name suggests otherwise.
|
|
44
|
+
#
|
|
45
|
+
# ONE WORD, THREE OTHER MEANINGS — worth naming too, because grepping
|
|
46
|
+
# "projection" turns all of these up and none is the above:
|
|
47
|
+
#
|
|
48
|
+
# Ports::Projection read-model catch-up, events folded into state
|
|
49
|
+
# bin/project forces that catch-up by hand
|
|
50
|
+
# RustProjection rust/project.rb's own separate toolchain
|
|
51
|
+
module Projector
|
|
52
|
+
class UnknownProjector < StandardError; end
|
|
53
|
+
# A chapter-scoped projector was handed something that is not a chapter.
|
|
54
|
+
class WrongConstruct < StandardError; end
|
|
55
|
+
|
|
56
|
+
module_function
|
|
57
|
+
|
|
58
|
+
# `projector` needs only to answer `call(bluebook:, options:)` — a
|
|
59
|
+
# module, a class with a class method, or any object responding to
|
|
60
|
+
# `call` all work. Re-registering a name replaces it outright,
|
|
61
|
+
# deliberately unguarded: a spec re-registering a stub under the same
|
|
62
|
+
# name between examples is the ordinary case, not a footgun to fence
|
|
63
|
+
# against.
|
|
64
|
+
def register(name, projector)
|
|
65
|
+
registry[name.to_sym] = projector
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# `bluebook:` is kept as the keyword because it is the shipped
|
|
69
|
+
# spelling and every existing caller uses it — but what it accepts is
|
|
70
|
+
# any construct that emits IR, and `admits!` is what decides whether
|
|
71
|
+
# THIS target can actually take the one handed over.
|
|
72
|
+
def call(name, bluebook:, options: {})
|
|
73
|
+
projector = registry.fetch(name.to_sym) {
|
|
74
|
+
raise UnknownProjector, "no projector registered for #{name.inspect} — registered: #{registered.sort.inspect}"
|
|
75
|
+
}
|
|
76
|
+
admits!(name, projector, bluebook)
|
|
77
|
+
projector.call(bluebook: bluebook, options: options)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# A projection names the CAPABILITIES it needs; this refuses a
|
|
81
|
+
# construct that lacks one, before the projector runs.
|
|
82
|
+
#
|
|
83
|
+
# ONE CHECK COVERS BOTH SHAPES. An ordinary construct INCLUDES its
|
|
84
|
+
# capabilities and a class-shaped one — Command, Entity, ValueObject
|
|
85
|
+
# — EXTENDS them, and `is_a?` consults the singleton chain, so it
|
|
86
|
+
# answers for an extended module as readily as an included one. This
|
|
87
|
+
# started as two checks on the assumption it would not; a spec
|
|
88
|
+
# asserting the assumption failed, which is the only reason the
|
|
89
|
+
# redundant half was noticed.
|
|
90
|
+
def admits!(name, projector, construct)
|
|
91
|
+
needed = projector.respond_to?(:projection_requires) ? projector.projection_requires : []
|
|
92
|
+
missing = needed.reject { |capability| capable?(construct, capability) }
|
|
93
|
+
unless missing.empty?
|
|
94
|
+
raise WrongConstruct,
|
|
95
|
+
"#{name.inspect} needs #{missing.map(&:name).join(' and ')}, and was handed " \
|
|
96
|
+
"#{construct.class} (#{construct.respond_to?(:hecks_name) ? construct.hecks_name : construct.inspect})."
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
declared = projector.respond_to?(:projection_declares) ? projector.projection_declares : []
|
|
100
|
+
absent = declared.reject { |named| construct.aggregate(named) }
|
|
101
|
+
return if absent.empty?
|
|
102
|
+
|
|
103
|
+
raise WrongConstruct,
|
|
104
|
+
"#{name.inspect} needs a chapter declaring #{absent.join(' and ')}; " \
|
|
105
|
+
"#{construct.name} declares no such aggregate."
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def capable?(construct, capability) = construct.is_a?(capability)
|
|
109
|
+
|
|
110
|
+
# What KIND of artifact a registered target emits — asked of the
|
|
111
|
+
# projection rather than inferred from what it returned.
|
|
112
|
+
def emits_for(name)
|
|
113
|
+
projector = registry.fetch(name.to_sym) { return :artifact }
|
|
114
|
+
projector.respond_to?(:projection_emits) ? projector.projection_emits : :artifact
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def registered?(name) = registry.key?(name.to_sym)
|
|
118
|
+
def registered = registry.keys
|
|
119
|
+
|
|
120
|
+
def registry
|
|
121
|
+
@registry ||= {}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# A target may be addressed by the constant that implements it
|
|
125
|
+
# (`Projections::OIDC`) or by the bare key it registered under
|
|
126
|
+
# (`:oidc`). Both resolve here, so the constant form is added
|
|
127
|
+
# surface rather than a replacement — every `Projector.call(:ir, ...)`
|
|
128
|
+
# written before this existed keeps working untouched.
|
|
129
|
+
def key_for(target)
|
|
130
|
+
return target.projection_key if target.respond_to?(:projection_key) && target.projection_key
|
|
131
|
+
|
|
132
|
+
target
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# WRITING IS THE CALLER'S CHOICE, NOT THE PROJECTOR'S. A projector
|
|
136
|
+
# returns an artifact and never touches disk, which is what lets
|
|
137
|
+
# spec/projector_spec.rb compare `:ir`'s output against a golden
|
|
138
|
+
# fixture without a tmpdir. `out:` is the only thing that writes.
|
|
139
|
+
#
|
|
140
|
+
# `as:` comes from the projection's own `emits:` declaration rather
|
|
141
|
+
# than from inspecting the artifact. A Hash of path => contents and a
|
|
142
|
+
# Hash that simply happens to hold strings are the same object to
|
|
143
|
+
# Ruby; only the projection knows which it meant.
|
|
144
|
+
def write(artifact, out, as: :artifact)
|
|
145
|
+
return write_tree(artifact, out) if as == :files
|
|
146
|
+
|
|
147
|
+
File.write(out, artifact.is_a?(String) ? artifact : "#{JSON.pretty_generate(artifact)}\n")
|
|
148
|
+
out
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Answers the paths written, in the order given — so a caller can
|
|
152
|
+
# report what happened without re-deriving it from the tree.
|
|
153
|
+
def write_tree(files, directory)
|
|
154
|
+
require "fileutils"
|
|
155
|
+
files.map do |relative, contents|
|
|
156
|
+
path = File.join(directory, relative)
|
|
157
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
158
|
+
File.write(path, contents)
|
|
159
|
+
path
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
require "json"
|
|
166
|
+
|
|
167
|
+
require_relative "projector/exporter"
|
|
168
|
+
require_relative "projector/ir_projector"
|
|
169
|
+
require_relative "projector/target"
|
|
170
|
+
# Neither of these requires this file back — they need `Naming` and nothing
|
|
171
|
+
# else — so unlike a target they are safe to pull in from here.
|
|
172
|
+
require_relative "projector/docs_projector"
|
|
173
|
+
require_relative "projector/narrate_projector"
|
|
174
|
+
require_relative "projector/cli_projector"
|
|
175
|
+
|
|
176
|
+
Hecks::Projector.register(:ir, Hecks::Projector::IRProjector)
|
|
177
|
+
Hecks::Projector.register(:docs, Hecks::Projector::DocsProjector)
|
|
178
|
+
Hecks::Projector.register(:narrate, Hecks::Projector::NarrateProjector)
|
|
179
|
+
Hecks::Projector.register(:cli, Hecks::Projector::CliProjector)
|
|
180
|
+
|
|
181
|
+
# The TARGETS are required from lib/hecks.rb, immediately after this
|
|
182
|
+
# file — deliberately not from here. A target requires this file (it
|
|
183
|
+
# needs `Target` and the registry), so requiring them back from here
|
|
184
|
+
# would close a genuine `circular require considered harmful` loop. Ruby
|
|
185
|
+
# tolerates it; the warning is still right, and load order is the kind of
|
|
186
|
+
# thing that only breaks once someone requires a target on its own.
|