hecks 3.14.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.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/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/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/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/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/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.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/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/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.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 +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 +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 +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 -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 +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 +82 -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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "rack"
|
|
3
|
+
require_relative "../composer"
|
|
4
|
+
require_relative "domains"
|
|
5
|
+
require_relative "workbench"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Composer
|
|
9
|
+
# The Composer's driving side, on the hecksagon side: a Rack app that serves the editor page
|
|
10
|
+
# and the JSON routes it speaks to, over one workspace directory at a time. It binds no
|
|
11
|
+
# business meaning. The routes are the tables below: the page and files, the editor's own
|
|
12
|
+
# (save, remove, parse, check, ask), the domain list, and the lower panels', which {Panels}
|
|
13
|
+
# answers.
|
|
14
|
+
class Web
|
|
15
|
+
PAGE = File.expand_path("page.html", __dir__).freeze
|
|
16
|
+
PREFIX = "/composer".freeze
|
|
17
|
+
|
|
18
|
+
# @param workspace [String] the domain directory the editor opens
|
|
19
|
+
# @param agents [Runtime::Registry, nil] see {Workbench#initialize}
|
|
20
|
+
# @param root [String] the checkout whose domains the picker lists
|
|
21
|
+
# @return [Web] a Rack app
|
|
22
|
+
def self.app(workspace:, agents: nil, root: Parse::ROOT) = new(workspace: workspace, agents: agents, root: root)
|
|
23
|
+
|
|
24
|
+
# @param workspace [String] the domain directory the editor opens
|
|
25
|
+
# @param agents [Runtime::Registry, nil] see {Workbench#initialize}
|
|
26
|
+
# @param root [String] the checkout whose domains the picker lists
|
|
27
|
+
def initialize(workspace:, agents: nil, root: Parse::ROOT)
|
|
28
|
+
@agents = agents
|
|
29
|
+
@root = File.expand_path(root)
|
|
30
|
+
@bench = Workbench.new(workspace: workspace, agents: agents)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param env [Hash] the Rack environment
|
|
34
|
+
# @return [Array] the Rack response triple
|
|
35
|
+
def call(env)
|
|
36
|
+
request = Rack::Request.new(env)
|
|
37
|
+
route(request)
|
|
38
|
+
rescue Workspace::OutsideWorkspace, KeyError, JSON::ParserError => e
|
|
39
|
+
json(400, error: e.message)
|
|
40
|
+
rescue Domains::Unknown => e
|
|
41
|
+
json(404, error: e.message)
|
|
42
|
+
rescue Parse::ParserMissing => e
|
|
43
|
+
json(503, error: e.message)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Routes that answer from the workspace alone, those that read a JSON body first, and those
|
|
49
|
+
# the lower panels ask (answered by {Panels}).
|
|
50
|
+
READS = { "GET " => :page, "GET #{PREFIX}" => :page, "GET #{PREFIX}/files" => :listing,
|
|
51
|
+
"GET #{PREFIX}/draft" => :conversation, "GET #{PREFIX}/domains" => :domains }.freeze
|
|
52
|
+
WRITES = { "PUT #{PREFIX}/file" => :save, "DELETE #{PREFIX}/file" => :remove,
|
|
53
|
+
"POST #{PREFIX}/parse" => :parse, "POST #{PREFIX}/check" => :check,
|
|
54
|
+
"POST #{PREFIX}/ask" => :ask, "POST #{PREFIX}/open" => :open_domain }.freeze
|
|
55
|
+
PANEL_ROUTES = { "POST #{PREFIX}/live/commands" => :commands, "POST #{PREFIX}/live/dispatch" => :dispatch,
|
|
56
|
+
"POST #{PREFIX}/live/records" => :records, "POST #{PREFIX}/behaviors" => :behaviors,
|
|
57
|
+
"POST #{PREFIX}/timeline" => :timeline, "POST #{PREFIX}/board/edit" => :board_edit,
|
|
58
|
+
"GET #{PREFIX}/catalog" => :catalog,
|
|
59
|
+
"POST #{PREFIX}/help" => :help, "POST #{PREFIX}/run" => :run,
|
|
60
|
+
"GET #{PREFIX}/eras" => :eras, "POST #{PREFIX}/job" => :start_job,
|
|
61
|
+
"GET #{PREFIX}/job" => :job_state, "DELETE #{PREFIX}/job" => :cancel_job }.freeze
|
|
62
|
+
private_constant :READS, :WRITES, :PANEL_ROUTES
|
|
63
|
+
|
|
64
|
+
def route(request)
|
|
65
|
+
key = "#{request.request_method} #{request.path_info.chomp("/")}"
|
|
66
|
+
answer_for(key, request) || json(404, error: "no such route")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The response the key's table gives, or nil when no table has the key.
|
|
70
|
+
def answer_for(key, request)
|
|
71
|
+
return send(READS[key]) if READS.key?(key)
|
|
72
|
+
return send(WRITES[key], body(request)) if WRITES.key?(key)
|
|
73
|
+
|
|
74
|
+
json(200, @bench.panels.public_send(PANEL_ROUTES[key], body(request))) if PANEL_ROUTES.key?(key)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def workspace = @bench.workspace
|
|
78
|
+
|
|
79
|
+
def listing = json(200, files: files)
|
|
80
|
+
|
|
81
|
+
def conversation = json(200, draft: @bench.session.summary(@bench.slug))
|
|
82
|
+
|
|
83
|
+
# The known domains, and the directory the editor has open now.
|
|
84
|
+
def domains = json(200, domains: Domains.known(root: @root), current: File.basename(workspace))
|
|
85
|
+
|
|
86
|
+
# Opens a known domain as a scratch copy, replacing everything kept for the old workspace.
|
|
87
|
+
def open_domain(body)
|
|
88
|
+
id = body.fetch("id")
|
|
89
|
+
opened = Domains.open(root: @root, id: id)
|
|
90
|
+
@bench.close
|
|
91
|
+
@bench = Workbench.new(workspace: opened, agents: @agents)
|
|
92
|
+
json(200, opened: id, current: File.basename(opened))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ask(body)
|
|
96
|
+
json(200, @bench.assistant.ask(body.fetch("prompt")))
|
|
97
|
+
rescue Agent::Unavailable => e
|
|
98
|
+
json(503, error: e.message)
|
|
99
|
+
rescue Agent::ValidationError => e
|
|
100
|
+
json(502, error: e.message)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def page = [200, { "content-type" => "text/html; charset=utf-8" }, [File.read(PAGE)]]
|
|
104
|
+
|
|
105
|
+
def files
|
|
106
|
+
Workspace.files(workspace).map { |path| { path: path, text: File.read(File.join(workspace, path)) } }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def save(body)
|
|
110
|
+
path = domain_path(body) or return refused(body)
|
|
111
|
+
|
|
112
|
+
target = File.join(workspace, path)
|
|
113
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
114
|
+
File.write(target, body.fetch("text"))
|
|
115
|
+
@bench.session.record(path: path, text: body.fetch("text"))
|
|
116
|
+
json(200, saved: path)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def remove(body)
|
|
120
|
+
path = domain_path(body) or return refused(body)
|
|
121
|
+
|
|
122
|
+
FileUtils.rm_f(File.join(workspace, path))
|
|
123
|
+
json(200, removed: path)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The request's path when it names a domain file inside the workspace, else nil.
|
|
127
|
+
def domain_path(body)
|
|
128
|
+
path = body.fetch("path")
|
|
129
|
+
Workspace.refuse_escapes!([path])
|
|
130
|
+
path if Workspace::EXTENSIONS.include?(File.extname(path).delete_prefix("."))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def refused(body) = json(400, error: "#{body["path"].inspect} is not a domain file")
|
|
134
|
+
|
|
135
|
+
def parse(body)
|
|
136
|
+
refusal = Workspace.stage(dir: workspace, buffers: buffers(body)) { |stage| Parse.call(stage) }
|
|
137
|
+
json(200, diagnostic: refusal)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def check(body)
|
|
141
|
+
json(200, Check.call(dir: workspace, buffers: buffers(body), chapter_name: body["chapter_name"]))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def buffers(body) = body.fetch("buffers", {})
|
|
145
|
+
|
|
146
|
+
def body(request)
|
|
147
|
+
raw = request.body&.read.to_s
|
|
148
|
+
raw.empty? ? {} : JSON.parse(raw)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def json(status, payload)
|
|
152
|
+
[status, { "content-type" => "application/json" }, [JSON.generate(payload)]]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require_relative "agent"
|
|
2
|
+
require_relative "assistant"
|
|
3
|
+
require_relative "panels"
|
|
4
|
+
require_relative "session"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Composer
|
|
8
|
+
# Everything the editor keeps for one workspace, on the hecksagon side: the draft bookkeeping,
|
|
9
|
+
# the agent that edits it and the panels beneath the editor. Opening another domain builds a
|
|
10
|
+
# new workbench, so nothing of the old workspace carries over.
|
|
11
|
+
class Workbench
|
|
12
|
+
# @return [String] the domain directory this workbench is for
|
|
13
|
+
attr_reader :workspace
|
|
14
|
+
|
|
15
|
+
# @param workspace [String] the domain directory
|
|
16
|
+
# @param agents [Runtime::Registry, nil] a registry holding the `composer_agent` port and one
|
|
17
|
+
# adapter; the one `HECKS_COMPOSER_AGENT` names (`claude`, the default, or `fake`) when nil
|
|
18
|
+
def initialize(workspace:, agents: nil)
|
|
19
|
+
@workspace = File.expand_path(workspace)
|
|
20
|
+
@agents = agents
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] the workspace's directory name, which names its draft
|
|
24
|
+
def slug = File.basename(workspace)
|
|
25
|
+
|
|
26
|
+
# @return [Session] the draft bookkeeping, booted on first use
|
|
27
|
+
def session = @session ||= Session.new
|
|
28
|
+
|
|
29
|
+
# @return [Assistant] the agent loop over this workspace
|
|
30
|
+
def assistant = @assistant ||= Assistant.new(workspace: workspace, agents: agents, session: session)
|
|
31
|
+
|
|
32
|
+
# @return [Panels] what answers the lower panels
|
|
33
|
+
def panels = @panels ||= Panels.new(workspace: workspace, session: session)
|
|
34
|
+
|
|
35
|
+
# Releases what the panels booted.
|
|
36
|
+
# @return [void]
|
|
37
|
+
def close = @panels&.close
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def agents = @agents ||= default_agents
|
|
42
|
+
|
|
43
|
+
# The adapter `HECKS_COMPOSER_AGENT` names: the real `claude`, or a scripted `fake`.
|
|
44
|
+
def default_agents
|
|
45
|
+
name = ENV.fetch("HECKS_COMPOSER_AGENT", "claude") == "fake" ? "fake_agent" : "claude_composer"
|
|
46
|
+
require_relative "adapters/#{name}"
|
|
47
|
+
Agent.registry_with(File.join(Agent::ADAPTERS, "#{name}.adapter"))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Composer
|
|
6
|
+
# A workspace is a directory of domain files; an editor holds unsaved text for some of them.
|
|
7
|
+
# Staging lays that text over a copy of the files on disk so a check sees what is on screen
|
|
8
|
+
# and the directory itself stays untouched.
|
|
9
|
+
module Workspace
|
|
10
|
+
# The files a domain is made of; anything else in the directory is data, not declaration.
|
|
11
|
+
EXTENSIONS = %w[bluebook hecksagon world adapter port behaviors].freeze
|
|
12
|
+
|
|
13
|
+
# Raised when an unsaved buffer names a path outside the workspace.
|
|
14
|
+
class OutsideWorkspace < ArgumentError; end
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Copies the workspace's declaration files into a temporary directory, lays the buffers over
|
|
19
|
+
# them, and yields that directory.
|
|
20
|
+
#
|
|
21
|
+
# @param dir [String] the workspace directory
|
|
22
|
+
# @param buffers [Hash{String => String}] unsaved text by path relative to `dir`; a path
|
|
23
|
+
# with no file on disk yet is created
|
|
24
|
+
# @yieldparam stage [String] the temporary directory holding the staged files
|
|
25
|
+
# @return [Object] what the block returned
|
|
26
|
+
# @raise [OutsideWorkspace] if a buffer's path is absolute or climbs out of the workspace
|
|
27
|
+
def stage(dir:, buffers: {})
|
|
28
|
+
stage = materialize(dir: dir, buffers: buffers)
|
|
29
|
+
yield stage
|
|
30
|
+
ensure
|
|
31
|
+
FileUtils.rm_rf(stage) if stage
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Stages as {stage} does, but leaves the directory for the caller to remove, so a booted
|
|
35
|
+
# domain can keep reading its files after this returns.
|
|
36
|
+
#
|
|
37
|
+
# @param dir [String] the workspace directory
|
|
38
|
+
# @param buffers [Hash{String => String}] unsaved text by path relative to `dir`
|
|
39
|
+
# @return [String] the temporary directory holding the staged files
|
|
40
|
+
# @raise [OutsideWorkspace] if a buffer's path is absolute or climbs out of the workspace
|
|
41
|
+
def materialize(dir:, buffers: {})
|
|
42
|
+
refuse_escapes!(buffers.keys)
|
|
43
|
+
stage = Dir.mktmpdir("hecks-composer")
|
|
44
|
+
copy_declarations(dir, stage)
|
|
45
|
+
buffers.each { |path, text| write(File.join(stage, path), text) }
|
|
46
|
+
stage
|
|
47
|
+
rescue StandardError
|
|
48
|
+
FileUtils.rm_rf(stage) if stage
|
|
49
|
+
raise
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param stage [String] a staged directory
|
|
53
|
+
# @return [Array<String>] the declaration files in it, relative and sorted
|
|
54
|
+
def files(stage)
|
|
55
|
+
Dir.glob("**/*.{#{EXTENSIONS.join(",")}}", base: stage).sort
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @api private
|
|
59
|
+
def copy_declarations(dir, stage)
|
|
60
|
+
files(dir).each do |relative|
|
|
61
|
+
FileUtils.mkdir_p(File.dirname(File.join(stage, relative)))
|
|
62
|
+
FileUtils.cp(File.join(dir, relative), File.join(stage, relative))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @api private
|
|
67
|
+
def write(path, text)
|
|
68
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
69
|
+
File.write(path, text)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
def refuse_escapes!(paths)
|
|
74
|
+
offender = paths.find { |path| path.start_with?("/") || path.split("/").include?("..") }
|
|
75
|
+
return unless offender
|
|
76
|
+
|
|
77
|
+
raise OutsideWorkspace, "#{offender.inspect} is not inside the workspace"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require_relative "composer/plain"
|
|
2
|
+
require_relative "composer/workspace"
|
|
3
|
+
require_relative "composer/parse"
|
|
4
|
+
require_relative "composer/outline"
|
|
5
|
+
require_relative "composer/layout"
|
|
6
|
+
require_relative "composer/behavior_run"
|
|
7
|
+
require_relative "composer/live"
|
|
8
|
+
require_relative "composer/toolbox"
|
|
9
|
+
require_relative "composer/jobs"
|
|
10
|
+
require_relative "composer/check"
|
|
11
|
+
|
|
12
|
+
module Hecks
|
|
13
|
+
# The Composer's outside, on the hecksagon side: staging a workspace, judging its text and
|
|
14
|
+
# projecting what it declares. The Composer chapter's bluebook is in `composer/bluebook/`.
|
|
15
|
+
module Composer
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Corpus
|
|
3
|
+
# Where `.adapter` files live, for the parser parity check. Extended onto `Corpus`.
|
|
4
|
+
module Adapters
|
|
5
|
+
# Directories that hold copies or build output, never a source `.adapter`.
|
|
6
|
+
EXCLUDED_DIRS = %w[vendor node_modules target tmp .git .aws-sam].freeze
|
|
7
|
+
|
|
8
|
+
ADAPTER_GLOBS = %w[lib/**/*.adapter examples/**/*.adapter spec/fixtures/**/*.adapter].freeze
|
|
9
|
+
|
|
10
|
+
# Every `.adapter` file the repo carries.
|
|
11
|
+
#
|
|
12
|
+
# @param root [String] repository root to search under
|
|
13
|
+
# @return [Array<String>] absolute paths, sorted, none under a vendored, `node_modules`,
|
|
14
|
+
# `target` or scratch directory
|
|
15
|
+
def adapter_files(root: Corpus::ROOT)
|
|
16
|
+
ADAPTER_GLOBS.flat_map { |glob| Dir.glob(File.join(root, glob)) }.uniq.sort.reject do |path|
|
|
17
|
+
path.delete_prefix("#{root}/").split("/").intersect?(EXCLUDED_DIRS)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/hecks/corpus/rust.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Hecks
|
|
|
3
3
|
# Every Rust-facing list reads `rust/Cargo.toml`'s `[features]` as its
|
|
4
4
|
# one source, rather than each consumer typing it out by hand.
|
|
5
5
|
GENERATED_DIR = "rust/src/generated".freeze
|
|
6
|
-
RUST_DOMAIN_KINDS = %i[example stress fixture].freeze
|
|
6
|
+
RUST_DOMAIN_KINDS = %i[example stress semantics fixture].freeze
|
|
7
7
|
|
|
8
8
|
RustDomain = Struct.new(:feature, :dir, :kind)
|
|
9
9
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Corpus
|
|
3
|
+
# Where `.world` files live, for the parser parity check. Extended onto `Corpus`.
|
|
4
|
+
module Worlds
|
|
5
|
+
WORLD_GLOBS = %w[lib/**/*.world examples/**/*.world qa/**/*.world spec/fixtures/**/*.world].freeze
|
|
6
|
+
|
|
7
|
+
# Every `.world` file the repo carries.
|
|
8
|
+
#
|
|
9
|
+
# @param root [String] repository root to search under
|
|
10
|
+
# @return [Array<String>] absolute paths, sorted, none under a vendored, `node_modules`,
|
|
11
|
+
# `target` or scratch directory
|
|
12
|
+
def world_files(root: Corpus::ROOT)
|
|
13
|
+
WORLD_GLOBS.flat_map { |glob| Dir.glob(File.join(root, glob)) }.uniq.sort.reject do |path|
|
|
14
|
+
path.delete_prefix("#{root}/").split("/").intersect?(Adapters::EXCLUDED_DIRS)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/hecks/corpus.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
require_relative "fuzzing/target_capabilities"
|
|
2
|
+
require_relative "corpus/adapters"
|
|
2
3
|
require_relative "corpus/routes"
|
|
3
4
|
require_relative "corpus/rust"
|
|
4
5
|
require_relative "corpus/sources"
|
|
6
|
+
require_relative "corpus/worlds"
|
|
5
7
|
|
|
6
8
|
module Hecks
|
|
7
9
|
# The corpus: every place in this repo that holds a real domain, named once.
|
|
@@ -36,6 +38,7 @@ module Hecks
|
|
|
36
38
|
deploy: "lib/hecks/deploy/bluebook/*.bluebook",
|
|
37
39
|
site: "lib/hecks/site/bluebook/*.bluebook",
|
|
38
40
|
tickets: "lib/hecks/tickets/bluebook/*.bluebook",
|
|
41
|
+
composer: "lib/hecks/composer/bluebook/*.bluebook",
|
|
39
42
|
tenancy: "lib/hecks/tenancy/bluebook/*.bluebook",
|
|
40
43
|
sme: "lib/hecks/sme/bluebook/*.bluebook",
|
|
41
44
|
fixture: "spec/fixtures/**/*.bluebook"
|
|
@@ -43,8 +46,10 @@ module Hecks
|
|
|
43
46
|
|
|
44
47
|
KINDS = (DIRECTORY_KINDS.keys + FILE_KINDS.keys).freeze
|
|
45
48
|
|
|
49
|
+
extend Adapters
|
|
46
50
|
extend Rust
|
|
47
51
|
extend Sources
|
|
52
|
+
extend Worlds
|
|
48
53
|
|
|
49
54
|
module_function
|
|
50
55
|
|
|
@@ -28,11 +28,10 @@ module Hecks
|
|
|
28
28
|
# @param page [Hash] optionally `status:`, the submitted `values:`, the `error:` to show and
|
|
29
29
|
# the `prefill:` values
|
|
30
30
|
def command_form(domain, aggregate, command, action, **page)
|
|
31
|
-
html(
|
|
31
|
+
html(Humanize.phrase(command.hecks_name),
|
|
32
32
|
CommandFormRenderer.render(registry: @registry, domain: domain, aggregate: aggregate, command: command,
|
|
33
33
|
action: action, **page.slice(:values, :error, :prefill)),
|
|
34
|
-
breadcrumbs:
|
|
35
|
-
[command.hecks_name, nil]],
|
|
34
|
+
breadcrumbs: verb_breadcrumbs(domain, aggregate, command),
|
|
36
35
|
status: page.fetch(:status, 200))
|
|
37
36
|
end
|
|
38
37
|
|
|
@@ -25,11 +25,10 @@ module Hecks
|
|
|
25
25
|
def query_html(domain, aggregate, query, asked)
|
|
26
26
|
action = "/#{domain}/#{aggregate.hecks_name}/#{query.hecks_name}.html"
|
|
27
27
|
results, error = asked.empty? ? [nil, nil] : run_query(domain, aggregate, query, asked)
|
|
28
|
-
html(
|
|
28
|
+
html(Humanize.phrase(query.hecks_name),
|
|
29
29
|
QueryFormRenderer.render(registry: @registry, domain: domain, aggregate: aggregate, query: query,
|
|
30
30
|
action: action, params: asked, results: results, error: error),
|
|
31
|
-
breadcrumbs:
|
|
32
|
-
[query.hecks_name, nil]],
|
|
31
|
+
breadcrumbs: verb_breadcrumbs(domain, aggregate, query),
|
|
33
32
|
status: error ? 422 : 200)
|
|
34
33
|
end
|
|
35
34
|
|
|
@@ -12,9 +12,9 @@ module Hecks
|
|
|
12
12
|
return respond(405, "text/plain", "GET only") unless request.get?
|
|
13
13
|
return aggregate_json(chapter, aggregate) unless format == "html"
|
|
14
14
|
|
|
15
|
-
html(
|
|
15
|
+
html(Naming.plural(Humanize.phrase(aggregate.hecks_name)),
|
|
16
16
|
RecordRenderer.index(registry: @registry, domain: chapter.name, aggregate: aggregate),
|
|
17
|
-
breadcrumbs: [[chapter.name, "/"], [aggregate.hecks_name, nil]])
|
|
17
|
+
breadcrumbs: [[chapter.name, "/"], [Humanize.phrase(aggregate.hecks_name), nil]])
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def aggregate_json(chapter, aggregate)
|
|
@@ -52,10 +52,22 @@ module Hecks
|
|
|
52
52
|
# `id:` last, as in `aggregate_route`.
|
|
53
53
|
return json(200, instance.state.merge(id: instance.id)) if format != "html"
|
|
54
54
|
|
|
55
|
-
html("#{
|
|
55
|
+
html("#{Humanize.phrase(aggregate.hecks_name)} #{id}",
|
|
56
56
|
RecordRenderer.show(registry: @registry, domain: domain, aggregate: aggregate, id: id),
|
|
57
|
-
breadcrumbs:
|
|
58
|
-
|
|
57
|
+
breadcrumbs: aggregate_crumbs(domain, aggregate) + [[id, nil]])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Array<Array(String, String)>] the domain and aggregate crumbs every page
|
|
61
|
+
# under an aggregate starts with, the aggregate named in words
|
|
62
|
+
def aggregate_crumbs(domain, aggregate)
|
|
63
|
+
[[domain, "/"], [Humanize.phrase(aggregate.hecks_name), "/#{domain}/#{aggregate.hecks_name}.html"]]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param verb [Bluebook::Command, Bluebook::Query] the command or query the page is for
|
|
67
|
+
# @return [Array<Array(String, String), Array(String, nil)>] the aggregate crumbs, then
|
|
68
|
+
# the verb named in words as the current page
|
|
69
|
+
def verb_breadcrumbs(domain, aggregate, verb)
|
|
70
|
+
aggregate_crumbs(domain, aggregate) + [[Humanize.phrase(verb.hecks_name), nil]]
|
|
59
71
|
end
|
|
60
72
|
|
|
61
73
|
def find_aggregate(chapter, name)
|
|
@@ -43,7 +43,7 @@ module Hecks
|
|
|
43
43
|
fields_html = fields_markup(registry, domain, all_fields, form)
|
|
44
44
|
action = form.fetch(:action)
|
|
45
45
|
cancel = "/#{domain}/#{aggregate.hecks_name}"
|
|
46
|
-
[header(
|
|
46
|
+
[header(aggregate, command), error_banner(form[:error]),
|
|
47
47
|
form_markup(action, fields_html, command, cancel).chomp,
|
|
48
48
|
inspect_panel(domain, aggregate, command, action, all_fields)].join("\n").concat("\n")
|
|
49
49
|
end
|
|
@@ -64,7 +64,7 @@ module Hecks
|
|
|
64
64
|
<form method="post" action="#{Escape.attr(action)}" novalidate>
|
|
65
65
|
#{fields_html}
|
|
66
66
|
<div class="actions">
|
|
67
|
-
<button type="submit">#{Escape.html(command.hecks_name)}</button>
|
|
67
|
+
<button type="submit">#{Escape.html(Humanize.phrase(command.hecks_name))}</button>
|
|
68
68
|
<a class="button secondary" href="#{Escape.attr(cancel)}">Cancel</a>
|
|
69
69
|
</div>
|
|
70
70
|
</form>
|
|
@@ -72,33 +72,58 @@ module Hecks
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# Builds the `to` field a non-creating command uses to name the record it acts on,
|
|
75
|
-
# labelled with the aggregate's
|
|
75
|
+
# labelled with the aggregate's name and helped by what identifies one ("the order's
|
|
76
|
+
# name").
|
|
76
77
|
#
|
|
77
78
|
# @param aggregate [Bluebook::Aggregate] the aggregate whose records the picker offers
|
|
78
79
|
# @return [Forms::Field] a `:reference` field at path `"to"` targeting `aggregate`
|
|
79
80
|
def self.identity_field(aggregate)
|
|
80
|
-
|
|
81
|
+
noun = Humanize.noun(aggregate.hecks_name)
|
|
82
|
+
Field.new(path: "to", label: Humanize.phrase(aggregate.hecks_name),
|
|
81
83
|
kind: :reference, html_type: "text", target_aggregate: aggregate,
|
|
82
|
-
help: "
|
|
84
|
+
help: "Which #{noun} this is for, by the #{noun}'s #{identity_words(aggregate)}.")
|
|
83
85
|
end
|
|
84
86
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
+
# Names what identifies a record in words: `["name.value"]` -> "name", a composite
|
|
88
|
+
# `["bank", "number"]` -> "bank and number". A dotted path names its first segment,
|
|
89
|
+
# since the hops after it reach inside a value object.
|
|
90
|
+
#
|
|
91
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate whose identity is named
|
|
92
|
+
# @return [String] the identity's field names as lowercase words; "id" when it has none
|
|
93
|
+
def self.identity_words(aggregate)
|
|
94
|
+
names = aggregate.identity_paths.map { |path| Humanize.noun(path.to_s.split(".").first) }.uniq
|
|
95
|
+
names.empty? ? "id" : Naming.to_sentence_list(names)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Renders the form's heading: the command's name in words, its role and creates
|
|
99
|
+
# badges, its goal, and its preconditions callout. The code name stays in the inspect
|
|
100
|
+
# panel.
|
|
87
101
|
#
|
|
88
|
-
# @param domain [String] name of the domain (chapter) the aggregate belongs to
|
|
89
102
|
# @param aggregate [Bluebook::Aggregate] the aggregate that declares the command
|
|
90
103
|
# @param command [Bluebook::Command] the command being described
|
|
91
104
|
# @return [String] HTML for the heading block
|
|
92
|
-
def self.header(
|
|
105
|
+
def self.header(aggregate, command)
|
|
93
106
|
<<~HTML
|
|
94
|
-
<h1>#{Escape.html(
|
|
95
|
-
#{
|
|
96
|
-
#{%(<span class="badge">
|
|
107
|
+
<h1>#{Escape.html(Humanize.phrase(command.hecks_name))}</h1>
|
|
108
|
+
#{role_badge(command)}
|
|
109
|
+
#{%(<span class="badge">Creates a new #{Escape.html(Humanize.noun(aggregate.hecks_name))}</span>) if command.creates?}
|
|
97
110
|
#{%(<p class="goal">#{Escape.html(command.goal)}</p>) if command.goal}
|
|
98
111
|
#{givens_callout(command)}
|
|
99
112
|
HTML
|
|
100
113
|
end
|
|
101
114
|
|
|
115
|
+
# Names who the command is for. The preview dispatches with no caller bound, so the role
|
|
116
|
+
# is shown but not checked, and the badge says so.
|
|
117
|
+
#
|
|
118
|
+
# @param command [Bluebook::Command] the command whose declared role is shown
|
|
119
|
+
# @return [String] HTML for the badge, or `""` when the command declares no role
|
|
120
|
+
def self.role_badge(command)
|
|
121
|
+
return "" unless command.role
|
|
122
|
+
|
|
123
|
+
title = "The bluebook declares who does this. This preview signs nobody in, so it does not check."
|
|
124
|
+
%(<span class="badge role" title="#{title}">Done by: #{Escape.html(command.role)} (not checked in this preview)</span>)
|
|
125
|
+
end
|
|
126
|
+
|
|
102
127
|
# Renders the command's `given` preconditions as a callout, so a caller sees what
|
|
103
128
|
# would refuse the submission before making it.
|
|
104
129
|
#
|
|
@@ -108,7 +133,8 @@ module Hecks
|
|
|
108
133
|
return "" if command.givens.empty?
|
|
109
134
|
|
|
110
135
|
items = command.givens.map { |given| "<li>#{Escape.html(given.description)}</li>" }
|
|
111
|
-
%(<div class="callout"><strong>
|
|
136
|
+
%(<div class="callout"><strong>Checked first.</strong> The action is refused if any of these fail:) \
|
|
137
|
+
"<ul>#{items.join}</ul></div>"
|
|
112
138
|
end
|
|
113
139
|
|
|
114
140
|
# Renders a refused submission as an alert banner carrying the refusal's class name
|
|
@@ -121,7 +147,7 @@ module Hecks
|
|
|
121
147
|
|
|
122
148
|
<<~HTML
|
|
123
149
|
<div class="error-banner" role="alert">
|
|
124
|
-
<p><strong
|
|
150
|
+
<p><strong>Refused (#{Escape.html(error.class.name.split("::").last)}):</strong> #{Escape.html(error.message)}</p>
|
|
125
151
|
</div>
|
|
126
152
|
HTML
|
|
127
153
|
end
|
|
@@ -174,9 +200,9 @@ module Hecks
|
|
|
174
200
|
def self.inspect_head(verb, command, curl)
|
|
175
201
|
<<~HTML
|
|
176
202
|
<details class="inspect">
|
|
177
|
-
<summary>Inspect
|
|
203
|
+
<summary>Inspect: #{Escape.html(verb)}</summary>
|
|
178
204
|
<p>Emits: #{emitted_events(command)}</p>
|
|
179
|
-
<p>
|
|
205
|
+
<p>The same request as <code>curl</code>. Every field is sent by its dotted path (<code>#{Escape.html("name.path")}</code>), as a form or as JSON:</p>
|
|
180
206
|
<div class="link-row"><code id="curl-snippet">#{Escape.html(curl)}</code><button type="button" class="copy" data-copy="#curl-snippet">copy</button></div>
|
|
181
207
|
HTML
|
|
182
208
|
end
|
|
@@ -65,7 +65,7 @@ module Hecks
|
|
|
65
65
|
%(<option value="#{Escape.attr(option_value)}"#{" selected" if option_value.to_s == selected.to_s}>) \
|
|
66
66
|
"#{Escape.html(option_label)}</option>"
|
|
67
67
|
end
|
|
68
|
-
blank = field.optional? ? %(<option value=""
|
|
68
|
+
blank = field.optional? ? %(<option value="">(none)</option>) : ""
|
|
69
69
|
select_tag(field, "#{blank}#{options.join}")
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -80,7 +80,7 @@ module Hecks
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def self.reference_blank(field, value)
|
|
83
|
-
return %(<option value=""
|
|
83
|
+
return %(<option value="">(none)</option>) if field.optional?
|
|
84
84
|
|
|
85
85
|
%(<option value="" disabled#{" selected" unless value}>choose one…</option>)
|
|
86
86
|
end
|
|
@@ -51,7 +51,7 @@ module Hecks
|
|
|
51
51
|
|
|
52
52
|
def self.list_tail(field, item)
|
|
53
53
|
<<~HTML
|
|
54
|
-
<span class="help" id="#{dom_id(field.path)}-help">One #{Escape.html(item.label.downcase)} per line#{"
|
|
54
|
+
<span class="help" id="#{dom_id(field.path)}-help">One #{Escape.html(item.label.downcase)} per line#{", each line one JSON object" unless item.leaf?}.</span>
|
|
55
55
|
</div>
|
|
56
56
|
HTML
|
|
57
57
|
end
|