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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "json"
|
|
3
|
+
require_relative "live"
|
|
4
|
+
require_relative "toolbox"
|
|
5
|
+
require_relative "behavior_run"
|
|
6
|
+
require_relative "board_edit"
|
|
7
|
+
require_relative "jobs"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Composer
|
|
11
|
+
# What the editor's lower panels ask of the server, on the hecksagon side: the Console and
|
|
12
|
+
# Records panels (a booted copy of the workspace), the Behaviors panel, Hecks's own commands for
|
|
13
|
+
# the palette and Eras, and the one background job the Fuzz panel polls. Every method takes the
|
|
14
|
+
# request's JSON body as a Hash and answers a Hash, so the router stays a table.
|
|
15
|
+
#
|
|
16
|
+
# The booted copy is kept between requests and rebuilt when the files or unsaved text change, so
|
|
17
|
+
# the records a Console command made survive until the text does.
|
|
18
|
+
class Panels
|
|
19
|
+
# Replaced, in a command's argument values, by the workspace directory.
|
|
20
|
+
WORKSPACE_TOKEN = "$WORKSPACE".freeze
|
|
21
|
+
|
|
22
|
+
# @param workspace [String] the domain directory the editor opens
|
|
23
|
+
# @param session [Session] the editor's in-memory draft bookkeeping, which keeps the timeline
|
|
24
|
+
def initialize(workspace:, session:)
|
|
25
|
+
@workspace = workspace
|
|
26
|
+
@session = session
|
|
27
|
+
@lock = Mutex.new
|
|
28
|
+
at_exit { close }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Removes the booted copy of the workspace, if one is held.
|
|
32
|
+
# @return [void]
|
|
33
|
+
def close
|
|
34
|
+
@live&.close
|
|
35
|
+
@live = @live_key = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param body [Hash] `buffers`
|
|
39
|
+
# @return [Hash] `chapter` and `commands` (see {Live#commands}), or `error`
|
|
40
|
+
def commands(body) = with_live(body) { |live| { chapter: live.chapter_name, commands: live.commands } }
|
|
41
|
+
|
|
42
|
+
# @param body [Hash] `buffers`, `verb`, `to` and `with`
|
|
43
|
+
# @return [Hash] see {Live#dispatch}, or `error` when the text does not boot
|
|
44
|
+
def dispatch(body)
|
|
45
|
+
with_live(body) { |live| live.dispatch(verb: body.fetch("verb"), to: body["to"], with: body.fetch("with", {})) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param body [Hash] `buffers`
|
|
49
|
+
# @return [Hash] `records` by aggregate and `history` of events, or `error`
|
|
50
|
+
def records(body) = with_live(body) { |live| { records: live.records, history: live.history } }
|
|
51
|
+
|
|
52
|
+
# @param body [Hash] `buffers`
|
|
53
|
+
# @return [Hash] see {BehaviorRun.call}
|
|
54
|
+
def behaviors(body) = BehaviorRun.call(dir: @workspace, buffers: body.fetch("buffers", {}))
|
|
55
|
+
|
|
56
|
+
# Carries out one board action on the workspace as it is on screen.
|
|
57
|
+
#
|
|
58
|
+
# @param body [Hash] `buffers`, `op` (see {BoardEdit::OPERATIONS}) and `args`
|
|
59
|
+
# @return [Hash] the patched file as `path`, `text` and `line`, or `error` when the action
|
|
60
|
+
# is refused
|
|
61
|
+
def board_edit(body)
|
|
62
|
+
Workspace.stage(dir: @workspace, buffers: body.fetch("buffers", {})) do |stage|
|
|
63
|
+
BoardEdit.call(stage: stage, action: body.fetch("op"), args: body.fetch("args", {}))
|
|
64
|
+
end
|
|
65
|
+
rescue BoardEdit::Refused => e
|
|
66
|
+
{ error: e.message }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @param body [Hash] `path`
|
|
70
|
+
# @return [Hash] `revisions`, newest first (see {Session#timeline})
|
|
71
|
+
def timeline(body) = { revisions: @session.timeline(body.fetch("path")) }
|
|
72
|
+
|
|
73
|
+
# @param _body [Hash] unused
|
|
74
|
+
# @return [Hash] `catalog` of Hecks's commands and queries (see {Toolbox#catalog})
|
|
75
|
+
def catalog(_body) = { catalog: toolbox.catalog }
|
|
76
|
+
|
|
77
|
+
# @param body [Hash] `kind` and `verb`
|
|
78
|
+
# @return [Hash] `help` text
|
|
79
|
+
def help(body) = { help: toolbox.help(kind: body.fetch("kind"), verb: body.fetch("verb")) }
|
|
80
|
+
|
|
81
|
+
# @param body [Hash] `kind`, `verb`, `args` and `confirm`
|
|
82
|
+
# @return [Hash] see {Toolbox#run}
|
|
83
|
+
def run(body) = toolbox.run(**request_of(body))
|
|
84
|
+
|
|
85
|
+
# @param _body [Hash] unused
|
|
86
|
+
# @return [Hash] the era queries over the workspace (see {Toolbox#eras})
|
|
87
|
+
def eras(_body) = toolbox.eras(@workspace)
|
|
88
|
+
|
|
89
|
+
# Starts the background job, unless one is running.
|
|
90
|
+
#
|
|
91
|
+
# @param body [Hash] `kind`, `verb`, `args` and `confirm`
|
|
92
|
+
# @return [Hash] `started`, and `error` when a job was already running
|
|
93
|
+
def start_job(body)
|
|
94
|
+
jobs.start(**request_of(body))
|
|
95
|
+
{ started: true }
|
|
96
|
+
rescue Jobs::Busy => e
|
|
97
|
+
{ started: false, error: e.message }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @param _body [Hash] unused
|
|
101
|
+
# @return [Hash] the job's state (see {Jobs#state})
|
|
102
|
+
def job_state(_body) = jobs.state
|
|
103
|
+
|
|
104
|
+
# @param _body [Hash] unused
|
|
105
|
+
# @return [Hash] `cancelled`, whether a job was running
|
|
106
|
+
def cancel_job(_body) = { cancelled: jobs.cancel }
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def with_live(body)
|
|
111
|
+
@lock.synchronize { yield live_for(body.fetch("buffers", {})) }
|
|
112
|
+
rescue StandardError, ScriptError => e
|
|
113
|
+
{ error: e.message.lines.first.to_s.chomp }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def live_for(buffers)
|
|
117
|
+
key = digest(buffers)
|
|
118
|
+
return @live if @live && key == @live_key
|
|
119
|
+
|
|
120
|
+
close
|
|
121
|
+
@live = Live.open(dir: @workspace, buffers: buffers)
|
|
122
|
+
@live_key = key
|
|
123
|
+
@live
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def digest(buffers)
|
|
127
|
+
disk = Workspace.files(@workspace).to_h { |path| [path, File.read(File.join(@workspace, path))] }
|
|
128
|
+
Digest::SHA256.hexdigest(JSON.generate([disk.sort, buffers.sort]))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def request_of(body)
|
|
132
|
+
args = body.fetch("args", {}).transform_values { |value| value.to_s.gsub(WORKSPACE_TOKEN, @workspace) }
|
|
133
|
+
{ kind: body.fetch("kind"), verb: body.fetch("verb"), args: args, confirm: body["confirm"] }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def toolbox = @toolbox ||= Toolbox.new
|
|
137
|
+
|
|
138
|
+
# A second toolbox, so a long job does not hold up the palette.
|
|
139
|
+
def jobs = @jobs ||= Jobs.new(runner: ->(**request) { Toolbox.new.run(**request) })
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Composer
|
|
5
|
+
# The fast stage of a check: the Rust parser's verdict on the staged bluebooks, which it gives
|
|
6
|
+
# in milliseconds, before anything is loaded. It reports the first refusal only, with the
|
|
7
|
+
# words that would have been accepted there.
|
|
8
|
+
module Parse
|
|
9
|
+
# The repository root, where the parser's build lands under `rust/parser/target/`.
|
|
10
|
+
ROOT = File.expand_path("../../..", __dir__).freeze
|
|
11
|
+
|
|
12
|
+
# Raised when no `hecks-parse` binary can be found.
|
|
13
|
+
class ParserMissing < StandardError; end
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Parses every bluebook in a staged workspace, one chapter at a time.
|
|
18
|
+
#
|
|
19
|
+
# @param stage [String] a staged directory (see {Workspace.stage})
|
|
20
|
+
# @return [Hash, nil] nil when everything parses; otherwise `{file:, line:, message:,
|
|
21
|
+
# expected:}` for the first refusal, with `file` relative to the stage
|
|
22
|
+
# @raise [ParserMissing] if `hecks-parse` is not built
|
|
23
|
+
def call(stage)
|
|
24
|
+
bluebooks(stage).each do |chapter, files|
|
|
25
|
+
refusal = refusal_for(stage, chapter, files)
|
|
26
|
+
return refusal if refusal
|
|
27
|
+
end
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param stage [String] a staged directory
|
|
32
|
+
# @return [Hash{String => Array<String>}] relative bluebook paths by the chapter their
|
|
33
|
+
# header names
|
|
34
|
+
def bluebooks(stage)
|
|
35
|
+
Workspace.files(stage).grep(/\.bluebook\z/).group_by { |relative| chapter_name(File.join(stage, relative)) }
|
|
36
|
+
.reject { |name, _| name.nil? }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param path [String] a bluebook file
|
|
40
|
+
# @return [String, nil] the name its `Hecks.bluebook "Name"` header declares
|
|
41
|
+
def chapter_name(path)
|
|
42
|
+
File.foreach(path) { |line| return Regexp.last_match(1) if line =~ /\A\s*Hecks\.bluebook\s+"([^"]+)"/ }
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @api private
|
|
47
|
+
def refusal_for(stage, chapter, files)
|
|
48
|
+
paths = files.map { |relative| File.join(stage, relative) }
|
|
49
|
+
_out, err, status = Open3.capture3(binary, "chapter", "--chapter", chapter, *paths)
|
|
50
|
+
return nil if status.success?
|
|
51
|
+
|
|
52
|
+
diagnostic(err.lines.first.to_s.chomp, stage)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Reads `<file>:<line>: <message>` with an optional `(expected one of: a, b, c)` suffix.
|
|
56
|
+
# @api private
|
|
57
|
+
def diagnostic(line, stage)
|
|
58
|
+
rest = line.delete_prefix("#{stage}/")
|
|
59
|
+
file, number, message = rest.split(":", 3)
|
|
60
|
+
return { file: nil, line: nil, message: line, expected: [] } unless number.to_s.strip.match?(/\A\d+\z/)
|
|
61
|
+
|
|
62
|
+
message = message.to_s.strip
|
|
63
|
+
expected = []
|
|
64
|
+
if (found = message.match(/\A(.*) \(expected one of: (.*)\)\z/))
|
|
65
|
+
message = found[1]
|
|
66
|
+
expected = found[2].split(", ")
|
|
67
|
+
end
|
|
68
|
+
{ file: file, line: number.to_i, message: message, expected: expected }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [String] the path to `hecks-parse`: `HECKS_PARSE_BIN`, then the release or debug
|
|
72
|
+
# build under `rust/parser/target/`
|
|
73
|
+
# @raise [ParserMissing] if none exists
|
|
74
|
+
def binary
|
|
75
|
+
candidates = [ENV.fetch("HECKS_PARSE_BIN", nil)] +
|
|
76
|
+
%w[release debug].map { |profile| File.join(ROOT, "rust/parser/target/#{profile}/hecks-parse") }
|
|
77
|
+
candidates.compact.find { |path| File.executable?(path) } or
|
|
78
|
+
raise ParserMissing, "hecks-parse is not built — run `cargo build --release " \
|
|
79
|
+
"--manifest-path rust/parser/Cargo.toml`"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Composer
|
|
3
|
+
# Reduces what a booted domain hands back (records, events, value objects) to plain Hashes,
|
|
4
|
+
# Arrays and scalars, so the page can show it as JSON.
|
|
5
|
+
module Plain
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# @param item [Object] a value object, record, event, or a Hash or Array of them
|
|
9
|
+
# @return [Object] the same, with every value object replaced by its bare value: one of a
|
|
10
|
+
# single field is that field's value, and one of several is a Hash of them
|
|
11
|
+
def call(item)
|
|
12
|
+
case item
|
|
13
|
+
when Runtime::Value then bare(item)
|
|
14
|
+
when Runtime::Instance, Runtime::Event then call(item.to_h)
|
|
15
|
+
when Hash then item.transform_values { |inner| call(inner) }
|
|
16
|
+
when Array then item.map { |inner| call(inner) }
|
|
17
|
+
else item
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @api private
|
|
22
|
+
def bare(value)
|
|
23
|
+
fields = call(value.to_h)
|
|
24
|
+
fields.size == 1 ? fields.values.first : fields
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
require "hecks"
|
|
2
|
+
require_relative "../adapters/driving/admission"
|
|
3
|
+
require_relative "plain"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Composer
|
|
7
|
+
# The editor's own bookkeeping, held in memory: one Composer chapter booted for the life of the
|
|
8
|
+
# server, with a `Draft` per chapter being edited. The files on disk stay the truth; the draft
|
|
9
|
+
# keeps the text last seen, the conversation about it, and what its last check said, so the
|
|
10
|
+
# page can show a history and an agent's turns survive a refresh.
|
|
11
|
+
class Session
|
|
12
|
+
CHAPTER_DIR = File.expand_path("bluebook", __dir__).freeze
|
|
13
|
+
VERB = "Composer::Draft".freeze
|
|
14
|
+
|
|
15
|
+
# @return [Runtime::Dispatcher] the booted Composer chapter
|
|
16
|
+
attr_reader :runtime
|
|
17
|
+
|
|
18
|
+
# Boots the Composer chapter, which admits only the `Web` driving adapter.
|
|
19
|
+
#
|
|
20
|
+
# @raise [Adapters::Driving::Admission::Refused] if the hecksagon does not admit `Web`
|
|
21
|
+
def initialize
|
|
22
|
+
@runtime = Hecks.boot(CHAPTER_DIR, install_driving: false, environment: "memory")
|
|
23
|
+
Adapters::Driving::Admission.admit!(@runtime.registry, "Web")
|
|
24
|
+
@sequence = Hash.new(0)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Opens a draft for a chapter, or leaves an open one as it is.
|
|
28
|
+
#
|
|
29
|
+
# @param slug [String] the draft's name
|
|
30
|
+
# @param chapter_name [String] the chapter's name
|
|
31
|
+
# @param text [String] the text it starts from
|
|
32
|
+
# @return [void]
|
|
33
|
+
def open(slug:, chapter_name:, text:)
|
|
34
|
+
return if draft(slug)
|
|
35
|
+
|
|
36
|
+
dispatch("Begin", slug: wrap(slug), chapter_name: wrap(chapter_name), text: wrap(text))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Records the text now on screen.
|
|
40
|
+
# @param slug [String] the draft's name
|
|
41
|
+
# @param text [String] the text
|
|
42
|
+
# @return [void]
|
|
43
|
+
def revise(slug:, text:)
|
|
44
|
+
dispatch("Revise", to: slug, text: wrap(text))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Keeps the text of one workspace file, one draft per file with the path as its slug: the
|
|
48
|
+
# first record of a path begins its draft, later ones revise it.
|
|
49
|
+
#
|
|
50
|
+
# @param path [String] the file's path relative to the workspace
|
|
51
|
+
# @param text [String] the text now on screen
|
|
52
|
+
# @return [void]
|
|
53
|
+
def record(path:, text:)
|
|
54
|
+
return revise(slug: path, text: text) if draft(path)
|
|
55
|
+
|
|
56
|
+
self.open(slug: path, chapter_name: chapter_name_in(text), text: text)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The text a file has held, from the chapter's own events.
|
|
60
|
+
#
|
|
61
|
+
# @param path [String] the file's path relative to the workspace
|
|
62
|
+
# @return [Array<Hash>] `name`, `at` and `text` per recorded revision, newest first; empty
|
|
63
|
+
# when the path was never recorded (`text` is nil for an event that carries none)
|
|
64
|
+
def timeline(path)
|
|
65
|
+
@runtime.events.filter_map { |event| revision(event.to_h, path) }.reverse
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Records a check's verdict: accepted when it found no refusal, rejected with its words
|
|
69
|
+
# when it did.
|
|
70
|
+
#
|
|
71
|
+
# @param slug [String] the draft's name
|
|
72
|
+
# @param diagnostic [Hash, nil] the check's refusal, or nil
|
|
73
|
+
# @return [void]
|
|
74
|
+
def judge(slug:, diagnostic:)
|
|
75
|
+
return dispatch("Accept", to: slug, note: wrap("ok")) unless diagnostic
|
|
76
|
+
|
|
77
|
+
where = diagnostic[:line] ? "#{diagnostic[:file]}:#{diagnostic[:line]} " : ""
|
|
78
|
+
dispatch("Reject", to: slug, note: wrap(where + diagnostic[:message].to_s))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Puts the author's request into the draft's conversation.
|
|
82
|
+
# @param slug [String] the draft's name
|
|
83
|
+
# @param said [String] the request
|
|
84
|
+
# @return [void]
|
|
85
|
+
def ask(slug:, said:)
|
|
86
|
+
dispatch("Ask", to: slug, sequence: wrap(next_sequence(slug)), said: wrap(said))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Puts the agent's reply into the draft's conversation, with the text it left behind.
|
|
90
|
+
#
|
|
91
|
+
# @param slug [String] the draft's name
|
|
92
|
+
# @param said [String] the reply
|
|
93
|
+
# @param text [String] the text the draft holds after the agent's edit
|
|
94
|
+
# @return [void]
|
|
95
|
+
def answer(slug:, said:, text:)
|
|
96
|
+
dispatch("Answer", to: slug, sequence: wrap(next_sequence(slug)), said: wrap(said), text: wrap(text))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @param slug [String] the draft's name
|
|
100
|
+
# @return [Hash, nil] the draft as the chapter holds it, or nil when none is open
|
|
101
|
+
def draft(slug)
|
|
102
|
+
@runtime.registry.repository("Composer", aggregate).find(slug)&.then { |row| plain(row) }
|
|
103
|
+
rescue StandardError
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The draft in plain terms, for the page.
|
|
108
|
+
#
|
|
109
|
+
# @param slug [String] the draft's name
|
|
110
|
+
# @return [Hash, nil] `status`, `note` and `turns` (`sequence`, `speaker`, `said`), or nil
|
|
111
|
+
def summary(slug)
|
|
112
|
+
held = draft(slug) or return nil
|
|
113
|
+
{ status: held[:status], note: Plain.call(held[:note]), turns: Plain.call(Array(held[:turns])) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# @api private
|
|
119
|
+
def chapter_name_in(text) = text[/Hecks\.\w+\s+"([A-Z][A-Za-z0-9]*)"/, 1] || "Workspace"
|
|
120
|
+
|
|
121
|
+
# @api private
|
|
122
|
+
def revision(event, path)
|
|
123
|
+
return unless event[:aggregate] == VERB && event[:id] == path
|
|
124
|
+
|
|
125
|
+
{ name: event[:name], at: event[:occurred_at], text: Plain.call(event[:payload])&.dig(:text) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def aggregate = @runtime.registry.bluebook("Composer").aggregate("Draft")
|
|
129
|
+
|
|
130
|
+
def dispatch(command, to: nil, **facts)
|
|
131
|
+
@runtime.dispatch("#{VERB}.#{command}", to: to, with: facts)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def wrap(value) = { value: value }
|
|
135
|
+
|
|
136
|
+
def next_sequence(slug) = @sequence[slug] += 1
|
|
137
|
+
|
|
138
|
+
def plain(row) = row.respond_to?(:to_h) ? row.to_h : row
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require "hecks"
|
|
2
|
+
require "hecks/composer/parse"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Composer
|
|
6
|
+
# Runs Hecks's own commands and queries in-process for a command palette and an Eras panel.
|
|
7
|
+
# It is on the hecksagon side like the other composer files: it boots the Hecks chapter once,
|
|
8
|
+
# on first use, and sends argv to the CLI runner one call at a time. A command that is not on
|
|
9
|
+
# the safe list is guarded: it runs only when the caller echoes back a confirmation phrase.
|
|
10
|
+
class Toolbox
|
|
11
|
+
SAFE_COMMANDS = %w[
|
|
12
|
+
model_check_run.model_check operation.run operation.run_behaviors operation.smoke_test
|
|
13
|
+
operation.refresh_projections fuzz_run.fuzz fuzz_run.bench build.check_conformance
|
|
14
|
+
build.fuzz_conformance conformance_run.check_engine_agreement
|
|
15
|
+
conformance_run.measure_doc_coverage kernel_run.measure_kernel_coverage
|
|
16
|
+
style_run.check_comments style_run.check_rust_comments style_run.check_comments_unchanged
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
ERA_QUERIES = {
|
|
20
|
+
shape: "introspection.shape", scaffold_translation: "era.scaffold_translation",
|
|
21
|
+
audit_translation: "era.audit_translation", compaction: "era.compaction"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# @return [Toolbox] a toolbox that boots the Hecks chapter on first use
|
|
25
|
+
def initialize
|
|
26
|
+
@lock = Mutex.new
|
|
27
|
+
@runtime = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Everything the palette can offer, commands and queries together.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Hash>] `{kind:, verb:, goal:, guarded:}` sorted by verb, without the
|
|
33
|
+
# commands whose role is System
|
|
34
|
+
def catalog
|
|
35
|
+
(rows("command", commands, :goal) + rows("query", queries, :description))
|
|
36
|
+
.sort_by { |row| [row[:verb], row[:kind]] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The runner's own help text for one verb.
|
|
40
|
+
#
|
|
41
|
+
# @param kind [String] "command" or "query"
|
|
42
|
+
# @param verb [String] a qualified verb such as `model_check_run.model_check`
|
|
43
|
+
# @return [String] the help text
|
|
44
|
+
def help(kind:, verb:)
|
|
45
|
+
invoke(argv_for(kind, verb, [], help: true)).first
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Runs one command or query and says what it printed.
|
|
49
|
+
#
|
|
50
|
+
# @param kind [String] "command" or "query"
|
|
51
|
+
# @param verb [String] a qualified verb
|
|
52
|
+
# @param args [Hash{String => #to_s}] argument names and values, such as `domain.value`
|
|
53
|
+
# @param confirm [String, nil] the phrase `RUN <verb>` that a guarded command requires
|
|
54
|
+
# @return [Hash] `ok`, `status`, `output` and, when nothing ran or the run failed, `error`
|
|
55
|
+
def run(kind:, verb:, args: {}, confirm: nil)
|
|
56
|
+
refusal = refusal_for(kind, verb, args, confirm)
|
|
57
|
+
return refusal if refusal
|
|
58
|
+
|
|
59
|
+
output, status = invoke(argv_for(kind, verb, pairs(args)))
|
|
60
|
+
{ ok: status.zero?, status: status, output: output }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The four era readings of a workspace.
|
|
64
|
+
#
|
|
65
|
+
# @param dir [String] the workspace directory
|
|
66
|
+
# @return [Hash{Symbol => Hash}] `shape`, `scaffold_translation`, `audit_translation` and
|
|
67
|
+
# `compaction`, each a {#run} result
|
|
68
|
+
def eras(dir)
|
|
69
|
+
ERA_QUERIES.transform_values { |verb| run(kind: "query", verb: verb, args: { "domain.value" => dir }) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# @api private
|
|
75
|
+
def rows(kind, items, reader)
|
|
76
|
+
items.map { |verb, item| entry(kind, verb, item.public_send(reader)) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @api private
|
|
80
|
+
def entry(kind, verb, goal)
|
|
81
|
+
guarded = kind == "command" && !SAFE_COMMANDS.include?(verb)
|
|
82
|
+
{ kind: kind, verb: verb, goal: goal.to_s, guarded: guarded }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @api private
|
|
86
|
+
def refusal_for(kind, verb, args, confirm)
|
|
87
|
+
return failure(2, "unknown kind #{kind.inspect}") unless %w[command query].include?(kind)
|
|
88
|
+
return failure(2, "unknown #{kind} #{verb.inspect}") unless known(kind).key?(verb.to_s)
|
|
89
|
+
return failure(2, "arguments may not contain a newline") if newline?(args)
|
|
90
|
+
return unless kind == "command" && !SAFE_COMMANDS.include?(verb) && confirm != "RUN #{verb}"
|
|
91
|
+
|
|
92
|
+
failure(2, "#{verb} is guarded: confirm with \"RUN #{verb}\"")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @api private
|
|
96
|
+
def failure(status, message)
|
|
97
|
+
{ ok: false, status: status, output: "", error: message }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @api private
|
|
101
|
+
def newline?(args)
|
|
102
|
+
args.any? { |name, value| [name, value].any? { |text| text.to_s.match?(/[\r\n]/) } }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @api private
|
|
106
|
+
def pairs(args)
|
|
107
|
+
args.map { |name, value| "#{name}=#{value}" }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @api private
|
|
111
|
+
def argv_for(kind, verb, pairs, help: false)
|
|
112
|
+
tail = help ? ["--help"] : pairs
|
|
113
|
+
return ["query", verb, *tail] if kind == "query"
|
|
114
|
+
|
|
115
|
+
[verb, *tail, *(help ? [] : ["--wait"])]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @api private
|
|
119
|
+
def known(kind)
|
|
120
|
+
kind == "query" ? queries : commands
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @api private
|
|
124
|
+
def commands
|
|
125
|
+
@commands ||= verbs(:commands).reject { |_verb, command| command.role.to_s == "System" }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @api private
|
|
129
|
+
def queries
|
|
130
|
+
@queries ||= verbs(:queries)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @api private
|
|
134
|
+
def verbs(member)
|
|
135
|
+
chapter.aggregates.flat_map do |aggregate|
|
|
136
|
+
aggregate.public_send(member).map do |item|
|
|
137
|
+
["#{Naming.snake(aggregate.hecks_name)}.#{Naming.snake(item.hecks_name)}", item]
|
|
138
|
+
end
|
|
139
|
+
end.to_h
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @api private
|
|
143
|
+
def chapter
|
|
144
|
+
runtime.registry.bluebook("Hecks")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @api private
|
|
148
|
+
def runtime
|
|
149
|
+
@lock.synchronize { @runtime ||= boot }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @api private
|
|
153
|
+
def boot
|
|
154
|
+
Hecks.boot(File.join(Parse::ROOT, "lib/hecks/hecks"), install_driving: false, environment: "memory")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @api private
|
|
158
|
+
def invoke(argv)
|
|
159
|
+
rt = runtime
|
|
160
|
+
@lock.synchronize do
|
|
161
|
+
Hecks::Adapters::Driving::CliRunner.call(runtime: rt, program: "hecks", argv: argv)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|