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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require_relative "../plain_english"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Statements
|
|
6
|
+
# What one thing holds or points at, said from its attribute's name, target and cardinality.
|
|
7
|
+
# A reference stored under its target's own name reads as belonging; one stored under a
|
|
8
|
+
# role ("source", "disputed_by") keeps the role, so two references to one target differ.
|
|
9
|
+
#
|
|
10
|
+
# Relationships.sentence(transfer, source) # => "Each transfer has one source account."
|
|
11
|
+
module Relationships
|
|
12
|
+
PREPOSITIONS = %w[by to for from with of on in at].freeze
|
|
13
|
+
|
|
14
|
+
# The built-in value types, which a list names by its field rather than its element.
|
|
15
|
+
PRIMITIVES = %w[String Integer Float TrueClass FalseClass Boolean].freeze
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct the attribute is on
|
|
20
|
+
# @param attribute [Bluebook::Attribute] one of its attributes
|
|
21
|
+
# @return [String, nil] the sentence, or nil for a plain field
|
|
22
|
+
def sentence(holder, attribute)
|
|
23
|
+
subject = "Each #{PlainEnglish.noun(holder.hecks_name)}"
|
|
24
|
+
return list_sentence(subject, attribute) if attribute.list?
|
|
25
|
+
return nil unless attribute.relationship
|
|
26
|
+
|
|
27
|
+
"#{subject} #{reference_phrase(attribute)}."
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# "Each order holds a list of toppings." The element is named when the field is not
|
|
31
|
+
# simply its plural: "Each account holds its ledger, a list of ledger entries."
|
|
32
|
+
def list_sentence(subject, attribute)
|
|
33
|
+
field = PlainEnglish.noun(attribute.name)
|
|
34
|
+
element = element_noun(attribute)
|
|
35
|
+
return "#{subject} holds a list of #{field}." if element.nil? || field.end_with?(element)
|
|
36
|
+
|
|
37
|
+
"#{subject} holds its #{field}, a list of #{element}."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The plural element noun, or nil for a list of plain values (text, numbers).
|
|
41
|
+
def element_noun(attribute)
|
|
42
|
+
type = attribute.type
|
|
43
|
+
name = type.respond_to?(:target_name) ? type.target_name : type.to_s
|
|
44
|
+
return nil if PRIMITIVES.include?(name) || name.include?("<")
|
|
45
|
+
|
|
46
|
+
PlainEnglish.plural(PlainEnglish.noun(name))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The verb phrase after "Each <holder>".
|
|
50
|
+
def reference_phrase(attribute)
|
|
51
|
+
target_name = attribute.type.target_name
|
|
52
|
+
target = PlainEnglish.noun(target_name)
|
|
53
|
+
return own_name_phrase(attribute, target) if Naming.snake(target_name) == attribute.name.to_s
|
|
54
|
+
|
|
55
|
+
role = PlainEnglish.noun(attribute.name)
|
|
56
|
+
return prepositional_phrase(role, target, attribute.optional?) if PREPOSITIONS.include?(role.split.last)
|
|
57
|
+
|
|
58
|
+
role_phrase(role, target, attribute.optional?)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# "belongs to one customer", or "has one" for `has_one`; "can" when it is optional.
|
|
62
|
+
def own_name_phrase(attribute, target)
|
|
63
|
+
has = attribute.relationship == "has_one"
|
|
64
|
+
return "#{has ? "can have" : "can belong to"} one #{target}" if attribute.optional?
|
|
65
|
+
|
|
66
|
+
"#{has ? "has" : "belongs to"} one #{target}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# "can be disputed by one customer", "reports to at most one employee".
|
|
70
|
+
def prepositional_phrase(role, target, optional)
|
|
71
|
+
if role.split.first.match?(/(ed|en|wn)\z/)
|
|
72
|
+
optional ? "can be #{role} one #{target}" : "is #{role} one #{target}"
|
|
73
|
+
else
|
|
74
|
+
optional ? "#{role} at most one #{target}" : "#{role} one #{target}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# "has one source account"; a role that already ends in its target is not repeated.
|
|
79
|
+
def role_phrase(role, target, optional)
|
|
80
|
+
named = role.end_with?(target.split.last) ? role : "#{role} #{target}"
|
|
81
|
+
optional ? "can have one #{named}" : "has one #{named}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
require_relative "../projector"
|
|
2
|
+
require_relative "statements/relationships"
|
|
3
|
+
require_relative "statements/actions"
|
|
2
4
|
|
|
3
5
|
module Hecks
|
|
4
6
|
module Projections
|
|
5
|
-
# A domain's declared facts as flat plain-English sentences, one checkable statement per fact
|
|
6
|
-
#
|
|
7
|
+
# A domain's declared facts as flat plain-English sentences, one checkable statement per fact:
|
|
8
|
+
# what each thing holds and points at, what is always true of it, how it moves between
|
|
9
|
+
# states, and what each of its commands checks. Never invents one: relationships read from
|
|
10
|
+
# shape and names, and every rule uses the author's own description.
|
|
7
11
|
#
|
|
8
|
-
# Pizzas.project(Projections::Statements) # => ["
|
|
12
|
+
# Pizzas.project(Projections::Statements) # => ["Each order holds a list of toppings.", ...]
|
|
9
13
|
module Statements
|
|
10
14
|
extend Hecks::Projector::Target
|
|
11
15
|
|
|
@@ -18,8 +22,8 @@ module Hecks
|
|
|
18
22
|
# @param bluebook [Bluebook::Chapter] the chapter being projected
|
|
19
23
|
# @param options [Hash{Symbol => Object}] ignored; present to satisfy the
|
|
20
24
|
# `Projector::Target` calling convention
|
|
21
|
-
# @return [Array<String>]
|
|
22
|
-
# in holder order
|
|
25
|
+
# @return [Array<String>] each holder's relationships, invariants, moves and command
|
|
26
|
+
# rules, in holder order
|
|
23
27
|
def call(bluebook:, options: {})
|
|
24
28
|
holders(bluebook).flat_map { |holder| statements_for(holder) }
|
|
25
29
|
end
|
|
@@ -27,31 +31,18 @@ module Hecks
|
|
|
27
31
|
def holders(bluebook) = bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
|
|
28
32
|
|
|
29
33
|
def statements_for(holder)
|
|
30
|
-
attribute_statements(holder) + invariant_statements(holder)
|
|
34
|
+
attribute_statements(holder) + invariant_statements(holder) +
|
|
35
|
+
Actions.lifecycle(holder) + Actions.rules(holder)
|
|
31
36
|
end
|
|
32
37
|
|
|
33
|
-
#
|
|
38
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct being read
|
|
39
|
+
# @return [Array<String>] one sentence per list or reference attribute
|
|
34
40
|
def attribute_statements(holder)
|
|
35
41
|
holder.attributes.filter_map { |attribute| attribute_statement(holder, attribute) }
|
|
36
42
|
end
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return "#{subject} has many #{attribute.name}." if attribute.list?
|
|
41
|
-
|
|
42
|
-
target = attribute.relationship && attribute.type.target_name
|
|
43
|
-
target_phrase = target && "#{article(target).downcase} #{target}"
|
|
44
|
-
case attribute.relationship
|
|
45
|
-
when "has_one" then "#{subject} has #{target_phrase}."
|
|
46
|
-
when "belongs_to" then "#{subject} belongs to #{target_phrase}."
|
|
47
|
-
when "reference_to" then "#{subject} references #{target_phrase}."
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Vowel-letter heuristic is safe: construct names are plain words, never initialisms.
|
|
52
|
-
# @param word [String] a construct name to prefix
|
|
53
|
-
# @return [String] `"An"` if `word` starts with a vowel letter, `"A"` otherwise
|
|
54
|
-
def article(word) = word.to_s.match?(/\A[AEIOUaeiou]/) ? "An" : "A"
|
|
44
|
+
# @return [String, nil] the attribute's sentence, or nil for a plain field
|
|
45
|
+
def attribute_statement(holder, attribute) = Relationships.sentence(holder, attribute)
|
|
55
46
|
|
|
56
47
|
# Invariants sit on the holder and on each nested value object; both are read.
|
|
57
48
|
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projector
|
|
5
|
+
module Exporter
|
|
6
|
+
# The declared `.adapter` constructs as plain Hashes, in the shape
|
|
7
|
+
# `hecks-parse adapter <file>...` prints as JSON, so the two parsers can be compared byte
|
|
8
|
+
# for byte.
|
|
9
|
+
module Adapters
|
|
10
|
+
# Every adapter declared in a registry, in declaration order.
|
|
11
|
+
#
|
|
12
|
+
# @param registry [Runtime::Registry] the registry the `.adapter` files were loaded into
|
|
13
|
+
# @return [Array<Hash{String => Object}>] each adapter as `adapter_hash` builds it
|
|
14
|
+
def adapters(registry)
|
|
15
|
+
registry.adapters.values.map { |adapter| adapter_hash(adapter) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Exports one adapter with string keys in the fixed order name, port, fields, secrets,
|
|
19
|
+
# defaults.
|
|
20
|
+
#
|
|
21
|
+
# @param adapter [Bluebook::Hecksagon::Adapter] the adapter to export
|
|
22
|
+
# @return [Hash{String => Object}] `"name"` (String), `"port"` (String, nil when never
|
|
23
|
+
# declared), `"fields"`/`"secrets"` (`Array<String>`), `"defaults"` (field name to its
|
|
24
|
+
# JSON-native default, in first-declaration order)
|
|
25
|
+
def adapter_hash(adapter)
|
|
26
|
+
{
|
|
27
|
+
"name" => adapter.name,
|
|
28
|
+
"port" => adapter.port,
|
|
29
|
+
"fields" => (adapter.fields || []).map(&:to_s),
|
|
30
|
+
"secrets" => (adapter.secrets || []).map(&:to_s),
|
|
31
|
+
"defaults" => adapter.declared_defaults.to_h { |field, value| [field.to_s, value] }
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Loads each `.adapter` file into its own fresh registry and exports what it declared,
|
|
36
|
+
# in file order then declaration order.
|
|
37
|
+
#
|
|
38
|
+
# @param paths [Array<String>] `.adapter` file paths
|
|
39
|
+
# @return [Array<Hash{String => Object}>] `adapters` of each loaded registry, concatenated
|
|
40
|
+
def adapters_in_files(paths)
|
|
41
|
+
paths.flat_map { |path| adapters(load_adapter_file(path)) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The adapters of the given files as the pretty JSON `hecks-parse adapter` prints for them,
|
|
45
|
+
# byte for byte: the bundled json's `JSON.pretty_generate`, whose spacing of an empty
|
|
46
|
+
# collection `hecks-parse` reproduces.
|
|
47
|
+
#
|
|
48
|
+
# @param paths [Array<String>] `.adapter` file paths
|
|
49
|
+
# @return [String] the JSON `hecks-parse adapter` prints for the same files
|
|
50
|
+
def adapters_json(paths)
|
|
51
|
+
JSON.pretty_generate(adapters_in_files(paths))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Directories that hold copies or build output, never a source `.adapter`; the Rust
|
|
55
|
+
# builder skips the same names (and any dot-directory).
|
|
56
|
+
SKIPPED_DIRS = %w[target vendor node_modules tmp .git].freeze
|
|
57
|
+
|
|
58
|
+
# Every `.adapter` file under the given directories, for the IR's `adapters` key. The
|
|
59
|
+
# Rust builder scans the same directories the same way.
|
|
60
|
+
#
|
|
61
|
+
# @param directories [Array<String>] directories to scan recursively
|
|
62
|
+
# @return [Array<String>] absolute paths, unique and in byte order, none beneath a
|
|
63
|
+
# skipped or hidden directory (relative to the directory scanned)
|
|
64
|
+
def adapter_files_under(directories)
|
|
65
|
+
directories.flat_map do |directory|
|
|
66
|
+
base = File.expand_path(directory)
|
|
67
|
+
Dir.glob(File.join(base, "**", "*.adapter")).reject do |path|
|
|
68
|
+
path.delete_prefix("#{base}/").split("/")[0...-1].any? { |part| SKIPPED_DIRS.include?(part) }
|
|
69
|
+
end
|
|
70
|
+
end.uniq.sort_by(&:b)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The declarations of the adapters a domain binds, the IR's `adapters` key: one entry
|
|
74
|
+
# per distinct bound name that some `.adapter` file declares, in name byte order. Only
|
|
75
|
+
# declarations: never a world's setting values. A bound name nothing declares is
|
|
76
|
+
# omitted, for the host to refuse. Of two files declaring one name, the first path wins.
|
|
77
|
+
# A file is loaded only if it quotes a bound name, which cannot change the result.
|
|
78
|
+
#
|
|
79
|
+
# @param persistence [Hash{Symbol => Object}] `persistence`'s `:aggregates` with `:adapter`
|
|
80
|
+
# @param paths [Array<String>] candidate `.adapter` files, in precedence order
|
|
81
|
+
# @return [Array<Hash{String => Object}>] each as `adapter_hash` builds it
|
|
82
|
+
def persistence_adapters(persistence, paths)
|
|
83
|
+
wanted = bound_adapter_names(persistence)
|
|
84
|
+
declared = adapters_in_files(paths.select { |path| quotes_any?(File.read(path), wanted) })
|
|
85
|
+
declared.select { |adapter| wanted.include?(adapter["name"]) }.uniq { |a| a["name"] }
|
|
86
|
+
.sort_by { |adapter| adapter["name"].b }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @param persistence [Hash{Symbol => Object}] `persistence`'s result
|
|
90
|
+
# @return [Array<String>] the distinct adapter names its aggregates bind
|
|
91
|
+
def bound_adapter_names(persistence) = persistence.fetch(:aggregates).map { |row| row.fetch(:adapter) }.uniq
|
|
92
|
+
|
|
93
|
+
# @param text [String] a file's text
|
|
94
|
+
# @param names [Array<String>] adapter names
|
|
95
|
+
# @return [Boolean] whether the text quotes any of the names
|
|
96
|
+
def quotes_any?(text, names) = names.any? { |name| text.include?("\"#{name}\"") }
|
|
97
|
+
|
|
98
|
+
# Loads one `.adapter` file into a fresh registry. The file's own `require "hecks/..."`
|
|
99
|
+
# line loads the implementing class and needs `lib/` on the load path, as it is under
|
|
100
|
+
# bundler.
|
|
101
|
+
#
|
|
102
|
+
# @param path [String] an `.adapter` file path
|
|
103
|
+
# @return [Runtime::Registry] a registry holding only that file's adapters
|
|
104
|
+
def load_adapter_file(path)
|
|
105
|
+
registry = Runtime::Registry.new
|
|
106
|
+
Hecks.with_registry(registry) { Kernel.load(path) }
|
|
107
|
+
registry
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -38,33 +38,15 @@ module Hecks
|
|
|
38
38
|
JSON.pretty_generate(translations(registry))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
#
|
|
42
|
-
# never the compiled SQL, so a compiler-output change can't invalidate an approval.
|
|
43
|
-
#
|
|
44
|
-
# @param translation [Bluebook::Translation] the translation to digest
|
|
45
|
-
# @return [Hash{Symbol => Object}] `:domain` (String), `:from`/`:to` (the era
|
|
46
|
-
# identifiers as declared), `:retired` (`Array<String>`), and `:aggregates`
|
|
47
|
-
# (each `translation_aggregate`'s own Hash)
|
|
48
|
-
def translation_hash(translation)
|
|
49
|
-
{
|
|
50
|
-
domain: translation.domain,
|
|
51
|
-
from: translation.from,
|
|
52
|
-
to: translation.to,
|
|
53
|
-
retired: translation.retired,
|
|
54
|
-
aggregates: translation.aggregates.map { |aggregate| translation_aggregate(aggregate) }
|
|
55
|
-
}
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Digests one aggregate's own declared translation rules.
|
|
41
|
+
# One aggregate's declared translation rules as plain data.
|
|
59
42
|
#
|
|
60
43
|
# @param aggregate [Bluebook::TranslationAggregate] the aggregate's own
|
|
61
|
-
# translation rules
|
|
44
|
+
# translation rules
|
|
62
45
|
# @return [Hash{Symbol => Object}] `:name` (String), `:was` (String, nil),
|
|
63
|
-
# `:renames` (`Hash{String => String}`), `:moves`/`:converts`/`:retypes
|
|
64
|
-
#
|
|
65
|
-
# (`Array<String>`)
|
|
46
|
+
# `:renames` (`Hash{String => String}`), `:moves`/`:converts`/`:retypes`/`:backfills`
|
|
47
|
+
# (each an `Array<Hash>`), `:drops` (`Array<String>`)
|
|
66
48
|
def translation_aggregate(aggregate)
|
|
67
|
-
structural_rules(aggregate).merge(typed_rules(aggregate)).merge(
|
|
49
|
+
structural_rules(aggregate).merge(typed_rules(aggregate)).merge(backfill_rules(aggregate))
|
|
68
50
|
end
|
|
69
51
|
|
|
70
52
|
# `translation_aggregate`'s fields, plus precompiled SQL from the same
|
|
@@ -78,7 +60,7 @@ module Hecks
|
|
|
78
60
|
def compiled_translation_aggregate(aggregate)
|
|
79
61
|
return translation_aggregate(aggregate) unless Ports::Persistence.plugin?(:era)
|
|
80
62
|
|
|
81
|
-
translation_aggregate(aggregate).merge(
|
|
63
|
+
translation_aggregate(aggregate).merge(derived_rules(aggregate)).merge(
|
|
82
64
|
compiled_state_expression: Translation::RuleCompiler.compile_rules(aggregate),
|
|
83
65
|
compiled_id_expression: (if Translation::RuleCompiler.rekeyed?(aggregate)
|
|
84
66
|
Translation::RuleCompiler.compile_id_expression(aggregate)
|
|
@@ -109,17 +91,18 @@ module Hecks
|
|
|
109
91
|
}
|
|
110
92
|
end
|
|
111
93
|
|
|
112
|
-
# The
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
94
|
+
# The derives and the `rekey_to` of an aggregate, in the export of compiled rules only. An
|
|
95
|
+
# aggregate with none adds no key.
|
|
96
|
+
def derived_rules(aggregate)
|
|
97
|
+
return {} if aggregate.derives.empty?
|
|
98
|
+
|
|
99
|
+
rules = { derives: aggregate.derives.map { |derive| { to: derive.to, expression: derive.expression } } }
|
|
100
|
+
aggregate.rekeys.empty? ? rules : rules.merge(rekey_to: aggregate.rekeys.first.to)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The backfills of an aggregate.
|
|
104
|
+
def backfill_rules(aggregate)
|
|
105
|
+
{ backfills: aggregate.backfills.map { |backfill| { name: backfill.name.to_s, default: backfill.default } } }
|
|
123
106
|
end
|
|
124
107
|
end
|
|
125
108
|
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projector
|
|
5
|
+
module Exporter
|
|
6
|
+
# The declared `.world` constructs as plain Hashes, in the shape `hecks-parse world
|
|
7
|
+
# <file>...` prints as JSON, so the two parsers can be compared byte for byte. Only names are
|
|
8
|
+
# exported: a bind's setting values (connection strings, keys) never leave the file, and the
|
|
9
|
+
# default database is reported as a presence flag.
|
|
10
|
+
module Worlds
|
|
11
|
+
# Every world declared in a registry, one per domain, in first-declaration order.
|
|
12
|
+
#
|
|
13
|
+
# @param registry [Runtime::Registry] the registry the `.world` files were loaded into
|
|
14
|
+
# @return [Array<Hash{String => Object}>] each world as `world_hash` builds it
|
|
15
|
+
def worlds(registry)
|
|
16
|
+
registry.worlds.values.map { |world| world_hash(world) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Exports one world with string keys in the fixed order domain, realm, latest,
|
|
20
|
+
# default_adapter, default_database, bindings.
|
|
21
|
+
#
|
|
22
|
+
# @param world [Bluebook::World] the world to export
|
|
23
|
+
# @return [Hash{String => Object}] `"domain"` (String); `"realm"`, `"latest"` and
|
|
24
|
+
# `"default_adapter"` (String, nil when unset or empty); `"default_database"` (true
|
|
25
|
+
# only when a non-empty connection is declared, never the connection itself);
|
|
26
|
+
# `"bindings"` (see `world_bindings`)
|
|
27
|
+
def world_hash(world)
|
|
28
|
+
{
|
|
29
|
+
"domain" => world.domain,
|
|
30
|
+
"realm" => present_string(world.realm),
|
|
31
|
+
"latest" => present_string(world.latest),
|
|
32
|
+
"default_adapter" => present_string(world.default_adapter),
|
|
33
|
+
"default_database" => !present_string(world.default_database).nil?,
|
|
34
|
+
"bindings" => world_bindings(world)
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The adapter binds of a world, from the qualified `"verb:adapter"` settings entries only
|
|
39
|
+
# (the bare verb entry repeats the last of them), in the order they were declared.
|
|
40
|
+
#
|
|
41
|
+
# @param world [Bluebook::World] the world whose settings to read
|
|
42
|
+
# @return [Array<Hash{String => Object}>] each `"verb"` (as written), `"adapter"` and
|
|
43
|
+
# `"settings"` (the setting names, never their values, in declaration order)
|
|
44
|
+
def world_bindings(world)
|
|
45
|
+
world.settings.filter_map do |key, value|
|
|
46
|
+
next unless key.include?(":")
|
|
47
|
+
|
|
48
|
+
adapter = value[:adapter].to_s
|
|
49
|
+
{ "verb" => key.delete_suffix(":#{adapter.downcase}"), "adapter" => adapter,
|
|
50
|
+
"settings" => value.keys.reject { |name| name == :adapter }.map(&:to_s) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Loads every given `.world` file, in order, into one fresh registry, so worlds of one
|
|
55
|
+
# domain merge as they do at boot, and exports what it holds.
|
|
56
|
+
#
|
|
57
|
+
# @param paths [Array<String>] `.world` file paths
|
|
58
|
+
# @return [Array<Hash{String => Object}>] `worlds` of that registry
|
|
59
|
+
def worlds_in_files(paths)
|
|
60
|
+
registry = Runtime::Registry.new
|
|
61
|
+
Hecks.with_registry(registry) { paths.each { |path| Kernel.load(path) } }
|
|
62
|
+
worlds(registry)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The worlds of the given files as the pretty JSON `hecks-parse world` prints for them,
|
|
66
|
+
# byte for byte: the bundled json's `JSON.pretty_generate`.
|
|
67
|
+
#
|
|
68
|
+
# @param paths [Array<String>] `.world` file paths
|
|
69
|
+
# @return [String] the JSON `hecks-parse world` prints for the same files
|
|
70
|
+
def worlds_json(paths)
|
|
71
|
+
JSON.pretty_generate(worlds_in_files(paths))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The IR's `world` key: what a domain's world says about persistence, names only. A missing
|
|
75
|
+
# file, or a file with no world for the domain, gives the empty default.
|
|
76
|
+
#
|
|
77
|
+
# @param path [String] the `.world` file the domain sits beside (it need not exist)
|
|
78
|
+
# @param domain [String] the target domain's name
|
|
79
|
+
# @return [Hash{String => Object}] `"default_adapter"` (String or nil), `"default_database"`
|
|
80
|
+
# (Boolean) and `"persistence"` (each `persisted_by` bind as `"adapter"` and the
|
|
81
|
+
# `"settings"` names)
|
|
82
|
+
def persistence_world(path, domain)
|
|
83
|
+
world = (File.file?(path) ? worlds_in_files([path]) : []).find { |entry| entry["domain"] == domain }
|
|
84
|
+
return { "default_adapter" => nil, "default_database" => false, "persistence" => [] } unless world
|
|
85
|
+
|
|
86
|
+
{ "default_adapter" => world["default_adapter"], "default_database" => world["default_database"],
|
|
87
|
+
"persistence" => persisted_by_binds(world["bindings"]) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @param bindings [Array<Hash{String => Object}>] a world's `"bindings"`
|
|
91
|
+
# @return [Array<Hash{String => Object}>] the `persisted_by` ones, adapter and setting names
|
|
92
|
+
def persisted_by_binds(bindings)
|
|
93
|
+
bindings.select { |bind| bind["verb"] == "persisted_by" }
|
|
94
|
+
.map { |bind| { "adapter" => bind["adapter"], "settings" => bind["settings"] } }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param value [String, nil] a scalar a world may leave unset
|
|
98
|
+
# @return [String, nil] the value, or nil when it is nil or empty
|
|
99
|
+
def present_string(value) = value.nil? || value.empty? ? nil : value
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require_relative "../ports/persistence"
|
|
3
|
+
require_relative "exporter/adapters"
|
|
3
4
|
require_relative "exporter/bindings"
|
|
4
5
|
require_relative "exporter/translations"
|
|
6
|
+
require_relative "exporter/worlds"
|
|
5
7
|
|
|
6
8
|
module Hecks
|
|
7
9
|
module Projector
|
|
@@ -9,7 +11,7 @@ module Hecks
|
|
|
9
11
|
# binding facts, and translation edges (declared and compiled).
|
|
10
12
|
#
|
|
11
13
|
# The capability bindings (`authorization`, `membership`, ...) are `Exporter::Bindings` and
|
|
12
|
-
# the translation edges (`translations`, `
|
|
14
|
+
# the translation edges (`translations`, `translations_json`, ...) are `Exporter::Translations`;
|
|
13
15
|
# both are reachable as methods of `Exporter` itself.
|
|
14
16
|
module Exporter
|
|
15
17
|
module_function
|
|
@@ -72,8 +74,10 @@ module Hecks
|
|
|
72
74
|
{ aggregates: aggregates }
|
|
73
75
|
end
|
|
74
76
|
|
|
77
|
+
extend Adapters
|
|
75
78
|
extend Bindings
|
|
76
79
|
extend Translations
|
|
80
|
+
extend Worlds
|
|
77
81
|
end
|
|
78
82
|
end
|
|
79
83
|
end
|
|
@@ -62,7 +62,7 @@ module Hecks
|
|
|
62
62
|
|
|
63
63
|
# Permission rules are a feature of `claude`: any other command would run unconfined.
|
|
64
64
|
def refuse_unconfinable!(words, profile)
|
|
65
|
-
return if profile.nil? ||
|
|
65
|
+
return if profile.nil? || words == default_command(profile)
|
|
66
66
|
|
|
67
67
|
raise Failed, "permission confinement applies only to the default claude command"
|
|
68
68
|
end
|
|
@@ -79,15 +79,14 @@ module Hecks
|
|
|
79
79
|
%w[claude -p --permission-mode] + [profile.permission_mode] + profile.tool_flags
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
# Runs `words`
|
|
83
|
-
#
|
|
82
|
+
# Runs `words` with only the environment the profile names, in its own process group so a
|
|
83
|
+
# timeout takes the agent's children with it. The reader starts first so an agent that
|
|
84
|
+
# writes before it has read its whole prompt cannot block both sides on a full pipe.
|
|
84
85
|
def run_confined(words, prompt, chdir, profile)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Open3.popen2e(profile.environment, *profile.confine(words),
|
|
86
|
+
Open3.popen2e(profile.environment, *words,
|
|
88
87
|
chdir: chdir, unsetenv_others: true, pgroup: true) do |stdin, out, waiter|
|
|
89
|
-
feed(stdin, prompt)
|
|
90
88
|
reader = Thread.new { out.read }
|
|
89
|
+
feed(stdin, prompt)
|
|
91
90
|
finish(waiter, reader, profile.timeout)
|
|
92
91
|
end
|
|
93
92
|
end
|