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
|
@@ -3,6 +3,7 @@ require_relative "../bluebook/attribute"
|
|
|
3
3
|
require_relative "../naming"
|
|
4
4
|
require_relative "value_object_shape"
|
|
5
5
|
require_relative "text_shape"
|
|
6
|
+
require_relative "rule_words"
|
|
6
7
|
|
|
7
8
|
module Hecks
|
|
8
9
|
# The forms surface: the `expose` DSL, the IR->HTML renderers and the Rack app (see forms.rb).
|
|
@@ -32,9 +33,20 @@ module Hecks
|
|
|
32
33
|
Naming.words(segment)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
# "
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
# A code name as a phrase: "AddTopping" -> "Add topping", "Order" -> "Order".
|
|
37
|
+
#
|
|
38
|
+
# @param name [String, Symbol, #to_s] a command, query or aggregate name
|
|
39
|
+
# @return [String] the sentence-cased words
|
|
40
|
+
def self.phrase(name) = Naming.words(name)
|
|
41
|
+
|
|
42
|
+
# A code name as words inside a sentence: "BankAccount" -> "bank account", "ATMCard" ->
|
|
43
|
+
# "ATM card".
|
|
44
|
+
#
|
|
45
|
+
# @param name [String, Symbol, #to_s] an aggregate or field name
|
|
46
|
+
# @return [String] the words, the first lowercased unless it is an acronym
|
|
47
|
+
def self.noun(name)
|
|
48
|
+
words = Naming.words(name)
|
|
49
|
+
words.match?(/\A[A-Z]{2,}\b/) ? words : words.sub(/\A./, &:downcase)
|
|
38
50
|
end
|
|
39
51
|
end
|
|
40
52
|
|
|
@@ -79,12 +91,8 @@ module Hecks
|
|
|
79
91
|
|
|
80
92
|
def self.reference_field(attribute, common)
|
|
81
93
|
target = attribute.type.resolve
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"References an existing #{target.hecks_name} by id."
|
|
85
|
-
else
|
|
86
|
-
"References an aggregate in another domain — enter its id."
|
|
87
|
-
end)
|
|
94
|
+
help = target ? "An existing #{Humanize.noun(target.hecks_name)}, by its id." : "A record in another domain, by its id."
|
|
95
|
+
Field.new(**common, kind: :reference, html_type: "text", target_aggregate: target, help: help)
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
# Mirrors Runtime::Value::Admission#admitted_members (same split, chapter walk and
|
|
@@ -119,16 +127,26 @@ module Hecks
|
|
|
119
127
|
return closed_set_field(shape, common) if shape.closed_set?
|
|
120
128
|
return money_field(shape, common) if ValueObjectShape.money?(shape)
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (inner = ValueObjectShape.sole_attribute(shape))
|
|
125
|
-
return resolve(inner, aggregate: aggregate, path: "#{common[:path]}.#{inner.name}")
|
|
126
|
-
.tap { |field| field.optional = common[:optional] || field.optional }
|
|
127
|
-
end
|
|
130
|
+
inner = ValueObjectShape.sole_attribute(shape)
|
|
131
|
+
return sole_attribute_field(shape, inner, aggregate, common) if inner
|
|
128
132
|
|
|
129
133
|
group_field(shape, aggregate, common)
|
|
130
134
|
end
|
|
131
135
|
|
|
136
|
+
# A single-attribute value object names a scalar, not a group: unwrap it so the form
|
|
137
|
+
# asks for one thing and the inner attribute's own pattern drives the input type. The
|
|
138
|
+
# label stays the command's own name for it (`topping`), never the inner `value`, and
|
|
139
|
+
# the help says what the value object's rules accept.
|
|
140
|
+
#
|
|
141
|
+
# @return [Forms::Field] the inner attribute's field at the dotted path
|
|
142
|
+
def self.sole_attribute_field(shape, inner, aggregate, common)
|
|
143
|
+
field = resolve(inner, aggregate: aggregate, path: "#{common[:path]}.#{inner.name}")
|
|
144
|
+
field.label = common[:label]
|
|
145
|
+
field.optional = common[:optional] || field.optional
|
|
146
|
+
field.help = RuleWords.describe(inner, shape.invariants) if field.leaf? && field.kind != :reference
|
|
147
|
+
field
|
|
148
|
+
end
|
|
149
|
+
|
|
132
150
|
def self.cross_aggregate_value_object(aggregate, type_name)
|
|
133
151
|
aggregate.hecks_owner&.aggregates&.each do |sibling|
|
|
134
152
|
found = sibling.value_object(type_name)
|
|
@@ -145,10 +163,10 @@ module Hecks
|
|
|
145
163
|
def self.money_field(shape, common)
|
|
146
164
|
cents = Field.new(path: "#{common[:path]}.cents", label: "Amount (cents)", kind: :number,
|
|
147
165
|
html_type: "number", step: "1", optional: common[:optional],
|
|
148
|
-
default: shape.attribute(:cents)&.default, help: "
|
|
166
|
+
default: shape.attribute(:cents)&.default, help: "In cents: 1050 is $10.50.")
|
|
149
167
|
currency = Field.new(path: "#{common[:path]}.currency", label: "Currency", kind: :text, html_type: "text",
|
|
150
168
|
optional: true, default: shape.attribute(:currency)&.default || "USD",
|
|
151
|
-
help: "Three-letter code.")
|
|
169
|
+
help: "Three-letter code, such as USD.")
|
|
152
170
|
Field.new(path: common[:path], label: common[:label], kind: :money, optional: common[:optional],
|
|
153
171
|
children: [cents, currency])
|
|
154
172
|
end
|
|
@@ -173,6 +191,7 @@ module Hecks
|
|
|
173
191
|
end
|
|
174
192
|
|
|
175
193
|
def self.primitive_field(attribute, common)
|
|
194
|
+
common = common.merge(help: RuleWords.describe(attribute))
|
|
176
195
|
case attribute.type.to_s
|
|
177
196
|
when "Integer" then Field.new(**common, kind: :number, html_type: "number", step: "1")
|
|
178
197
|
when "Float" then Field.new(**common, kind: :number, html_type: "number", step: "any")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "html"
|
|
2
|
+
require_relative "field_shape"
|
|
2
3
|
|
|
3
4
|
module Hecks
|
|
4
5
|
module Forms
|
|
@@ -40,7 +41,7 @@ module Hecks
|
|
|
40
41
|
"#{aggregate.queries.size} quer#{aggregate.queries.size == 1 ? "y" : "ies"}"
|
|
41
42
|
<<~HTML
|
|
42
43
|
<li><a href="/#{Escape.attr(name)}/#{Escape.attr(aggregate.hecks_name)}.html">
|
|
43
|
-
<span>#{Escape.html(aggregate.hecks_name)}</span><span class="kind">#{Escape.html(counts)}</span>
|
|
44
|
+
<span>#{Escape.html(Humanize.phrase(aggregate.hecks_name))}</span><span class="kind">#{Escape.html(counts)}</span>
|
|
44
45
|
</a></li>
|
|
45
46
|
HTML
|
|
46
47
|
end
|
data/lib/hecks/forms/page.rb
CHANGED
|
@@ -35,7 +35,7 @@ module Hecks
|
|
|
35
35
|
#{body}
|
|
36
36
|
</main>
|
|
37
37
|
<footer class="site-footer">
|
|
38
|
-
<p>
|
|
38
|
+
<p>Every page here is generated from the domain's description (its bluebook). None is written by hand.</p>
|
|
39
39
|
</footer>
|
|
40
40
|
<script>#{SCRIPT}</script>
|
|
41
41
|
</body>
|
data/lib/hecks/forms/params.rb
CHANGED
|
@@ -203,19 +203,6 @@ module Hecks
|
|
|
203
203
|
end
|
|
204
204
|
into
|
|
205
205
|
end
|
|
206
|
-
|
|
207
|
-
# Renders held values as a URL query string, percent-encoding each flattened path and
|
|
208
|
-
# value.
|
|
209
|
-
#
|
|
210
|
-
# @param fields [Array<Forms::Field>] the field tree naming which paths to read
|
|
211
|
-
# @param values [Hash{String, Symbol => Object}] held values, flat or nested; see
|
|
212
|
-
# `FieldRenderer.dig`
|
|
213
|
-
# @return [String] `key=value` pairs joined with `&`, without a leading `?`; `""` when
|
|
214
|
-
# `flatten` yields no pairs
|
|
215
|
-
def self.to_query_string(fields, values)
|
|
216
|
-
pairs = flatten(fields, values)
|
|
217
|
-
pairs.map { |key, value| "#{URI.encode_www_form_component(key)}=#{URI.encode_www_form_component(value)}" }.join("&")
|
|
218
|
-
end
|
|
219
206
|
end
|
|
220
207
|
end
|
|
221
208
|
end
|
|
@@ -22,7 +22,7 @@ module Hecks
|
|
|
22
22
|
reference_options = ReferenceOptions.collect(registry, domain, fields)
|
|
23
23
|
|
|
24
24
|
<<~HTML
|
|
25
|
-
#{header(
|
|
25
|
+
#{header(query)}
|
|
26
26
|
#{canonical_link(action, fields)}
|
|
27
27
|
#{quick_links(action, fields)}
|
|
28
28
|
#{filter_form(action, fields, view.fetch(:params, {}), reference_options)}
|
|
@@ -32,9 +32,9 @@ module Hecks
|
|
|
32
32
|
HTML
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def self.header(
|
|
35
|
+
def self.header(query)
|
|
36
36
|
<<~HTML
|
|
37
|
-
<h1>#{Escape.html(
|
|
37
|
+
<h1>#{Escape.html(Humanize.phrase(query.hecks_name))}</h1>
|
|
38
38
|
#{%(<p class="goal">#{Escape.html(query.description)}</p>) if query.description}
|
|
39
39
|
#{badges(query)}
|
|
40
40
|
HTML
|
|
@@ -50,7 +50,7 @@ module Hecks
|
|
|
50
50
|
paths = Params.paths(fields)
|
|
51
51
|
template = paths.empty? ? action : "#{action}?#{paths.map { |path| "#{path}={#{path}}" }.join("&")}"
|
|
52
52
|
<<~HTML
|
|
53
|
-
<p class="help">
|
|
53
|
+
<p class="help">This query is a plain link. Bookmark it, share it, or put it on a dashboard:</p>
|
|
54
54
|
<div class="link-row"><code id="canonical-link">#{Escape.html(template)}</code><button type="button" class="copy" data-copy="#canonical-link">copy</button></div>
|
|
55
55
|
HTML
|
|
56
56
|
end
|
|
@@ -81,7 +81,8 @@ module Hecks
|
|
|
81
81
|
def self.error_banner(error)
|
|
82
82
|
return "" unless error
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
kind = Escape.html(error.class.name.split("::").last)
|
|
85
|
+
%(<div class="error-banner" role="alert"><p><strong>Refused (#{kind}):</strong> ) \
|
|
85
86
|
"#{Escape.html(error.message)}</p></div>"
|
|
86
87
|
end
|
|
87
88
|
|
|
@@ -96,7 +97,7 @@ module Hecks
|
|
|
96
97
|
paths = Params.paths(fields)
|
|
97
98
|
<<~HTML
|
|
98
99
|
<details class="inspect">
|
|
99
|
-
<summary>Inspect
|
|
100
|
+
<summary>Inspect: #{Escape.html(verb)}</summary>
|
|
100
101
|
<p>Parameters: #{paths.empty? ? "<em>none</em>" : paths.map { |p| "<code>#{Escape.html(p)}</code>" }.join(", ")}</p>
|
|
101
102
|
<pre>#{Escape.html(JSON.pretty_generate(query.to_h))}</pre>
|
|
102
103
|
</details>
|
|
@@ -11,7 +11,7 @@ module Hecks
|
|
|
11
11
|
def self.index(registry:, domain:, aggregate:)
|
|
12
12
|
instances = registry.repository(domain, aggregate).all
|
|
13
13
|
<<~HTML
|
|
14
|
-
<h1>#{Escape.html(
|
|
14
|
+
<h1>#{Escape.html(Naming.plural(Humanize.phrase(aggregate.hecks_name)))}</h1>
|
|
15
15
|
#{%(<p class="goal">#{Escape.html(aggregate.description)}</p>) if aggregate.description}
|
|
16
16
|
#{creating_links(domain, aggregate)}
|
|
17
17
|
<h2>Records (#{instances.size})</h2>
|
|
@@ -26,7 +26,7 @@ module Hecks
|
|
|
26
26
|
|
|
27
27
|
links = creators.map do |cmd|
|
|
28
28
|
%(<a class="button" href="/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html">) \
|
|
29
|
-
"+ #{Escape.html(cmd.hecks_name)}</a>"
|
|
29
|
+
"+ #{Escape.html(Humanize.phrase(cmd.hecks_name))}</a>"
|
|
30
30
|
end
|
|
31
31
|
%(<div class="actions">#{links.join}</div>)
|
|
32
32
|
end
|
|
@@ -40,7 +40,7 @@ module Hecks
|
|
|
40
40
|
state = instance.state
|
|
41
41
|
current = aggregate.lifecycle && state[aggregate.lifecycle.field]
|
|
42
42
|
<<~HTML
|
|
43
|
-
<h1>#{Escape.html(
|
|
43
|
+
<h1>#{Escape.html(Humanize.phrase(aggregate.hecks_name))} <span class="mono">#{Escape.html(id)}</span></h1>
|
|
44
44
|
#{lifecycle_badge(aggregate, current)}
|
|
45
45
|
#{state_table(state)}
|
|
46
46
|
<h2>Commands</h2>
|
|
@@ -76,7 +76,8 @@ module Hecks
|
|
|
76
76
|
# Omits lifecycle transitions that dispatch would refuse from the current state.
|
|
77
77
|
def self.command_links(domain, aggregate, id, current_state)
|
|
78
78
|
commands = aggregate.commands.reject(&:creates?).select { |cmd| applies?(aggregate, cmd, current_state) }
|
|
79
|
-
|
|
79
|
+
noun = Escape.html(Humanize.noun(aggregate.hecks_name))
|
|
80
|
+
return "<p><em>No commands act on an existing #{noun}.</em></p>" if commands.empty?
|
|
80
81
|
|
|
81
82
|
items = commands.map { |cmd| command_item(domain, aggregate, cmd, id) }
|
|
82
83
|
%(<ul class="verb-list">#{items.join}</ul>)
|
|
@@ -86,7 +87,7 @@ module Hecks
|
|
|
86
87
|
def self.command_item(domain, aggregate, cmd, id)
|
|
87
88
|
# The id is free-form, so it is percent-encoded as a query value.
|
|
88
89
|
href = "/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html?to=#{Escape.url(id)}"
|
|
89
|
-
%(<li><a href="#{Escape.attr(href)}"><span>#{Escape.html(cmd.hecks_name)}</span>) \
|
|
90
|
+
%(<li><a href="#{Escape.attr(href)}"><span>#{Escape.html(Humanize.phrase(cmd.hecks_name))}</span>) \
|
|
90
91
|
"<span class=\"kind\">#{Escape.html(cmd.goal.to_s)}</span></a></li>"
|
|
91
92
|
end
|
|
92
93
|
|
|
@@ -110,15 +111,18 @@ module Hecks
|
|
|
110
111
|
|
|
111
112
|
cmd_items = commands.map { |c| verb_item(domain, aggregate, c, "command") }
|
|
112
113
|
query_items = aggregate.queries.map { |q| verb_item(domain, aggregate, q, "query") }
|
|
113
|
-
%(<h2>
|
|
114
|
+
%(<h2>Commands and queries for #{plural_noun(aggregate)}</h2>) \
|
|
114
115
|
"<ul class=\"verb-list\">#{(cmd_items + query_items).join}</ul>"
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
# @param kind [String] the word shown beside the verb, `"command"` or `"query"`
|
|
118
119
|
# @return [String] the list item linking to the verb's own page
|
|
120
|
+
# @return [String] the aggregate's name as a lowercase plural, HTML-escaped: "orders"
|
|
121
|
+
def self.plural_noun(aggregate) = Escape.html(Naming.plural(Humanize.noun(aggregate.hecks_name)))
|
|
122
|
+
|
|
119
123
|
def self.verb_item(domain, aggregate, verb, kind)
|
|
120
124
|
%(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{verb.hecks_name}.html">) \
|
|
121
|
-
"<span>#{Escape.html(verb.hecks_name)}</span><span class=\"kind\">#{kind}</span></a></li>"
|
|
125
|
+
"<span>#{Escape.html(Humanize.phrase(verb.hecks_name))}</span><span class=\"kind\">#{kind}</span></a></li>"
|
|
122
126
|
end
|
|
123
127
|
end
|
|
124
128
|
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require_relative "../bluebook/named_pattern"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Forms
|
|
5
|
+
module FieldShape
|
|
6
|
+
# Says in plain words what a field accepts, read only from what the bluebook declares:
|
|
7
|
+
# the attribute's type, its named `matches:` pattern, and the invariants of the value
|
|
8
|
+
# object it sits in. An invariant this module cannot read is quoted by its own
|
|
9
|
+
# description rather than guessed at.
|
|
10
|
+
module RuleWords
|
|
11
|
+
# Words for each `Bluebook::NamedPattern` name; a raw regex has none and is not shown.
|
|
12
|
+
PATTERN_WORDS = {
|
|
13
|
+
present: "Must not be blank.",
|
|
14
|
+
not_empty: "Must not be empty.",
|
|
15
|
+
no_whitespace: "No spaces.",
|
|
16
|
+
snake_name: "Lowercase letters, digits and underscores, starting with a letter.",
|
|
17
|
+
git_sha: "A commit id: 7 to 40 hexadecimal characters.",
|
|
18
|
+
digits: "Digits only."
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# The noun a numeric type reads as, for "A whole number above zero."
|
|
22
|
+
NUMBER_NOUNS = { "Integer" => "A whole number", "Float" => "A number" }.freeze
|
|
23
|
+
|
|
24
|
+
# One comparison of the attribute against a literal number, as its canonical source
|
|
25
|
+
# spells it: `value > 0`, `cents >= 0`.
|
|
26
|
+
COMPARISON = /\A(?<name>\w+) (?<op>>=|<=|>|<) (?<bound>-?\d+(?:\.\d+)?)\z/
|
|
27
|
+
|
|
28
|
+
# How each comparison reads after the noun: "above 5", "10 or less".
|
|
29
|
+
BOUND_WORDS = { ">" => "above %s", ">=" => "%s or more", "<" => "below %s", "<=" => "%s or less" }.freeze
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Describes what `attribute` accepts, or nil when nothing declared can be said in words.
|
|
34
|
+
#
|
|
35
|
+
# @param attribute [Bluebook::Attribute] the scalar attribute the field asks for
|
|
36
|
+
# @param invariants [Array<Bluebook::Invariant>] the rules of the value object that
|
|
37
|
+
# wraps `attribute`; empty for a bare attribute
|
|
38
|
+
# @return [String, nil] one or more sentences, such as "A whole number above zero."
|
|
39
|
+
def describe(attribute, invariants = [])
|
|
40
|
+
readable, quoted = invariants.partition { |rule| reading(attribute, rule) }
|
|
41
|
+
sentences = [type_sentence(attribute, readable), pattern_sentence(attribute, readable)]
|
|
42
|
+
sentences += quoted.map { |rule| "Rule: #{rule.description}." }
|
|
43
|
+
text = sentences.compact.uniq.join(" ")
|
|
44
|
+
text.empty? ? nil : text
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [String, nil] the numeric noun with every readable bound, such as
|
|
48
|
+
# "A whole number above zero." or "A whole number, zero or more."; nil for a
|
|
49
|
+
# non-numeric attribute
|
|
50
|
+
def type_sentence(attribute, readable)
|
|
51
|
+
noun = NUMBER_NOUNS[attribute.type.to_s]
|
|
52
|
+
return nil unless noun
|
|
53
|
+
|
|
54
|
+
bounds = Naming.to_sentence_list(readable.map { |rule| reading(attribute, rule) }.grep_v(:not_blank))
|
|
55
|
+
return "#{noun}." if bounds.empty?
|
|
56
|
+
|
|
57
|
+
"#{noun}#{bounds.start_with?("above", "below") ? " " : ", "}#{bounds}."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [String, nil] "Must not be blank." when the named pattern or a readable
|
|
61
|
+
# invariant says so, or the named pattern's own words
|
|
62
|
+
def pattern_sentence(attribute, readable)
|
|
63
|
+
named = Bluebook::NamedPattern::TABLE.key(attribute.pattern.to_s)
|
|
64
|
+
return PATTERN_WORDS.fetch(:present) if readable.any? { |rule| reading(attribute, rule) == :not_blank }
|
|
65
|
+
|
|
66
|
+
named && PATTERN_WORDS[named]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Reads one invariant over the attribute alone.
|
|
70
|
+
#
|
|
71
|
+
# @return [String, Symbol, nil] a bound's words ("above zero"), `:not_blank`, or nil
|
|
72
|
+
# when the rule is not one this module can read
|
|
73
|
+
def reading(attribute, rule)
|
|
74
|
+
name = attribute.name.to_s
|
|
75
|
+
canonical = rule.canonical.to_s.strip
|
|
76
|
+
return :not_blank if blank_check?(name, canonical)
|
|
77
|
+
return "above zero" if canonical == "#{name}.positive?"
|
|
78
|
+
|
|
79
|
+
comparison_words(name, canonical)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Boolean] whether the rule refuses an empty or all-whitespace value
|
|
83
|
+
def blank_check?(name, canonical)
|
|
84
|
+
["!#{name}.to_s.empty?", "!#{name}.to_s.strip.empty?", "!#{name}.empty?", "!#{name}.strip.empty?"]
|
|
85
|
+
.include?(canonical)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @return [String, nil] the comparison's words, with zero spelled out; nil when the
|
|
89
|
+
# rule is not one comparison of `name` against a number
|
|
90
|
+
def comparison_words(name, canonical)
|
|
91
|
+
match = COMPARISON.match(canonical)
|
|
92
|
+
return nil unless match && match[:name] == name
|
|
93
|
+
|
|
94
|
+
bound = match[:bound] == "0" ? "zero" : match[:bound]
|
|
95
|
+
format(BOUND_WORDS.fetch(match[:op]), bound)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Forms
|
|
3
3
|
# Shared value-object shape classification, so callers such as
|
|
4
|
-
# field_shape.rb
|
|
4
|
+
# field_shape.rb and sql_query_builder.rb agree on it. There is no Ruby ui_schema; the Rust
|
|
5
|
+
# host builds the UI schema (rust/host/src/ui_schema.rs) from the same field-shape rules.
|
|
5
6
|
module ValueObjectShape
|
|
6
7
|
module_function
|
|
7
8
|
|
|
@@ -35,9 +35,6 @@ module Hecks
|
|
|
35
35
|
array: %w[arr_num arr_str].freeze
|
|
36
36
|
}.freeze
|
|
37
37
|
|
|
38
|
-
# Element type per array attribute, so a block parameter gets the right leaf set.
|
|
39
|
-
ARRAY_ELEMENT_TYPE = { "arr_num" => :numeric, "arr_str" => :string }.freeze
|
|
40
|
-
|
|
41
38
|
BLOCK_PARAM = "el".freeze
|
|
42
39
|
|
|
43
40
|
MAX_DEPTH = 3
|
|
@@ -74,12 +74,9 @@ module Hecks
|
|
|
74
74
|
|
|
75
75
|
def hecksagon_names_in(path) = File.read(path).scan(HECKSAGON_NAME).flatten
|
|
76
76
|
|
|
77
|
-
# Deletes every `translations/*.bluebook` in the copy.
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# than tripping that refusal. Safe to drop: an ephemeral, zero-history boot never
|
|
81
|
-
# has a pre-existing era-1 row to translate — the edge only matters at mint time,
|
|
82
|
-
# against a real database, never here.
|
|
77
|
+
# Deletes every `translations/*.bluebook` in the copy. Safe to drop: an ephemeral,
|
|
78
|
+
# zero-history boot never has a pre-existing era-1 row to translate; the edge only
|
|
79
|
+
# matters at mint time, against a real database, never here.
|
|
83
80
|
def strip_translations!(copy)
|
|
84
81
|
Dir.glob(File.join(copy, "**", "translations", "*.bluebook")).each { |path| File.delete(path) }
|
|
85
82
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative "../../
|
|
1
|
+
require_relative "../../vocabulary"
|
|
2
2
|
require_relative "../../query_specification/common/where_clause"
|
|
3
3
|
require_relative "../../query_specification/field_path"
|
|
4
4
|
require_relative "../../ports/query/in_memory"
|
|
@@ -9,10 +9,6 @@ module Hecks
|
|
|
9
9
|
# Answers the ad hoc `{aggregate:, field:, op:, value:}` filter steps that bypass the
|
|
10
10
|
# declared query DSL.
|
|
11
11
|
module Filters
|
|
12
|
-
# Mirrors QuerySpecification::Common::COMPARATORS, not the Rust
|
|
13
|
-
# kernel's own comparator enum, which is missing `none_in_state` and has drifted.
|
|
14
|
-
FILTER_COMPARATORS = Hecks::QuerySpecification::Common::COMPARATORS.map(&:to_s).freeze
|
|
15
|
-
|
|
16
12
|
# Answers one ad hoc filter step, the mirror image of kernel/cli.rs's own
|
|
17
13
|
# `run_filter`, calling the same production `Ports::Query::InMemory` rather than
|
|
18
14
|
# re-deriving comparator behavior by hand. Sorted by id ascending regardless,
|
|
@@ -31,7 +27,7 @@ module Hecks
|
|
|
31
27
|
|
|
32
28
|
# A malformed ad-hoc ask is a fault (C8.3), not a bare RuntimeError.
|
|
33
29
|
def check_comparator!(comparator)
|
|
34
|
-
return if
|
|
30
|
+
return if Hecks::Vocabulary.fetch("QueryComparator").include?(comparator)
|
|
35
31
|
|
|
36
32
|
raise Bluebook::Expression::EvaluationError, "unknown query comparator #{comparator.inspect}"
|
|
37
33
|
end
|
data/lib/hecks/gate/stages.yml
CHANGED
|
@@ -84,6 +84,20 @@ pre_push:
|
|
|
84
84
|
title: comment blocks, no run of comment lines past ADR 0075's cap
|
|
85
85
|
run: [bundle, exec, exe/hecks, style_run.check_comments, "paths=lib,spec,examples", only=long_block, --wait]
|
|
86
86
|
blocked: a comment block ran past the cap; shorten it.
|
|
87
|
+
- &check_rust_fmt
|
|
88
|
+
id: rust_fmt
|
|
89
|
+
title: rustfmt, every Rust crate laid out as `cargo fmt` would
|
|
90
|
+
run:
|
|
91
|
+
- sh
|
|
92
|
+
- -ec
|
|
93
|
+
- |
|
|
94
|
+
# The main crate's generated modules are not committed, so cargo fmt would stop at a
|
|
95
|
+
# missing file on a fresh checkout; its hand-written entry points are named instead.
|
|
96
|
+
rustfmt --check --edition 2021 rust/src/main.rs rust/src/kernel/mod.rs rust/tests/*.rs
|
|
97
|
+
for crate in parser codegen lsp host web build; do
|
|
98
|
+
cargo fmt --check --manifest-path "rust/$crate/Cargo.toml"
|
|
99
|
+
done
|
|
100
|
+
blocked: a Rust file is not laid out as rustfmt has it; run `cargo fmt` in its crate (rust/src/exemplar is exempt, it is text templates). Generated Rust is laid out by its generators, so fix the generator, not the output.
|
|
87
101
|
|
|
88
102
|
# The checks a push of only unguarded lanes (`main`) runs in place of pre_push. The slow suite is
|
|
89
103
|
# skipped there; the style checks are not, since an offense that lands on `main` is a red CI run,
|
|
@@ -140,6 +154,17 @@ ci:
|
|
|
140
154
|
- *check_comment_style
|
|
141
155
|
- *check_comment_blocks
|
|
142
156
|
- *check_codegen_drift
|
|
157
|
+
- *check_rust_fmt
|
|
158
|
+
- id: rust_clippy
|
|
159
|
+
title: clippy, every Rust crate with its warnings as errors
|
|
160
|
+
run:
|
|
161
|
+
- sh
|
|
162
|
+
- -ec
|
|
163
|
+
- |
|
|
164
|
+
for crate in . parser codegen lsp host web build; do
|
|
165
|
+
cargo clippy --all-targets --manifest-path "rust/$crate/Cargo.toml" -- -D warnings
|
|
166
|
+
done
|
|
167
|
+
blocked: clippy found a warning in a Rust crate, under the strict `[lints.clippy]` table of its Cargo.toml. The main crate needs rust/src/generated, so run `hecks regenerate_corpus --confirm` first on a fresh checkout. Fix the code, or allow a lint at the one site with a line saying why; a warning in generated Rust is fixed in its generator.
|
|
143
168
|
- id: lint_deploy_recipes
|
|
144
169
|
title: generated deploy recipes, a command with no checked exit status or no echo
|
|
145
170
|
run: [bundle, exec, exe/hecks, deploy, makefile_check.lint, --wait]
|
|
@@ -157,15 +157,9 @@ module Hecks
|
|
|
157
157
|
# First `end` after the opener is the block's own — member rows sit bare (ADR 0025).
|
|
158
158
|
def keyword_blocks(source) = seed_blocks(source, "KeywordSeed")
|
|
159
159
|
|
|
160
|
-
# The first KeywordSeed block's source text.
|
|
161
|
-
def keyword_block(source) = seed_block(source, "KeywordSeed", required: true)
|
|
162
|
-
|
|
163
160
|
# ArgumentSeed blocks; see keyword_blocks for why the first bare end
|
|
164
161
|
# after the opener is already the block's own.
|
|
165
162
|
def argument_blocks(source) = seed_blocks(source, "ArgumentSeed")
|
|
166
|
-
|
|
167
|
-
# The first ArgumentSeed block's source text.
|
|
168
|
-
def argument_block(source) = seed_block(source, "ArgumentSeed", required: true)
|
|
169
163
|
end
|
|
170
164
|
end
|
|
171
165
|
end
|
data/lib/hecks/grammar/evolve.rb
CHANGED
|
@@ -96,9 +96,6 @@ module Hecks
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
# Narrow compatibility entry point for single-file callers; syntax_paths is normal.
|
|
100
|
-
def syntax_path = syntax_paths.first
|
|
101
|
-
|
|
102
99
|
# Resolves the file(s) a call should search or write.
|
|
103
100
|
def paths_for(path) = path ? Array(path) : syntax_paths
|
|
104
101
|
|
|
@@ -23,7 +23,7 @@ Hecks.bluebook "Translation" do
|
|
|
23
23
|
# "retired" is the edge-level declaration (TranslationBuilder#retired).
|
|
24
24
|
value_object "Kind" do
|
|
25
25
|
attribute :value, String,
|
|
26
|
-
one_of: ["rename", "move", "convert", "drop", "retype", "retired", "
|
|
26
|
+
one_of: ["rename", "move", "convert", "drop", "retype", "retired", "derive", "rekey_to",
|
|
27
27
|
"backfill", "unresolved"]
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require_relative "corpus_tasks"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module Codebase
|
|
6
|
+
# What Codebase's `CorpusRun` asks of the working tree to open the Composer: serve its editor
|
|
7
|
+
# over one domain directory, on the loopback address only, until the server is stopped.
|
|
8
|
+
#
|
|
9
|
+
# The server hook is the one `CorpusTasks` has, so a spec replaces both servers the same way.
|
|
10
|
+
module Composing
|
|
11
|
+
# Every operation this family carries out.
|
|
12
|
+
OPERATIONS = %w[compose].freeze
|
|
13
|
+
|
|
14
|
+
# The port the editor listens on when none is named.
|
|
15
|
+
DEFAULT_PORT = 4570
|
|
16
|
+
|
|
17
|
+
# The directory the editor opens when none is named, relative to the checkout.
|
|
18
|
+
DEFAULT_WORKSPACE = "examples/pizzas/bluebook".freeze
|
|
19
|
+
|
|
20
|
+
# The only address the editor binds: it carries no authentication.
|
|
21
|
+
HOST = "127.0.0.1".freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Opens the editor.
|
|
26
|
+
#
|
|
27
|
+
# @param operation [String] `compose`
|
|
28
|
+
# @param held [Hash] the `CorpusRun` record's fields: `port`, `domains` (the directory)
|
|
29
|
+
# @param tree [Tree] the working tree, already known to be a hecks checkout
|
|
30
|
+
# @param shell [#capture, nil] unused
|
|
31
|
+
# @return [String] a note that the server stopped
|
|
32
|
+
def call(operation, held, tree, shell: nil)
|
|
33
|
+
_ = [operation, shell]
|
|
34
|
+
require "hecks/composer/web"
|
|
35
|
+
fields = held.transform_values { |value| value.is_a?(Hash) ? value[:value] : value }
|
|
36
|
+
workspace = File.expand_path(fields[:domains] || DEFAULT_WORKSPACE, tree.root)
|
|
37
|
+
port = fields[:port] || DEFAULT_PORT
|
|
38
|
+
warn "composer over #{workspace} on http://#{HOST}:#{port}/composer - no authentication, " \
|
|
39
|
+
"no CSRF protection, no caller identity; do not expose beyond localhost"
|
|
40
|
+
app = Composer::Web.app(workspace: workspace)
|
|
41
|
+
(CorpusTasks.web_server || method(:serve)).call(app: app, port: port, host: HOST)
|
|
42
|
+
"composed on port #{port}, now stopped"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param app [#call] the Rack app
|
|
46
|
+
# @param port [Integer] the port
|
|
47
|
+
# @param host [String] the address to bind
|
|
48
|
+
# @return [void]
|
|
49
|
+
def serve(app:, port:, host:)
|
|
50
|
+
require "rackup"
|
|
51
|
+
Rackup::Server.start(app: app, Host: host, Port: port, server: "webrick")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -11,6 +11,7 @@ require_relative "inspection"
|
|
|
11
11
|
require_relative "codemods"
|
|
12
12
|
require_relative "test_suite"
|
|
13
13
|
require_relative "corpus_tasks"
|
|
14
|
+
require_relative "composing"
|
|
14
15
|
require_relative "publishing"
|
|
15
16
|
require_relative "promotion"
|
|
16
17
|
require_relative "../git"
|
|
@@ -183,7 +184,8 @@ module Hecks
|
|
|
183
184
|
# The task families, each carrying out the operations it lists.
|
|
184
185
|
FAMILIES = [Codebase::Language, Codebase::KernelTables, Codebase::Conformance, Codebase::Regeneration,
|
|
185
186
|
Codebase::Gate, Codebase::Style, Codebase::Inspection, Codebase::Codemods, Codebase::TestSuite,
|
|
186
|
-
Codebase::CorpusTasks, Codebase::
|
|
187
|
+
Codebase::CorpusTasks, Codebase::Composing, Codebase::Publishing,
|
|
188
|
+
Codebase::Promotion].freeze
|
|
187
189
|
private_constant :FAMILIES
|
|
188
190
|
|
|
189
191
|
# The facts of a promotion or a watch, naming the operation so policies can tell them apart.
|
|
@@ -57,7 +57,7 @@ module Hecks
|
|
|
57
57
|
provider = membership_provider
|
|
58
58
|
verbs, gate, people = check_unadministered(provider)
|
|
59
59
|
|
|
60
|
-
admitted = people.none? { |person| person
|
|
60
|
+
admitted = people.none? { |person| person[:email] == email }
|
|
61
61
|
admit(provider, verbs.fetch(:admit), email, name || email[/\A[^@]+/]) if admitted
|
|
62
62
|
grant(provider, verbs.fetch(:grant), email, role || gate)
|
|
63
63
|
Result.new(email: email, role: role || gate, admitted: admitted)
|
|
@@ -103,10 +103,10 @@ module Hecks
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def refuse_if_administered!(people, roles)
|
|
106
|
-
held = people.select { |person| roles.map(&:downcase).include?(person
|
|
106
|
+
held = people.select { |person| roles.map(&:downcase).include?(person[:role].to_s.downcase) }
|
|
107
107
|
return if held.empty?
|
|
108
108
|
|
|
109
|
-
who = held.map { |person| person
|
|
109
|
+
who = held.map { |person| person[:email] }.join(", ")
|
|
110
110
|
raise Runtime::NotFound, "an administrator already exists (#{who}); grant roles through the " \
|
|
111
111
|
"domain's own #{roles.first} access instead"
|
|
112
112
|
end
|
|
@@ -117,32 +117,6 @@ module Hecks
|
|
|
117
117
|
def apply_compaction(held, kind)
|
|
118
118
|
Compaction.new(plain(held[:domain]), aggregates: names(held[:aggregates]), kind: kind).apply!.join("\n")
|
|
119
119
|
end
|
|
120
|
-
|
|
121
|
-
# Writes `translations/<edge>.approval`; the host reads it at its next boot.
|
|
122
|
-
def apply_approve_translation(held)
|
|
123
|
-
domain = HeldDomain.open(plain(held[:domain]))
|
|
124
|
-
finding = domain.reading { |lineage| audited_edge(domain, lineage) }
|
|
125
|
-
document = approval_document(domain, finding, held)
|
|
126
|
-
path = Translation::ApprovalFile.write!(domain.directory, finding.edge, document)
|
|
127
|
-
"approval of edge #{document["edge"]} written to #{path} (digest #{document["edge_digest"][0, 12]}…, " \
|
|
128
|
-
"by #{document["approved_by"]}). Commit it with the edge; the host applies it at its next boot."
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def approval_document(domain, finding, held)
|
|
132
|
-
Translation::ApprovalFile.build(
|
|
133
|
-
edge: finding.edge, approved_by: Git.new.identity(chdir: domain.directory),
|
|
134
|
-
approved_at: Time.now.utc.iso8601, rehearsal: rehearsal_block(held)
|
|
135
|
-
)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def audited_edge(domain, lineage)
|
|
139
|
-
eras = domain.held_eras(lineage)
|
|
140
|
-
finding = eras.empty? ? nil : EdgeAudit.call(domain, lineage, eras)
|
|
141
|
-
raise Runtime::WiringError, "#{domain.bluebook.name} has no edge to approve" unless finding
|
|
142
|
-
raise Runtime::WiringError, "#{finding.report}\nAUDIT REFUSED." unless finding.ok
|
|
143
|
-
|
|
144
|
-
finding
|
|
145
|
-
end
|
|
146
120
|
end
|
|
147
121
|
end
|
|
148
122
|
end
|