hecks 3.14.0 → 5.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.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/exe/hecks +1 -1
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +9 -16
- 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/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 +2 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
- 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/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/app/responses.rb +3 -1
- 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/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- 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/first_admin.rb +3 -3
- 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 +21 -0
- 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 +3 -80
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +7 -13
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- 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/site/cms_editor/templates/src/host.ts.tmpl +7 -2
- 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/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/unwrapping.rb +60 -0
- data/lib/hecks/runtime/value.rb +10 -21
- 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/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/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
- data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
- 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 +306 -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 +151 -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/presenters.rs +335 -0
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +574 -229
- 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 +1037 -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 +39 -7
- data/rust/host/src/payments/tests.rs +1427 -282
- data/rust/host/src/payments.rs +391 -112
- 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 +555 -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 -1
- data/rust/parser/src/build/named_pattern.rs +4 -1
- 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 -516
- 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 +22 -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 +244 -114
- 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 +244 -148
- data/rust/src/kernel/mod.rs +47 -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/present.rs +261 -0
- 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/rust/host/src/approval.rs +0 -652
data/rust/Cargo.toml
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
# `--no-default-features --features <name>`.
|
|
7
7
|
[features]
|
|
8
8
|
default = ["reserved_names_fixture"]
|
|
9
|
+
tally = []
|
|
10
|
+
relay = []
|
|
11
|
+
meter = []
|
|
12
|
+
courier = []
|
|
9
13
|
attribute_constraints_fixture = []
|
|
10
14
|
optional_nested_value_object_fixture = []
|
|
11
15
|
computed_sets_fixture = []
|
|
@@ -67,3 +71,39 @@ path = "src/main.rs"
|
|
|
67
71
|
# Only this dev profile is affected; release builds are unchanged.
|
|
68
72
|
[profile.dev]
|
|
69
73
|
debug = 0
|
|
74
|
+
|
|
75
|
+
# Clippy beyond its defaults: pedantic and nursery, plus the restriction lints that keep a panic
|
|
76
|
+
# out of library code. `hecks gate_run.gate stage=ci only=rust_clippy` makes each of these an error.
|
|
77
|
+
# The same table is in every crate's Cargo.toml; change them together.
|
|
78
|
+
[lints.clippy]
|
|
79
|
+
pedantic = { level = "warn", priority = -1 }
|
|
80
|
+
nursery = { level = "warn", priority = -1 }
|
|
81
|
+
unwrap_used = "warn"
|
|
82
|
+
expect_used = "warn"
|
|
83
|
+
panic = "warn"
|
|
84
|
+
dbg_macro = "warn"
|
|
85
|
+
todo = "warn"
|
|
86
|
+
unimplemented = "warn"
|
|
87
|
+
# Lints that ask for prose or an attribute on every item rather than for better code.
|
|
88
|
+
missing_errors_doc = "allow"
|
|
89
|
+
missing_panics_doc = "allow"
|
|
90
|
+
must_use_candidate = "allow"
|
|
91
|
+
return_self_not_must_use = "allow"
|
|
92
|
+
module_name_repetitions = "allow"
|
|
93
|
+
too_many_lines = "allow"
|
|
94
|
+
too_long_first_doc_paragraph = "allow"
|
|
95
|
+
doc_markdown = "allow"
|
|
96
|
+
# Numeric conversions here are deliberate: JSON numbers are f64 and ids are small.
|
|
97
|
+
cast_possible_truncation = "allow"
|
|
98
|
+
cast_possible_wrap = "allow"
|
|
99
|
+
cast_precision_loss = "allow"
|
|
100
|
+
cast_sign_loss = "allow"
|
|
101
|
+
# Naming heuristics that fire on ordinary short names.
|
|
102
|
+
similar_names = "allow"
|
|
103
|
+
many_single_char_names = "allow"
|
|
104
|
+
struct_field_names = "allow"
|
|
105
|
+
# Heuristics that cost more clarity than they buy here.
|
|
106
|
+
redundant_pub_crate = "allow"
|
|
107
|
+
future_not_send = "allow"
|
|
108
|
+
significant_drop_tightening = "allow"
|
|
109
|
+
match_same_arms = "allow"
|
data/rust/build/Cargo.toml
CHANGED
|
@@ -17,3 +17,39 @@ path = "src/main.rs"
|
|
|
17
17
|
# Debug info is off: CI binaries are run once and never attached to a debugger.
|
|
18
18
|
[profile.dev]
|
|
19
19
|
debug = 0
|
|
20
|
+
|
|
21
|
+
# Clippy beyond its defaults: pedantic and nursery, plus the restriction lints that keep a panic
|
|
22
|
+
# out of library code. `hecks gate_run.gate stage=ci only=rust_clippy` makes each of these an error.
|
|
23
|
+
# The same table is in every crate's Cargo.toml; change them together.
|
|
24
|
+
[lints.clippy]
|
|
25
|
+
pedantic = { level = "warn", priority = -1 }
|
|
26
|
+
nursery = { level = "warn", priority = -1 }
|
|
27
|
+
unwrap_used = "warn"
|
|
28
|
+
expect_used = "warn"
|
|
29
|
+
panic = "warn"
|
|
30
|
+
dbg_macro = "warn"
|
|
31
|
+
todo = "warn"
|
|
32
|
+
unimplemented = "warn"
|
|
33
|
+
# Lints that ask for prose or an attribute on every item rather than for better code.
|
|
34
|
+
missing_errors_doc = "allow"
|
|
35
|
+
missing_panics_doc = "allow"
|
|
36
|
+
must_use_candidate = "allow"
|
|
37
|
+
return_self_not_must_use = "allow"
|
|
38
|
+
module_name_repetitions = "allow"
|
|
39
|
+
too_many_lines = "allow"
|
|
40
|
+
too_long_first_doc_paragraph = "allow"
|
|
41
|
+
doc_markdown = "allow"
|
|
42
|
+
# Numeric conversions here are deliberate: JSON numbers are f64 and ids are small.
|
|
43
|
+
cast_possible_truncation = "allow"
|
|
44
|
+
cast_possible_wrap = "allow"
|
|
45
|
+
cast_precision_loss = "allow"
|
|
46
|
+
cast_sign_loss = "allow"
|
|
47
|
+
# Naming heuristics that fire on ordinary short names.
|
|
48
|
+
similar_names = "allow"
|
|
49
|
+
many_single_char_names = "allow"
|
|
50
|
+
struct_field_names = "allow"
|
|
51
|
+
# Heuristics that cost more clarity than they buy here.
|
|
52
|
+
redundant_pub_crate = "allow"
|
|
53
|
+
future_not_send = "allow"
|
|
54
|
+
significant_drop_tightening = "allow"
|
|
55
|
+
match_same_arms = "allow"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
//! Rust port of `Projector::Exporter#persistence_adapters`: the `adapters` key, the declarations
|
|
2
|
+
//! of the persistence adapters a domain binds, so `rust/host` looks adapters up in the IR instead
|
|
3
|
+
//! of a hard-coded list. Only declarations: a world's setting values never enter it.
|
|
4
|
+
|
|
5
|
+
use std::path::{Path, PathBuf};
|
|
6
|
+
|
|
7
|
+
use crate::json::Json;
|
|
8
|
+
use crate::subprocess;
|
|
9
|
+
|
|
10
|
+
// Never a source `.adapter`: build output and copies. Hidden directories are skipped too, as
|
|
11
|
+
// Ruby's `Dir.glob` skips them.
|
|
12
|
+
const SKIPPED_DIRS: [&str; 5] = ["target", "vendor", "node_modules", "tmp", ".git"];
|
|
13
|
+
|
|
14
|
+
/// The `adapters` array for `ir`: one entry per distinct adapter name in
|
|
15
|
+
/// `ir["persistence"]["aggregates"][].adapter` that an `.adapter` file under `directories`
|
|
16
|
+
/// declares, in name byte order. `hecks-parse adapter` reads all the files in one run.
|
|
17
|
+
///
|
|
18
|
+
/// # Errors
|
|
19
|
+
///
|
|
20
|
+
/// Returns the parser's failure, or a message naming an unreadable directory or output that
|
|
21
|
+
/// is not the JSON array `hecks-parse adapter` prints.
|
|
22
|
+
pub fn declarations(parser_bin: &Path, directories: &[PathBuf], ir: &Json) -> Result<Json, String> {
|
|
23
|
+
let names = bound_names(ir);
|
|
24
|
+
if names.is_empty() {
|
|
25
|
+
return Ok(Json::Array(Vec::new()));
|
|
26
|
+
}
|
|
27
|
+
let files = files_under(directories)?;
|
|
28
|
+
if files.is_empty() {
|
|
29
|
+
return Ok(Json::Array(Vec::new()));
|
|
30
|
+
}
|
|
31
|
+
let mut args = vec!["adapter".to_string()];
|
|
32
|
+
args.extend(files.iter().map(|p| p.to_string_lossy().to_string()));
|
|
33
|
+
let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
|
|
34
|
+
let printed = subprocess::run_capture(&parser_bin.to_string_lossy(), &arg_refs)?;
|
|
35
|
+
let parsed = Json::parse(&printed).map_err(|e| format!("parsing hecks-parse adapter: {e}"))?;
|
|
36
|
+
Ok(select(&parsed, &names))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// The distinct adapter names `ir`'s persistence block binds, in byte order.
|
|
40
|
+
pub fn bound_names(ir: &Json) -> Vec<String> {
|
|
41
|
+
let aggregates = ir
|
|
42
|
+
.get("persistence")
|
|
43
|
+
.and_then(|persistence| persistence.get("aggregates"))
|
|
44
|
+
.map_or(&[][..], Json::each);
|
|
45
|
+
let mut names: Vec<String> = aggregates
|
|
46
|
+
.iter()
|
|
47
|
+
.filter_map(|aggregate| aggregate.get("adapter").and_then(Json::as_str))
|
|
48
|
+
.map(str::to_string)
|
|
49
|
+
.collect();
|
|
50
|
+
names.sort();
|
|
51
|
+
names.dedup();
|
|
52
|
+
names
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// The entries of `declared` whose name is in `names`, the first of any repeated name
|
|
56
|
+
/// winning, in name byte order.
|
|
57
|
+
pub fn select(declared: &Json, names: &[String]) -> Json {
|
|
58
|
+
let mut chosen: Vec<(String, Json)> = Vec::new();
|
|
59
|
+
for adapter in declared.each() {
|
|
60
|
+
let Some(name) = adapter.get("name").and_then(Json::as_str) else {
|
|
61
|
+
continue;
|
|
62
|
+
};
|
|
63
|
+
if names.iter().any(|wanted| wanted == name) && !chosen.iter().any(|(n, _)| n == name) {
|
|
64
|
+
chosen.push((name.to_string(), adapter.clone()));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
chosen.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes()));
|
|
68
|
+
Json::Array(chosen.into_iter().map(|(_, adapter)| adapter).collect())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Every `.adapter` file beneath `directories`, as absolute paths, unique and in byte order of
|
|
72
|
+
/// the path text. A directory that does not exist contributes none.
|
|
73
|
+
///
|
|
74
|
+
/// # Errors
|
|
75
|
+
///
|
|
76
|
+
/// Returns a message when a directory that exists cannot be read.
|
|
77
|
+
pub fn files_under(directories: &[PathBuf]) -> Result<Vec<PathBuf>, String> {
|
|
78
|
+
let mut found = Vec::new();
|
|
79
|
+
for directory in directories {
|
|
80
|
+
let absolute = std::path::absolute(directory)
|
|
81
|
+
.map_err(|e| format!("resolving {}: {e}", directory.display()))?;
|
|
82
|
+
if absolute.is_dir() {
|
|
83
|
+
walk(&absolute, &mut found)?;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
found.sort_by(|a, b| {
|
|
87
|
+
a.to_string_lossy()
|
|
88
|
+
.as_bytes()
|
|
89
|
+
.cmp(b.to_string_lossy().as_bytes())
|
|
90
|
+
});
|
|
91
|
+
found.dedup();
|
|
92
|
+
Ok(found)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn walk(directory: &Path, found: &mut Vec<PathBuf>) -> Result<(), String> {
|
|
96
|
+
let entries = std::fs::read_dir(directory)
|
|
97
|
+
.map_err(|e| format!("reading {}: {e}", directory.display()))?;
|
|
98
|
+
for entry in entries {
|
|
99
|
+
let entry = entry.map_err(|e| format!("reading {}: {e}", directory.display()))?;
|
|
100
|
+
let path = entry.path();
|
|
101
|
+
let file_name = entry.file_name().to_string_lossy().to_string();
|
|
102
|
+
let is_dir = entry.file_type().is_ok_and(|kind| kind.is_dir());
|
|
103
|
+
if is_dir {
|
|
104
|
+
if !file_name.starts_with('.') && !SKIPPED_DIRS.contains(&file_name.as_str()) {
|
|
105
|
+
walk(&path, found)?;
|
|
106
|
+
}
|
|
107
|
+
} else if !file_name.starts_with('.') && file_name.ends_with(".adapter") && path.is_file() {
|
|
108
|
+
found.push(path);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
Ok(())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[cfg(test)]
|
|
115
|
+
mod tests {
|
|
116
|
+
use super::*;
|
|
117
|
+
use crate::tmp::TempDir;
|
|
118
|
+
|
|
119
|
+
fn parsed(text: &str) -> Json {
|
|
120
|
+
Json::parse(text).expect("fixture json")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn ir(adapters: &[&str]) -> Json {
|
|
124
|
+
let rows: Vec<String> = adapters
|
|
125
|
+
.iter()
|
|
126
|
+
.map(|a| format!("{{\"name\":\"A\",\"storage_name\":\"a\",\"adapter\":\"{a}\"}}"))
|
|
127
|
+
.collect();
|
|
128
|
+
parsed(&format!(
|
|
129
|
+
"{{\"persistence\":{{\"aggregates\":[{}]}}}}",
|
|
130
|
+
rows.join(",")
|
|
131
|
+
))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[test]
|
|
135
|
+
fn bound_names_are_distinct_and_sorted() {
|
|
136
|
+
let names = bound_names(&ir(&["Postgres", "Heki", "Postgres"]));
|
|
137
|
+
assert_eq!(names, vec!["Heki".to_string(), "Postgres".to_string()]);
|
|
138
|
+
assert!(bound_names(&parsed("{}")).is_empty());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn select_keeps_bound_declarations_sorted_and_first_wins() {
|
|
143
|
+
let declared = parsed(
|
|
144
|
+
"[{\"name\":\"Postgres\",\"port\":\"persistence\",\"fields\":[],\"secrets\":[],\"defaults\":{}},\
|
|
145
|
+
{\"name\":\"Unbound\",\"port\":null,\"fields\":[],\"secrets\":[],\"defaults\":{}},\
|
|
146
|
+
{\"name\":\"Heki\",\"port\":\"persistence\",\"fields\":[\"dir\"],\"secrets\":[],\"defaults\":{\"dir\":\"data\"}},\
|
|
147
|
+
{\"name\":\"Heki\",\"port\":null,\"fields\":[],\"secrets\":[],\"defaults\":{}}]",
|
|
148
|
+
);
|
|
149
|
+
let wanted = ["Heki", "Missing", "Postgres"].map(str::to_string);
|
|
150
|
+
let chosen = select(&declared, &wanted);
|
|
151
|
+
let names: Vec<&str> = chosen
|
|
152
|
+
.each()
|
|
153
|
+
.iter()
|
|
154
|
+
.filter_map(|a| a.get("name").and_then(Json::as_str))
|
|
155
|
+
.collect();
|
|
156
|
+
assert_eq!(names, vec!["Heki", "Postgres"]);
|
|
157
|
+
let heki_fields = chosen.each()[0].get("fields").map(|f| f.each().len());
|
|
158
|
+
assert_eq!(heki_fields, Some(1));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[test]
|
|
162
|
+
fn files_under_skips_build_and_hidden_directories() {
|
|
163
|
+
let dir = TempDir::new("adapters-pass").expect("temp dir");
|
|
164
|
+
let root = dir.path();
|
|
165
|
+
for relative in [
|
|
166
|
+
"lib/a/heki.adapter",
|
|
167
|
+
"lib/b/memory.adapter",
|
|
168
|
+
"lib/target/skipped.adapter",
|
|
169
|
+
"lib/vendor/skipped.adapter",
|
|
170
|
+
"lib/node_modules/skipped.adapter",
|
|
171
|
+
"lib/tmp/skipped.adapter",
|
|
172
|
+
"lib/.hidden/skipped.adapter",
|
|
173
|
+
"lib/a/notes.txt",
|
|
174
|
+
] {
|
|
175
|
+
let path = root.join(relative);
|
|
176
|
+
std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
|
|
177
|
+
std::fs::write(&path, "").expect("write");
|
|
178
|
+
}
|
|
179
|
+
let scanned = [root.join("lib"), root.join("lib"), root.join("absent")];
|
|
180
|
+
let found = files_under(&scanned).expect("scan");
|
|
181
|
+
let relative: Vec<String> = found
|
|
182
|
+
.iter()
|
|
183
|
+
.map(|p| {
|
|
184
|
+
let under = p.strip_prefix(root).expect("under root");
|
|
185
|
+
under.to_string_lossy().to_string()
|
|
186
|
+
})
|
|
187
|
+
.collect();
|
|
188
|
+
assert_eq!(relative, vec!["lib/a/heki.adapter", "lib/b/memory.adapter"]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[test]
|
|
192
|
+
fn no_bound_adapter_means_an_empty_array_without_running_the_parser() {
|
|
193
|
+
let none = declarations(Path::new("/no/such/parser"), &[], &ir(&[])).expect("empty");
|
|
194
|
+
assert_eq!(none, Json::Array(Vec::new()));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//! Rust port of the binding facts `lib/hecks/rust_build/project_rust/target_ir.rb` adds beside the
|
|
2
|
-
//! declared IR: `persistence`, `
|
|
2
|
+
//! declared IR: `persistence`, `adapters` (the bound adapters' declarations, from the closure
|
|
3
|
+
//! the caller passes), `translations` and `source_text`. The capability seams
|
|
3
4
|
//! (authorization, identity, payments, ...) are `seams_pass`'s.
|
|
4
5
|
|
|
5
6
|
use std::collections::HashMap;
|
|
@@ -10,10 +11,21 @@ use crate::lineage_pass;
|
|
|
10
11
|
use crate::translation_pass;
|
|
11
12
|
|
|
12
13
|
/// Sets `persistence`, the first of the binding facts, in `TargetIr#call`'s order.
|
|
13
|
-
pub fn run(
|
|
14
|
-
|
|
14
|
+
pub fn run(
|
|
15
|
+
ir: &mut Json,
|
|
16
|
+
hecksagon_path: Option<&Path>,
|
|
17
|
+
world_path: Option<&Path>,
|
|
18
|
+
adapter_declarations: impl FnOnce(&Json) -> Result<Json, String>,
|
|
19
|
+
) -> Result<(), String> {
|
|
20
|
+
let chapter_name = ir
|
|
21
|
+
.get("name")
|
|
22
|
+
.and_then(Json::as_str)
|
|
23
|
+
.unwrap_or_default()
|
|
24
|
+
.to_string();
|
|
15
25
|
let persistence = persistence(ir, &chapter_name, hecksagon_path, world_path)?;
|
|
16
26
|
ir.set("persistence", persistence);
|
|
27
|
+
let adapters = adapter_declarations(ir)?;
|
|
28
|
+
ir.set("adapters", adapters);
|
|
17
29
|
Ok(())
|
|
18
30
|
}
|
|
19
31
|
|
|
@@ -23,7 +35,11 @@ pub fn run(ir: &mut Json, hecksagon_path: Option<&Path>, world_path: Option<&Pat
|
|
|
23
35
|
/// `translations` carries each `translations/*.bluebook` edge with its precompiled SQL, from
|
|
24
36
|
/// `translation_pass`; an edge using a rule kind that pass does not compile is refused, never
|
|
25
37
|
/// shipped without its SQL. A `translates` declaration in a bluebook is refused outright.
|
|
26
|
-
pub fn finish(
|
|
38
|
+
pub fn finish(
|
|
39
|
+
ir: &mut Json,
|
|
40
|
+
bluebook_files: &[PathBuf],
|
|
41
|
+
bluebook_dir: &Path,
|
|
42
|
+
) -> Result<(), String> {
|
|
27
43
|
let mut texts = Vec::new();
|
|
28
44
|
for path in bluebook_files {
|
|
29
45
|
let text = read(path)?;
|
|
@@ -35,7 +51,10 @@ pub fn finish(ir: &mut Json, bluebook_files: &[PathBuf], bluebook_dir: &Path) ->
|
|
|
35
51
|
}
|
|
36
52
|
texts.push(text);
|
|
37
53
|
}
|
|
38
|
-
ir.set(
|
|
54
|
+
ir.set(
|
|
55
|
+
"translations",
|
|
56
|
+
Json::Array(translation_pass::edges(bluebook_dir)?),
|
|
57
|
+
);
|
|
39
58
|
if !texts.is_empty() {
|
|
40
59
|
ir.set("source_text", Json::String(texts.join("\n")));
|
|
41
60
|
}
|
|
@@ -47,11 +66,19 @@ fn declares_translation(line: &str) -> bool {
|
|
|
47
66
|
line.starts_with("translates") || line.starts_with("translation ")
|
|
48
67
|
}
|
|
49
68
|
|
|
50
|
-
fn persistence(
|
|
69
|
+
fn persistence(
|
|
70
|
+
ir: &Json,
|
|
71
|
+
chapter: &str,
|
|
72
|
+
hecksagon: Option<&Path>,
|
|
73
|
+
world: Option<&Path>,
|
|
74
|
+
) -> Result<Json, String> {
|
|
51
75
|
let (binds, hecksagon_default) = match hecksagon {
|
|
52
76
|
Some(path) => {
|
|
53
77
|
let text = read(path)?;
|
|
54
|
-
(
|
|
78
|
+
(
|
|
79
|
+
lineage_pass::persistence_binds(&text),
|
|
80
|
+
domain_default(&text, chapter),
|
|
81
|
+
)
|
|
55
82
|
}
|
|
56
83
|
None => (HashMap::new(), None),
|
|
57
84
|
};
|
|
@@ -61,8 +88,12 @@ fn persistence(ir: &Json, chapter: &str, hecksagon: Option<&Path>, world: Option
|
|
|
61
88
|
};
|
|
62
89
|
let declared = hecksagon_default.or(world_default);
|
|
63
90
|
let mut aggregates = Vec::new();
|
|
64
|
-
for aggregate in ir.get("aggregates").
|
|
65
|
-
let name = aggregate
|
|
91
|
+
for aggregate in ir.get("aggregates").map_or(&[][..], Json::each) {
|
|
92
|
+
let name = aggregate
|
|
93
|
+
.get("name")
|
|
94
|
+
.and_then(Json::as_str)
|
|
95
|
+
.unwrap_or_default()
|
|
96
|
+
.to_string();
|
|
66
97
|
let adapter = match (binds.get(&name), &declared, hecksagon.is_some()) {
|
|
67
98
|
(Some(adapter), _, _) => adapter.clone(),
|
|
68
99
|
(None, Some(adapter), _) => adapter.clone(),
|
|
@@ -73,11 +104,17 @@ fn persistence(ir: &Json, chapter: &str, hecksagon: Option<&Path>, world: Option
|
|
|
73
104
|
};
|
|
74
105
|
aggregates.push(Json::Object(vec![
|
|
75
106
|
("name".to_string(), Json::String(name.clone())),
|
|
76
|
-
(
|
|
107
|
+
(
|
|
108
|
+
"storage_name".to_string(),
|
|
109
|
+
Json::String(crate::hecks_naming::snake(&name)),
|
|
110
|
+
),
|
|
77
111
|
("adapter".to_string(), Json::String(adapter)),
|
|
78
112
|
]));
|
|
79
113
|
}
|
|
80
|
-
Ok(Json::Object(vec![(
|
|
114
|
+
Ok(Json::Object(vec![(
|
|
115
|
+
"aggregates".to_string(),
|
|
116
|
+
Json::Array(aggregates),
|
|
117
|
+
)]))
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
// The bare `persisted_by "X"` line of the chapter's own `Hecks.hecksagon` block: every aggregate
|
|
@@ -90,8 +127,12 @@ fn domain_default(text: &str, chapter: &str) -> Option<String> {
|
|
|
90
127
|
in_chapter = rest.contains(&format!("\"{chapter}\""));
|
|
91
128
|
continue;
|
|
92
129
|
}
|
|
93
|
-
let Some(rest) = line.trim_start().strip_prefix("persisted_by ") else {
|
|
94
|
-
|
|
130
|
+
let Some(rest) = line.trim_start().strip_prefix("persisted_by ") else {
|
|
131
|
+
continue;
|
|
132
|
+
};
|
|
133
|
+
let Some(open) = rest.find('"').filter(|_| in_chapter) else {
|
|
134
|
+
continue;
|
|
135
|
+
};
|
|
95
136
|
let quoted = &rest[open + 1..];
|
|
96
137
|
found = quoted.find('"').map(|close| quoted[..close].to_string());
|
|
97
138
|
}
|
|
@@ -26,20 +26,38 @@ pub fn build_wasm(root: &Path, rust_dir: &Path, target_mod_name: &str) -> Result
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
println!("== cargo build --release --target wasm32-wasip1 (via rustup run stable) ==");
|
|
29
|
-
subprocess::run_in(
|
|
29
|
+
subprocess::run_in(
|
|
30
|
+
rust_dir,
|
|
31
|
+
"rustup",
|
|
32
|
+
&[
|
|
33
|
+
"run",
|
|
34
|
+
"stable",
|
|
35
|
+
"cargo",
|
|
36
|
+
"build",
|
|
37
|
+
"--release",
|
|
38
|
+
"--target",
|
|
39
|
+
"wasm32-wasip1",
|
|
40
|
+
],
|
|
41
|
+
)?;
|
|
30
42
|
|
|
31
43
|
let dist_dir = root.join("rust/dist");
|
|
32
|
-
std::fs::create_dir_all(&dist_dir)
|
|
44
|
+
std::fs::create_dir_all(&dist_dir)
|
|
45
|
+
.map_err(|e| format!("creating {}: {e}", dist_dir.display()))?;
|
|
33
46
|
|
|
34
47
|
let built = rust_dir.join("target/wasm32-wasip1/release/rust.wasm");
|
|
35
48
|
let out = dist_dir.join(format!("{target_mod_name}.wasm"));
|
|
36
|
-
std::fs::copy(&built, &out)
|
|
49
|
+
std::fs::copy(&built, &out)
|
|
50
|
+
.map_err(|e| format!("copying {} to {}: {e}", built.display(), out.display()))?;
|
|
37
51
|
println!("wrote {}", out.display());
|
|
38
52
|
|
|
39
|
-
let ir_json = root
|
|
53
|
+
let ir_json = root
|
|
54
|
+
.join("rust/src/generated")
|
|
55
|
+
.join(target_mod_name)
|
|
56
|
+
.join("ir.json");
|
|
40
57
|
if ir_json.is_file() {
|
|
41
58
|
let ir_out = dist_dir.join(format!("{target_mod_name}.ir.json"));
|
|
42
|
-
std::fs::copy(&ir_json, &ir_out)
|
|
59
|
+
std::fs::copy(&ir_json, &ir_out)
|
|
60
|
+
.map_err(|e| format!("copying {} to {}: {e}", ir_json.display(), ir_out.display()))?;
|
|
43
61
|
println!("wrote {}", ir_out.display());
|
|
44
62
|
}
|
|
45
63
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! Rewrites the root `generated/mod.rs` and syncs `rust/Cargo.toml` `[features]`.
|
|
2
2
|
//! Ports `sync_mod_and_cargo!`, keeping its output byte-identical.
|
|
3
3
|
|
|
4
|
+
use std::fmt::Write as _;
|
|
4
5
|
use std::path::Path;
|
|
5
6
|
|
|
6
7
|
// Both tables come from the reserved-word vocabularies via `hecks project_reserved_names`.
|
|
@@ -11,21 +12,30 @@ pub fn valid_domain_mod_name(name: &str) -> bool {
|
|
|
11
12
|
let mut chars = name.chars();
|
|
12
13
|
let starts_ok = matches!(chars.next(), Some(c) if c == '_' || c.is_ascii_lowercase());
|
|
13
14
|
let rest_ok = chars.all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit());
|
|
14
|
-
starts_ok
|
|
15
|
+
starts_ok
|
|
16
|
+
&& rest_ok
|
|
17
|
+
&& !RUST_KEYWORDS.contains(&name)
|
|
18
|
+
&& !CARGO_RESERVED_DOMAIN_NAMES.contains(&name)
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/// Lists the directories under `out_root`, and those with a `merged.rs` (activatable domains).
|
|
18
22
|
fn scan_generated_dirs(out_root: &Path) -> Result<(Vec<String>, Vec<String>), String> {
|
|
19
23
|
let mut all_dirs = Vec::new();
|
|
20
|
-
for entry in
|
|
24
|
+
for entry in
|
|
25
|
+
std::fs::read_dir(out_root).map_err(|e| format!("reading {}: {e}", out_root.display()))?
|
|
26
|
+
{
|
|
21
27
|
let entry = entry.map_err(|e| format!("reading {}: {e}", out_root.display()))?;
|
|
22
|
-
if entry.file_type().
|
|
28
|
+
if entry.file_type().is_ok_and(|t| t.is_dir()) {
|
|
23
29
|
all_dirs.push(entry.file_name().to_string_lossy().to_string());
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
32
|
all_dirs.sort();
|
|
27
33
|
|
|
28
|
-
let domains = all_dirs
|
|
34
|
+
let domains = all_dirs
|
|
35
|
+
.iter()
|
|
36
|
+
.filter(|name| out_root.join(name).join("merged.rs").is_file())
|
|
37
|
+
.cloned()
|
|
38
|
+
.collect();
|
|
29
39
|
|
|
30
40
|
Ok((all_dirs, domains))
|
|
31
41
|
}
|
|
@@ -35,7 +45,12 @@ fn scan_generated_dirs(out_root: &Path) -> Result<(Vec<String>, Vec<String>), St
|
|
|
35
45
|
/// Cargo has no mutually-exclusive features, so the default domain's re-export backs off when
|
|
36
46
|
/// another domain feature is on, and two non-default features hit an explicit `compile_error!`
|
|
37
47
|
/// instead of a duplicate-import E0252.
|
|
38
|
-
fn write_root_mod(
|
|
48
|
+
fn write_root_mod(
|
|
49
|
+
out_root: &Path,
|
|
50
|
+
all_dirs: &[String],
|
|
51
|
+
domains: &[String],
|
|
52
|
+
target_mod_name: &str,
|
|
53
|
+
) -> Result<(), String> {
|
|
39
54
|
let mut out = String::new();
|
|
40
55
|
out.push_str("// GENERATED by hecks project_rust — re-run it to refresh this list.\n");
|
|
41
56
|
out.push_str("//\n");
|
|
@@ -53,9 +68,9 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
|
|
|
53
68
|
out.push_str("// it via `attaches`.\n");
|
|
54
69
|
for name in all_dirs {
|
|
55
70
|
if domains.iter().any(|d| d == name) {
|
|
56
|
-
|
|
71
|
+
let _ = writeln!(out, "#[cfg(feature = {name:?})]");
|
|
57
72
|
}
|
|
58
|
-
|
|
73
|
+
let _ = writeln!(out, "pub mod {name};");
|
|
59
74
|
}
|
|
60
75
|
out.push('\n');
|
|
61
76
|
out.push_str("// `active` is whichever ONE domain's own merged Store/dispatch table\n");
|
|
@@ -65,7 +80,10 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
|
|
|
65
80
|
out.push_str("// no matter which domain that resolves to.\n");
|
|
66
81
|
out.push_str("//\n");
|
|
67
82
|
out.push_str("// DOMAIN FEATURES ARE MUTUALLY EXCLUSIVE (R5) — Cargo has no native\n");
|
|
68
|
-
|
|
83
|
+
let _ = writeln!(
|
|
84
|
+
out,
|
|
85
|
+
"// concept of that, and `default = [{target_mod_name:?}]` in"
|
|
86
|
+
);
|
|
69
87
|
out.push_str("// Cargo.toml stays enabled unless a build passes\n");
|
|
70
88
|
out.push_str("// `--no-default-features`, so a plain `cargo build --features banking`\n");
|
|
71
89
|
out.push_str("// used to enable BOTH the default domain's feature and banking's,\n");
|
|
@@ -84,17 +102,27 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
|
|
|
84
102
|
out.push_str("// `compile_error!` naming both features instead of a cryptic\n");
|
|
85
103
|
out.push_str("// \"defined multiple times\".\n");
|
|
86
104
|
for name in domains {
|
|
87
|
-
let other_domains: Vec<&String> = domains
|
|
105
|
+
let other_domains: Vec<&String> = domains
|
|
106
|
+
.iter()
|
|
107
|
+
.filter(|n| n.as_str() != name.as_str())
|
|
108
|
+
.collect();
|
|
88
109
|
if name.as_str() == target_mod_name && !other_domains.is_empty() {
|
|
89
|
-
let guard = other_domains
|
|
90
|
-
|
|
110
|
+
let guard = other_domains
|
|
111
|
+
.iter()
|
|
112
|
+
.map(|o| format!("feature = {o:?}"))
|
|
113
|
+
.collect::<Vec<_>>()
|
|
114
|
+
.join(", ");
|
|
115
|
+
let _ = writeln!(out, "#[cfg(all(feature = {name:?}, not(any({guard}))))]");
|
|
91
116
|
} else {
|
|
92
|
-
|
|
117
|
+
let _ = writeln!(out, "#[cfg(feature = {name:?})]");
|
|
93
118
|
}
|
|
94
|
-
|
|
119
|
+
let _ = writeln!(out, "pub use {name}::merged as active;");
|
|
95
120
|
}
|
|
96
121
|
|
|
97
|
-
let non_default_domains: Vec<&String> = domains
|
|
122
|
+
let non_default_domains: Vec<&String> = domains
|
|
123
|
+
.iter()
|
|
124
|
+
.filter(|n| n.as_str() != target_mod_name)
|
|
125
|
+
.collect();
|
|
98
126
|
if non_default_domains.len() > 1 {
|
|
99
127
|
out.push('\n');
|
|
100
128
|
out.push_str("// Explicit conflict guard — see point 2 above. Only fires when TWO\n");
|
|
@@ -106,10 +134,10 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
|
|
|
106
134
|
for j in (i + 1)..non_default_domains.len() {
|
|
107
135
|
let a = non_default_domains[i];
|
|
108
136
|
let b = non_default_domains[j];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"compile_error!(\"domain features are mutually exclusive — enable only one of: {domains_joined} (both {a} and {b} are enabled)\")
|
|
112
|
-
)
|
|
137
|
+
let _ = writeln!(out, "#[cfg(all(feature = {a:?}, feature = {b:?}))]");
|
|
138
|
+
let _ = writeln!(out,
|
|
139
|
+
"compile_error!(\"domain features are mutually exclusive — enable only one of: {domains_joined} (both {a} and {b} are enabled)\");"
|
|
140
|
+
);
|
|
113
141
|
}
|
|
114
142
|
}
|
|
115
143
|
}
|
|
@@ -123,7 +151,9 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
|
|
|
123
151
|
/// The `[features]` table text, from its header to the next `[section]` header or EOF.
|
|
124
152
|
/// Empty if there is no `[features]` header.
|
|
125
153
|
fn features_table_slice(text: &str) -> &str {
|
|
126
|
-
let Some(start) = text.find("[features]") else {
|
|
154
|
+
let Some(start) = text.find("[features]") else {
|
|
155
|
+
return "";
|
|
156
|
+
};
|
|
127
157
|
let rest = &text[start..];
|
|
128
158
|
let mut pos = 0;
|
|
129
159
|
for (i, line) in rest.split_inclusive('\n').enumerate() {
|
|
@@ -135,8 +165,13 @@ fn features_table_slice(text: &str) -> &str {
|
|
|
135
165
|
rest
|
|
136
166
|
}
|
|
137
167
|
|
|
138
|
-
fn sync_cargo_toml_features(
|
|
139
|
-
|
|
168
|
+
fn sync_cargo_toml_features(
|
|
169
|
+
cargo_toml_path: &Path,
|
|
170
|
+
domains: &[String],
|
|
171
|
+
target_mod_name: &str,
|
|
172
|
+
) -> Result<(), String> {
|
|
173
|
+
let mut cargo_toml = std::fs::read_to_string(cargo_toml_path)
|
|
174
|
+
.map_err(|e| format!("reading {}: {e}", cargo_toml_path.display()))?;
|
|
140
175
|
|
|
141
176
|
if !cargo_toml.contains("[features]") {
|
|
142
177
|
let preamble = "# ONE FEATURE PER GENERATED DOMAIN — selects which domain's own\n\
|
|
@@ -157,7 +192,9 @@ fn sync_cargo_toml_features(cargo_toml_path: &Path, domains: &[String], target_m
|
|
|
157
192
|
// Scoped to `[features]`: matching `name = ` file-wide hits `[package]` keys like `version`.
|
|
158
193
|
for name in domains {
|
|
159
194
|
let already_declared = features_table_slice(&cargo_toml).lines().any(|line| {
|
|
160
|
-
line.trim_start()
|
|
195
|
+
line.trim_start()
|
|
196
|
+
.strip_prefix(name.as_str())
|
|
197
|
+
.is_some_and(|rest| rest.trim_start().starts_with('='))
|
|
161
198
|
});
|
|
162
199
|
if already_declared {
|
|
163
200
|
continue;
|
|
@@ -165,10 +202,14 @@ fn sync_cargo_toml_features(cargo_toml_path: &Path, domains: &[String], target_m
|
|
|
165
202
|
cargo_toml = insert_after_default(&cargo_toml, &format!("{name} = []"));
|
|
166
203
|
}
|
|
167
204
|
|
|
168
|
-
cargo_toml = replace_default_line(&cargo_toml, &format!("default = [{:?}]"
|
|
205
|
+
cargo_toml = replace_default_line(&cargo_toml, &format!("default = [{target_mod_name:?}]"));
|
|
169
206
|
|
|
170
|
-
std::fs::write(cargo_toml_path, &cargo_toml)
|
|
171
|
-
|
|
207
|
+
std::fs::write(cargo_toml_path, &cargo_toml)
|
|
208
|
+
.map_err(|e| format!("writing {}: {e}", cargo_toml_path.display()))?;
|
|
209
|
+
println!(
|
|
210
|
+
"wrote {} (default feature: {target_mod_name})",
|
|
211
|
+
cargo_toml_path.display()
|
|
212
|
+
);
|
|
172
213
|
Ok(())
|
|
173
214
|
}
|
|
174
215
|
|
|
@@ -209,7 +250,9 @@ fn replace_default_line(text: &str, new_line: &str) -> String {
|
|
|
209
250
|
|
|
210
251
|
fn is_default_line(line_content: &str) -> bool {
|
|
211
252
|
let trimmed = line_content.trim_start();
|
|
212
|
-
trimmed
|
|
253
|
+
trimmed
|
|
254
|
+
.strip_prefix("default")
|
|
255
|
+
.is_some_and(|rest| rest.trim_start().starts_with('='))
|
|
213
256
|
}
|
|
214
257
|
|
|
215
258
|
/// Rewrites only the root `mod.rs`, for a generation into a directory other than `rust/src/generated`.
|