hecks 0.2.0 → 1.0.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 +427 -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 +101 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +179 -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 +145 -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 +88 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +438 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +17 -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 +80 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +306 -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 +54 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +125 -0
- data/lib/hecks/behaviors/expectations.rb +340 -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 +438 -0
- data/lib/hecks/bluebook/assembly/marks.rb +228 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +91 -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 +68 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -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 +78 -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 +1018 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +714 -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 +430 -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 +135 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -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 +173 -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 +228 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +117 -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 +123 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
- data/lib/hecks/bluebook/expression/projection.json +218 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +781 -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 +621 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -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 +542 -0
- data/lib/hecks/bluebook/model_check.rb +445 -0
- data/lib/hecks/bluebook/pattern_subset.rb +184 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +128 -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 +66 -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 +410 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +119 -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 +166 -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 +341 -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 +177 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +84 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +161 -0
- data/lib/hecks/forms/port_argument.rb +46 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +119 -0
- data/lib/hecks/forms/record_table.rb +68 -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 +145 -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/bounded_exhaustive_expressions.rb +527 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
- data/lib/hecks/fuzzing/properties.rb +1192 -0
- data/lib/hecks/fuzzing/replay.rb +668 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
- data/lib/hecks/fuzzing/value_generator.rb +200 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +324 -0
- data/lib/hecks/grammar/expression.bluebook +420 -0
- data/lib/hecks/grammar/expression_operators.json +1648 -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 +471 -0
- data/lib/hecks/language/bluebook/entity.bluebook +392 -0
- data/lib/hecks/language/bluebook/policy.bluebook +189 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +381 -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 +448 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -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 +333 -0
- data/lib/hecks/language/port.bluebook +120 -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 +174 -0
- data/lib/hecks/ports/access_control.port +9 -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 +8 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +7 -0
- data/lib/hecks/ports/authorization.rb +62 -0
- data/lib/hecks/ports/clock.port +5 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +5 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +5 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +5 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +5 -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 +138 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +23 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/plugin.rb +54 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
- data/lib/hecks/ports/persistence/plugins/era.rb +48 -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 +96 -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 +642 -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 +44 -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 +446 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +158 -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 +411 -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 +92 -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 +107 -0
- data/lib/hecks/query_specification/hop_path.rb +132 -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/aggregate_lock.rb +45 -0
- data/lib/hecks/runtime/boot_gates.rb +41 -0
- data/lib/hecks/runtime/caller.rb +66 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
- data/lib/hecks/runtime/command_interpreter.rb +497 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +189 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +245 -0
- data/lib/hecks/runtime/dispatcher.rb +301 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +299 -0
- data/lib/hecks/runtime/errors.rb +122 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +156 -0
- data/lib/hecks/runtime/instance.rb +175 -0
- data/lib/hecks/runtime/interpreting.rb +90 -0
- data/lib/hecks/runtime/loader.rb +184 -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 +286 -0
- data/lib/hecks/runtime/reaction_invocation.rb +253 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +341 -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 +126 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
- data/lib/hecks/runtime/registry/verification.rb +246 -0
- data/lib/hecks/runtime/registry.rb +279 -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 +503 -0
- data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -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 +130 -0
- data/lib/hecks/runtime/value/coercion.rb +565 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +107 -0
- data/lib/hecks/storehouse.rb +632 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +218 -0
- data/lib/hecks.rb +123 -6
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
- metadata +412 -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,410 @@
|
|
|
1
|
+
require_relative "../bluebook/meta_validator"
|
|
2
|
+
require_relative "../naming"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Doc
|
|
6
|
+
# The DSL reference, projected from the language's own Syntax chapter
|
|
7
|
+
# — the same Keyword/Argument rows the conformance specs hold equal
|
|
8
|
+
# to the live builders. Nothing here is described twice: the tables
|
|
9
|
+
# come from the declaration, the PROSE is hand-written between
|
|
10
|
+
# markers the generator preserves, and the golden spec refuses a
|
|
11
|
+
# tree where the two have drifted.
|
|
12
|
+
#
|
|
13
|
+
# Regenerate with bin/reference. A new word arrives with a TODO
|
|
14
|
+
# sentinel; the coverage gate refuses to let an admitted word ship
|
|
15
|
+
# undocumented; prose for a word the language no longer declares is
|
|
16
|
+
# a hard error naming its orphans — deleting someone's writing is a
|
|
17
|
+
# human's decision.
|
|
18
|
+
module Reference
|
|
19
|
+
GENERATED_END = "<!-- generated:end -->".freeze
|
|
20
|
+
TODO_SENTINEL = "<!-- TODO: document this word -->".freeze
|
|
21
|
+
|
|
22
|
+
# A PAGE'S OWN HAND-WRITTEN OPENING, harvested under a key no word
|
|
23
|
+
# can ever collide with (words are strings off the Syntax chapter;
|
|
24
|
+
# this is a Symbol). It exists so a page can boot ONCE — load a real
|
|
25
|
+
# corpus chapter, wire its hexagon — and have every word's example
|
|
26
|
+
# below run against that single boot, the way a guide's opening
|
|
27
|
+
# `ruby boot` block already does. Without it each word would have to
|
|
28
|
+
# stand up its own domain, and 105 invented chapters would collide
|
|
29
|
+
# on the facade constants `Surface.install` never uninstalls.
|
|
30
|
+
PREAMBLE = :preamble
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
def generated_begin(word) = "<!-- generated:begin word=#{word} -->"
|
|
35
|
+
|
|
36
|
+
# Keyed by REGION rather than by word — the same marker convention,
|
|
37
|
+
# used for the parts of a page that are not about one word: a
|
|
38
|
+
# page's generated lede here, README's generated indexes below.
|
|
39
|
+
def region_begin(id) = "<!-- generated:begin id=#{id} -->"
|
|
40
|
+
|
|
41
|
+
def syntax
|
|
42
|
+
meta = Bluebook::MetaValidator.grammar_registry.bluebook("Bluebook")
|
|
43
|
+
meta.aggregates.find { |aggregate| aggregate.hecks_name == "Syntax" }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def rows(name)
|
|
47
|
+
syntax.value_objects.find { |vo| vo.hecks_name == name }
|
|
48
|
+
.members.map { |row| row.to_h.transform_values(&:to_s) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# S14, ADR 0026 — Keyword/Argument are genuine entities of Syntax
|
|
52
|
+
# now, dispatched (not merely declared) so their own `status`
|
|
53
|
+
# really is a lifecycle. `SyntaxBoot.call` discovers the static
|
|
54
|
+
# aggregate-local seed rows (`KeywordSeed`/`ArgumentSeed`), dispatches each
|
|
55
|
+
# one through the real admission/lifecycle door, and hands back the
|
|
56
|
+
# same shape `rows` used to produce — nothing below this needed to
|
|
57
|
+
# change.
|
|
58
|
+
#
|
|
59
|
+
# NO SEPARATE `@keywords ||=` HERE ANYMORE. This module used to
|
|
60
|
+
# memoize its own copy on top of `SyntaxBoot.call`'s own memo — a
|
|
61
|
+
# double cache with no way to invalidate either half, and a real
|
|
62
|
+
# bug: whichever call in the whole process happened to land first
|
|
63
|
+
# got locked in forever, even one caught mid-build missing every
|
|
64
|
+
# Paging-attached word (limit/offset/cursor/nulls). `SyntaxBoot.call`
|
|
65
|
+
# now carries the one cache that matters (keyed on the grammar
|
|
66
|
+
# registry's own chapter set — see its comment) ; this delegates
|
|
67
|
+
# straight through instead of shadowing it.
|
|
68
|
+
def keywords = Bluebook::MetaValidator::SyntaxBoot.call[:keywords]
|
|
69
|
+
def arguments = Bluebook::MetaValidator::SyntaxBoot.call[:arguments]
|
|
70
|
+
|
|
71
|
+
def status_of(row) = row[:status].to_s.empty? ? "admitted" : row[:status].to_s
|
|
72
|
+
def live?(row) = %w[admitted deprecated].include?(status_of(row))
|
|
73
|
+
|
|
74
|
+
def contexts = keywords.map { |row| row[:context] }.uniq
|
|
75
|
+
|
|
76
|
+
def page_name(context) = "#{Naming.snake(context)}.md"
|
|
77
|
+
|
|
78
|
+
# Every reference page, rendered fresh — prose carried over from
|
|
79
|
+
# the committed pages, new words seeded with the sentinel, orphaned
|
|
80
|
+
# prose refused.
|
|
81
|
+
def pages(directory)
|
|
82
|
+
contexts.each_with_object({}) do |context, pages|
|
|
83
|
+
path = File.join(directory, page_name(context))
|
|
84
|
+
prose = File.exist?(path) ? harvest(File.read(path)) : {}
|
|
85
|
+
pages[page_name(context)] = render_page(context, prose, path)
|
|
86
|
+
end.merge("index.md" => render_index)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# A WORD ADMITTING TWO FORMS HAS TWO ROWS — syntax.bluebook's own
|
|
90
|
+
# stated rule, and `identified_by` (a block, or a bare argument and
|
|
91
|
+
# none) is the case that made it real again. One SECTION per word all
|
|
92
|
+
# the same: the prose is the word's rather than the form's, and the
|
|
93
|
+
# argument rows join by (word, context) and so already cover every
|
|
94
|
+
# form. Grouped rather than rendered per row, or a reader would meet
|
|
95
|
+
# the same heading and the same paragraph twice.
|
|
96
|
+
def render_page(context, prose, path)
|
|
97
|
+
words = keywords.select { |row| row[:context] == context }.group_by { |row| row[:word] }
|
|
98
|
+
orphans = prose.keys - words.keys - [PREAMBLE]
|
|
99
|
+
unless orphans.empty?
|
|
100
|
+
raise "#{path} carries prose for #{orphans.join(', ')}, which the language no longer " \
|
|
101
|
+
"declares in #{context} — deleting writing is a human's decision, so decide"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
sections = words.map { |word, forms| render_word(forms, prose[word]) }
|
|
105
|
+
preamble = prose[PREAMBLE].to_s.strip
|
|
106
|
+
<<~PAGE
|
|
107
|
+
# #{context}
|
|
108
|
+
|
|
109
|
+
#{region_begin('page')}
|
|
110
|
+
#{context_lede(context)}
|
|
111
|
+
|
|
112
|
+
*The tables on this page are generated from the language's own
|
|
113
|
+
aggregate-local syntax tables (`lib/hecks/language/**/*.bluebook`)
|
|
114
|
+
by `bin/reference` — do not edit inside the markers. The prose
|
|
115
|
+
between them is hand-written and survives regeneration.*
|
|
116
|
+
#{GENERATED_END}
|
|
117
|
+
#{preamble.empty? ? '' : "\n#{preamble}\n"}
|
|
118
|
+
#{sections.join("\n")}
|
|
119
|
+
PAGE
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def context_lede(context)
|
|
123
|
+
openers = keywords.select { |row| row[:opens] == context }
|
|
124
|
+
return "Words available at the top of a file." if context == "File"
|
|
125
|
+
return "Words available in the type position of an `attribute`." if context == "Type"
|
|
126
|
+
|
|
127
|
+
inside = openers.map { |row| "`#{row[:word]} do ... end`" }.uniq.join(" / ")
|
|
128
|
+
inside.empty? ? "Words available in the #{context} body." : "Words available inside #{inside}."
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# One SPELLING per form, everything else off the first row — the
|
|
132
|
+
# columns that differ between two forms of one word are `body` (which
|
|
133
|
+
# is what the spelling shows) and nothing else.
|
|
134
|
+
def render_word(forms, prose)
|
|
135
|
+
row = forms.first
|
|
136
|
+
table = argument_table(row)
|
|
137
|
+
facts = []
|
|
138
|
+
facts << "opens a `#{row[:opens]}` body" unless row[:opens].to_s.empty?
|
|
139
|
+
facts << "fills `#{row[:fills]}`" unless row[:fills].to_s.empty?
|
|
140
|
+
facts << "**status: #{status_of(row)}**" unless status_of(row) == "admitted"
|
|
141
|
+
facts << "was `#{row[:was]}`" unless row[:was].to_s.empty?
|
|
142
|
+
spellings = forms.map { |form| "`#{signature(form)}`" }.join(" / ")
|
|
143
|
+
|
|
144
|
+
<<~WORD
|
|
145
|
+
## #{row[:word]}
|
|
146
|
+
|
|
147
|
+
#{generated_begin(row[:word])}
|
|
148
|
+
#{spellings}#{facts.empty? ? '' : " — #{facts.join(', ')}"}
|
|
149
|
+
#{table}#{GENERATED_END}
|
|
150
|
+
|
|
151
|
+
#{prose_or_sentinel(prose)}
|
|
152
|
+
WORD
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def prose_or_sentinel(prose)
|
|
156
|
+
text = prose.to_s.strip
|
|
157
|
+
text.empty? ? TODO_SENTINEL : text
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def word_arguments(row)
|
|
161
|
+
arguments.select { |arg| arg[:keyword] == row[:word] && arg[:context] == row[:context] }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def signature(row)
|
|
165
|
+
positional = word_arguments(row).reject { |arg| arg[:at].to_s.empty? }
|
|
166
|
+
.sort_by { |arg| arg[:at].to_i }
|
|
167
|
+
.map { |arg| arg[:fills].to_s.empty? ? arg[:kind] : arg[:fills] }
|
|
168
|
+
named = word_arguments(row).select { |arg| arg[:at].to_s.empty? }
|
|
169
|
+
.map { |arg| "#{arg[:named]}:" }
|
|
170
|
+
parts = positional + named
|
|
171
|
+
base = parts.empty? ? row[:word] : "#{row[:word]} #{parts.join(', ')}"
|
|
172
|
+
row[:body].to_s == "none" ? base : "#{base} do ... end"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def argument_table(row)
|
|
176
|
+
args = word_arguments(row)
|
|
177
|
+
return "" if args.empty?
|
|
178
|
+
|
|
179
|
+
lines = ["", "| argument | kind | required | fills |", "|---|---|---|---|"]
|
|
180
|
+
args.each do |arg|
|
|
181
|
+
name = arg[:at].to_s.empty? ? "`#{arg[:named]}:`" : "positional #{arg[:at]}"
|
|
182
|
+
lines << "| #{name} | #{arg[:kind]} | #{arg[:required]} | #{arg[:fills]} |"
|
|
183
|
+
end
|
|
184
|
+
lines.join("\n") + "\n"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def render_index
|
|
188
|
+
listed = contexts.map do |context|
|
|
189
|
+
count = keywords.select { |row| row[:context] == context }.map { |row| row[:word] }.uniq.size
|
|
190
|
+
"- [#{context}](#{page_name(context)}) — #{count} #{count == 1 ? 'word' : 'words'}"
|
|
191
|
+
end
|
|
192
|
+
<<~INDEX
|
|
193
|
+
# The DSL reference
|
|
194
|
+
|
|
195
|
+
One page per context — the place in a file where a word may be
|
|
196
|
+
typed. Generated by `bin/reference` from the Syntax chapter;
|
|
197
|
+
the prose between the generated markers is hand-written and
|
|
198
|
+
survives regeneration.
|
|
199
|
+
|
|
200
|
+
#{listed.join("\n")}
|
|
201
|
+
INDEX
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Prose keyed by word: everything between a section's generated
|
|
205
|
+
# region and the next `## ` heading (or end of file).
|
|
206
|
+
#
|
|
207
|
+
# Starts on PREAMBLE rather than nil so the text between the PAGE's
|
|
208
|
+
# own generated lede and its first word heading is carried over too
|
|
209
|
+
# instead of being silently dropped. A page written before that
|
|
210
|
+
# region existed has no generated marker ahead of its first `## `,
|
|
211
|
+
# so nothing is collecting when that heading arrives and no empty
|
|
212
|
+
# preamble is invented — the older shape reads back unchanged.
|
|
213
|
+
def harvest(text)
|
|
214
|
+
prose = {}
|
|
215
|
+
current = PREAMBLE
|
|
216
|
+
collecting = false
|
|
217
|
+
buffer = []
|
|
218
|
+
|
|
219
|
+
# A HEADING INSIDE A FENCE IS NOT A HEADING. `## something` is an
|
|
220
|
+
# ordinary Ruby comment, and now that every word's section carries
|
|
221
|
+
# runnable code, one written at the left margin would otherwise
|
|
222
|
+
# end that section mid-example and orphan the rest of it under a
|
|
223
|
+
# word the language never declared.
|
|
224
|
+
in_fence = false
|
|
225
|
+
|
|
226
|
+
text.each_line do |line|
|
|
227
|
+
in_fence = !in_fence if line.start_with?("```")
|
|
228
|
+
|
|
229
|
+
if !in_fence && (match = line.match(/\A## (\S+)\s*\z/))
|
|
230
|
+
prose[current] = buffer.join.strip if current && collecting
|
|
231
|
+
current = match[1]
|
|
232
|
+
collecting = false
|
|
233
|
+
buffer = []
|
|
234
|
+
elsif line.include?(GENERATED_END)
|
|
235
|
+
collecting = true
|
|
236
|
+
elsif collecting
|
|
237
|
+
buffer << line
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
prose[current] = buffer.join.strip if current && collecting
|
|
241
|
+
prose.reject { |_word, text_| text_.empty? || text_ == TODO_SENTINEL }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def write!(directory)
|
|
245
|
+
FileUtils.mkdir_p(directory)
|
|
246
|
+
pages(directory).each do |name, content|
|
|
247
|
+
File.write(File.join(directory, name), content)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# README's own generated regions — the same marker convention as a
|
|
252
|
+
# reference page, keyed by region id instead of a word, so the
|
|
253
|
+
# index a reader lands on first can't drift from what actually
|
|
254
|
+
# exists on disk either.
|
|
255
|
+
def readme_regions(root)
|
|
256
|
+
{
|
|
257
|
+
"guides" => guide_index(root),
|
|
258
|
+
"reference" => reference_index(root),
|
|
259
|
+
"tools" => tool_table(root),
|
|
260
|
+
"corpus" => corpus_roster(root),
|
|
261
|
+
"diagrams" => diagram_showcase(root)
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def guide_index(root)
|
|
266
|
+
paths = Dir.glob(File.join(root, "docs/implemented/guides/*.md")).sort
|
|
267
|
+
.reject { |p| %w[AUTHORING.md].include?(File.basename(p)) }
|
|
268
|
+
lines = paths.map do |path|
|
|
269
|
+
heading = File.foreach(path).find { |line| line.start_with?("# ") }
|
|
270
|
+
title = heading ? heading.sub(/\A#\s*/, "").strip : File.basename(path)
|
|
271
|
+
"- [#{title}](docs/implemented/guides/#{File.basename(path)})"
|
|
272
|
+
end
|
|
273
|
+
lines.join("\n")
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def reference_index(root)
|
|
277
|
+
count = contexts.size
|
|
278
|
+
"[The DSL reference](docs/implemented/reference/index.md) — #{count} contexts, generated from " \
|
|
279
|
+
"the aggregate-local tables under `lib/hecks/language/` and held to them by " \
|
|
280
|
+
"`spec/reference_golden_spec.rb`."
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def tool_table(root)
|
|
284
|
+
scripts = Dir.glob(File.join(root, "bin/*")).select { |p| File.file?(p) }.sort
|
|
285
|
+
rows = scripts.filter_map { |path| [path, tool_summary(path)] }.select { |_, desc| desc }
|
|
286
|
+
lines = ["| tool | |", "|---|---|"]
|
|
287
|
+
rows.each { |path, desc| lines << "| `bin/#{File.basename(path)}` | #{desc} |" }
|
|
288
|
+
lines.join("\n")
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# The opening comment PARAGRAPH, not just the first line — a table
|
|
292
|
+
# cell that trails off mid-clause reads worse than one that runs a
|
|
293
|
+
# little long and says "...". A code-bearing comment (`field.name`,
|
|
294
|
+
# `pattern:`) makes naive sentence-splitting on "." or ":" cut in
|
|
295
|
+
# the wrong place, so this truncates on LENGTH alone.
|
|
296
|
+
def tool_summary(path)
|
|
297
|
+
comment_lines = []
|
|
298
|
+
started = false
|
|
299
|
+
File.foreach(path).first(10).each do |line|
|
|
300
|
+
if line.start_with?("#") && !line.start_with?("#!")
|
|
301
|
+
next if !started && line.strip == "#"
|
|
302
|
+
|
|
303
|
+
started = true
|
|
304
|
+
comment_lines << line.sub(/\A#\s?/, "").rstrip
|
|
305
|
+
elsif started
|
|
306
|
+
break
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
return nil if comment_lines.empty?
|
|
310
|
+
|
|
311
|
+
text = comment_lines.join(" ").squeeze(" ")
|
|
312
|
+
text.length > 140 ? "#{text[0, 137]}..." : text
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# ONE REAL, COMMITTED FILE, READ FRESH — not re-derived from a boot
|
|
316
|
+
# (this module never requires `hecks/projections/diagrams`, and
|
|
317
|
+
# shouldn't just to draw one example). `docs/generated/diagrams/`
|
|
318
|
+
# is already held to the declaration by `spec/diagrams_spec.rb`'s
|
|
319
|
+
# own drift check; this just quotes its own output, so the two
|
|
320
|
+
# can't independently drift from each other either — a stale
|
|
321
|
+
# Order_lifecycle.mmd fails THAT spec long before this one runs.
|
|
322
|
+
def diagram_showcase(root)
|
|
323
|
+
lifecycle = File.read(File.join(root, "docs/generated/diagrams/pizzas/Order_lifecycle.mmd")).strip
|
|
324
|
+
<<~MARKDOWN.strip
|
|
325
|
+
`bin/project_diagrams` reads a booted domain's own declaration and draws it as Mermaid — nine kinds so far: `<Name>_lifecycle.mmd`, `relationships.mmd`, `dispatch.mmd`, `roles.mmd`, `ports.mmd`, `read_models.mmd`, `<Name>_surface.mmd` (what a command does, and what it writes), `<Name>_saga.mmd`, and `frameworks.mmd`. Nothing hand-drawn — the same reason a domain is data at all. Order's own lifecycle, straight off the bluebook above:
|
|
326
|
+
|
|
327
|
+
```mermaid
|
|
328
|
+
#{lifecycle}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The full set for every domain in this checkout — `examples/pizzas`, `examples/banking` — lives in [`docs/generated/diagrams/`](docs/generated/diagrams/), held to the declaration by `spec/diagrams_spec.rb` the same drift-refusing way this page is held to its own source.
|
|
332
|
+
MARKDOWN
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def corpus_roster(root)
|
|
336
|
+
dirs = Dir.glob(File.join(root, "examples/*/")).sort
|
|
337
|
+
lines = dirs.filter_map do |dir|
|
|
338
|
+
name = File.basename(dir.chomp("/"))
|
|
339
|
+
bluebooks = Dir.glob(File.join(dir, "bluebook/*.bluebook")).sort
|
|
340
|
+
bluebooks = Dir.glob(File.join(dir, "*.bluebook")).sort if bluebooks.empty?
|
|
341
|
+
next if bluebooks.empty?
|
|
342
|
+
|
|
343
|
+
vision = bluebooks.filter_map { |bluebook| File.read(bluebook)[/vision\s+"([^"]*)"/, 1] }.first
|
|
344
|
+
"- **#{name}** — #{vision}"
|
|
345
|
+
end
|
|
346
|
+
lines.join("\n")
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def render_readme(root, text)
|
|
350
|
+
readme_regions(root).reduce(text) do |current, (id, content)|
|
|
351
|
+
pattern = /#{Regexp.escape(region_begin(id))}.*?#{Regexp.escape(GENERATED_END)}/m
|
|
352
|
+
current.sub(pattern) { "#{region_begin(id)}\n#{content}\n#{GENERATED_END}" }
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def write_readme!(root)
|
|
357
|
+
path = File.join(root, "README.md")
|
|
358
|
+
File.write(path, render_readme(root, File.read(path)))
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# An example A READER CAN SEE and the harness will actually run.
|
|
362
|
+
# `ruby skip` is display-only by the doctest harness's own rule, and
|
|
363
|
+
# a hidden `<!-- doctest:boot -->` block is setup rather than an
|
|
364
|
+
# example — a word whose only "example" is invisible or inert is a
|
|
365
|
+
# word still shipping on its prose alone, which is the thing this
|
|
366
|
+
# gate exists to refuse.
|
|
367
|
+
EXAMPLE_FENCE = /^```ruby(?: bluebook| boot)?[ \t]*$/.freeze
|
|
368
|
+
|
|
369
|
+
def exemplified?(prose) = prose.to_s.match?(EXAMPLE_FENCE)
|
|
370
|
+
|
|
371
|
+
# EVERY LIVE WORD, PAIRED WITH ITS PROSE. Both coverage gates ask a
|
|
372
|
+
# question about this same walk and differ only in what they ask of
|
|
373
|
+
# the prose, so they share it rather than each re-deriving the page
|
|
374
|
+
# set — the two are meant to move together, and one drifting past
|
|
375
|
+
# the other is how a word ends up counted documented by one and
|
|
376
|
+
# missing to the other.
|
|
377
|
+
#
|
|
378
|
+
# `harvest` already rejects empty prose and the TODO sentinel, so a
|
|
379
|
+
# word with nothing written for it arrives here with a nil.
|
|
380
|
+
def live_words(directory)
|
|
381
|
+
contexts.flat_map { |context|
|
|
382
|
+
path = File.join(directory, page_name(context))
|
|
383
|
+
prose = File.exist?(path) ? harvest(File.read(path)) : {}
|
|
384
|
+
keywords.select { |row| row[:context] == context && live?(row) }
|
|
385
|
+
.map { |row| [row[:word], context, prose[row[:word]]] }
|
|
386
|
+
}.uniq { |word, context, _| [word, context] }
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def name_of(word, context) = "#{word} (#{context})"
|
|
390
|
+
|
|
391
|
+
# The coverage gate's question: every LIVE word with no prose yet.
|
|
392
|
+
def undocumented(directory)
|
|
393
|
+
live_words(directory).reject { |_word, _context, prose| prose }
|
|
394
|
+
.map { |word, context, _| name_of(word, context) }
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# The SECOND coverage gate: prose is a declaration, and a
|
|
398
|
+
# declaration nothing runs cannot disagree with anything. A word
|
|
399
|
+
# documented only in sentences can go stale — or describe a word
|
|
400
|
+
# the runtime never wired at all, which this repository has already
|
|
401
|
+
# shipped twice (`read_model`'s where/order_by/limit/offset, and
|
|
402
|
+
# `role`/`goal` on a command). An example that RUNS is the only
|
|
403
|
+
# documentation that can go red.
|
|
404
|
+
def unexemplified(directory)
|
|
405
|
+
live_words(directory).reject { |_word, _context, prose| exemplified?(prose) }
|
|
406
|
+
.map { |word, context, _| name_of(word, context) }
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# A VENDORED, EXTERNAL BLUEBOOK — same shape as Framework (framework.rb),
|
|
3
|
+
# for members that don't ship inside hecks's own lib/ at all: a
|
|
4
|
+
# separate, independently-versioned package
|
|
5
|
+
# (github.com/chrisyoung/embryonaut_bluebooks) that a consuming project
|
|
6
|
+
# vendors into its OWN checkout, the same way a project already vendors
|
|
7
|
+
# hecks itself (bin/vendor_hecks, vendor/hecks/).
|
|
8
|
+
#
|
|
9
|
+
# RECOVERED, NOT REBUILT — this module and its `uses_embryonaut_bluebook`
|
|
10
|
+
# DSL word (hecksagon_builder.rb) were built on a prior commit of this
|
|
11
|
+
# repo (933d1dd), vendored out to a real consumer (lifeadelics/domain,
|
|
12
|
+
# for embryonaut_bluebooks/payments), and then lost from this repo's own
|
|
13
|
+
# reachable history — a hard reset or rebase left no branch containing
|
|
14
|
+
# that commit. The lifeadelics vendor snapshot (a `git archive` of that
|
|
15
|
+
# commit, committed into their repo) was the only surviving copy; this
|
|
16
|
+
# file is ported forward from it, checked against current `main`'s own
|
|
17
|
+
# conventions rather than copied wholesale, since the two trees had
|
|
18
|
+
# otherwise diverged for weeks in both directions.
|
|
19
|
+
#
|
|
20
|
+
# RESOLVED FROM THE CONSUMING REGISTRY'S OWN ROOT, not this gem's
|
|
21
|
+
# __dir__ — Framework::ROOT can be a fixed, `__dir__`-relative constant
|
|
22
|
+
# because framework members ship inside this gem; an embryonaut bluebook
|
|
23
|
+
# ships inside the CONSUMER's own checkout instead, at
|
|
24
|
+
# `<registry.root>/vendor/embryonaut_bluebooks/<name>/bluebook/`. There
|
|
25
|
+
# is no fixed answer until a registry (and its root) actually exists, so
|
|
26
|
+
# this resolves lazily, per call — the same reason `uses_framework`
|
|
27
|
+
# itself only runs at hecksagon-build time, when a real registry is
|
|
28
|
+
# current.
|
|
29
|
+
#
|
|
30
|
+
# EVERY `.bluebook` FILE IN THE PACKAGE, SORTED — not just one. Unlike a
|
|
31
|
+
# framework member (one file, named by its own stem), a vendored package
|
|
32
|
+
# can span several bluebook files that reopen the SAME `Hecks.bluebook`
|
|
33
|
+
# (embryonaut_bluebooks/payments/bluebook/{payment,payments,policies}
|
|
34
|
+
# .bluebook all reopen "Payments"). Load order matters — policies
|
|
35
|
+
# .bluebook names `Payment::Succeed` and needs the aggregate already
|
|
36
|
+
# built — and a plain alphabetical sort already gives the right order:
|
|
37
|
+
# payment < payments < policies, the same reason that package's own
|
|
38
|
+
# files are named to fall in that order in the first place.
|
|
39
|
+
#
|
|
40
|
+
# ONLY THE BLUEBOOK FILES — same restriction Framework draws, same
|
|
41
|
+
# reason: a `.hecksagon`/`.port`/`.adapter` is a WIRING decision
|
|
42
|
+
# (persistence, which processor adapter is bound) that belongs to
|
|
43
|
+
# whoever is deploying, never baked into the vendored package itself.
|
|
44
|
+
# embryonaut_bluebooks/payments ships its own mock `.hecksagon` for its
|
|
45
|
+
# own spec suite; a consumer declares its OWN separate
|
|
46
|
+
# `Hecks.hecksagon "Payments" do ... end` to bind real storage/adapters
|
|
47
|
+
# — see Framework's own comment for the fuller reasoning, identical here.
|
|
48
|
+
#
|
|
49
|
+
# IDEMPOTENT THE SAME WAY Framework.load! IS — checked against the
|
|
50
|
+
# bluebook this package actually declares (`Naming.pascal("payments")`
|
|
51
|
+
# => "Payments"), not a separate ledger. A vendored package's directory
|
|
52
|
+
# name and its declared `Hecks.bluebook` name are the one convention
|
|
53
|
+
# this reuses from Framework rather than reinventing.
|
|
54
|
+
module EmbryonautBluebook
|
|
55
|
+
def self.load!(name, registry: Hecks.current_registry)
|
|
56
|
+
unless registry&.root
|
|
57
|
+
raise Runtime::WiringError,
|
|
58
|
+
"uses_embryonaut_bluebook(#{name.inspect}) needs a registry with a root to vendor from"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return if registry.bluebook(Naming.pascal(name.to_s))
|
|
62
|
+
|
|
63
|
+
dir = File.join(registry.root, "vendor", "embryonaut_bluebooks", name.to_s, "bluebook")
|
|
64
|
+
files = Dir.glob(File.join(dir, "*.bluebook"))
|
|
65
|
+
|
|
66
|
+
if files.empty?
|
|
67
|
+
raise Runtime::WiringError,
|
|
68
|
+
"no vendored embryonaut bluebook named #{name.inspect} at #{dir} — " \
|
|
69
|
+
"run bin/vendor_embryonaut_bluebooks #{name}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
files.each { |file| Kernel.load(file) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require_relative "../runtime/errors"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Facade
|
|
5
|
+
# THE CLI DOOR — WHERE Facade MEETS A CALLER HOLDING FLAT STRINGS.
|
|
6
|
+
#
|
|
7
|
+
# `JsonDoor` beside this one translates for a caller holding parsed JSON:
|
|
8
|
+
# String keys, already-nested objects, real Integers. A command line has
|
|
9
|
+
# neither of those. It has `sequence.value=99` — one flat string, with the
|
|
10
|
+
# nesting spelled as a path and the type not spelled at all.
|
|
11
|
+
#
|
|
12
|
+
# So this does the two things that turns into: rebuild the nesting, and
|
|
13
|
+
# give every leaf the type the chapter declared for it.
|
|
14
|
+
#
|
|
15
|
+
# THE TYPE COMES FROM THE PROJECTION, NEVER FROM THE VALUE. A door that
|
|
16
|
+
# guessed — "99 looks like a number" — would send the Integer 99 for a
|
|
17
|
+
# version string of "99", and be wrong in a way nothing downstream could
|
|
18
|
+
# detect, because both are perfectly good arguments. `Projector::CliProjector`
|
|
19
|
+
# already read the declared field type out of the value object; this only
|
|
20
|
+
# applies it.
|
|
21
|
+
module CliDoor
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# `["reference.value=BUG#1", "sequence.value=99"]` against a projected
|
|
25
|
+
# verb spec -> `{ reference: { value: "BUG#1" }, sequence: { value: 99 } }`
|
|
26
|
+
def arguments(spec, pairs)
|
|
27
|
+
# Legacy options are accepted but not printed in help. This lets an
|
|
28
|
+
# existing id=... aggregate invocation cross the new receiver boundary
|
|
29
|
+
# while the projected surface teaches to=... exclusively.
|
|
30
|
+
options = (spec[:arguments] + Array(spec[:legacy_arguments])).to_h do |argument|
|
|
31
|
+
[argument[:path], argument]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
pairs.each_with_object({}) do |pair, args|
|
|
35
|
+
path, value = split(pair)
|
|
36
|
+
# key? first, never `||` — full names whichever spelling (the
|
|
37
|
+
# bare path, or its one-argument expansion) actually declares
|
|
38
|
+
# this option, and the lookup below must hold to that same
|
|
39
|
+
# decision rather than re-guessing which one exists.
|
|
40
|
+
full = options.key?(path) ? path : expand(path, options)
|
|
41
|
+
argument = options.key?(full) ? options[full] : raise(Runtime::NotFound, unknown(path, options.keys))
|
|
42
|
+
|
|
43
|
+
next append(args, full.split("."), cast(value, argument[:type])) if argument[:list]
|
|
44
|
+
|
|
45
|
+
bury(args, full.split("."), cast(value, argument[:type]))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def split(pair)
|
|
50
|
+
name, value = pair.split("=", 2)
|
|
51
|
+
raise Runtime::NotFound, "#{pair.inspect} is not name=value" if value.nil?
|
|
52
|
+
|
|
53
|
+
[name, value]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# THE SHORT FORM, FOR THE COMMON CASE. Almost every value object in this
|
|
57
|
+
# corpus has exactly one field, so `reference=BUG#1` is unambiguous and
|
|
58
|
+
# is what anybody types. Expanded only when precisely one option starts
|
|
59
|
+
# with that prefix — two would be a guess, and a guess about which field
|
|
60
|
+
# a caller meant is worse than asking them to say.
|
|
61
|
+
def expand(path, options)
|
|
62
|
+
candidates = options.keys.select { |key| key.start_with?("#{path}.") }
|
|
63
|
+
candidates.length == 1 ? candidates.first : path
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def cast(value, type)
|
|
67
|
+
case type
|
|
68
|
+
when "Integer" then Integer(value)
|
|
69
|
+
when "Float" then Float(value)
|
|
70
|
+
when "Boolean" then %w[true yes 1].include?(value.downcase)
|
|
71
|
+
else value
|
|
72
|
+
end
|
|
73
|
+
rescue ArgumentError
|
|
74
|
+
raise Runtime::TypeMismatch, "#{value.inspect} is not #{type} — the chapter declares this field as #{type}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# A LIST GROWS RATHER THAN OVERWRITES, and getting this wrong is silent.
|
|
78
|
+
#
|
|
79
|
+
# `tags.value=framework tags.value=model-checker` used to reach `bury`
|
|
80
|
+
# twice and store the second one alone — no refusal, no warning, one tag
|
|
81
|
+
# simply gone. That is the failure the interview named first: not the
|
|
82
|
+
# loud kind, the kind where a value is forgotten and the caller has no
|
|
83
|
+
# way to notice.
|
|
84
|
+
#
|
|
85
|
+
# A LIST OF ONE IS STILL A LIST. `tags.value=flaky` produces
|
|
86
|
+
# `[{ value: "flaky" }]`, not `{ value: "flaky" }`, because the chapter
|
|
87
|
+
# declared a collection and a caller who sent one element did not
|
|
88
|
+
# thereby declare a different shape. The old behaviour handed a bare
|
|
89
|
+
# object to a `list_of` attribute, and everything downstream that walks
|
|
90
|
+
# it — a query's `contains`, a projection, the Postgres adapter's own
|
|
91
|
+
# array handling — is entitled to assume it can iterate.
|
|
92
|
+
#
|
|
93
|
+
# MULTI-FIELD ELEMENTS ARE NOT SUPPORTED HERE, deliberately. A flat
|
|
94
|
+
# command line has no way to say which `a.x=` goes with which `a.y=`,
|
|
95
|
+
# and inventing an index syntax would be a language nobody asked for.
|
|
96
|
+
# Every list in this corpus is a list of single-field value objects; a
|
|
97
|
+
# richer one is a job for `JsonDoor`, which has real nesting.
|
|
98
|
+
def append(hash, path, value)
|
|
99
|
+
*branches, leaf = path.map(&:to_sym)
|
|
100
|
+
holder = branches[0..-2].reduce(hash) { |node, key| node[key] ||= {} }
|
|
101
|
+
list = holder[branches.last] ||= []
|
|
102
|
+
|
|
103
|
+
list << { leaf => value }
|
|
104
|
+
hash
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def bury(hash, path, value)
|
|
108
|
+
*branches, leaf = path.map(&:to_sym)
|
|
109
|
+
target = branches.reduce(hash) { |node, key| node[key] ||= {} }
|
|
110
|
+
target[leaf] = value
|
|
111
|
+
hash
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def unknown(path, known)
|
|
115
|
+
"no argument #{path.inspect} — this verb takes #{known.sort.join(', ')}"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|