hecks 3.9.1 → 3.11.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/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +61 -18
- data/lib/hecks/hecks/hecks.world +11 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +9 -1
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +174 -36
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
__BANNER__
|
|
2
|
+
|
|
3
|
+
// A record's values shown read-only, by shape: a moment in the viewer's zone, a rich-text body as
|
|
4
|
+
// typeset text, a key with a button that copies it, anything else as `outline` shows it. The detail
|
|
5
|
+
// page lists them as a document, and a related record's panel lists the ones it holds.
|
|
6
|
+
import type { Aggregate, Attr } from "../schema.ts";
|
|
7
|
+
import { bodyToHtml } from "./body_model.js";
|
|
8
|
+
import { kindOf, summaryOf } from "./block_list.js";
|
|
9
|
+
import { copyable, esc } from "./html.ts";
|
|
10
|
+
import { isMoment, momentValue } from "./moments.ts";
|
|
11
|
+
import { outline } from "./outline.ts";
|
|
12
|
+
import { label } from "./words.ts";
|
|
13
|
+
|
|
14
|
+
/** A body shown on a page sits under the page's h1 and its section's h2, so its own headings start at h3. */
|
|
15
|
+
const READ_ONLY_DEMOTE = 2;
|
|
16
|
+
|
|
17
|
+
type State = Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
/** A list of mixed blocks as a numbered list: each block's kind and the line that stands for it. */
|
|
20
|
+
function blockLines(agg: Aggregate, attr: Attr, held: unknown): string {
|
|
21
|
+
const blocks = Array.isArray(held) ? (held as Record<string, unknown>[]) : [];
|
|
22
|
+
if (blocks.length === 0 || !attr.blockList) return `<span class="text-muted">None</span>`;
|
|
23
|
+
const parts = agg.valueObjects[attr.type] ?? [];
|
|
24
|
+
const lines = blocks.map((block) => {
|
|
25
|
+
const kind = kindOf(attr.blockList, block);
|
|
26
|
+
const text = String(summaryOf(attr.blockList, parts, block));
|
|
27
|
+
return `<li class="flex flex-wrap items-baseline gap-2"><span class="badge badge-soft badge-neutral font-semibold">${esc(label(kind))}</span><span class="min-w-0 break-words">${esc(text)}</span></li>`;
|
|
28
|
+
});
|
|
29
|
+
return `<ol class="grid list-decimal gap-1 pl-6">${lines.join("")}</ol>`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isBlank = (value: unknown): boolean => value === undefined || value === null || value === "" || (Array.isArray(value) && value.length === 0);
|
|
33
|
+
|
|
34
|
+
/** A value object of several parts as its named parts, each shown by what it is (a body typeset, a picture by its description, a list as a run). */
|
|
35
|
+
function parts(agg: Aggregate, type: string, held: unknown, depth: number): string {
|
|
36
|
+
const value = held && typeof held === "object" ? (held as State) : {};
|
|
37
|
+
const rows = (agg.valueObjects[type] ?? [])
|
|
38
|
+
.filter((part) => !isBlank(value[part.name]))
|
|
39
|
+
.map((part) => `<div class="grid gap-1 sm:grid-cols-[8rem_minmax(0,1fr)] sm:gap-3"><dt class="text-muted">${esc(label(part.name))}</dt><dd class="min-w-0 break-words">${valueOf(agg, part, value, depth + 1)}</dd></div>`);
|
|
40
|
+
return rows.length === 0 ? `<span class="text-muted">Not set</span>` : `<dl class="grid gap-2">${rows.join("")}</dl>`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Whether the attribute's value object has several parts to show (a one-part value object is shown as its part). */
|
|
44
|
+
const composite = (agg: Aggregate, attr: Attr): boolean => attr.kind === "object" && (agg.valueObjects[attr.type] ?? []).length > 1;
|
|
45
|
+
|
|
46
|
+
/** One attribute's value as markup. */
|
|
47
|
+
export function valueOf(agg: Aggregate, attr: Attr, values: State, depth = 0): string {
|
|
48
|
+
const held = values[attr.name];
|
|
49
|
+
if (isMoment(attr)) return momentValue(attr, held);
|
|
50
|
+
if (attr.widget === "body") return held && typeof held === "object" ? `<div class="prose">${bodyToHtml(held, READ_ONLY_DEMOTE)}</div>` : `<span class="text-muted">Not set</span>`;
|
|
51
|
+
if (attr.blockList && attr.list) return blockLines(agg, attr, held);
|
|
52
|
+
if (composite(agg, attr) && depth < 5) {
|
|
53
|
+
if (!attr.list) return parts(agg, attr.type, held, depth);
|
|
54
|
+
const items = Array.isArray(held) ? held : [];
|
|
55
|
+
return items.length === 0 ? `<span class="text-muted">None</span>` : `<ol class="grid list-decimal gap-3 pl-6">${items.map((item) => `<li class="pl-1">${parts(agg, attr.type, item, depth)}</li>`).join("")}</ol>`;
|
|
56
|
+
}
|
|
57
|
+
const copy = attr.name === agg.identity || attr.kind === "reference";
|
|
58
|
+
return copy && typeof held !== "object" && held !== undefined && held !== null && held !== "" ? copyable(String(held), label(attr.name).toLowerCase()) : outline(held);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `attrs` as a list of names and values, a wide value (a body, a list, a value object) on a line of its own. */
|
|
62
|
+
export function valueRows(agg: Aggregate, attrs: Attr[], values: State): string {
|
|
63
|
+
const rows = attrs.map((attr) => {
|
|
64
|
+
const wide = attr.widget === "body" || attr.list || attr.kind === "object";
|
|
65
|
+
const layout = wide ? "grid gap-2 py-4" : "grid gap-1 py-4 sm:grid-cols-[11rem_minmax(0,1fr)] sm:gap-4";
|
|
66
|
+
return `<div class="${layout}"><dt class="font-medium text-muted">${esc(label(attr.name))}</dt><dd class="min-w-0 break-words">${valueOf(agg, attr, values)}</dd></div>`;
|
|
67
|
+
});
|
|
68
|
+
return `<dl class="divide-y divide-base-300">\n${rows.join("\n")}\n</dl>`;
|
|
69
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
__BANNER__
|
|
2
|
+
|
|
3
|
+
// The words the editor shows people. The domain's names are CamelCase and singular; people read
|
|
4
|
+
// sentence case, plural for a list, and the same verb through a whole flow: the button that says
|
|
5
|
+
// "Publish" leaves a notice that says "Published." Nothing here asks the domain; it reads names.
|
|
6
|
+
import type { Aggregate, Command } from "../schema.ts";
|
|
7
|
+
|
|
8
|
+
/** The lower-case words of a CamelCase or snake_case name: "MediaItem" is "media item". */
|
|
9
|
+
export const humanize = (name: string): string =>
|
|
10
|
+
name
|
|
11
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
12
|
+
.replace(/_/g, " ")
|
|
13
|
+
.toLowerCase();
|
|
14
|
+
|
|
15
|
+
/** `text` with its first letter capitalised. */
|
|
16
|
+
export const capital = (text: string): string => text.charAt(0).toUpperCase() + text.slice(1);
|
|
17
|
+
|
|
18
|
+
/** A name as a sentence-case label: "SaveDraft" is "Save draft". */
|
|
19
|
+
export const label = (name: string): string => capital(humanize(name));
|
|
20
|
+
|
|
21
|
+
/** The plural of an English noun, for the common regular shapes. */
|
|
22
|
+
export function pluralOf(noun: string): string {
|
|
23
|
+
if (/[^aeiou]y$/.test(noun)) return `${noun.slice(0, -1)}ies`;
|
|
24
|
+
if (/(s|x|z|ch|sh)$/.test(noun)) return `${noun}es`;
|
|
25
|
+
return `${noun}s`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** An aggregate as a list's name: "MediaItem" is "Media items". */
|
|
29
|
+
export function plural(name: string): string {
|
|
30
|
+
const words = humanize(name).split(" ");
|
|
31
|
+
words[words.length - 1] = pluralOf(words[words.length - 1]);
|
|
32
|
+
return capital(words.join(" "));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The limits a list's holder gives it as a sentence ("At least 1 and at most 60."), or nothing when it gives none. */
|
|
36
|
+
export function limitsNote(min?: number, max?: number): string {
|
|
37
|
+
if (min && max) return `At least ${min} and at most ${max}.`;
|
|
38
|
+
return min ? `At least ${min}.` : max ? `At most ${max}.` : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** An aggregate as one thing, lower case: "MediaItem" is "media item". */
|
|
42
|
+
export const singular = (name: string): string => humanize(name);
|
|
43
|
+
|
|
44
|
+
const IRREGULAR: Record<string, string> = {
|
|
45
|
+
set: "set", put: "put", send: "sent", make: "made", undo: "undone", redo: "redone", show: "shown", withdraw: "withdrawn",
|
|
46
|
+
draw: "drawn", hide: "hidden", write: "written", take: "taken", give: "given", keep: "kept", read: "read", pay: "paid", sell: "sold",
|
|
47
|
+
begin: "begun", run: "run", get: "got", find: "found", build: "built", choose: "chosen", leave: "left", hold: "held", lose: "lost",
|
|
48
|
+
break: "broken", bring: "brought", cut: "cut", shut: "shut", split: "split", lay: "laid", say: "said", spend: "spent", lend: "lent",
|
|
49
|
+
bind: "bound", wind: "wound", know: "known", grow: "grown", throw: "thrown", see: "seen", do: "done", go: "gone", have: "had",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const DOUBLED = new Set([
|
|
53
|
+
"submit", "admit", "commit", "omit", "plan", "drop", "ship", "stop", "skip", "tag", "pin", "log", "map", "ban", "trim", "wrap",
|
|
54
|
+
"grab", "strip", "refer", "transfer", "permit", "equip", "occur", "defer", "prefer",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const PREFIXES = ["re", "un", "over", "out", "pre", "mis"];
|
|
58
|
+
|
|
59
|
+
/** The irregular participle of `verb`, or of the verb under a prefix ("reset" is "set" again), or undefined. */
|
|
60
|
+
function irregular(verb: string): string | undefined {
|
|
61
|
+
if (Object.hasOwn(IRREGULAR, verb)) return IRREGULAR[verb];
|
|
62
|
+
const prefix = PREFIXES.find((one) => verb.startsWith(one) && Object.hasOwn(IRREGULAR, verb.slice(one.length)));
|
|
63
|
+
return prefix ? `${prefix}${IRREGULAR[verb.slice(prefix.length)]}` : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The past participle of a verb: "publish" is "published", "submit" is "submitted", "begin" is "begun", "cancel" is "canceled". */
|
|
67
|
+
export function past(verb: string): string {
|
|
68
|
+
const odd = irregular(verb);
|
|
69
|
+
if (odd) return odd;
|
|
70
|
+
if (verb.endsWith("e")) return `${verb}d`;
|
|
71
|
+
if (/[^aeiou]y$/.test(verb)) return `${verb.slice(0, -1)}ied`;
|
|
72
|
+
return DOUBLED.has(verb) ? `${verb}${verb.slice(-1)}ed` : `${verb}ed`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A word cut back to what its forms share: "schedule", "scheduled" and "scheduling" are all "schedul". */
|
|
76
|
+
const stem = (word: string): string => word.replace(/(ing|ed|es|e|d|s)$/, "");
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What a command's button says. A command named by one word acts on a thing the page already
|
|
80
|
+
* names, except one that makes something new: "Draft" on the articles page is "Draft article".
|
|
81
|
+
*/
|
|
82
|
+
export function commandLabel(agg: Aggregate, command: Command): string {
|
|
83
|
+
const words = humanize(command.name).split(" ");
|
|
84
|
+
return capital(command.creates && words.length === 1 ? `${words[0]} ${singular(agg.name)}` : words.join(" "));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* What the notice says when the command has run, in the same verb as its button: "Published draft."
|
|
89
|
+
* When the thing's name already starts with the verb ("Schedule" making a "scheduled action") the
|
|
90
|
+
* repeated word is dropped, so the notice reads "Scheduled action." and never "Scheduled scheduled action."
|
|
91
|
+
*/
|
|
92
|
+
export function done(agg: Aggregate, command: Command): string {
|
|
93
|
+
const [verb, ...rest] = commandLabel(agg, command).toLowerCase().split(" ");
|
|
94
|
+
const repeated = rest.length > 0 && stem(rest[0]) === stem(verb);
|
|
95
|
+
const object = repeated ? rest.slice(1) : rest;
|
|
96
|
+
return capital(`${[past(verb), ...object].join(" ")}.`);
|
|
97
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
__BANNER__
|
|
2
|
+
|
|
3
|
+
// What a page of one record may read of the rest of the domain: the records that go with it, and the
|
|
4
|
+
// actions scheduled for it. The server builds it from the one read of the host the page already makes
|
|
5
|
+
// (app.ts), so a page with panels costs no more reads than a page without.
|
|
6
|
+
import type { Aggregate, Related } from "../schema.ts";
|
|
7
|
+
import { SCHEMA } from "../schema.ts";
|
|
8
|
+
|
|
9
|
+
type State = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
export interface World {
|
|
12
|
+
/** The person's role, which decides which aggregates' records may be shown. */
|
|
13
|
+
role: string | null;
|
|
14
|
+
/** One record of an aggregate by its identity, or undefined when the host holds none. */
|
|
15
|
+
lookup(agg: Aggregate, id: string): State | undefined;
|
|
16
|
+
/** Every record of an aggregate, as `[id, state]` pairs. */
|
|
17
|
+
rows(agg: Aggregate): [string, State][];
|
|
18
|
+
/** The scheduled actions of the record being shown, when the host answered a query for them. */
|
|
19
|
+
actions?: [string, State][];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The aggregate a `related` entry names. */
|
|
23
|
+
export const relatedAggregate = (rel: Related): Aggregate | undefined =>
|
|
24
|
+
SCHEMA.aggregates.find((candidate) => candidate.name === rel.aggregate && candidate.chapter === rel.chapter);
|
|
25
|
+
|
|
26
|
+
/** The aggregate that registers pictures, when the editor has it among its own. */
|
|
27
|
+
export function pictureAggregate(): Aggregate | undefined {
|
|
28
|
+
const media = SCHEMA.media;
|
|
29
|
+
if (!media) return undefined;
|
|
30
|
+
return SCHEMA.aggregates.find((agg) => agg.name === media.aggregate && (media.domain ? agg.chapter === media.domain : !agg.chapter || agg.chapter === SCHEMA.domain));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The aggregate whose records are the scheduled actions, when the editor has one. */
|
|
34
|
+
export const schedulingAggregate = (): Aggregate | undefined =>
|
|
35
|
+
SCHEMA.scheduling && SCHEMA.aggregates.find((candidate) => candidate.name === SCHEMA.scheduling?.aggregate && candidate.chapter === SCHEMA.scheduling?.chapter);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"erasableSyntaxOnly": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"types": ["node"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"],
|
|
16
|
+
"exclude": ["src/browser"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "palette"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
module Site
|
|
8
|
+
module CmsEditor
|
|
9
|
+
# The editor's theme as stylesheet text: the light and dark colour properties of a
|
|
10
|
+
# `Palette`, as the lines that go inside the two daisyUI themes `src/browser/app.css`
|
|
11
|
+
# declares (`editor-light`, and `editor-dark` for a visitor whose system prefers dark).
|
|
12
|
+
module Theme
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# @param accent [String, nil] the Editor row's accent, a six-digit hex colour, or nil
|
|
16
|
+
# @return [Hash{String => String}] the placeholders of `app.css.tmpl` and their text
|
|
17
|
+
def tokens(accent)
|
|
18
|
+
themes = Palette.new(accent).themes
|
|
19
|
+
{ "__LIGHT__" => lines(themes[:light]), "__DARK__" => lines(themes[:dark]) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param properties [Hash{String => String}] custom properties and their values
|
|
23
|
+
# @return [String] one declaration per line
|
|
24
|
+
def lines(properties) = properties.map { |name, value| " #{name}: #{value};" }.join("\n")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "color"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
module Site
|
|
8
|
+
module CmsEditor
|
|
9
|
+
# What the `Editor` row's look settings must satisfy: an accent that is a six-digit hex
|
|
10
|
+
# colour, a logo that is a relative path to a picture file the server can read from its
|
|
11
|
+
# own directory, and a brand that is one line of text.
|
|
12
|
+
module ThemeChecks
|
|
13
|
+
# The file types a logo may be.
|
|
14
|
+
LOGO_TYPES = %w[png jpg jpeg webp gif avif svg].freeze
|
|
15
|
+
|
|
16
|
+
# The longest brand the header has room for.
|
|
17
|
+
BRAND_LENGTH = 60
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# @param row [Hash{Symbol => String}] the row, defaults filled
|
|
22
|
+
# @return [Array<String>] every problem with `brand`, `accent` and `logo`
|
|
23
|
+
def problems(row)
|
|
24
|
+
[*accent_problems(row[:accent].strip), *logo_problems(row[:logo].strip), *brand_problems(row[:brand].strip)]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Array<String>] the problem with an accent that is not blank or six hex digits
|
|
28
|
+
def accent_problems(accent)
|
|
29
|
+
return [] if accent.empty? || Color::HEX.match?(accent)
|
|
30
|
+
|
|
31
|
+
["accent #{accent.inspect} must be a six-digit hex colour such as #1f7a6d"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Array<String>] the problem with a logo that is not a safe relative picture path
|
|
35
|
+
def logo_problems(logo)
|
|
36
|
+
return [] if logo.empty? || safe_path?(logo)
|
|
37
|
+
|
|
38
|
+
["logo #{logo.inspect} must be a relative path to a #{LOGO_TYPES.join(", ")} file, with no .. or hidden part"]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Array<String>] the problem with a brand that is long or has more than one line
|
|
42
|
+
def brand_problems(brand)
|
|
43
|
+
return [] if brand.length <= BRAND_LENGTH && !brand.match?(/[[:cntrl:]]/)
|
|
44
|
+
|
|
45
|
+
["brand must be one line of at most #{BRAND_LENGTH} characters"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] whether `path` is relative, has no hidden part, and is a picture
|
|
49
|
+
def safe_path?(path)
|
|
50
|
+
LOGO_TYPES.include?(File.extname(path).delete_prefix(".").downcase) && !path.start_with?("/") &&
|
|
51
|
+
path.split("/", -1).none? { |part| part.empty? || part.start_with?(".") } && !path.match?(/[\\[:cntrl:]]/)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../../projector"
|
|
5
|
+
require_relative "cms_editor/setting"
|
|
6
|
+
require_relative "cms_editor/schema"
|
|
7
|
+
require_relative "cms_editor/pretty"
|
|
8
|
+
require_relative "cms_editor/icons"
|
|
9
|
+
require_relative "cms_editor/theme"
|
|
10
|
+
|
|
11
|
+
module Hecks
|
|
12
|
+
module Projections
|
|
13
|
+
module Site
|
|
14
|
+
# A small Node/TypeScript editor for a domain's content, as the files of a package: a server
|
|
15
|
+
# that signs editors in through the site's own hand-off and shows a page per aggregate, a list
|
|
16
|
+
# from its queries, a detail page per instance and a form per command.
|
|
17
|
+
#
|
|
18
|
+
# It is generic by aggregate. The pages and forms are not written per aggregate; they read
|
|
19
|
+
# `src/schema.ts`, which is the domain chapter's aggregates, attributes, value objects,
|
|
20
|
+
# lifecycles, commands and queries as one typed constant. The domain holds no editor data: the
|
|
21
|
+
# editor reads and dispatches through the host like any other client, and the output is a pure
|
|
22
|
+
# function of the chapter and the `Editor` row, so `--check` can hold it current.
|
|
23
|
+
module CmsEditor
|
|
24
|
+
extend Projector::Target
|
|
25
|
+
|
|
26
|
+
projects_as :site_cms_editor, emits: :files
|
|
27
|
+
|
|
28
|
+
# Where the template files are.
|
|
29
|
+
TEMPLATES = File.join(__dir__, "cms_editor", "templates")
|
|
30
|
+
|
|
31
|
+
# The files, by path relative to the directory they are written to.
|
|
32
|
+
FILES = %w[
|
|
33
|
+
package.json tsconfig.json src/config.ts src/schema.ts src/names.ts src/host.ts src/commands.ts src/choices.ts
|
|
34
|
+
src/app.ts src/server.ts src/assets.ts src/flash.ts src/auth/membership.ts src/auth/session.ts src/auth/sso.ts
|
|
35
|
+
src/ui/html.ts src/ui/words.ts src/ui/icons.ts src/ui/shell.ts src/ui/outline.ts src/ui/fields.ts
|
|
36
|
+
src/ui/input.ts src/ui/table.ts src/ui/paging.ts src/ui/pickers.ts src/ui/moments.ts src/ui/detail.ts
|
|
37
|
+
src/ui/pages.ts src/ui/theme.js src/ui/body_model.js src/ui/body_parse.js src/ui/availability.ts src/ui/dialogs.ts
|
|
38
|
+
src/ui/links.ts src/ui/preview.ts src/ui/related.ts src/ui/schedule.ts src/ui/values.ts src/ui/world.ts
|
|
39
|
+
src/browser/app.css src/browser/main.js src/browser/shell.js src/browser/forms.js src/browser/toast.js
|
|
40
|
+
src/browser/lists.js src/browser/pickers.js src/browser/epoch.js src/browser/moments.js
|
|
41
|
+
src/browser/body_doc.js src/browser/body_editor.js src/browser/autosave.js src/browser/preview.js
|
|
42
|
+
src/ui/blocks.ts src/ui/block_list.js src/browser/blocks.js
|
|
43
|
+
].freeze
|
|
44
|
+
|
|
45
|
+
# The files only an editor of a chapter with a picture aggregate has: the upload, the
|
|
46
|
+
# storage port with its local-disk adapter, and the picker the rich-text editor opens.
|
|
47
|
+
MEDIA_FILES = %w[
|
|
48
|
+
src/media/sniff.ts src/media/multipart.ts src/media/storage.ts src/media/handler.ts src/browser/media_picker.js
|
|
49
|
+
].freeze
|
|
50
|
+
|
|
51
|
+
# What `app.ts` and the body editor hold for a picture upload; each is left out when the
|
|
52
|
+
# chapter has none.
|
|
53
|
+
MEDIA_LINES = {
|
|
54
|
+
"__MEDIA_IMPORT__" => ["import { mediaHandler, mediaOpen } from \"./media/handler.ts\";",
|
|
55
|
+
"import type { MediaStorage } from \"./media/storage.ts\";"].join("\n"),
|
|
56
|
+
"__MEDIA_OPTION__" => [" /** Where picture bytes are kept: the local disk unless given (see media/storage.ts). */",
|
|
57
|
+
" storage?: MediaStorage;"].join("\n"),
|
|
58
|
+
"__MEDIA_SETUP__" => " const media = mediaHandler(options.storage, { url: options.url, fetch: options.fetch });",
|
|
59
|
+
"__MEDIA_ROUTE__" => [" if (segments[0] === \"media\") {",
|
|
60
|
+
" if (!mediaOpen(role)) return plain(\"Pictures are not open to your role.\", 403);",
|
|
61
|
+
" return media(client, request, segments.slice(1));", " }"].join("\n"),
|
|
62
|
+
"__PICKER_LOADER__" => "() => import(\"./media_picker.js\")"
|
|
63
|
+
}.freeze
|
|
64
|
+
|
|
65
|
+
module_function
|
|
66
|
+
|
|
67
|
+
# Renders the editor's files for a project that declares an `Editor` row.
|
|
68
|
+
#
|
|
69
|
+
# @param bluebook [Bluebook::Chapter] the chapter that declares the route table and the row
|
|
70
|
+
# @param options [Hash{Symbol => Object}] `:domain_chapters` the chapters to edit, in the
|
|
71
|
+
# order the row names them (`:domain_chapter` for one);
|
|
72
|
+
# `:media_chapter` the chapter the row's `media` names, whose picture aggregate the
|
|
73
|
+
# pictures use; `:table` the checked route table, when the login page is to be checked
|
|
74
|
+
# against it
|
|
75
|
+
# @return [Hash{String => String}] each file's path relative to the editor's directory to
|
|
76
|
+
# its text; empty when the project declares no `Editor` row
|
|
77
|
+
# @raise [Table::Invalid] when the row is refused
|
|
78
|
+
# @raise [ArgumentError] when the chapter has no aggregate to edit
|
|
79
|
+
def call(bluebook:, options: {})
|
|
80
|
+
setting = Setting.read(bluebook, table: options[:table])
|
|
81
|
+
return {} unless setting
|
|
82
|
+
|
|
83
|
+
chapters = options[:domain_chapters] || options.fetch(:domain_chapter)
|
|
84
|
+
schema = Schema.read(chapters, skip: setting.skip, pictures: options[:media_chapter], roles: setting.chapter_roles)
|
|
85
|
+
media = schema.key?("media")
|
|
86
|
+
tokens = tokens(setting, schema, media)
|
|
87
|
+
[*FILES, *(MEDIA_FILES if media)].to_h { |path| [path, fill(File.read(File.join(TEMPLATES, "#{path}.tmpl")), tokens)] }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Hash{String => String}] each placeholder of the templates and its text
|
|
91
|
+
def tokens(setting, schema, media)
|
|
92
|
+
setting.tokens.merge(look_tokens(setting, schema), "__SCHEMA__" => Pretty.generate(schema), **media_tokens(media))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param media [Boolean] whether the chapter has a picture aggregate
|
|
96
|
+
# @return [Hash{String => String}] each media placeholder to its text, empty when none
|
|
97
|
+
def media_tokens(media)
|
|
98
|
+
return MEDIA_LINES if media
|
|
99
|
+
|
|
100
|
+
MEDIA_LINES.transform_values { "" }.merge("__PICKER_LOADER__" => "null")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @param setting [Setting] the checked row
|
|
104
|
+
# @param schema [Hash{String => Object}] the chapter as the editor reads it
|
|
105
|
+
# @return [Hash{String => String}] the brand, the icon sprite and the theme's colours
|
|
106
|
+
def look_tokens(setting, schema)
|
|
107
|
+
{ "__BRAND__" => JSON.generate(setting.brand || schema["domain"]), "__ICONS__" => Icons.sprite,
|
|
108
|
+
"__CSS_BANNER__" => "/* #{RoutesTs::BANNER.delete_prefix("// ")} */", **Theme.tokens(setting.accent) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @param text [String] a template with `__NAME__` placeholders
|
|
112
|
+
# @param tokens [Hash{String => String}] each placeholder to its text
|
|
113
|
+
# @return [String] `text` with each placeholder replaced in one pass, so a value that looks
|
|
114
|
+
# like a placeholder is left as it is; a placeholder alone on its line whose text is empty
|
|
115
|
+
# takes the line with it
|
|
116
|
+
def fill(text, tokens)
|
|
117
|
+
text.gsub(/^(__[A-Z_]+__)\n|__[A-Z_]+__/) do
|
|
118
|
+
line = Regexp.last_match(1)
|
|
119
|
+
token = line || Regexp.last_match(0)
|
|
120
|
+
replacement(tokens.fetch(token, token), alone: !line.nil?)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [String] `value`, with its line's newline when it stood alone and is not empty
|
|
125
|
+
def replacement(value, alone:)
|
|
126
|
+
return value unless alone
|
|
127
|
+
|
|
128
|
+
value.empty? ? "" : "#{value}\n"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../bluebook/synthesizer"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
module Site
|
|
8
|
+
# The commands of a project that declare a role, and the verdict on a host's answer to each
|
|
9
|
+
# sent with well-formed arguments but no role and no actor. It sends nothing: the caller
|
|
10
|
+
# dispatches each command and hands the answer back.
|
|
11
|
+
#
|
|
12
|
+
# The arguments are synthesized from the declaration, because a host checks that arguments are
|
|
13
|
+
# present and well-typed before it checks who is calling. A host that enforces roles refuses
|
|
14
|
+
# such a caller as `Unauthorized`. Any other answer (an accepted command, or a refusal for
|
|
15
|
+
# another reason) means the role did not stop it.
|
|
16
|
+
class RoleProbe
|
|
17
|
+
# One command that declares a role.
|
|
18
|
+
#
|
|
19
|
+
# @!attribute [r] verb
|
|
20
|
+
# @return [String] the command as a host is asked to run it, `Chapter::Aggregate.Command`
|
|
21
|
+
# @!attribute [r] role
|
|
22
|
+
# @return [String] the role the command declares
|
|
23
|
+
# @!attribute [r] arguments
|
|
24
|
+
# @return [Hash] one synthesized value per declared attribute
|
|
25
|
+
Check = Struct.new(:verb, :role, :arguments, keyword_init: true)
|
|
26
|
+
|
|
27
|
+
# The refusal kind a host enforcing roles answers a caller of the wrong role with.
|
|
28
|
+
REFUSAL = "Unauthorized"
|
|
29
|
+
|
|
30
|
+
# The refusal kinds of an argument the host would not build, before it asks who is calling.
|
|
31
|
+
ARGUMENT_REFUSALS = %w[AbsentArgument TypeMismatch InvariantViolation].freeze
|
|
32
|
+
|
|
33
|
+
# @param registry [Runtime::Registry] the registry a project booted into
|
|
34
|
+
# @return [Array<Check>] every command that declares a role, in the order declared
|
|
35
|
+
def self.checks(registry)
|
|
36
|
+
registry.bluebooks.values.flat_map do |chapter|
|
|
37
|
+
chapter.aggregates.flat_map do |aggregate|
|
|
38
|
+
aggregate.commands.select(&:role).map { |command| check_for(chapter, aggregate, command) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Check] the check for one command that declares a role
|
|
44
|
+
def self.check_for(chapter, aggregate, command)
|
|
45
|
+
Check.new(verb: "#{chapter.name}::#{aggregate.hecks_name}.#{command.hecks_name}", role: command.role,
|
|
46
|
+
arguments: Bluebook::Synthesizer.args_for(chapter, aggregate, command))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Whether the host refused the synthesized arguments themselves, so the role gate was never
|
|
50
|
+
# reached and the check proves nothing either way.
|
|
51
|
+
#
|
|
52
|
+
# @param answer [Hash] the host's parsed JSON answer
|
|
53
|
+
# @return [Boolean] true when every refusal is an argument refusal
|
|
54
|
+
def self.unchecked?(answer)
|
|
55
|
+
kinds = Array(answer["refusals"]).map { |refusal| refusal["kind"] }
|
|
56
|
+
kinds.any? && (kinds - ARGUMENT_REFUSALS).empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Judges a host's answer to one check.
|
|
60
|
+
#
|
|
61
|
+
# @param check [Check] the command that was dispatched with no role and no actor
|
|
62
|
+
# @param answer [Hash] the host's parsed JSON answer
|
|
63
|
+
# @return [String, nil] why the answer is wrong, or nil when the host refused the caller
|
|
64
|
+
def self.verdict(check, answer)
|
|
65
|
+
refusals = Array(answer["refusals"])
|
|
66
|
+
return nil if refusals.any? { |refusal| refusal["kind"] == REFUSAL }
|
|
67
|
+
return "was refused, but not as #{REFUSAL}: #{refusals.map { |refusal| refusal["kind"] }.join(", ")}" if refusals.any?
|
|
68
|
+
|
|
69
|
+
"was not refused for its role (#{check.role}); is the host running with HECKS_ROLE_ENFORCEMENT=enforce?"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -2,7 +2,8 @@ module Hecks
|
|
|
2
2
|
module Projector
|
|
3
3
|
# What a launcher's help leaves out and points at for whoever typed the line: the aggregates
|
|
4
4
|
# only a maintainer of a hecks checkout runs, and the attached chapters worth a line each.
|
|
5
|
-
# `CliProjector` renders the help; the runner decides the audience
|
|
5
|
+
# `CliProjector` renders the help; the runner decides the audience
|
|
6
|
+
# (`Adapters::Driving::LauncherOptions`).
|
|
6
7
|
module CliAudience
|
|
7
8
|
module_function
|
|
8
9
|
|
|
@@ -67,7 +67,7 @@ module Hecks
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
# " (also: mcp!)" for a spec the chapter gave a short name, else nothing. A short name that
|
|
70
|
-
# is only the command's own name (`init` for `
|
|
70
|
+
# is only the command's own name (`init` for `launch.init`) is already in the line.
|
|
71
71
|
def alias_note(spec)
|
|
72
72
|
return "" if spec[:short_was].nil? || spec[:short_was].split(".").last == spec[:short]
|
|
73
73
|
|
|
@@ -29,7 +29,7 @@ module Hecks
|
|
|
29
29
|
# One option per field of a value object, recursing into a field that is itself one.
|
|
30
30
|
#
|
|
31
31
|
# The list flag rides on each leaf: without it a repeated flag overwrote the
|
|
32
|
-
# leaf silently, and
|
|
32
|
+
# leaf silently, and Cli only ever sees a path and a spec.
|
|
33
33
|
def leaf_options(value_object, aggregate, path, optional)
|
|
34
34
|
value_object.attributes.flat_map do |field|
|
|
35
35
|
nested = value_object_for(field, value_object, aggregate)
|
|
@@ -18,7 +18,7 @@ module Hecks
|
|
|
18
18
|
return [] if bluebook.policies.empty?
|
|
19
19
|
|
|
20
20
|
rows = bluebook.policies.map do |policy|
|
|
21
|
-
["`#{policy.on_event}`", "`#{policy.
|
|
21
|
+
["`#{policy.on_event}`", "`#{policy.reaches}`", policy.target_domain || bluebook.name]
|
|
22
22
|
end
|
|
23
23
|
[DocsProjector.h(depth, "Reactions"), "",
|
|
24
24
|
"These fire on their own. Issuing the verb on the left also causes the one on the right.", "",
|
|
@@ -110,7 +110,7 @@ module Hecks
|
|
|
110
110
|
out.compact.join("\n")
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
# An entity has no
|
|
113
|
+
# An entity has no entry point of its own; its verbs go through the holding aggregate.
|
|
114
114
|
def entity_addressing(aggregate, entity)
|
|
115
115
|
"Addressed through its holder — `#{aggregate.hecks_name}.#{entity.hecks_name}.<Verb>`, " \
|
|
116
116
|
"passing the #{aggregate.hecks_name}'s `id` and this element's " \
|