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,114 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "uri"
|
|
3
|
+
require_relative "html"
|
|
4
|
+
require_relative "field_shape"
|
|
5
|
+
require_relative "field_renderer"
|
|
6
|
+
require_relative "reference_options"
|
|
7
|
+
require_relative "params"
|
|
8
|
+
require_relative "record_table"
|
|
9
|
+
|
|
10
|
+
module Hecks
|
|
11
|
+
module Forms
|
|
12
|
+
# Query -> the page body for its GET view — the working half of what
|
|
13
|
+
# `query_form.bluebook` names (see docs/command-form-and-query-form-
|
|
14
|
+
# bluebook.md): a canonical, shareable link (queries are GETs — every
|
|
15
|
+
# one of them is already a URL, so this says so up front rather than
|
|
16
|
+
# hiding that behind a form); a row of ready-made links for any
|
|
17
|
+
# closed-set parameter, since a caller filtering by an enum should not
|
|
18
|
+
# have to fill in a form to get a link they could just click; a filter
|
|
19
|
+
# FORM for everything else, which — being a GET form — produces
|
|
20
|
+
# exactly the same kind of link on submit; and the results table once
|
|
21
|
+
# a request actually supplies parameters.
|
|
22
|
+
module QueryFormRenderer
|
|
23
|
+
def self.render(registry:, domain:, aggregate:, query:, action:, params: {}, results: nil, error: nil)
|
|
24
|
+
fields = query.attributes.map { |a| FieldShape.resolve(a, aggregate: aggregate) }
|
|
25
|
+
reference_options = ReferenceOptions.collect(registry, domain, fields)
|
|
26
|
+
|
|
27
|
+
<<~HTML
|
|
28
|
+
#{header(domain, aggregate, query)}
|
|
29
|
+
#{canonical_link(action, fields)}
|
|
30
|
+
#{quick_links(action, fields)}
|
|
31
|
+
#{filter_form(action, fields, params, reference_options)}
|
|
32
|
+
#{error_banner(error)}
|
|
33
|
+
#{results_section(aggregate, results, domain)}
|
|
34
|
+
#{inspect_panel(domain, aggregate, query, fields)}
|
|
35
|
+
HTML
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.header(domain, aggregate, query)
|
|
39
|
+
<<~HTML
|
|
40
|
+
<h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}")}</h1>
|
|
41
|
+
#{query.description ? %(<p class="goal">#{Escape.html(query.description)}</p>) : ''}
|
|
42
|
+
#{badges(query)}
|
|
43
|
+
HTML
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.badges(query)
|
|
47
|
+
parts = []
|
|
48
|
+
parts << %(<span class="badge">limit #{Escape.html(query.limit.to_h[:value])}</span>) if query.limit
|
|
49
|
+
parts.join
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.canonical_link(action, fields)
|
|
53
|
+
paths = Params.paths(fields)
|
|
54
|
+
template = paths.empty? ? action : "#{action}?#{paths.map { |path| "#{path}={#{path}}" }.join('&')}"
|
|
55
|
+
<<~HTML
|
|
56
|
+
<p class="help">Every query is a plain GET — this exact URL is bookmarkable, linkable from a dashboard, curlable, whatever a feature needs:</p>
|
|
57
|
+
<div class="link-row"><code id="canonical-link">#{Escape.html(template)}</code><button type="button" class="copy" data-copy="#canonical-link">copy</button></div>
|
|
58
|
+
HTML
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The one enum-shaped parameter (if there is one) rendered as literal,
|
|
62
|
+
# clickable links — no form to fill in for "show me the suspended
|
|
63
|
+
# ones". Capped at the FIRST closed-set field on purpose: a second one
|
|
64
|
+
# would mean a cross product of links, which reads as noise rather
|
|
65
|
+
# than help. A query with more than one is still fully reachable
|
|
66
|
+
# through the filter form below.
|
|
67
|
+
def self.quick_links(action, fields)
|
|
68
|
+
field = fields.find { |f| %i[select radio].include?(f.kind) }
|
|
69
|
+
return "" unless field
|
|
70
|
+
|
|
71
|
+
links = field.options.map do |value, label|
|
|
72
|
+
href = "#{action}?#{field.path}=#{URI.encode_www_form_component(value)}"
|
|
73
|
+
%(<a href="#{Escape.attr(href)}">#{Escape.html(field.label)}: #{Escape.html(label)}</a>)
|
|
74
|
+
end
|
|
75
|
+
%(<div class="example-links">#{links.join}</div>)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.filter_form(action, fields, params, reference_options)
|
|
79
|
+
return "" if fields.empty?
|
|
80
|
+
|
|
81
|
+
<<~HTML
|
|
82
|
+
<form method="get" action="#{Escape.attr(action)}">
|
|
83
|
+
#{fields.map { |f| FieldRenderer.render(f, values: params, reference_options: reference_options) }.join}
|
|
84
|
+
<div class="actions"><button type="submit">Run query</button></div>
|
|
85
|
+
</form>
|
|
86
|
+
HTML
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.error_banner(error)
|
|
90
|
+
return "" unless error
|
|
91
|
+
|
|
92
|
+
%(<div class="error-banner" role="alert"><p><strong>#{Escape.html(error.class.name.split('::').last)}</strong> — #{Escape.html(error.message)}</p></div>)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.results_section(aggregate, results, domain)
|
|
96
|
+
return "" unless results
|
|
97
|
+
|
|
98
|
+
"<h2>Results (#{results.size})</h2>#{RecordTable.render(aggregate, results, domain: domain)}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.inspect_panel(domain, aggregate, query, fields)
|
|
102
|
+
verb = "#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}"
|
|
103
|
+
paths = Params.paths(fields)
|
|
104
|
+
<<~HTML
|
|
105
|
+
<details class="inspect">
|
|
106
|
+
<summary>Inspect — #{Escape.html(verb)}</summary>
|
|
107
|
+
<p>Parameters: #{paths.empty? ? '<em>none</em>' : paths.map { |p| "<code>#{Escape.html(p)}</code>" }.join(', ')}</p>
|
|
108
|
+
<pre>#{Escape.html(JSON.pretty_generate(query.to_h))}</pre>
|
|
109
|
+
</details>
|
|
110
|
+
HTML
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require_relative "html"
|
|
2
|
+
require_relative "field_shape"
|
|
3
|
+
require_relative "record_table"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Forms
|
|
7
|
+
# The two pages a `.bluebook` doesn't declare but a browsing developer
|
|
8
|
+
# always wants: every record of an aggregate, and one record's own
|
|
9
|
+
# state plus what it can legally do next. Neither is a `query` — they
|
|
10
|
+
# read the repository directly, the same way `AggregateDoor#all`/`#find`
|
|
11
|
+
# do (facade/surface/aggregate_door.rb) — so they exist for every
|
|
12
|
+
# aggregate whether or not its bluebook declared a query at all.
|
|
13
|
+
module RecordRenderer
|
|
14
|
+
def self.index(registry:, domain:, aggregate:)
|
|
15
|
+
instances = registry.repository(domain, aggregate).all
|
|
16
|
+
<<~HTML
|
|
17
|
+
<h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}")}</h1>
|
|
18
|
+
#{aggregate.description ? %(<p class="goal">#{Escape.html(aggregate.description)}</p>) : ''}
|
|
19
|
+
#{creating_links(domain, aggregate)}
|
|
20
|
+
<h2>Records (#{instances.size})</h2>
|
|
21
|
+
#{RecordTable.render(aggregate, instances, domain: domain)}
|
|
22
|
+
#{verb_list(domain, aggregate)}
|
|
23
|
+
HTML
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.creating_links(domain, aggregate)
|
|
27
|
+
creators = aggregate.commands.select(&:creates?)
|
|
28
|
+
return "" if creators.empty?
|
|
29
|
+
|
|
30
|
+
links = creators.map { |cmd|
|
|
31
|
+
%(<a class="button" href="/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html">+ #{Escape.html(cmd.hecks_name)}</a>)
|
|
32
|
+
}
|
|
33
|
+
%(<div class="actions">#{links.join}</div>)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.show(registry:, domain:, aggregate:, id:)
|
|
37
|
+
instance = registry.repository(domain, aggregate).find(id)
|
|
38
|
+
return nil unless instance
|
|
39
|
+
|
|
40
|
+
state = instance.state
|
|
41
|
+
current = aggregate.lifecycle && state[aggregate.lifecycle.field]
|
|
42
|
+
<<~HTML
|
|
43
|
+
<h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}")} <span class="mono">#{Escape.html(id)}</span></h1>
|
|
44
|
+
#{current ? %(<span class="badge role">#{Escape.html(aggregate.lifecycle.field)}: #{Escape.html(current)}</span>) : ''}
|
|
45
|
+
#{state_table(state)}
|
|
46
|
+
<h2>Commands</h2>
|
|
47
|
+
#{command_links(domain, aggregate, id, current)}
|
|
48
|
+
#{query_links(domain, aggregate)}
|
|
49
|
+
HTML
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.state_table(state)
|
|
53
|
+
rows = state.map { |key, value|
|
|
54
|
+
"<tr><th>#{Escape.html(Humanize.label(key.to_s))}</th><td>#{Escape.html(render_value(value))}</td></tr>"
|
|
55
|
+
}
|
|
56
|
+
%(<div class="table-scroll"><table><tbody>#{rows.join}</tbody></table></div>)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# `Value.materialize` first — a stored field is a `Runtime::Value`
|
|
60
|
+
# wherever its attribute is a value object, not a plain Hash (see
|
|
61
|
+
# record_table.rb's own note on the same read).
|
|
62
|
+
def self.render_value(value)
|
|
63
|
+
case (value = Runtime::Value.materialize(value))
|
|
64
|
+
when Hash then value.map { |k, v| "#{k}: #{render_value(v)}" }.join(", ")
|
|
65
|
+
when Array then value.map { |v| render_value(v) }.join("; ")
|
|
66
|
+
else value.to_s
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Every non-creating command, EXCEPT a lifecycle transition that does
|
|
71
|
+
# not apply from the record's current state — the same rule
|
|
72
|
+
# `Rules#admissible_transition` enforces at dispatch, read here so a
|
|
73
|
+
# link that would only refuse is never offered in the first place.
|
|
74
|
+
def self.command_links(domain, aggregate, id, current_state)
|
|
75
|
+
commands = aggregate.commands.reject(&:creates?).select { |cmd| applies?(aggregate, cmd, current_state) }
|
|
76
|
+
return "<p><em>No commands act on an existing #{Escape.html(aggregate.hecks_name)}.</em></p>" if commands.empty?
|
|
77
|
+
|
|
78
|
+
items = commands.map do |cmd|
|
|
79
|
+
href = "/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html?to=#{Escape.attr(id)}"
|
|
80
|
+
%(<li><a href="#{href}"><span>#{Escape.html(cmd.hecks_name)}</span><span class="kind">#{Escape.html(cmd.goal.to_s)}</span></a></li>)
|
|
81
|
+
end
|
|
82
|
+
%(<ul class="verb-list">#{items.join}</ul>)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.applies?(aggregate, command, current_state)
|
|
86
|
+
transitions = aggregate.lifecycle&.transitions_for(command.hecks_name) || []
|
|
87
|
+
return true if transitions.empty?
|
|
88
|
+
|
|
89
|
+
transitions.any? { |t| t.from.nil? || Array(t.from).map(&:to_s).include?(current_state.to_s) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.query_links(domain, aggregate)
|
|
93
|
+
return "" if aggregate.queries.empty?
|
|
94
|
+
|
|
95
|
+
items = aggregate.queries.map do |query|
|
|
96
|
+
%(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{query.hecks_name}.html"><span>#{Escape.html(query.hecks_name)}</span><span class="kind">query</span></a></li>)
|
|
97
|
+
end
|
|
98
|
+
%(<h2>Queries</h2><ul class="verb-list">#{items.join}</ul>)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.verb_list(domain, aggregate)
|
|
102
|
+
commands = aggregate.commands.reject(&:creates?)
|
|
103
|
+
return "" if commands.empty? && aggregate.queries.empty?
|
|
104
|
+
|
|
105
|
+
cmd_items = commands.map { |c|
|
|
106
|
+
%(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{c.hecks_name}.html"><span>#{Escape.html(c.hecks_name)}</span><span class="kind">command</span></a></li>)
|
|
107
|
+
}
|
|
108
|
+
query_items = aggregate.queries.map { |q|
|
|
109
|
+
%(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{q.hecks_name}.html"><span>#{Escape.html(q.hecks_name)}</span><span class="kind">query</span></a></li>)
|
|
110
|
+
}
|
|
111
|
+
%(<h2>Every command & query on #{Escape.html(aggregate.hecks_name)}</h2><ul class="verb-list">#{(cmd_items + query_items).join}</ul>)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require_relative "html"
|
|
2
|
+
require_relative "field_shape"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Forms
|
|
6
|
+
# The one shape every renderer in this directory reads a record as —
|
|
7
|
+
# `id` plus its state hash. `Runtime::Instance` already answers both,
|
|
8
|
+
# so `repository.all`'s own records pass straight through; a QUERY's
|
|
9
|
+
# answer does not (`QueryInterpreter#call` flattens `{id:}.merge(state)`
|
|
10
|
+
# into one hash with no method to call — see
|
|
11
|
+
# docs/command-form-and-query-form-bluebook.md's note on why), so
|
|
12
|
+
# app.rb wraps those results in one of these before they ever reach a
|
|
13
|
+
# renderer, and every renderer here only has to know one shape.
|
|
14
|
+
Record = Struct.new(:id, :state)
|
|
15
|
+
|
|
16
|
+
# An array of Record (or anything answering `#id`/`#state` — a
|
|
17
|
+
# Runtime::Instance qualifies without wrapping) -> an HTML table.
|
|
18
|
+
# Shared by the aggregate index page (record_renderer.rb) and a
|
|
19
|
+
# query's own results (query_form_renderer.rb) — the same records
|
|
20
|
+
# read the same columns either way.
|
|
21
|
+
module RecordTable
|
|
22
|
+
def self.columns(aggregate)
|
|
23
|
+
lifecycle = aggregate.lifecycle&.field
|
|
24
|
+
scalars = aggregate.attributes.reject { |a| a.reference? || a.list? }.map(&:name)
|
|
25
|
+
[*aggregate.identity_heads, lifecycle, *scalars].compact.uniq.first(7)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.render(aggregate, instances, domain:)
|
|
29
|
+
cols = columns(aggregate)
|
|
30
|
+
head = (["id"] + cols).map { |name| "<th>#{Escape.html(Humanize.label(name.to_s))}</th>" }.join
|
|
31
|
+
rows = instances.map { |instance| row(instance, aggregate, cols, domain) }
|
|
32
|
+
return "<p><em>No records.</em></p>" if rows.empty?
|
|
33
|
+
|
|
34
|
+
<<~HTML
|
|
35
|
+
<div class="table-scroll"><table>
|
|
36
|
+
<thead><tr>#{head}</tr></thead>
|
|
37
|
+
<tbody>#{rows.join}</tbody>
|
|
38
|
+
</table></div>
|
|
39
|
+
HTML
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.row(instance, aggregate, cols, domain)
|
|
43
|
+
cells = cols.map { |name| "<td>#{Escape.html(cell(instance, name))}</td>" }.join
|
|
44
|
+
href = "/#{domain}/#{aggregate.hecks_name}/#{instance.id}.html"
|
|
45
|
+
"<tr><td><a href=\"#{Escape.attr(href)}\">#{Escape.html(instance.id)}</a></td>#{cells}</tr>"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.cell(instance, name)
|
|
49
|
+
# `state` holds `Runtime::Value` wherever an attribute is a value
|
|
50
|
+
# object, not a plain Hash — `.materialize` is the runtime's own
|
|
51
|
+
# unwrap (value.rb), the same one a `to_h` read anywhere else in
|
|
52
|
+
# this codebase goes through, so a table cell agrees with every
|
|
53
|
+
# other reader about what a stored field actually contains.
|
|
54
|
+
value = Runtime::Value.materialize(instance.state[name])
|
|
55
|
+
case value
|
|
56
|
+
when Hash then value.values.first
|
|
57
|
+
when nil then ""
|
|
58
|
+
else value
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Forms
|
|
3
|
+
# A Field tree (field_shape.rb) -> `{path => [[id, label], ...]}` for
|
|
4
|
+
# every `:reference` field in it — the options a `<select>` needs.
|
|
5
|
+
# Shared by CommandFormRenderer and QueryFormRenderer alike: a
|
|
6
|
+
# command's own reference field and a query's own reference parameter
|
|
7
|
+
# resolve against the same repository the same way, and neither word
|
|
8
|
+
# owns this more than the other.
|
|
9
|
+
module ReferenceOptions
|
|
10
|
+
def self.collect(registry, domain, fields)
|
|
11
|
+
targets = {}
|
|
12
|
+
walk(fields) { |field| targets[field.path] = field.target_aggregate if field.kind == :reference }
|
|
13
|
+
targets.transform_values { |aggregate| aggregate && options_for(registry, domain, aggregate) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.walk(fields, &block)
|
|
17
|
+
fields.each do |field|
|
|
18
|
+
block.call(field)
|
|
19
|
+
walk(field.children, &block) if field.children
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.options_for(registry, domain, aggregate)
|
|
24
|
+
registry.repository(domain, aggregate).all.first(200).map { |instance| [instance.id, instance.id] }
|
|
25
|
+
rescue StandardError
|
|
26
|
+
nil # a wiring gap (no repository bound) degrades to a plain text id input, not a 500
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Forms
|
|
3
|
+
# THE VALUE-OBJECT CLASSIFICATION every consumer of a resolved VO
|
|
4
|
+
# shape needs, spelled once — `field_shape.rb` (a command's own
|
|
5
|
+
# form), `ui_schema.rb` (embryonaut_console's table/detail-panel
|
|
6
|
+
# renderer, a separate app, not this repo), and
|
|
7
|
+
# `adapters/driven/sql_query_builder.rb` (a query's own ORDER BY/
|
|
8
|
+
# WHERE compiler) each grew their own copy of "is this VO money-
|
|
9
|
+
# shaped," "does it have exactly one attribute, worth unwrapping,"
|
|
10
|
+
# and "which member of it is numeric" — the same three questions,
|
|
11
|
+
# answered identically by definition (a VO's shape doesn't change
|
|
12
|
+
# depending on who's asking), so a fix to one copy was never
|
|
13
|
+
# guaranteed to reach the others.
|
|
14
|
+
module ValueObjectShape
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# `Price{cents:, currency:}`/`ContractValue{cents:, currency:}` —
|
|
18
|
+
# every money-shaped VO in the corpus spells it exactly this way,
|
|
19
|
+
# the two-attribute convention the language itself never enforces
|
|
20
|
+
# but every real chapter follows.
|
|
21
|
+
def money?(value_object)
|
|
22
|
+
value_object.attributes.map { |a| a.name.to_s }.sort == %w[cents currency]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# A VO with exactly one attribute is a NAME for a scalar, not a
|
|
26
|
+
# genuine group (EmailAddress{address}, CustomerNumber{value}) —
|
|
27
|
+
# [[feedback_name_the_scalar_field]]'s own reasoning, shared here
|
|
28
|
+
# rather than re-decided per caller. Returns the sole attribute,
|
|
29
|
+
# or nil for anything else.
|
|
30
|
+
def sole_attribute(value_object)
|
|
31
|
+
return nil unless value_object.attributes.size == 1
|
|
32
|
+
|
|
33
|
+
value_object.attributes.first
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The first Integer/Float member, or nil — what a numeric
|
|
37
|
+
# comparison or an ORDER BY compiles against when the VO isn't
|
|
38
|
+
# money-shaped (money's own two members are handled by `money?`
|
|
39
|
+
# instead, since which one governs ordering is a money-specific
|
|
40
|
+
# decision, not a general "pick the first number" one).
|
|
41
|
+
def numeric_member(value_object)
|
|
42
|
+
value_object.attributes.find { |a| %w[Integer Float].include?(a.type.to_s) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/hecks/forms.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# The forms surface: `expose` (the DSL a `command_form.bluebook`/
|
|
2
|
+
# `query_form.bluebook` will eventually become — see docs/
|
|
3
|
+
# command-form-and-query-form-bluebook.md), the IR->HTML renderers, and
|
|
4
|
+
# the Rack app that content-negotiates between them and a plain JSON
|
|
5
|
+
# reading of the same dispatch. NOT required by `require "hecks"`
|
|
6
|
+
# itself — a project that never boots this file never pays for `rack`,
|
|
7
|
+
# the same lazy-dependency discipline the Gemfile's own comment already
|
|
8
|
+
# holds `pg`/`oauth2`/`aws-sdk-lambda` to.
|
|
9
|
+
#
|
|
10
|
+
# See docs/command-form-and-query-form-bluebook.md for the design this
|
|
11
|
+
# implements and what it deliberately leaves for later.
|
|
12
|
+
require "hecks"
|
|
13
|
+
require_relative "forms/app"
|
|
14
|
+
|
|
15
|
+
module Hecks
|
|
16
|
+
module Forms
|
|
17
|
+
# `expose`'s own declaration — "which chapters does this app expose" —
|
|
18
|
+
# kept OUTSIDE the `Hecks.*` collector convention (`Hecks.bluebook`,
|
|
19
|
+
# `Hecks.hecksagon`, ...) and outside `Runtime::Registry` entirely, on
|
|
20
|
+
# purpose: a real language word goes through `syntax.bluebook` and
|
|
21
|
+
# `MetaValidator` (see docs/implemented/guides/extending-hecks.md, "a new word is a
|
|
22
|
+
# declared row before it is a line of Ruby") and is judged by
|
|
23
|
+
# spec/syntax_conformance_spec.rb + spec/dsl_coverage_spec.rb — gates
|
|
24
|
+
# this construct has not earned yet. An ordinary Ruby DSL, one level of
|
|
25
|
+
# module state, no different in kind from a project's own initializer —
|
|
26
|
+
# see docs/command-form-and-query-form-bluebook.md, "why this isn't
|
|
27
|
+
# syntax.bluebook yet".
|
|
28
|
+
#
|
|
29
|
+
# ONE `expose` GRANTS A WHOLE CHAPTER, not a command or a query
|
|
30
|
+
# individually — the future `command_form.bluebook`/`query_form.bluebook`
|
|
31
|
+
# words are PER-DECLARATION (one command, one query, its own form/view,
|
|
32
|
+
# possibly its own overrides), which `expose` doesn't do today and was
|
|
33
|
+
# never trying to; it's the coarse "turn this chapter's forms on at
|
|
34
|
+
# all" switch those finer words will eventually sit inside.
|
|
35
|
+
class Config
|
|
36
|
+
attr_reader :name, :exposes
|
|
37
|
+
|
|
38
|
+
def initialize(name)
|
|
39
|
+
@name = name.to_s
|
|
40
|
+
@exposes = []
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def expose(chapter_name) = @exposes << chapter_name.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.configure(name, &block)
|
|
47
|
+
config = Config.new(name)
|
|
48
|
+
config.instance_eval(&block) if block
|
|
49
|
+
(@configs ||= {})[config.name] = config
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.config(name) = (@configs || {})[name.to_s]
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/hecks/fqn.rb
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# A public Bluebook command/query address.
|
|
3
|
+
#
|
|
4
|
+
# Realm is deployment identity from a world. Domain@version pins a domain
|
|
5
|
+
# contract; an unpinned domain is the world's configured latest alias.
|
|
6
|
+
class Fqn
|
|
7
|
+
class Invalid < ArgumentError; end
|
|
8
|
+
|
|
9
|
+
attr_reader :realm, :domain, :version, :aggregate, :verb
|
|
10
|
+
|
|
11
|
+
def self.command(realm:, domain:, aggregate:, command:, version: nil)
|
|
12
|
+
new(realm: realm, domain: domain, version: version, aggregate: aggregate, verb: command, kind: :command)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.query(realm:, domain:, aggregate: nil, query:, version: nil)
|
|
16
|
+
new(realm: realm, domain: domain, version: version, aggregate: aggregate, verb: query, kind: :query)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.parse(text)
|
|
20
|
+
head, separator, verb = text.to_s.rpartition(".")
|
|
21
|
+
raise Invalid, "FQN must be Realm::Domain::Aggregate.verb — got #{text.inspect}" if separator.empty?
|
|
22
|
+
|
|
23
|
+
segments = head.split("::", -1)
|
|
24
|
+
raise Invalid, "FQN must be Realm::Domain::Aggregate.verb — got #{text.inspect}" unless [1, 2, 3].include?(segments.length) && segments.none?(&:empty?) && !verb.empty?
|
|
25
|
+
|
|
26
|
+
realm, domain_spec, aggregate = case segments.length
|
|
27
|
+
when 3 then segments
|
|
28
|
+
# A two-segment lowercase address is the
|
|
29
|
+
# public realm/domain form for a
|
|
30
|
+
# domain-level read model.
|
|
31
|
+
when 2 then query_name?(verb) ? [segments[0], segments[1], nil] : [nil, *segments]
|
|
32
|
+
else [nil, segments.first, nil]
|
|
33
|
+
end
|
|
34
|
+
domain, version = split_domain(domain_spec)
|
|
35
|
+
kind = if command_name?(verb)
|
|
36
|
+
:command
|
|
37
|
+
elsif query_name?(verb)
|
|
38
|
+
:query
|
|
39
|
+
else
|
|
40
|
+
raise Invalid, "FQN verb must be PascalCase command or snake_case query — got #{text.inspect}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
raise Invalid, "domain-level FQNs are query-only — got #{text.inspect}" if aggregate.nil? && kind == :command
|
|
44
|
+
|
|
45
|
+
new(realm: realm, domain: domain, version: version, aggregate: aggregate, verb: verb, kind: kind)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.command_name?(name) = /\A[A-Z][A-Za-z0-9]*\z/.match?(name.to_s)
|
|
49
|
+
def self.query_name?(name) = /\A[a-z][a-z0-9_]*\z/.match?(name.to_s)
|
|
50
|
+
|
|
51
|
+
def initialize(realm:, domain:, version: nil, aggregate:, verb:, kind:)
|
|
52
|
+
@realm = realm && segment(realm, "realm")
|
|
53
|
+
@domain = segment(domain, "domain")
|
|
54
|
+
@version = version && segment(version, "version")
|
|
55
|
+
@aggregate = aggregate && segment(aggregate, "aggregate")
|
|
56
|
+
@verb = segment(verb, "verb")
|
|
57
|
+
@kind = kind.to_sym
|
|
58
|
+
|
|
59
|
+
valid = command? ? self.class.command_name?(@verb) : self.class.query_name?(@verb)
|
|
60
|
+
raise Invalid, "#{@kind} FQN has an invalid verb #{@verb.inspect}" unless valid
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def command? = @kind == :command
|
|
64
|
+
def query? = @kind == :query
|
|
65
|
+
def kind = @kind
|
|
66
|
+
|
|
67
|
+
def to_s
|
|
68
|
+
domain = @version ? "#{@domain}@#{@version}" : @domain
|
|
69
|
+
[[@realm, domain, @aggregate].compact.join("::"), @verb].join(".")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def ==(other) = other.is_a?(Fqn) && to_s == other.to_s
|
|
73
|
+
alias eql? ==
|
|
74
|
+
def hash = to_s.hash
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def self.split_domain(value)
|
|
79
|
+
domain, version, extra = value.to_s.split("@", 3)
|
|
80
|
+
raise Invalid,
|
|
81
|
+
"FQN domain version is malformed: #{value.inspect}" if domain.empty? || extra || (value.include?("@") && version.to_s.empty?)
|
|
82
|
+
|
|
83
|
+
[domain, version]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def segment(value, label)
|
|
87
|
+
text = value.to_s
|
|
88
|
+
raise Invalid,
|
|
89
|
+
"FQN #{label} cannot be empty or contain a separator" if text.empty? || text.include?("::") || text.include?(".") || text.include?("@")
|
|
90
|
+
|
|
91
|
+
text
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../../../../examples/compliance/bluebook/compliance.bluebook
|