hecks 3.13.0 → 4.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 +4 -4
- data/examples/pizzas/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +1 -7
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/named_pattern.rb +33 -0
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +3 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/interview_aggregate.rb +4 -4
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/framework/bluebook/change.bluebook +2 -2
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +58 -37
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +15 -92
- data/lib/hecks/hecks/hecks.bluebook +2 -2
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +10 -16
- data/lib/hecks/hecks/tooling.bluebook +22 -22
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
- data/lib/hecks/language/bluebook/command.bluebook +1 -0
- data/lib/hecks/language/bluebook/entity.bluebook +1 -0
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +8 -7
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +51 -51
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value.rb +13 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/site/bluebook/site.bluebook +5 -5
- data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
- data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +300 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +145 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +606 -205
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +990 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +191 -0
- data/rust/host/src/payments/tests.rs +1445 -275
- data/rust/host/src/payments.rs +409 -121
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +483 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -0
- data/rust/parser/src/build/named_pattern.rs +26 -0
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -510
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +43 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +229 -112
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +230 -148
- data/rust/src/kernel/mod.rs +46 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
- data/rust/host/src/approval.rs +0 -652
|
@@ -203,17 +203,6 @@ module Hecks
|
|
|
203
203
|
registry
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
-
# Whether `grammar_registry`'s memoized singleton has finished its
|
|
207
|
-
# fixpoint judge and attached-chapter load.
|
|
208
|
-
#
|
|
209
|
-
# A cache keyed on "finished" instead of its own inputs already missed
|
|
210
|
-
# data for a whole build window once.
|
|
211
|
-
#
|
|
212
|
-
# @return [Boolean]
|
|
213
|
-
def self.grammar_registry_ready?
|
|
214
|
-
@grammar_registry && @grammar_ready_for == @grammar_registry.object_id
|
|
215
|
-
end
|
|
216
|
-
|
|
217
206
|
# Loads every `ATTACHED_GRAMMAR_DIR` chapter file into `registry`,
|
|
218
207
|
# judged the ordinary way since the fixpoint has already run.
|
|
219
208
|
#
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
# The patterns a bluebook names instead of spelling: `matches: :snake_name` says what
|
|
4
|
+
# `pattern: '^[a-z][a-z0-9_]*$'` does. A name stands for exactly its regex, so the IR, the
|
|
5
|
+
# generated Rust and the runtime check see the pattern and never the name.
|
|
6
|
+
#
|
|
7
|
+
# `rust/parser/src/build/named_pattern.rs` holds the same table; spec/named_pattern_spec.rb
|
|
8
|
+
# holds the two to each other.
|
|
9
|
+
module NamedPattern
|
|
10
|
+
# Name => the regex source it stands for, each the exact source the corpus spelled by hand.
|
|
11
|
+
TABLE = {
|
|
12
|
+
present: '[^ \t\n\r]',
|
|
13
|
+
not_empty: '[^\t\n\r]',
|
|
14
|
+
no_whitespace: '^[^ \t\n\r]+$',
|
|
15
|
+
snake_name: "^[a-z][a-z0-9_]*$",
|
|
16
|
+
git_sha: "^[0-9a-fA-F]{7,40}$",
|
|
17
|
+
digits: "^[0-9]+$"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# @param name [Symbol, String] a name in `TABLE`
|
|
23
|
+
# @return [String] the regex source it stands for
|
|
24
|
+
# @raise [ArgumentError] when the name is not one the language gives
|
|
25
|
+
def source(name)
|
|
26
|
+
TABLE.fetch(name.to_sym) do
|
|
27
|
+
raise ArgumentError, "matches: #{name.inspect} is not a named pattern — " \
|
|
28
|
+
"the names are #{TABLE.keys.map(&:inspect).join(", ")}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -16,13 +16,16 @@ module Hecks
|
|
|
16
16
|
# The attribute kept its name, so `rename`/`move` cannot express it.
|
|
17
17
|
TranslationRetype = Struct.new(:from, :to)
|
|
18
18
|
|
|
19
|
-
# A computed
|
|
20
|
-
#
|
|
21
|
-
|
|
19
|
+
# A new field computed from the record's own text by an expression, read before any other
|
|
20
|
+
# rule runs. The fields the expression reads stay where they are; `drop` removes them.
|
|
21
|
+
# `expression` is the source text and `tree` its parsed plan form.
|
|
22
|
+
TranslationDerive = Struct.new(:to, :expression) do
|
|
23
|
+
def tree = TranslationExpression.parse(expression)
|
|
24
|
+
end
|
|
22
25
|
|
|
23
|
-
# The aggregate's
|
|
24
|
-
#
|
|
25
|
-
TranslationRekey = Struct.new(:
|
|
26
|
+
# The aggregate's id is the value of the field a `derive` writes, for every record where that
|
|
27
|
+
# derive has a value; a record where it has none keeps its id.
|
|
28
|
+
TranslationRekey = Struct.new(:to)
|
|
26
29
|
|
|
27
30
|
# A newly added, required attribute with no source in old data.
|
|
28
31
|
# `default` is what an existing record reads until a command writes a real value;
|
|
@@ -32,12 +35,12 @@ module Hecks
|
|
|
32
35
|
# One aggregate's part of a translation between eras.
|
|
33
36
|
# `drops` makes data loss a declared decision.
|
|
34
37
|
class TranslationAggregate
|
|
35
|
-
attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :
|
|
38
|
+
attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :derives, :rekeys, :backfills
|
|
36
39
|
|
|
37
40
|
# Every optional field and what it holds when the declaration omits it.
|
|
38
41
|
FIELD_DEFAULTS = {
|
|
39
42
|
was: nil, renames: {}, moves: [], converts: [], drops: [], retypes: [],
|
|
40
|
-
|
|
43
|
+
derives: [], rekeys: [], backfills: []
|
|
41
44
|
}.freeze
|
|
42
45
|
|
|
43
46
|
# @param name [String, Symbol] the aggregate's name in the destination era
|
|
@@ -47,8 +50,8 @@ module Hecks
|
|
|
47
50
|
# @param converts [Array<Bluebook::TranslationConvert>] fields remapped via a lookup table
|
|
48
51
|
# @param drops [Array<Symbol>] fields deliberately not carried forward
|
|
49
52
|
# @param retypes [Array<Bluebook::TranslationRetype>] value object or entity type changes
|
|
50
|
-
# @param
|
|
51
|
-
# @param rekeys [Array<Bluebook::TranslationRekey>]
|
|
53
|
+
# @param derives [Array<Bluebook::TranslationDerive>] fields computed from the record's text
|
|
54
|
+
# @param rekeys [Array<Bluebook::TranslationRekey>] the aggregate's identity, recomputed
|
|
52
55
|
# @param backfills [Array<Bluebook::TranslationBackfill>] new required fields, no old source
|
|
53
56
|
def initialize(name:, **given)
|
|
54
57
|
KeywordFields.assign(self, KeywordFields.fill(given, FIELD_DEFAULTS))
|
|
@@ -83,6 +86,11 @@ module Hecks
|
|
|
83
86
|
# @return [Bluebook::TranslationAggregate, nil] the aggregate's translation, or
|
|
84
87
|
# `nil` if `name` carries no translation rules
|
|
85
88
|
def for_aggregate(name) = @aggregates.find { |aggregate| aggregate.name == name.to_s }
|
|
89
|
+
|
|
90
|
+
# This edge as plain data: each aggregate's rules as an ordered list of ops.
|
|
91
|
+
#
|
|
92
|
+
# @return [Hash] see `Bluebook::TranslationPlan`
|
|
93
|
+
def to_plan = TranslationPlan.build(self)
|
|
86
94
|
end
|
|
87
95
|
end
|
|
88
96
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
module TranslationExpression
|
|
4
|
+
# Computes a parsed expression for one record's state. The reference semantics the
|
|
5
|
+
# compiled Postgres form is held to.
|
|
6
|
+
module Evaluator
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# @param tree [Array] a tree `TranslationExpression.parse` returned
|
|
10
|
+
# @param state [Hash] the record's stored state
|
|
11
|
+
# @return [String, nil] the computed text; nil when the expression has no value
|
|
12
|
+
def call(tree, state)
|
|
13
|
+
case tree.first
|
|
14
|
+
when "lit" then tree[1]
|
|
15
|
+
when "null" then nil
|
|
16
|
+
when "ref" then reference(tree[1], state)
|
|
17
|
+
else apply(tree.first, tree.drop(1), state)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Reads a dotted path out of a record; only text counts as a value.
|
|
22
|
+
def reference(path, state)
|
|
23
|
+
value = path.split(".").inject(state) { |held, key| held.is_a?(Hash) ? member(held, key) : nil }
|
|
24
|
+
value.is_a?(String) ? value : nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A record's top-level keys are symbols, a value object's are strings.
|
|
28
|
+
def member(hash, key) = hash.key?(key.to_sym) ? hash[key.to_sym] : hash[key]
|
|
29
|
+
|
|
30
|
+
def apply(name, arguments, state)
|
|
31
|
+
return lookup(arguments, state) if name == "lookup"
|
|
32
|
+
return replace(arguments, state) if name == "replace"
|
|
33
|
+
|
|
34
|
+
values = arguments.map { |argument| call(argument, state) }
|
|
35
|
+
return nil if values.any?(&:nil?)
|
|
36
|
+
|
|
37
|
+
fold(name, values)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fold(name, values)
|
|
41
|
+
case name
|
|
42
|
+
when "lower" then values.first.downcase(:ascii)
|
|
43
|
+
when "upper" then values.first.upcase(:ascii)
|
|
44
|
+
else values.join
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def replace(arguments, state)
|
|
49
|
+
target, from, to = arguments
|
|
50
|
+
call(target, state)&.gsub(from.last) { to.last }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def lookup(arguments, state)
|
|
54
|
+
key, table, fallback = arguments
|
|
55
|
+
value = call(key, state)
|
|
56
|
+
return nil if value.nil?
|
|
57
|
+
|
|
58
|
+
table.fetch(value) { call(fallback, state) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "strscan"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Bluebook
|
|
6
|
+
module TranslationExpression
|
|
7
|
+
# Reads the text of an expression into its tree, refusing anything outside the language.
|
|
8
|
+
class Parser
|
|
9
|
+
NAME = /[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*/
|
|
10
|
+
STRING = /"(?:[^"\\]|\\.)*"/
|
|
11
|
+
|
|
12
|
+
# @param text [String] the expression's source text
|
|
13
|
+
def initialize(text)
|
|
14
|
+
@text = text.to_s
|
|
15
|
+
@scan = StringScanner.new(@text)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Array] the expression tree
|
|
19
|
+
# @raise [TranslationExpression::Refused] if the text is not a well-formed expression
|
|
20
|
+
def call
|
|
21
|
+
tree = expression
|
|
22
|
+
skip
|
|
23
|
+
refuse("unexpected #{@scan.rest[0, 12].inspect} after the expression") unless @scan.eos?
|
|
24
|
+
tree
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def expression
|
|
30
|
+
skip
|
|
31
|
+
return ["lit", string] if @scan.check(/"/)
|
|
32
|
+
|
|
33
|
+
name = @scan.scan(NAME) || refuse("expected a field, a string or a function")
|
|
34
|
+
return function(name) if @scan.check(/\s*\(/)
|
|
35
|
+
|
|
36
|
+
name == "null" ? ["null"] : ["ref", name]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def function(name)
|
|
40
|
+
refuse("#{name} is not a function (#{FUNCTIONS.keys.join(", ")})") unless FUNCTIONS.key?(name)
|
|
41
|
+
punct("(")
|
|
42
|
+
arguments = list { argument(name, _1) }
|
|
43
|
+
punct(")")
|
|
44
|
+
check_arguments(name, arguments)
|
|
45
|
+
[name, *arguments]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def argument(name, index)
|
|
49
|
+
skip
|
|
50
|
+
return table if name == "lookup" && index == 1
|
|
51
|
+
|
|
52
|
+
expression
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def list
|
|
56
|
+
items = []
|
|
57
|
+
loop do
|
|
58
|
+
items << yield(items.size)
|
|
59
|
+
skip
|
|
60
|
+
break unless @scan.skip(/,/)
|
|
61
|
+
end
|
|
62
|
+
items
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def table
|
|
66
|
+
punct("{")
|
|
67
|
+
pairs = list { pair }
|
|
68
|
+
punct("}")
|
|
69
|
+
refuse("a lookup table repeats a key") unless pairs.map(&:first).uniq.size == pairs.size
|
|
70
|
+
pairs.to_h
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def pair
|
|
74
|
+
skip
|
|
75
|
+
key = string
|
|
76
|
+
punct(":")
|
|
77
|
+
skip
|
|
78
|
+
[key, string]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def string
|
|
82
|
+
token = @scan.scan(STRING) || refuse("expected a string")
|
|
83
|
+
JSON.parse(token, quirks_mode: true)
|
|
84
|
+
rescue JSON::ParserError
|
|
85
|
+
refuse("#{token} is not a well-formed string")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def punct(mark)
|
|
89
|
+
skip
|
|
90
|
+
@scan.skip(Regexp.new(Regexp.escape(mark))) || refuse("expected #{mark.inspect}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def skip = @scan.skip(/\s+/)
|
|
94
|
+
|
|
95
|
+
def check_arguments(name, arguments)
|
|
96
|
+
arity = FUNCTIONS.fetch(name)
|
|
97
|
+
refuse("#{name} takes #{arity} expressions, not #{arguments.size}") if arity && arguments.size != arity
|
|
98
|
+
refuse("concat takes two or more expressions") if arity.nil? && arguments.size < 2
|
|
99
|
+
check_literals(name, arguments)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def check_literals(name, arguments)
|
|
103
|
+
case name
|
|
104
|
+
when "replace" then check_replace(arguments)
|
|
105
|
+
when "lookup" then check_lookup(arguments)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def check_replace(arguments)
|
|
110
|
+
_target, from, to = arguments
|
|
111
|
+
refuse("replace needs string literals for what to find and what to put") unless [from, to].all? { _1.first == "lit" }
|
|
112
|
+
refuse("replace cannot look for an empty string") if from.last.empty?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def check_lookup(arguments)
|
|
116
|
+
_key, table, fallback = arguments
|
|
117
|
+
refuse("lookup needs a table of at least one pair") unless table.is_a?(Hash) && !table.empty?
|
|
118
|
+
refuse("lookup needs a string literal or null as its fallback") unless %w[lit null].include?(fallback.first)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def refuse(why)
|
|
122
|
+
raise Refused, "#{why} (in #{@text.inspect} at offset #{@scan.pos})"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require_relative "translation_expression/parser"
|
|
2
|
+
require_relative "translation_expression/evaluator"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Bluebook
|
|
6
|
+
# The expression a `derive` rule computes a new field from: text functions over the record's
|
|
7
|
+
# stored text, with no storage engine in sight. It parses to a tree of plain arrays, which
|
|
8
|
+
# is also its plan form:
|
|
9
|
+
#
|
|
10
|
+
# ["ref", "name.value"] ["lit", "text"] ["null"]
|
|
11
|
+
# ["lower", x] ["upper", x] ["concat", x, y, ...]
|
|
12
|
+
# ["replace", x, ["lit", from], ["lit", to]]
|
|
13
|
+
# ["lookup", x, {"key" => "value"}, ["lit", default] | ["null"]]
|
|
14
|
+
#
|
|
15
|
+
# A reference that is missing or not text has no value, and a function with a valueless
|
|
16
|
+
# argument has none either.
|
|
17
|
+
module TranslationExpression
|
|
18
|
+
# Raised when the text of an expression is not one the language accepts.
|
|
19
|
+
class Refused < StandardError; end
|
|
20
|
+
|
|
21
|
+
# The functions and how many expressions each takes; `nil` means "two or more".
|
|
22
|
+
FUNCTIONS = { "lower" => 1, "upper" => 1, "concat" => nil, "replace" => 3, "lookup" => 3 }.freeze
|
|
23
|
+
|
|
24
|
+
# Parses an expression's text into its tree.
|
|
25
|
+
#
|
|
26
|
+
# @param text [String] the expression, e.g. `lower(name.value)`
|
|
27
|
+
# @return [Array] the expression tree
|
|
28
|
+
# @raise [Refused] if the text is not a well-formed expression
|
|
29
|
+
def self.parse(text) = Parser.new(text).call
|
|
30
|
+
|
|
31
|
+
# Computes an expression's value for one record's state.
|
|
32
|
+
#
|
|
33
|
+
# @param tree [Array] a tree `parse` returned
|
|
34
|
+
# @param state [Hash] the record's stored state
|
|
35
|
+
# @return [String, nil] the text it computes; nil when it has no value
|
|
36
|
+
def self.evaluate(tree, state) = Evaluator.call(tree, state)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
# A translation as data: each aggregate's rules as an ordered list of ops any runtime can
|
|
4
|
+
# interpret, with nothing in them that names a storage engine. In the order they apply to a
|
|
5
|
+
# record, each op has its name under "op" plus:
|
|
6
|
+
#
|
|
7
|
+
# derive "to" "expression" (a tree) rename "from" "to" move "from" "to" (dotted paths)
|
|
8
|
+
# convert "from" "to" "values" ([old, new] pairs) drop "path" retype "from" "to"
|
|
9
|
+
# backfill "path" "default" rekey_to "to"
|
|
10
|
+
module TranslationPlan
|
|
11
|
+
VERSION = 1
|
|
12
|
+
|
|
13
|
+
# The op names, in the order they apply; one per rule list of a `TranslationAggregate`.
|
|
14
|
+
OPS = %w[derive rename move convert drop retype backfill rekey_to].freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# @param translation [Bluebook::Translation] a declared translation edge
|
|
19
|
+
# @return [Hash] the edge as plain data, ready for `JSON.generate`
|
|
20
|
+
def build(translation)
|
|
21
|
+
{
|
|
22
|
+
"version" => VERSION, "domain" => translation.domain, "from" => translation.from.to_s,
|
|
23
|
+
"to" => translation.to.to_s, "retired" => translation.retired.map(&:to_s),
|
|
24
|
+
"aggregates" => translation.aggregates.map { |aggregate| aggregate_plan(aggregate) }
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def aggregate_plan(aggregate)
|
|
29
|
+
{ "name" => aggregate.name, "was" => aggregate.was, "ops" => ops(aggregate) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ops(aggregate)
|
|
33
|
+
[*derives(aggregate), *renames(aggregate), *moves(aggregate), *converts(aggregate), *drops(aggregate),
|
|
34
|
+
*retypes(aggregate), *backfills(aggregate), *rekeys(aggregate)]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def derives(aggregate)
|
|
38
|
+
aggregate.derives.map { |d| { "op" => "derive", "to" => d.to, "expression" => d.tree } }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def renames(aggregate)
|
|
42
|
+
aggregate.renames.map { |from, to| { "op" => "rename", "from" => from.to_s, "to" => to.to_s } }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def moves(aggregate)
|
|
46
|
+
aggregate.moves.map { |m| { "op" => "move", "from" => m.from, "to" => m.to } }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def converts(aggregate)
|
|
50
|
+
aggregate.converts.map do |c|
|
|
51
|
+
{ "op" => "convert", "from" => c.from, "to" => c.to, "values" => c.values.map { |old, new| [old, new] } }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def drops(aggregate) = aggregate.drops.map { |path| { "op" => "drop", "path" => path.to_s } }
|
|
56
|
+
|
|
57
|
+
def retypes(aggregate)
|
|
58
|
+
aggregate.retypes.map { |r| { "op" => "retype", "from" => r.from, "to" => r.to } }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def backfills(aggregate)
|
|
62
|
+
aggregate.backfills.map { |b| { "op" => "backfill", "path" => b.name.to_s, "default" => b.default } }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rekeys(aggregate)
|
|
66
|
+
aggregate.rekeys.map { |rekey| { "op" => "rekey_to", "to" => rekey.to } }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/hecks/bluebook.rb
CHANGED
|
@@ -29,7 +29,9 @@ require_relative "bluebook/ask_resolution"
|
|
|
29
29
|
require_relative "bluebook/process_manager"
|
|
30
30
|
require_relative "bluebook/aggregate"
|
|
31
31
|
require_relative "bluebook/hecksagon"
|
|
32
|
+
require_relative "bluebook/translation_expression"
|
|
32
33
|
require_relative "bluebook/translation"
|
|
34
|
+
require_relative "bluebook/translation_plan"
|
|
33
35
|
|
|
34
36
|
require_relative "bluebook/assembly/contract"
|
|
35
37
|
require_relative "bluebook/assembly/contracts"
|
|
@@ -44,6 +46,7 @@ require_relative "bluebook/project_loader"
|
|
|
44
46
|
|
|
45
47
|
require_relative "bluebook/dsl"
|
|
46
48
|
require_relative "bluebook/pattern_subset"
|
|
49
|
+
require_relative "bluebook/named_pattern"
|
|
47
50
|
|
|
48
51
|
require_relative "bluebook/meta_validator"
|
|
49
52
|
require_relative "bluebook/meta_validator/plan"
|
data/lib/hecks/chapters.rb
CHANGED
|
@@ -5,8 +5,8 @@ require_relative "bluebook/meta_validator"
|
|
|
5
5
|
|
|
6
6
|
module Hecks
|
|
7
7
|
# The chapters the gem carries that a hecksagon can attach by name (ADR 0080): the language
|
|
8
|
-
# declared in itself, Expression, Tenancy, Deploy, Site, Tickets and QualityControl.
|
|
9
|
-
# members stay in `Framework`; `table` and `attach!` find a name across both.
|
|
8
|
+
# declared in itself, Expression, Tenancy, Deploy, Site, Tickets, Composer and QualityControl.
|
|
9
|
+
# Framework members stay in `Framework`; `table` and `attach!` find a name across both.
|
|
10
10
|
#
|
|
11
11
|
# A chapter is named by the `Hecks.bluebook "Name"` header of its files, and may span several.
|
|
12
12
|
# Beside its bluebook a chapter may carry what every hecksagon attaching it needs, whatever
|
|
@@ -16,7 +16,7 @@ module Hecks
|
|
|
16
16
|
# Where attachable chapters live, relative to `lib/hecks/`.
|
|
17
17
|
GLOBS = %w[language/**/*.bluebook grammar/expression.bluebook tenancy/bluebook/*.bluebook
|
|
18
18
|
deploy/bluebook/*.bluebook site/bluebook/*.bluebook tickets/bluebook/*.bluebook
|
|
19
|
-
quality_control/*.bluebook].freeze
|
|
19
|
+
composer/bluebook/*.bluebook quality_control/*.bluebook].freeze
|
|
20
20
|
|
|
21
21
|
# Every attachable chapter, by name, with the files that declare it.
|
|
22
22
|
#
|
|
@@ -96,7 +96,7 @@ module Hecks
|
|
|
96
96
|
attribute :name, ExampleName
|
|
97
97
|
|
|
98
98
|
value_object "ExampleName" do
|
|
99
|
-
attribute :value, String,
|
|
99
|
+
attribute :value, String, matches: :present
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
# TODO: replace Create with the first thing that happens to an Example.
|
|
@@ -6,8 +6,8 @@ module Hecks
|
|
|
6
6
|
# The bluebook text of one accepted thing: its aggregate, with its value objects, commands and
|
|
7
7
|
# lifecycle. `InterviewDraft` extends it, and supplies the naming and citing helpers it calls.
|
|
8
8
|
module InterviewAggregate
|
|
9
|
-
# The
|
|
10
|
-
|
|
9
|
+
# The option every generated string field carries: it refuses a blank value.
|
|
10
|
+
PRESENT = "matches: :present".freeze
|
|
11
11
|
|
|
12
12
|
# What stands in for a creating command nothing in the interview named.
|
|
13
13
|
STUB_NOTE = " # TODO: no action that creates was accepted; replace Create with the real one.".freeze
|
|
@@ -15,7 +15,7 @@ module Hecks
|
|
|
15
15
|
# One accepted thing: what it is called and identified by, its fields, its transitions, the
|
|
16
16
|
# actions that create it and the others.
|
|
17
17
|
Shape = Struct.new(:name, :identifier, :type, :fields, :steps, :creating, :rest, keyword_init: true)
|
|
18
|
-
private_constant :
|
|
18
|
+
private_constant :PRESENT, :STUB_NOTE, :Shape
|
|
19
19
|
|
|
20
20
|
# @api private
|
|
21
21
|
def aggregate(interview, thing, actions)
|
|
@@ -66,7 +66,7 @@ module Hecks
|
|
|
66
66
|
# @api private
|
|
67
67
|
def type_lines(shape)
|
|
68
68
|
[" value_object #{shape.type.inspect} do",
|
|
69
|
-
" attribute :value, String,
|
|
69
|
+
" attribute :value, String, #{PRESENT}", " end",
|
|
70
70
|
*InterviewShape.value_objects(shape.fields)]
|
|
71
71
|
end
|
|
72
72
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "open3"
|
|
3
|
+
require "timeout"
|
|
4
|
+
|
|
5
|
+
require_relative "../agent"
|
|
6
|
+
require_relative "jev"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Adapters
|
|
10
|
+
# Real `composer_agent` fulfillment for `draft` and `wire`: the developer's own `claude` run
|
|
11
|
+
# as the `hecks-composer` agent, which edits the workspace's files in place. Transport only;
|
|
12
|
+
# whether the edit is any good is the Composer's own check to judge.
|
|
13
|
+
#
|
|
14
|
+
# The agent's write guard confines it to domain files inside the git checkout, so the
|
|
15
|
+
# workspace must sit inside one.
|
|
16
|
+
module ClaudeComposer
|
|
17
|
+
TIMEOUT_SECONDS = 300
|
|
18
|
+
|
|
19
|
+
AGENT = "hecks-composer".freeze
|
|
20
|
+
|
|
21
|
+
DRAFT = "Edit the domain files in %<dir>s as the request below says. Change only what it asks " \
|
|
22
|
+
"for, write the bluebook first, then any hecksagon wiring it needs. Reply with one line " \
|
|
23
|
+
"saying what you changed.".freeze
|
|
24
|
+
|
|
25
|
+
WIRE = "Change how the chapter in %<dir>s is wired, as the request below says: edit only its " \
|
|
26
|
+
".hecksagon and .world files, and bind adapters that already exist. Reply with one line " \
|
|
27
|
+
"saying what you changed.".freeze
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# @param prompt [String] what the author asked for
|
|
32
|
+
# @param dir [String] the workspace directory
|
|
33
|
+
# @return [Hash] `"note"` the agent's account of what it changed
|
|
34
|
+
# @raise [Ports::Agent::Unavailable] see {Composer::Agent::Unavailable}
|
|
35
|
+
def draft(prompt:, dir:) = call(DRAFT, prompt, dir)
|
|
36
|
+
|
|
37
|
+
# @param prompt [String] what the author asked for
|
|
38
|
+
# @param dir [String] the workspace directory
|
|
39
|
+
# @return [Hash] `"note"` the agent's account of what it changed
|
|
40
|
+
def wire(prompt:, dir:) = call(WIRE, prompt, dir)
|
|
41
|
+
|
|
42
|
+
# Naming what a prompt asks for is the `jev` categorizer's work, which this adapter lends.
|
|
43
|
+
#
|
|
44
|
+
# @param prompt [String] what the author typed
|
|
45
|
+
# @return [Hash] `"intent"` one of {Composer::Agent::INTENTS}
|
|
46
|
+
# @raise [Composer::Agent::Unavailable] if `jev` cannot answer
|
|
47
|
+
def classify(prompt:) = Jev.classify(prompt: prompt)
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
def call(task, prompt, dir)
|
|
51
|
+
root = checkout_of(dir)
|
|
52
|
+
message = "#{format(task, dir: dir)}\n\nRequest: #{prompt}"
|
|
53
|
+
stdout, status = run(root, message)
|
|
54
|
+
raise Composer::Agent::Unavailable, "claude exited #{status.exitstatus}: #{stdout}" unless status.success?
|
|
55
|
+
|
|
56
|
+
{ "note" => JSON.parse(stdout).fetch("result").to_s.lines.first.to_s.strip }
|
|
57
|
+
rescue JSON::ParserError, KeyError => e
|
|
58
|
+
raise Composer::Agent::ValidationError, "claude's reply was not the expected envelope: #{e.message}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Argv spawn, never a shell, so a `claude` shell alias cannot apply.
|
|
62
|
+
# @api private
|
|
63
|
+
def run(root, message)
|
|
64
|
+
Timeout.timeout(TIMEOUT_SECONDS) do
|
|
65
|
+
Open3.capture2("claude", "-p", "--agent", AGENT, "--output-format", "json",
|
|
66
|
+
"--permission-mode", "acceptEdits", stdin_data: message, chdir: root)
|
|
67
|
+
end
|
|
68
|
+
rescue Timeout::Error
|
|
69
|
+
raise Composer::Agent::Unavailable, "claude did not answer within #{TIMEOUT_SECONDS}s"
|
|
70
|
+
rescue Errno::ENOENT => e
|
|
71
|
+
raise Composer::Agent::Unavailable, "claude is not on PATH: #{e.message}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @api private
|
|
75
|
+
def checkout_of(dir)
|
|
76
|
+
top, status = Open3.capture2("git", "-C", dir, "rev-parse", "--show-toplevel")
|
|
77
|
+
raise Composer::Agent::Unavailable, "#{dir} is not inside a git checkout" unless status.success?
|
|
78
|
+
|
|
79
|
+
top.strip
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require_relative "../agent"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
# A scripted `composer_agent`, for specs and for trying the editor with no model behind it.
|
|
6
|
+
# Each ask is recorded; `draft` and `wire` run a script that may rewrite the workspace's files,
|
|
7
|
+
# so a test sees the same thing the real agent leaves behind.
|
|
8
|
+
module FakeAgent
|
|
9
|
+
class << self
|
|
10
|
+
# @return [Array<Hash>] every ask, as `{ask:, prompt:, dir:}` in the order made
|
|
11
|
+
def calls = @calls ||= []
|
|
12
|
+
|
|
13
|
+
# @return [#call, nil] takes `(prompt, dir)` and edits files under `dir`; may return a note
|
|
14
|
+
attr_accessor :edit
|
|
15
|
+
|
|
16
|
+
# @return [#call, nil] takes `prompt` and answers an intent
|
|
17
|
+
attr_accessor :intent
|
|
18
|
+
|
|
19
|
+
# Forgets the calls and both scripts.
|
|
20
|
+
# @return [void]
|
|
21
|
+
def reset!
|
|
22
|
+
@calls = []
|
|
23
|
+
@edit = nil
|
|
24
|
+
@intent = nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# @param prompt [String] what the author asked for
|
|
31
|
+
# @param dir [String] the workspace directory
|
|
32
|
+
# @return [Hash] `"note"` what the script reported
|
|
33
|
+
def draft(prompt:, dir:) = edited(:draft, prompt, dir)
|
|
34
|
+
|
|
35
|
+
# @param prompt [String] what the author asked for
|
|
36
|
+
# @param dir [String] the workspace directory
|
|
37
|
+
# @return [Hash] `"note"` what the script reported
|
|
38
|
+
def wire(prompt:, dir:) = edited(:wire, prompt, dir)
|
|
39
|
+
|
|
40
|
+
# @param prompt [String] what the author typed
|
|
41
|
+
# @return [Hash] `"intent"` what the script answered, `"edit"` without one
|
|
42
|
+
def classify(prompt:)
|
|
43
|
+
FakeAgent.calls << { ask: :classify, prompt: prompt, dir: nil }
|
|
44
|
+
{ "intent" => FakeAgent.intent ? FakeAgent.intent.call(prompt) : "edit" }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @api private
|
|
48
|
+
def edited(ask, prompt, dir)
|
|
49
|
+
FakeAgent.calls << { ask: ask, prompt: prompt, dir: dir }
|
|
50
|
+
note = FakeAgent.edit&.call(prompt, dir)
|
|
51
|
+
{ "note" => note.is_a?(String) ? note : "changed nothing" }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|