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,219 @@
|
|
|
1
|
+
# A TOOLING-INTERNAL DOMAIN, not a business one — never attached via
|
|
2
|
+
# `uses_framework`, never seen by an end-user domain. `bin/project_deploy`
|
|
3
|
+
# boots this directory directly (Ports::Loading.bootstrap + load_domain,
|
|
4
|
+
# the same pipeline lib/hecks/runtime/loader.rb uses, minus a
|
|
5
|
+
# `.world`/`.hecksagon` this domain has no need for) and dispatches one
|
|
6
|
+
# command against it before generating anything from a domain's own
|
|
7
|
+
# `deployed_to("AwsLambda")` settings.
|
|
8
|
+
#
|
|
9
|
+
# WHY THIS EXISTS: `world.bluebook`'s `Wiring`/`Setting` is a DELIBERATELY
|
|
10
|
+
# open, untyped map — "a verb's schema is not even contained in the file
|
|
11
|
+
# being parsed" (syntax.bluebook's own header on this). That's the right
|
|
12
|
+
# call for `Wiring` itself (an adapter verb's shape genuinely isn't the
|
|
13
|
+
# world language's business), but it leaves `deployed_to`'s ACTUAL shape
|
|
14
|
+
# validated nowhere — `bin/project_deploy` used to check it by hand
|
|
15
|
+
# (`deploy_settings[:adapter] == "AwsLambda" or abort ...`,
|
|
16
|
+
# `fetch(:region) { abort ... }`), the exact raw-Ruby-refusal pattern
|
|
17
|
+
# every other kind of bluebook mistake in this codebase does NOT use.
|
|
18
|
+
# This closes that gap the same way any other rule gets enforced here:
|
|
19
|
+
# as a real command, real invariants, a real corpus-testable refusal —
|
|
20
|
+
# not a hand-written abort string discovered only by running the script.
|
|
21
|
+
Hecks.bluebook "Deploy" do
|
|
22
|
+
aggregate "LambdaTarget" do
|
|
23
|
+
description "One domain's validated AWS Lambda deploy target — what deployed_to(\"AwsLambda\") must resolve to before bin/project_deploy generates anything from it."
|
|
24
|
+
|
|
25
|
+
identified_by :domain
|
|
26
|
+
|
|
27
|
+
attribute :domain, DomainName
|
|
28
|
+
attribute :region, Region
|
|
29
|
+
attribute :memory, Memory
|
|
30
|
+
attribute :timeout, Timeout
|
|
31
|
+
attribute :database, Database
|
|
32
|
+
attribute :web, Web
|
|
33
|
+
|
|
34
|
+
value_object "DomainName" do
|
|
35
|
+
attribute :value, String
|
|
36
|
+
invariant("a domain is named") { !value.to_s.empty? }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
value_object "Region" do
|
|
40
|
+
attribute :value, String
|
|
41
|
+
invariant("a region is named") { !value.to_s.empty? }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# "Postgres" (a plain AWS::RDS::DBInstance, Embryonaut's own live
|
|
45
|
+
# choice — bin/project_deploy's Ruby-side default when a domain's
|
|
46
|
+
# own deployed_to("AwsLambda") names none, so every domain
|
|
47
|
+
# declared before this attribute existed keeps generating the
|
|
48
|
+
# identical template it always has) or "Aurora" (Aurora Serverless
|
|
49
|
+
# v2, Postgres-compatible — the SAME `Adapters::Postgres`/
|
|
50
|
+
# `tokio_postgres` code on both the Ruby and rust/host sides
|
|
51
|
+
# either way; this only changes which CloudFormation resource
|
|
52
|
+
# bin/project_deploy emits, never a runtime adapter).
|
|
53
|
+
# "Shared" — no RDS/VPC of this domain's own at all; it borrows
|
|
54
|
+
# another already-deployed domain's instance instead (the
|
|
55
|
+
# storehouse), isolated from that domain's own data by a native
|
|
56
|
+
# Postgres SCHEMA (this domain's own lowercase name), not a
|
|
57
|
+
# separate database. Requires a sibling `owner "OtherDomain"`
|
|
58
|
+
# setting inside the SAME deployed_to("AwsLambda") block, naming
|
|
59
|
+
# which already-deployed domain owns the shared instance —
|
|
60
|
+
# bin/project_deploy checks for that setting itself (Ruby-level,
|
|
61
|
+
# the same as `pg_version`/`google_oauth_present` below it), not
|
|
62
|
+
# here: which domain owns the instance is a deploy-time fact, not
|
|
63
|
+
# a business invariant this validated aggregate's job to hold.
|
|
64
|
+
value_object "Database" do
|
|
65
|
+
attribute :value, String, one_of: ["Postgres", "Aurora", "Shared"]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# "None" (the default — nothing changes for a domain that names
|
|
69
|
+
# neither this nor drops a `lambda_handler.rb`, e.g. Banking) or
|
|
70
|
+
# "Rust" — the domain's OWN Lambda (rust/host) also serves a public
|
|
71
|
+
# web UI in-process (rust/host/src/web.rs), reading HECKS_IR_PATH
|
|
72
|
+
# (a plain-JSON sidecar bin/project_wasm writes beside the .wasm
|
|
73
|
+
# artifact) and dispatching through the SAME in-process
|
|
74
|
+
# dispatch::handle/read the internal {"verb"}/{"read"} path already
|
|
75
|
+
# uses — no second Lambda, no VPC Interface Endpoint, no Ruby.
|
|
76
|
+
# Deliberately NOT "Ruby" as a third member here: a Ruby WebFunction
|
|
77
|
+
# is the pre-existing, SEPARATE `lambda_handler.rb`-file-presence
|
|
78
|
+
# mechanism (Embryonaut's own), untouched by this attribute —
|
|
79
|
+
# bin/project_deploy refuses generation outright if a domain
|
|
80
|
+
# declares `web "Rust"` AND has a `lambda_handler.rb`, rather than
|
|
81
|
+
# silently picking one.
|
|
82
|
+
value_object "Web" do
|
|
83
|
+
attribute :value, String, one_of: ["None", "Rust"]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# 128-10240 MB — AWS Lambda's own real ceiling and floor, not a
|
|
87
|
+
# number this domain invented. A typo here used to fail deep inside
|
|
88
|
+
# a real `sam deploy`, sometimes minutes in; now it refuses before
|
|
89
|
+
# a single file is generated.
|
|
90
|
+
value_object "Memory" do
|
|
91
|
+
attribute :value, Integer
|
|
92
|
+
invariant("memory is at least 128 MB") { value >= 128 }
|
|
93
|
+
invariant("memory is at most 10240 MB") { value <= 10240 }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 900s — Lambda's own real ceiling.
|
|
97
|
+
value_object "Timeout" do
|
|
98
|
+
attribute :value, Integer
|
|
99
|
+
invariant("a timeout is positive") { value.positive? }
|
|
100
|
+
invariant("a timeout fits Lambda's own 900s ceiling") { value <= 900 }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ALL FOUR REQUIRED — deliberately, not `optional: true` +
|
|
104
|
+
# `default:`. Confirmed directly against CommandInterpreter's own
|
|
105
|
+
# `normalize_args`/`coerce_declared_arguments` (interpreting.rb):
|
|
106
|
+
# `next unless normalized.key?(attribute.name)` skips coercion
|
|
107
|
+
# entirely for an absent key, so a command-level `default:` is
|
|
108
|
+
# honored ONLY at the JSON/Rust-codegen boundary (`scalar_from_json_
|
|
109
|
+
# expr`'s own domain), never by Ruby's own direct dispatch — a
|
|
110
|
+
# `default:` here would be quietly false advertising. "What memory
|
|
111
|
+
# defaults to when unspecified" stays exactly where it already
|
|
112
|
+
# lived (bin/project_deploy's own `fetch(:memory, 512)`); this
|
|
113
|
+
# command validates a FULLY-RESOLVED target, and bin/project_deploy
|
|
114
|
+
# resolves the default before ever dispatching.
|
|
115
|
+
command "Declare" do
|
|
116
|
+
goal "Validate one domain's AwsLambda deploy target before anything is generated from it"
|
|
117
|
+
|
|
118
|
+
attribute :domain, DomainName
|
|
119
|
+
attribute :region, Region
|
|
120
|
+
attribute :memory, Memory
|
|
121
|
+
attribute :timeout, Timeout
|
|
122
|
+
attribute :database, Database
|
|
123
|
+
attribute :web, Web
|
|
124
|
+
|
|
125
|
+
sets :domain
|
|
126
|
+
sets :region
|
|
127
|
+
sets :memory
|
|
128
|
+
sets :timeout
|
|
129
|
+
sets :database
|
|
130
|
+
sets :web
|
|
131
|
+
|
|
132
|
+
emits "LambdaTargetDeclared"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# `bin/project_tenant`'S OWN VALIDATED SHAPE — same split LambdaTarget
|
|
137
|
+
# draws with `bin/project_deploy`: this aggregate validates, that
|
|
138
|
+
# script GENERATES/ACTS (writes `environments/<slug>.world`, ensures
|
|
139
|
+
# the Postgres schema, boots once to prove it). Deliberately NOT a
|
|
140
|
+
# saga/process manager with durable runtime state — every step
|
|
141
|
+
# `bin/project_tenant` takes is already idempotent by construction
|
|
142
|
+
# (`CREATE SCHEMA IF NOT EXISTS` — PostgresEra#connect_for's own,
|
|
143
|
+
# File.write of the overlay — a plain overwrite, `Hecks.boot` itself),
|
|
144
|
+
# the same "generator script, not a business process" shape
|
|
145
|
+
# `bin/project_deploy` already is; a saga's own resumability earns
|
|
146
|
+
# its cost coordinating a real distributed transaction across
|
|
147
|
+
# multiple ports with real failure boundaries (Payments::Payment's
|
|
148
|
+
# own PaymentGateway, say) — three local, idempotent, single-process
|
|
149
|
+
# steps have no such boundary to resume across. A durable, reactable
|
|
150
|
+
# RUNTIME fact ("this tenant exists, is active, is suspended") is a
|
|
151
|
+
# different, later, business-facing thing — deliberately not built
|
|
152
|
+
# here, and deliberately not named Deploy::Tenant to avoid colliding
|
|
153
|
+
# with this validation-only aggregate should it ever land.
|
|
154
|
+
aggregate "Tenant" do
|
|
155
|
+
description "One tenant's validated identity — which realm and Postgres schema `bin/project_tenant` provisions before booting a domain directory under Runtime.boot's own `environment:` overlay for it."
|
|
156
|
+
|
|
157
|
+
identified_by :slug
|
|
158
|
+
|
|
159
|
+
attribute :slug, Slug
|
|
160
|
+
attribute :domain, DomainName
|
|
161
|
+
attribute :realm, Realm
|
|
162
|
+
attribute :schema, Schema
|
|
163
|
+
|
|
164
|
+
# LOWERCASE, NO SPACES — a slug becomes both the `environments/
|
|
165
|
+
# <slug>.world` filename and (by convention, not enforced here —
|
|
166
|
+
# `schema` is its own, separately-declared field precisely so a
|
|
167
|
+
# tenant CAN choose a different schema name than its slug)
|
|
168
|
+
# ordinarily the schema name too.
|
|
169
|
+
value_object "Slug" do
|
|
170
|
+
attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
|
|
171
|
+
invariant("a tenant slug is named") { !value.to_s.empty? }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
value_object "DomainName" do
|
|
175
|
+
attribute :value, String
|
|
176
|
+
invariant("a tenant names the domain it is for") { !value.to_s.empty? }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
value_object "Realm" do
|
|
180
|
+
attribute :value, String
|
|
181
|
+
invariant("a tenant realm is named") { !value.to_s.empty? }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# POSTGRES'S OWN REAL IDENTIFIER RULES, loosely — lowercase (this
|
|
185
|
+
# adapter never quotes a mixed-case identifier back out consistently
|
|
186
|
+
# once created), letters/digits/underscores, starting with a letter.
|
|
187
|
+
# NOT a byte-length ceiling too (Postgres's own real 63-byte
|
|
188
|
+
# NAMEDATALEN limit) — the expression grammar invariants compile
|
|
189
|
+
# through admits a narrow arithmetic/comparison subset, not
|
|
190
|
+
# arbitrary method dispatch (`bytesize` included; found live,
|
|
191
|
+
# writing this: `Bluebook::Expression::Evaluator` tries to resolve
|
|
192
|
+
# a dotted chain as a FIELD lookup, not a method call, the same
|
|
193
|
+
# "admits + but not -, no parentheses" narrowness
|
|
194
|
+
# embryonaut_bluebooks/payments' own Refund command already
|
|
195
|
+
# documents). Postgres itself refuses a too-long identifier loudly
|
|
196
|
+
# on its own; not worth fighting the expression grammar for a
|
|
197
|
+
# corner case this rare.
|
|
198
|
+
value_object "Schema" do
|
|
199
|
+
attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
|
|
200
|
+
invariant("a schema is named") { !value.to_s.empty? }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
command "Declare" do
|
|
204
|
+
goal "Validate one tenant's shape before bin/project_tenant provisions its schema and overlay world"
|
|
205
|
+
|
|
206
|
+
attribute :slug, Slug
|
|
207
|
+
attribute :domain, DomainName
|
|
208
|
+
attribute :realm, Realm
|
|
209
|
+
attribute :schema, Schema
|
|
210
|
+
|
|
211
|
+
sets :slug
|
|
212
|
+
sets :domain
|
|
213
|
+
sets :realm
|
|
214
|
+
sets :schema
|
|
215
|
+
|
|
216
|
+
emits "TenantDeclared"
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"audience": "Deploy",
|
|
3
|
+
"scopes": [
|
|
4
|
+
{
|
|
5
|
+
"scope": "deploy:lambda_target.declare",
|
|
6
|
+
"verb": "Deploy::LambdaTarget.Declare",
|
|
7
|
+
"role": null
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"scope": "deploy:tenant.declare",
|
|
11
|
+
"verb": "Deploy::Tenant.Declare",
|
|
12
|
+
"role": null
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"roles": [
|
|
16
|
+
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
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
|
+
}
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def guide_index(root)
|
|
265
|
+
paths = Dir.glob(File.join(root, "docs/implemented/guides/*.md")).sort
|
|
266
|
+
.reject { |p| %w[AUTHORING.md].include?(File.basename(p)) }
|
|
267
|
+
lines = paths.map do |path|
|
|
268
|
+
heading = File.foreach(path).find { |line| line.start_with?("# ") }
|
|
269
|
+
title = heading ? heading.sub(/\A#\s*/, "").strip : File.basename(path)
|
|
270
|
+
"- [#{title}](docs/implemented/guides/#{File.basename(path)})"
|
|
271
|
+
end
|
|
272
|
+
lines.join("\n")
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def reference_index(root)
|
|
276
|
+
count = contexts.size
|
|
277
|
+
"[The DSL reference](docs/implemented/reference/index.md) — #{count} contexts, generated from " \
|
|
278
|
+
"the aggregate-local tables under `lib/hecks/language/` and held to them by " \
|
|
279
|
+
"`spec/reference_golden_spec.rb`."
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def tool_table(root)
|
|
283
|
+
scripts = Dir.glob(File.join(root, "bin/*")).select { |p| File.file?(p) }.sort
|
|
284
|
+
rows = scripts.filter_map { |path| [path, tool_summary(path)] }.select { |_, desc| desc }
|
|
285
|
+
lines = ["| tool | |", "|---|---|"]
|
|
286
|
+
rows.each { |path, desc| lines << "| `bin/#{File.basename(path)}` | #{desc} |" }
|
|
287
|
+
lines.join("\n")
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# The opening comment PARAGRAPH, not just the first line — a table
|
|
291
|
+
# cell that trails off mid-clause reads worse than one that runs a
|
|
292
|
+
# little long and says "...". A code-bearing comment (`field.name`,
|
|
293
|
+
# `pattern:`) makes naive sentence-splitting on "." or ":" cut in
|
|
294
|
+
# the wrong place, so this truncates on LENGTH alone.
|
|
295
|
+
def tool_summary(path)
|
|
296
|
+
comment_lines = []
|
|
297
|
+
started = false
|
|
298
|
+
File.foreach(path).first(10).each do |line|
|
|
299
|
+
if line.start_with?("#") && !line.start_with?("#!")
|
|
300
|
+
next if !started && line.strip == "#"
|
|
301
|
+
|
|
302
|
+
started = true
|
|
303
|
+
comment_lines << line.sub(/\A#\s?/, "").rstrip
|
|
304
|
+
elsif started
|
|
305
|
+
break
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
return nil if comment_lines.empty?
|
|
309
|
+
|
|
310
|
+
text = comment_lines.join(" ").squeeze(" ")
|
|
311
|
+
text.length > 140 ? "#{text[0, 137]}..." : text
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def corpus_roster(root)
|
|
315
|
+
dirs = Dir.glob(File.join(root, "examples/*/")).sort
|
|
316
|
+
lines = dirs.filter_map do |dir|
|
|
317
|
+
name = File.basename(dir.chomp("/"))
|
|
318
|
+
bluebooks = Dir.glob(File.join(dir, "bluebook/*.bluebook")).sort
|
|
319
|
+
bluebooks = Dir.glob(File.join(dir, "*.bluebook")).sort if bluebooks.empty?
|
|
320
|
+
next if bluebooks.empty?
|
|
321
|
+
|
|
322
|
+
vision = bluebooks.filter_map { |bluebook| File.read(bluebook)[/vision\s+"([^"]*)"/, 1] }.first
|
|
323
|
+
"- **#{name}** — #{vision}"
|
|
324
|
+
end
|
|
325
|
+
lines.join("\n")
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def render_readme(root, text)
|
|
329
|
+
readme_regions(root).reduce(text) do |current, (id, content)|
|
|
330
|
+
pattern = /#{Regexp.escape(region_begin(id))}.*?#{Regexp.escape(GENERATED_END)}/m
|
|
331
|
+
current.sub(pattern) { "#{region_begin(id)}\n#{content}\n#{GENERATED_END}" }
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def write_readme!(root)
|
|
336
|
+
path = File.join(root, "README.md")
|
|
337
|
+
File.write(path, render_readme(root, File.read(path)))
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# An example A READER CAN SEE and the harness will actually run.
|
|
341
|
+
# `ruby skip` is display-only by the doctest harness's own rule, and
|
|
342
|
+
# a hidden `<!-- doctest:boot -->` block is setup rather than an
|
|
343
|
+
# example — a word whose only "example" is invisible or inert is a
|
|
344
|
+
# word still shipping on its prose alone, which is the thing this
|
|
345
|
+
# gate exists to refuse.
|
|
346
|
+
EXAMPLE_FENCE = /^```ruby(?: bluebook| boot)?[ \t]*$/.freeze
|
|
347
|
+
|
|
348
|
+
def exemplified?(prose) = prose.to_s.match?(EXAMPLE_FENCE)
|
|
349
|
+
|
|
350
|
+
# EVERY LIVE WORD, PAIRED WITH ITS PROSE. Both coverage gates ask a
|
|
351
|
+
# question about this same walk and differ only in what they ask of
|
|
352
|
+
# the prose, so they share it rather than each re-deriving the page
|
|
353
|
+
# set — the two are meant to move together, and one drifting past
|
|
354
|
+
# the other is how a word ends up counted documented by one and
|
|
355
|
+
# missing to the other.
|
|
356
|
+
#
|
|
357
|
+
# `harvest` already rejects empty prose and the TODO sentinel, so a
|
|
358
|
+
# word with nothing written for it arrives here with a nil.
|
|
359
|
+
def live_words(directory)
|
|
360
|
+
contexts.flat_map { |context|
|
|
361
|
+
path = File.join(directory, page_name(context))
|
|
362
|
+
prose = File.exist?(path) ? harvest(File.read(path)) : {}
|
|
363
|
+
keywords.select { |row| row[:context] == context && live?(row) }
|
|
364
|
+
.map { |row| [row[:word], context, prose[row[:word]]] }
|
|
365
|
+
}.uniq { |word, context, _| [word, context] }
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def name_of(word, context) = "#{word} (#{context})"
|
|
369
|
+
|
|
370
|
+
# The coverage gate's question: every LIVE word with no prose yet.
|
|
371
|
+
def undocumented(directory)
|
|
372
|
+
live_words(directory).reject { |_word, _context, prose| prose }
|
|
373
|
+
.map { |word, context, _| name_of(word, context) }
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# The SECOND coverage gate: prose is a declaration, and a
|
|
377
|
+
# declaration nothing runs cannot disagree with anything. A word
|
|
378
|
+
# documented only in sentences can go stale — or describe a word
|
|
379
|
+
# the runtime never wired at all, which this repository has already
|
|
380
|
+
# shipped twice (`read_model`'s where/order_by/limit/offset, and
|
|
381
|
+
# `role`/`goal` on a command). An example that RUNS is the only
|
|
382
|
+
# documentation that can go red.
|
|
383
|
+
def unexemplified(directory)
|
|
384
|
+
live_words(directory).reject { |_word, _context, prose| exemplified?(prose) }
|
|
385
|
+
.map { |word, context, _| name_of(word, context) }
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|