hecks 3.14.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/exe/hecks +1 -1
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +9 -16
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/app/responses.rb +3 -1
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +3 -80
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +7 -13
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value/unwrapping.rb +60 -0
- data/lib/hecks/runtime/value.rb +10 -21
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
- data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +306 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +151 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/presenters.rs +335 -0
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +574 -229
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +1037 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +39 -7
- data/rust/host/src/payments/tests.rs +1427 -282
- data/rust/host/src/payments.rs +391 -112
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +555 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -1
- data/rust/parser/src/build/named_pattern.rs +4 -1
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -516
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +22 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +244 -114
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +244 -148
- data/rust/src/kernel/mod.rs +47 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/present.rs +261 -0
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/rust/host/src/approval.rs +0 -652
data/rust/src/kernel/cli.rs
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
//! The stdin/stdout JSON CLI shared by the native binary and the wasm32-wasip1 module.
|
|
2
|
-
//! `run` reads `{"steps": [...]}` and hands each step to `kernel::orchestrate` (ADR 0012)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
use
|
|
2
|
+
//! `run` reads `{"steps": [...]}` and hands each step to `kernel::orchestrate` (ADR 0012), or
|
|
3
|
+
//! `{"present": answer}` and answers that answer bare (`kernel::present`).
|
|
4
|
+
|
|
5
|
+
use super::{
|
|
6
|
+
named_query, orchestrate, query_comparators, read_model, repository, AggregateScan,
|
|
7
|
+
CompletedCompensation, Event, Json, MutationRecord, PendingCrossDomainReaction, Refusal,
|
|
8
|
+
SagaInstance, Tables,
|
|
9
|
+
};
|
|
10
|
+
use crate::generated::active::{
|
|
11
|
+
command_attributes_for_verb, command_creates, dispatch_by_name, entity_identity_head_for_path,
|
|
12
|
+
identity_head_for_aggregate, reference_key_for_aggregate, Store, CROSS_DOMAIN_POLICIES,
|
|
13
|
+
POLICIES, PROCESS_MANAGERS, QUERIES, READ_MODELS,
|
|
14
|
+
};
|
|
6
15
|
use std::collections::HashMap;
|
|
7
16
|
|
|
8
17
|
// Builds and invariant-checks a named query's value-object arguments (ADR 0037).
|
|
@@ -16,6 +25,18 @@ pub fn run(input: &str) -> String {
|
|
|
16
25
|
Err(e) => return error_output(&format!("invalid JSON on stdin: {e}")),
|
|
17
26
|
};
|
|
18
27
|
|
|
28
|
+
// `{"present": answer}`: a run's answer in the bare shape a caller reads (single-field value
|
|
29
|
+
// objects as their sole field); no step runs, and stored state stays wrapped.
|
|
30
|
+
// Parsed again keeping number text, so a Float the answer carries is written back unchanged.
|
|
31
|
+
if parsed.get("present").is_some() {
|
|
32
|
+
let answer = Json::parse_keeping_number_text(input)
|
|
33
|
+
.ok()
|
|
34
|
+
.and_then(|whole| whole.get("present").cloned())
|
|
35
|
+
.unwrap_or(Json::Null);
|
|
36
|
+
return super::present::present_answer(answer, crate::generated::active::present)
|
|
37
|
+
.to_json_string();
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
// Optional `"needs"`: which commands need which outside facts (ADR 0081), passed by the host.
|
|
20
41
|
super::needs::install(parsed.get("needs"));
|
|
21
42
|
// Optional `"query_needs"`: which queries need which outside facts, filled the same way.
|
|
@@ -23,9 +44,8 @@ pub fn run(input: &str) -> String {
|
|
|
23
44
|
// Optional `"defaults"`: each command's declared argument defaults, filled the same way.
|
|
24
45
|
super::needs::install_defaults(parsed.get("defaults"));
|
|
25
46
|
|
|
26
|
-
let steps =
|
|
27
|
-
|
|
28
|
-
None => return error_output("expected a top-level {\"steps\": [...]} object"),
|
|
47
|
+
let Some(steps) = parsed.get("steps").and_then(Json::as_array) else {
|
|
48
|
+
return error_output("expected a top-level {\"steps\": [...]} object");
|
|
29
49
|
};
|
|
30
50
|
|
|
31
51
|
// Optional `"seed"`: prior state as `"instances"` emits it, so a host need not replay history.
|
|
@@ -40,34 +60,46 @@ pub fn run(input: &str) -> String {
|
|
|
40
60
|
let mut refusals: Vec<(String, Refusal)> = Vec::new();
|
|
41
61
|
// Process-manager instances are domain-level, so not a `Store` field.
|
|
42
62
|
// Optional `"sagas"` seeds in-flight saga state the way `"seed"` seeds aggregates.
|
|
43
|
-
let mut sagas: HashMap<(String, String), SagaInstance> =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
let mut sagas: HashMap<(String, String), SagaInstance> = parsed
|
|
64
|
+
.get("sagas")
|
|
65
|
+
.and_then(Json::as_array)
|
|
66
|
+
.map_or_else(HashMap::new, |entries| {
|
|
67
|
+
entries
|
|
68
|
+
.iter()
|
|
69
|
+
.filter_map(|entry| {
|
|
70
|
+
let process_manager = entry.get("process_manager")?.as_str()?.to_string();
|
|
71
|
+
let correlation = entry.get("correlation")?.as_str()?.to_string();
|
|
72
|
+
let state = entry.get("state")?.as_str()?.to_string();
|
|
73
|
+
let memory = entry
|
|
74
|
+
.get("memory")
|
|
75
|
+
.cloned()
|
|
76
|
+
.unwrap_or_else(|| Json::Object(vec![]));
|
|
77
|
+
// Optional: an instance persisted without a ledger rehydrates with an empty one.
|
|
78
|
+
let completed_compensations = entry
|
|
79
|
+
.get("completed_compensations")
|
|
80
|
+
.and_then(Json::as_array)
|
|
81
|
+
.map(|entries| {
|
|
82
|
+
entries
|
|
83
|
+
.iter()
|
|
84
|
+
.filter_map(|e| {
|
|
85
|
+
let command_name = e.get("command_name")?.as_str()?.to_string();
|
|
86
|
+
let args = e.get("args").cloned().unwrap_or(Json::Null);
|
|
87
|
+
Some(CompletedCompensation { command_name, args })
|
|
88
|
+
})
|
|
89
|
+
.collect()
|
|
90
|
+
})
|
|
91
|
+
.unwrap_or_default();
|
|
92
|
+
Some((
|
|
93
|
+
(process_manager, correlation),
|
|
94
|
+
SagaInstance {
|
|
95
|
+
state,
|
|
96
|
+
memory,
|
|
97
|
+
completed_compensations,
|
|
98
|
+
},
|
|
99
|
+
))
|
|
100
|
+
})
|
|
101
|
+
.collect()
|
|
102
|
+
});
|
|
71
103
|
// One entry per step, parallel to `steps`, so a host can read `.last()` for the newest step.
|
|
72
104
|
let mut mutations_per_step: Vec<Vec<MutationRecord>> = Vec::new();
|
|
73
105
|
// Parallel to `mutations_per_step`: policies that named a domain not compiled into this
|
|
@@ -92,7 +124,11 @@ pub fn run(input: &str) -> String {
|
|
|
92
124
|
// for the run alone (ADR 0081), so the log holds no value that depends on the clock.
|
|
93
125
|
let offered = args;
|
|
94
126
|
let filled = match step.get("query") {
|
|
95
|
-
Some(Json::Str(question)) => super::needs::enrich_query(
|
|
127
|
+
Some(Json::Str(question)) => super::needs::enrich_query(
|
|
128
|
+
question,
|
|
129
|
+
args,
|
|
130
|
+
step.get("occurred_at").and_then(Json::as_str),
|
|
131
|
+
),
|
|
96
132
|
_ => None,
|
|
97
133
|
};
|
|
98
134
|
let args = filled.as_ref().unwrap_or(args);
|
|
@@ -110,27 +146,15 @@ pub fn run(input: &str) -> String {
|
|
|
110
146
|
// reference twin is the same engine.
|
|
111
147
|
// `reference_interpret` delegates to the same `entity_rows`).
|
|
112
148
|
Json::Str(question) if question.contains("::") && named_query::find_entity(ENTITY_QUERIES, question).is_some() => {
|
|
149
|
+
// reason: the match guard above only admits a `question` that resolves.
|
|
150
|
+
#[allow(clippy::expect_used)]
|
|
113
151
|
let def = named_query::find_entity(ENTITY_QUERIES, question).expect("matched by the guard above");
|
|
114
152
|
match named_query::run_entity(&store, def, args) {
|
|
115
153
|
Ok(rows) => {
|
|
116
|
-
|
|
117
|
-
query_results.push(Json::obj(vec![
|
|
118
|
-
("query", Json::Str(question.clone())),
|
|
119
|
-
("args", offered.clone()),
|
|
120
|
-
("rows", rows.clone()),
|
|
121
|
-
("reference_rows", rows),
|
|
122
|
-
]));
|
|
154
|
+
query_results.push(answered_entry(question, offered, Json::Array(rows), true));
|
|
123
155
|
}
|
|
124
156
|
Err(refusal) => {
|
|
125
|
-
|
|
126
|
-
query_results.push(Json::obj(vec![
|
|
127
|
-
("query", Json::Str(question.clone())),
|
|
128
|
-
("args", offered.clone()),
|
|
129
|
-
("rows", Json::Null),
|
|
130
|
-
("error", Json::Str(message.clone())),
|
|
131
|
-
("reference_rows", Json::Null),
|
|
132
|
-
("reference_error", Json::Str(message)),
|
|
133
|
-
]));
|
|
157
|
+
query_results.push(refused_entry(question, offered, &refusal, true));
|
|
134
158
|
refusals.push((question.clone(), refusal));
|
|
135
159
|
}
|
|
136
160
|
}
|
|
@@ -139,26 +163,13 @@ pub fn run(input: &str) -> String {
|
|
|
139
163
|
Some(def) => match check_query_args(question, args).and_then(|()| named_query::run(&store, def, args, caller_role)) {
|
|
140
164
|
Ok(entries) => {
|
|
141
165
|
let rows = Json::Array(entries.into_iter().map(|(id, record)| repository::row_json(id, record)).collect());
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
("args", offered.clone()),
|
|
145
|
-
("rows", rows.clone()),
|
|
146
|
-
// Same as `rows`: one compiled path (named_query.rs).
|
|
147
|
-
("reference_rows", rows),
|
|
148
|
-
]));
|
|
166
|
+
// The reference twin is `rows`: one compiled path (named_query.rs).
|
|
167
|
+
query_results.push(answered_entry(question, offered, rows, true));
|
|
149
168
|
}
|
|
150
169
|
// A refused query still gets an entry with `rows: null`, as in Ruby; one
|
|
151
170
|
// compiled path means `error` and `reference_error` are the same string.
|
|
152
171
|
Err(refusal) => {
|
|
153
|
-
|
|
154
|
-
query_results.push(Json::obj(vec![
|
|
155
|
-
("query", Json::Str(question.clone())),
|
|
156
|
-
("args", offered.clone()),
|
|
157
|
-
("rows", Json::Null),
|
|
158
|
-
("error", Json::Str(message.clone())),
|
|
159
|
-
("reference_rows", Json::Null),
|
|
160
|
-
("reference_error", Json::Str(message)),
|
|
161
|
-
]));
|
|
172
|
+
query_results.push(refused_entry(question, offered, &refusal, true));
|
|
162
173
|
refusals.push((question.clone(), refusal));
|
|
163
174
|
}
|
|
164
175
|
},
|
|
@@ -179,24 +190,11 @@ pub fn run(input: &str) -> String {
|
|
|
179
190
|
},
|
|
180
191
|
Json::Str(question) => match read_model::find(READ_MODELS, question) {
|
|
181
192
|
Some(def) => match read_model::run(&store, def, args) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
("args", offered.clone()),
|
|
186
|
-
("rows", Json::Array(vec![row])),
|
|
187
|
-
// No `reference_rows` key: Ruby sets it only for questions with a
|
|
188
|
-
// reference twin, and a read model has none.
|
|
189
|
-
]));
|
|
190
|
-
}
|
|
191
|
-
// As above, minus the reference keys.
|
|
193
|
+
// No reference keys: Ruby sets them only for questions with a reference twin,
|
|
194
|
+
// and a read model has none.
|
|
195
|
+
Ok(row) => query_results.push(answered_entry(question, offered, Json::Array(vec![row]), false)),
|
|
192
196
|
Err(refusal) => {
|
|
193
|
-
|
|
194
|
-
query_results.push(Json::obj(vec![
|
|
195
|
-
("query", Json::Str(question.clone())),
|
|
196
|
-
("args", offered.clone()),
|
|
197
|
-
("rows", Json::Null),
|
|
198
|
-
("error", Json::Str(message)),
|
|
199
|
-
]));
|
|
197
|
+
query_results.push(refused_entry(question, offered, &refusal, false));
|
|
200
198
|
refusals.push((question.clone(), refusal));
|
|
201
199
|
}
|
|
202
200
|
},
|
|
@@ -235,16 +233,21 @@ pub fn run(input: &str) -> String {
|
|
|
235
233
|
// See `actor_id` on the command step below.
|
|
236
234
|
let caller_actor_id = step.get("actor_id").and_then(Json::as_str);
|
|
237
235
|
let command_input = dry_run_command_input(args);
|
|
238
|
-
dry_runs.push(dry_run(
|
|
236
|
+
dry_runs.push(dry_run(
|
|
237
|
+
&store,
|
|
238
|
+
verb,
|
|
239
|
+
&command_input,
|
|
240
|
+
caller_role,
|
|
241
|
+
caller_actor_id,
|
|
242
|
+
));
|
|
239
243
|
mutations_per_step.push(Vec::new());
|
|
240
244
|
cross_domain_per_step.push(Vec::new());
|
|
241
245
|
reactions_per_step.push(Json::Array(Vec::new()));
|
|
242
246
|
continue;
|
|
243
247
|
}
|
|
244
248
|
|
|
245
|
-
let verb =
|
|
246
|
-
|
|
247
|
-
None => return error_output("step missing \"verb\", \"dry_run\", or \"query\""),
|
|
249
|
+
let Some(verb) = step.get("verb").and_then(Json::as_str) else {
|
|
250
|
+
return error_output("step missing \"verb\", \"dry_run\", or \"query\"");
|
|
248
251
|
};
|
|
249
252
|
|
|
250
253
|
// Per-step `role:` stands in for Ruby's ambient `Hecks.as_caller`; passed to the top-level
|
|
@@ -306,19 +309,34 @@ pub fn run(input: &str) -> String {
|
|
|
306
309
|
let refusals_json = Json::Array(
|
|
307
310
|
refusals
|
|
308
311
|
.iter()
|
|
309
|
-
.map(|(verb, r)|
|
|
312
|
+
.map(|(verb, r)| {
|
|
313
|
+
Json::obj(vec![
|
|
314
|
+
("verb", Json::str(verb.clone())),
|
|
315
|
+
("error", Json::str(r.to_string())),
|
|
316
|
+
("kind", Json::str(r.kind().to_string())),
|
|
317
|
+
])
|
|
318
|
+
})
|
|
310
319
|
.collect(),
|
|
311
320
|
);
|
|
312
321
|
let mutations_json = Json::Array(
|
|
313
322
|
mutations_per_step
|
|
314
323
|
.iter()
|
|
315
|
-
.map(|step_mutations|
|
|
324
|
+
.map(|step_mutations| {
|
|
325
|
+
Json::Array(step_mutations.iter().map(mutation_to_json).collect())
|
|
326
|
+
})
|
|
316
327
|
.collect(),
|
|
317
328
|
);
|
|
318
329
|
let cross_domain_json = Json::Array(
|
|
319
330
|
cross_domain_per_step
|
|
320
331
|
.iter()
|
|
321
|
-
.map(|step_reactions|
|
|
332
|
+
.map(|step_reactions| {
|
|
333
|
+
Json::Array(
|
|
334
|
+
step_reactions
|
|
335
|
+
.iter()
|
|
336
|
+
.map(cross_domain_reaction_to_json)
|
|
337
|
+
.collect(),
|
|
338
|
+
)
|
|
339
|
+
})
|
|
322
340
|
.collect(),
|
|
323
341
|
);
|
|
324
342
|
|
|
@@ -332,7 +350,10 @@ pub fn run(input: &str) -> String {
|
|
|
332
350
|
("cross_domain_reactions".to_string(), cross_domain_json),
|
|
333
351
|
// Whole-run logs, not per-step.
|
|
334
352
|
("reactions".to_string(), Json::Array(reaction_log)),
|
|
335
|
-
(
|
|
353
|
+
(
|
|
354
|
+
"reactions_per_step".to_string(),
|
|
355
|
+
Json::Array(reactions_per_step),
|
|
356
|
+
),
|
|
336
357
|
("sagas".to_string(), Json::Array(saga_log)),
|
|
337
358
|
// State at the end of the run, fed back as `"sagas"` next time; the `"sagas"` key above
|
|
338
359
|
// is the transition log, including refused transitions.
|
|
@@ -343,11 +364,31 @@ pub fn run(input: &str) -> String {
|
|
|
343
364
|
|
|
344
365
|
/// Evaluates a command against a throwaway copy of the store; `"error"` carries the refusal
|
|
345
366
|
/// a real dispatch would have raised.
|
|
346
|
-
fn dry_run(
|
|
367
|
+
fn dry_run(
|
|
368
|
+
store: &Store,
|
|
369
|
+
verb: &str,
|
|
370
|
+
command_input: &Json,
|
|
371
|
+
caller_role: Option<&str>,
|
|
372
|
+
caller_actor_id: Option<&str>,
|
|
373
|
+
) -> Json {
|
|
347
374
|
let mut scratch = store.clone();
|
|
348
|
-
match dispatch_by_name(
|
|
349
|
-
|
|
350
|
-
|
|
375
|
+
match dispatch_by_name(
|
|
376
|
+
&mut scratch,
|
|
377
|
+
verb,
|
|
378
|
+
command_input,
|
|
379
|
+
caller_role,
|
|
380
|
+
caller_actor_id,
|
|
381
|
+
&mut Vec::new(),
|
|
382
|
+
) {
|
|
383
|
+
Ok(_) => Json::obj(vec![
|
|
384
|
+
("verb", Json::str(verb.to_string())),
|
|
385
|
+
("ok", Json::Bool(true)),
|
|
386
|
+
]),
|
|
387
|
+
Err(refusal) => Json::obj(vec![
|
|
388
|
+
("verb", Json::str(verb.to_string())),
|
|
389
|
+
("ok", Json::Bool(false)),
|
|
390
|
+
("error", Json::str(refusal.to_string())),
|
|
391
|
+
]),
|
|
351
392
|
}
|
|
352
393
|
}
|
|
353
394
|
|
|
@@ -365,14 +406,17 @@ fn tables() -> Tables<'static> {
|
|
|
365
406
|
}
|
|
366
407
|
}
|
|
367
408
|
|
|
409
|
+
/// Saga instances by (saga, correlation id), as `serve` keeps them across steps.
|
|
410
|
+
type SagaMap = HashMap<(String, String), SagaInstance>;
|
|
411
|
+
|
|
368
412
|
/// `--serve` mode: one JSON step per stdin line, one answer per stdout line, store kept across
|
|
369
413
|
/// lines. Beyond `verb` it takes `dry_run`, `snapshot`, `restore` and `instances` steps.
|
|
370
414
|
pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
|
|
371
415
|
let mut store = Store::new();
|
|
372
|
-
let mut sagas
|
|
416
|
+
let mut sagas = SagaMap::new();
|
|
373
417
|
let mut reaction_log: Vec<Json> = Vec::new();
|
|
374
418
|
let mut saga_log: Vec<Json> = Vec::new();
|
|
375
|
-
let mut snapshot: Option<(Store,
|
|
419
|
+
let mut snapshot: Option<(Store, SagaMap)> = None;
|
|
376
420
|
let ok = || Json::obj(vec![("ok", Json::Bool(true))]);
|
|
377
421
|
|
|
378
422
|
for line in input.lines() {
|
|
@@ -381,7 +425,10 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
|
|
|
381
425
|
continue;
|
|
382
426
|
}
|
|
383
427
|
let answer = match Json::parse(&line) {
|
|
384
|
-
Err(e) => Json::obj(vec![
|
|
428
|
+
Err(e) => Json::obj(vec![
|
|
429
|
+
("ok", Json::Bool(false)),
|
|
430
|
+
("error", Json::str(format!("invalid JSON: {e}"))),
|
|
431
|
+
]),
|
|
385
432
|
Ok(step) => {
|
|
386
433
|
let empty_args = Json::Object(vec![]);
|
|
387
434
|
let args = step.get("args").unwrap_or(&empty_args);
|
|
@@ -395,15 +442,27 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
|
|
|
395
442
|
match &snapshot {
|
|
396
443
|
Some((s, g)) => {
|
|
397
444
|
store = s.clone();
|
|
398
|
-
sagas
|
|
445
|
+
sagas.clone_from(g);
|
|
399
446
|
ok()
|
|
400
447
|
}
|
|
401
|
-
None => Json::obj(vec![
|
|
448
|
+
None => Json::obj(vec![
|
|
449
|
+
("ok", Json::Bool(false)),
|
|
450
|
+
("error", Json::str("no snapshot to restore")),
|
|
451
|
+
]),
|
|
402
452
|
}
|
|
403
453
|
} else if step.get("instances").is_some() {
|
|
404
|
-
Json::obj(vec![
|
|
454
|
+
Json::obj(vec![
|
|
455
|
+
("ok", Json::Bool(true)),
|
|
456
|
+
("instances", Json::Object(store.instances())),
|
|
457
|
+
])
|
|
405
458
|
} else if let Some(verb) = step.get("dry_run").and_then(Json::as_str) {
|
|
406
|
-
dry_run(
|
|
459
|
+
dry_run(
|
|
460
|
+
&store,
|
|
461
|
+
verb,
|
|
462
|
+
&dry_run_command_input(args),
|
|
463
|
+
caller_role,
|
|
464
|
+
caller_actor_id,
|
|
465
|
+
)
|
|
407
466
|
} else if let Some(verb) = step.get("verb").and_then(Json::as_str) {
|
|
408
467
|
let mut events: Vec<Event> = Vec::new();
|
|
409
468
|
let outcome = orchestrate(
|
|
@@ -425,11 +484,26 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
|
|
|
425
484
|
&mut saga_log,
|
|
426
485
|
);
|
|
427
486
|
match outcome {
|
|
428
|
-
Ok(()) => Json::obj(vec![
|
|
429
|
-
|
|
487
|
+
Ok(()) => Json::obj(vec![
|
|
488
|
+
("ok", Json::Bool(true)),
|
|
489
|
+
(
|
|
490
|
+
"events",
|
|
491
|
+
Json::Array(events.iter().map(event_to_json).collect()),
|
|
492
|
+
),
|
|
493
|
+
]),
|
|
494
|
+
Err(refusal) => Json::obj(vec![
|
|
495
|
+
("ok", Json::Bool(false)),
|
|
496
|
+
("error", Json::str(refusal.to_string())),
|
|
497
|
+
]),
|
|
430
498
|
}
|
|
431
499
|
} else {
|
|
432
|
-
Json::obj(vec![
|
|
500
|
+
Json::obj(vec![
|
|
501
|
+
("ok", Json::Bool(false)),
|
|
502
|
+
(
|
|
503
|
+
"error",
|
|
504
|
+
Json::str("step needs verb, dry_run, snapshot, restore, or instances"),
|
|
505
|
+
),
|
|
506
|
+
])
|
|
433
507
|
}
|
|
434
508
|
}
|
|
435
509
|
};
|
|
@@ -452,7 +526,10 @@ fn run_filter(store: &Store, filter: &Json) -> Result<Vec<Json>, Refusal> {
|
|
|
452
526
|
.ok_or_else(|| Refusal::Fault(format!("unknown aggregate {aggregate:?}")))?;
|
|
453
527
|
|
|
454
528
|
let matched = repository::filter_entries(entries, field, comparator, value);
|
|
455
|
-
Ok(matched
|
|
529
|
+
Ok(matched
|
|
530
|
+
.into_iter()
|
|
531
|
+
.map(|(id, record)| repository::row_json(id, record))
|
|
532
|
+
.collect())
|
|
456
533
|
}
|
|
457
534
|
|
|
458
535
|
fn required_str<'a>(filter: &'a Json, key: &str) -> Result<&'a str, Refusal> {
|
|
@@ -464,9 +541,45 @@ fn required_str<'a>(filter: &'a Json, key: &str) -> Result<&'a str, Refusal> {
|
|
|
464
541
|
|
|
465
542
|
/// The `refusals` verb column for a refused ad hoc filter, which has no verb; tolerates
|
|
466
543
|
/// missing or mistyped fields.
|
|
544
|
+
/// The log entry for an answered `query` step. A question with a reference twin also carries
|
|
545
|
+
/// `reference_rows`, the same rows because both come from one compiled path.
|
|
546
|
+
fn answered_entry(question: &str, offered: &Json, rows: Json, with_reference: bool) -> Json {
|
|
547
|
+
let mut fields = vec![
|
|
548
|
+
("query", Json::Str(question.to_string())),
|
|
549
|
+
("args", offered.clone()),
|
|
550
|
+
("rows", rows.clone()),
|
|
551
|
+
];
|
|
552
|
+
if with_reference {
|
|
553
|
+
fields.push(("reference_rows", rows));
|
|
554
|
+
}
|
|
555
|
+
Json::obj(fields)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/// The log entry for a refused `query` step: `rows: null` and the refusal's text, with the
|
|
559
|
+
/// reference twin's null rows and identical error when the question has one.
|
|
560
|
+
fn refused_entry(question: &str, offered: &Json, refusal: &Refusal, with_reference: bool) -> Json {
|
|
561
|
+
let message = refusal.to_string();
|
|
562
|
+
let mut fields = vec![
|
|
563
|
+
("query", Json::Str(question.to_string())),
|
|
564
|
+
("args", offered.clone()),
|
|
565
|
+
("rows", Json::Null),
|
|
566
|
+
("error", Json::Str(message.clone())),
|
|
567
|
+
];
|
|
568
|
+
if with_reference {
|
|
569
|
+
fields.push(("reference_rows", Json::Null));
|
|
570
|
+
fields.push(("reference_error", Json::Str(message)));
|
|
571
|
+
}
|
|
572
|
+
Json::obj(fields)
|
|
573
|
+
}
|
|
574
|
+
|
|
467
575
|
fn filter_label(filter: &Json) -> String {
|
|
468
576
|
let field = |key: &str| filter.get(key).and_then(Json::as_str).unwrap_or("");
|
|
469
|
-
format!(
|
|
577
|
+
format!(
|
|
578
|
+
"filter {}.{} {}",
|
|
579
|
+
field("aggregate"),
|
|
580
|
+
field("field"),
|
|
581
|
+
field("op")
|
|
582
|
+
)
|
|
470
583
|
}
|
|
471
584
|
|
|
472
585
|
fn command_input<'a>(step: &'a Json, legacy_args: &'a Json) -> &'a Json {
|
|
@@ -544,7 +657,10 @@ fn event_to_json(event: &Event) -> Json {
|
|
|
544
657
|
("id", Json::str(event.id.clone())),
|
|
545
658
|
("payload", event.payload.clone()),
|
|
546
659
|
// Key order matches Ruby's `Event#to_h`; `None` serialises as null.
|
|
547
|
-
(
|
|
660
|
+
(
|
|
661
|
+
"occurred_at",
|
|
662
|
+
event.occurred_at.clone().map_or(Json::Null, Json::Str),
|
|
663
|
+
),
|
|
548
664
|
])
|
|
549
665
|
}
|
|
550
666
|
|
|
@@ -573,8 +689,14 @@ mod routing_tests {
|
|
|
573
689
|
let selected = command_input(&step, &legacy_args);
|
|
574
690
|
let invocation = crate::kernel::CommandInvocation::from_json(selected).unwrap();
|
|
575
691
|
assert_eq!(invocation.route(), None);
|
|
576
|
-
assert_eq!(
|
|
577
|
-
|
|
692
|
+
assert_eq!(
|
|
693
|
+
invocation.facts().get("branch_code").and_then(Json::as_str),
|
|
694
|
+
Some("DOWNTOWN")
|
|
695
|
+
);
|
|
696
|
+
assert_eq!(
|
|
697
|
+
invocation.facts().get("box_number").and_then(Json::as_i64),
|
|
698
|
+
Some(12)
|
|
699
|
+
);
|
|
578
700
|
}
|
|
579
701
|
|
|
580
702
|
// A domain fact named `to` must dry-run as a plain fact, never a routing attempt
|
|
@@ -591,11 +713,19 @@ mod routing_tests {
|
|
|
591
713
|
|
|
592
714
|
assert_eq!(invocation.route(), None);
|
|
593
715
|
assert_eq!(
|
|
594
|
-
invocation
|
|
716
|
+
invocation
|
|
717
|
+
.facts()
|
|
718
|
+
.get("to")
|
|
719
|
+
.and_then(|to| to.get("value"))
|
|
720
|
+
.and_then(Json::as_i64),
|
|
595
721
|
Some(570)
|
|
596
722
|
);
|
|
597
723
|
assert_eq!(
|
|
598
|
-
invocation
|
|
724
|
+
invocation
|
|
725
|
+
.facts()
|
|
726
|
+
.get("name")
|
|
727
|
+
.and_then(|name| name.get("value"))
|
|
728
|
+
.and_then(Json::as_str),
|
|
599
729
|
Some("crew-1")
|
|
600
730
|
);
|
|
601
731
|
}
|