hecks 6.0.0 → 7.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/exe/hecks +19 -12
- data/lib/hecks/adapters/driven/claude_code.rb +2 -2
- data/lib/hecks/adapters/driven/folder.rb +10 -1
- data/lib/hecks/adapters/driven/folder_overlay.rb +15 -0
- data/lib/hecks/adapters/driven/heki.rb +1 -1
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +1 -1
- data/lib/hecks/adapters/driven/memory/outbox_rows.rb +2 -2
- data/lib/hecks/adapters/driven/postgres/name_guard.rb +25 -0
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -3
- data/lib/hecks/adapters/driven/postgres/repository.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +1 -1
- data/lib/hecks/adapters/driven/postgres.rb +3 -0
- data/lib/hecks/adapters/driven/postgres_era/boot.rb +28 -16
- data/lib/hecks/adapters/driven/postgres_era/class_methods.rb +27 -7
- data/lib/hecks/adapters/driven/postgres_era/dialect.rb +10 -5
- data/lib/hecks/adapters/driven/postgres_era/field_cache_provisioner.rb +39 -0
- data/lib/hecks/adapters/driven/postgres_era/journal.rb +52 -13
- data/lib/hecks/adapters/driven/postgres_era/lineage/chain_sql.rb +29 -49
- data/lib/hecks/adapters/driven/postgres_era/lineage/direct_sql.rb +73 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/direct_verified.json +10 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/direct_verified.rb +65 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/domain_rename.rb +77 -25
- data/lib/hecks/adapters/driven/postgres_era/lineage/domain_rename_state.rb +108 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_integrity.rb +11 -6
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +63 -5
- data/lib/hecks/adapters/driven/postgres_era/lineage/layered_sql.rb +64 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/ledger_reset.rb +150 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_provisioning.rb +85 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +11 -7
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +11 -5
- data/lib/hecks/adapters/driven/postgres_era/lineage/split_sql.rb +76 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/sql_templates.rb +9 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +10 -2
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/context.rb +23 -3
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +3 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/disposable_ledger.rb +119 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolution.rb +219 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +68 -71
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/held_era.rb +68 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/ledger_change.rb +63 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/mint_decision.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/mint_request.rb +24 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +59 -21
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/refusals.rb +24 -11
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +40 -6
- data/lib/hecks/adapters/driven/postgres_era/shared_tables.rb +32 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +12 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +25 -10
- data/lib/hecks/adapters/driven/prism.rb +0 -1
- data/lib/hecks/adapters/driven/sql_query_builder.rb +1 -0
- data/lib/hecks/adapters/driven/sqlite/dispatch_claims.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/name_guard.rb +25 -0
- data/lib/hecks/adapters/driven/sqlite/outbox.rb +2 -2
- data/lib/hecks/adapters/driven/sqlite/repository.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +20 -0
- data/lib/hecks/adapters/driven/sqlite.rb +9 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +1 -1
- data/lib/hecks/adapters/driven/worktree_overlay.rb +117 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +5 -1
- data/lib/hecks/adapters/driving/cli.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +77 -22
- data/lib/hecks/adapters/driving/cli_runner/bang_rule.rb +35 -0
- data/lib/hecks/adapters/driving/cli_runner/completion_words.rb +36 -0
- data/lib/hecks/adapters/driving/cli_runner/json_words.rb +55 -0
- data/lib/hecks/adapters/driving/cli_runner/name_index.rb +63 -0
- data/lib/hecks/adapters/driving/cli_runner/read_context.rb +32 -0
- data/lib/hecks/adapters/driving/cli_runner/renamed_names.rb +49 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +43 -17
- data/lib/hecks/adapters/driving/cli_runner/resume_words.rb +63 -0
- data/lib/hecks/adapters/driving/cli_runner/run_reference.rb +66 -0
- data/lib/hecks/adapters/driving/cli_runner/run_replay.rb +118 -0
- data/lib/hecks/adapters/driving/cli_runner/search_words.rb +35 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +6 -5
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +11 -10
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/tour_words.rb +77 -0
- data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +36 -4
- data/lib/hecks/adapters/driving/cli_runner/verbosity.rb +75 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +106 -7
- data/lib/hecks/adapters/driving/context_line.rb +85 -0
- data/lib/hecks/adapters/driving/exit_status.rb +31 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +7 -5
- data/lib/hecks/adapters/driving/mcp/progress_sink.rb +45 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +7 -5
- data/lib/hecks/adapters/driving/mcp.rb +25 -5
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +40 -7
- data/lib/hecks/adapters/driving/mcp_scope.rb +8 -7
- data/lib/hecks/adapters/driving/ruby/aggregate_module/hooks.rb +154 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +64 -93
- data/lib/hecks/adapters/driving/usage_log.rb +146 -0
- data/lib/hecks/adapters/driving/write_guard.rb +55 -0
- data/lib/hecks/adapters/driving.rb +0 -1
- data/lib/hecks/behaviors/dsl.rb +5 -1
- data/lib/hecks/behaviors/expectations/boot.rb +0 -1
- data/lib/hecks/bluebook/acknowledgements.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +21 -18
- data/lib/hecks/bluebook/assembly/contracts/sagas.rb +2 -0
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +18 -13
- data/lib/hecks/bluebook/behaviour/aggregate.rb +27 -3
- data/lib/hecks/bluebook/command.rb +16 -5
- data/lib/hecks/bluebook/declaration.rb +32 -4
- data/lib/hecks/bluebook/domain_port.rb +7 -3
- data/lib/hecks/bluebook/dsl/aggregate_builder/relationships.rb +0 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder/renaming.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +3 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +0 -1
- data/lib/hecks/bluebook/dsl/audience_word.rb +40 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/metadata.rb +56 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/modules.rb +80 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +51 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +1 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +17 -34
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +8 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +13 -3
- data/lib/hecks/bluebook/dsl/entity_builder.rb +0 -2
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +6 -4
- data/lib/hecks/bluebook/dsl/kinds.rb +53 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +9 -2
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +25 -15
- data/lib/hecks/bluebook/dsl/translation_builder.rb +16 -1
- data/lib/hecks/bluebook/dsl/word_gate.rb +1 -1
- data/lib/hecks/bluebook/dsl.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge/identity.rb +9 -4
- data/lib/hecks/bluebook/meta_validator/plan/category.rb +10 -2
- data/lib/hecks/bluebook/meta_validator/plan/verbs.rb +9 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +3 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/reading.rb +7 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +7 -2
- data/lib/hecks/bluebook/meta_validator/shapes.rb +3 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot/disk_cache.rb +1 -1
- data/lib/hecks/bluebook/model_check/aggregate_checks.rb +27 -0
- data/lib/hecks/bluebook/model_check/ask_checks.rb +25 -0
- data/lib/hecks/bluebook/model_check/reaction_chains.rb +114 -0
- data/lib/hecks/bluebook/model_check/reaction_cycles.rb +53 -0
- data/lib/hecks/bluebook/model_check/reaction_depth_checks.rb +58 -0
- data/lib/hecks/bluebook/model_check/reaction_walk.rb +62 -0
- data/lib/hecks/bluebook/model_check.rb +5 -27
- data/lib/hecks/bluebook/process_manager.rb +20 -3
- data/lib/hecks/bluebook/translation.rb +5 -3
- data/lib/hecks/bluebook/translation_plan.rb +9 -2
- data/lib/hecks/ci_pipeline/adapters/ci_cache.adapter +7 -0
- data/lib/hecks/ci_pipeline/adapters/ci_cache.rb +39 -0
- data/lib/hecks/ci_pipeline/adapters/ci_runner.adapter +7 -0
- data/lib/hecks/ci_pipeline/adapters/ci_runner.rb +55 -0
- data/lib/hecks/ci_pipeline/ci_pipeline.behaviors +322 -0
- data/lib/hecks/ci_pipeline/ci_pipeline.bluebook +938 -0
- data/lib/hecks/ci_pipeline/ci_pipeline.hecksagon +12 -0
- data/lib/hecks/ci_pipeline/ci_pipeline.ports.hecksagon +16 -0
- data/lib/hecks/ci_speed/adapters/ci_reader.adapter +7 -0
- data/lib/hecks/ci_speed/adapters/ci_reader.rb +141 -0
- data/lib/hecks/ci_speed/adapters/local_runner/answer.rb +41 -0
- data/lib/hecks/ci_speed/adapters/local_runner/catalog.rb +89 -0
- data/lib/hecks/ci_speed/adapters/local_runner/checkouts.rb +89 -0
- data/lib/hecks/ci_speed/adapters/local_runner/launcher.rb +69 -0
- data/lib/hecks/ci_speed/adapters/local_runner/load_average.rb +38 -0
- data/lib/hecks/ci_speed/adapters/local_runner/refs.rb +39 -0
- data/lib/hecks/ci_speed/adapters/local_runner/refused.rb +11 -0
- data/lib/hecks/ci_speed/adapters/local_runner/verdict.rb +71 -0
- data/lib/hecks/ci_speed/adapters/local_runner.adapter +7 -0
- data/lib/hecks/ci_speed/adapters/local_runner.rb +168 -0
- data/lib/hecks/ci_speed/adapters/replayed_runner.rb +39 -0
- data/lib/hecks/ci_speed/ci_speed.behaviors +341 -0
- data/lib/hecks/ci_speed/ci_speed.bluebook +1073 -0
- data/lib/hecks/ci_speed/ci_speed.hecksagon +11 -0
- data/lib/hecks/ci_speed/ci_speed.ports.hecksagon +21 -0
- data/lib/hecks/ci_workflows/ci_workflows.behaviors +13 -0
- data/lib/hecks/ci_workflows/ci_workflows.bluebook +173 -0
- data/lib/hecks/ci_workflows/ci_workflows.hecksagon +15 -0
- data/lib/hecks/coder/coder.bluebook +385 -0
- data/lib/hecks/coder/coder.hecksagon +12 -0
- data/lib/hecks/command_names/check.rb +39 -0
- data/lib/hecks/command_names/extractor.rb +67 -0
- data/lib/hecks/command_names/index.rb +40 -0
- data/lib/hecks/command_names/resolution.rb +81 -0
- data/lib/hecks/command_names/scan.rb +54 -0
- data/lib/hecks/command_names/sources.rb +73 -0
- data/lib/hecks/command_names.rb +26 -0
- data/lib/hecks/composer/jobs.rb +1 -1
- data/lib/hecks/composer/toolbox.rb +8 -1
- data/lib/hecks/corpus/generated_key.rb +205 -0
- data/lib/hecks/corpus/listing.rb +34 -0
- data/lib/hecks/corpus/rust.rb +27 -11
- data/lib/hecks/corpus/sources.rb +2 -1
- data/lib/hecks/corpus.rb +37 -42
- data/lib/hecks/deprecation.rb +23 -0
- data/lib/hecks/doc/reference/readme.rb +1 -1
- data/lib/hecks/doc/reference/rendering.rb +2 -2
- data/lib/hecks/doc/reference.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +1 -1
- data/lib/hecks/fixer/fixer.bluebook +240 -0
- data/lib/hecks/fixer/fixer.hecksagon +17 -0
- data/lib/hecks/framework_bluebooks.rb +32 -7
- data/lib/hecks/fuzzing/concurrent_dispatch/world_copy.rb +1 -0
- data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +11 -12
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +23 -5
- data/lib/hecks/fuzzing/isolated_boot.rb +1 -1
- data/lib/hecks/fuzzing/mutation/checks.rb +3 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/grammar/translation_rules.json +64 -0
- data/lib/hecks/hecks/adapter_words.json +7 -0
- data/lib/hecks/hecks/adapters/area_overlap.rb +156 -0
- data/lib/hecks/hecks/adapters/claude_agents.rb +8 -3
- data/lib/hecks/hecks/adapters/codebase/batch_runs.rb +60 -0
- data/lib/hecks/hecks/adapters/codebase/changed_specs.rb +52 -0
- data/lib/hecks/hecks/adapters/codebase/codemods.rb +3 -2
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +14 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_queries.rb +53 -0
- data/lib/hecks/hecks/adapters/codebase/generated_keys.rb +31 -0
- data/lib/hecks/hecks/adapters/codebase/impacted_specs.rb +110 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +2 -2
- data/lib/hecks/hecks/adapters/codebase/launcher_text.rb +39 -0
- data/lib/hecks/hecks/adapters/codebase/minitest_runs.rb +52 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +51 -8
- data/lib/hecks/hecks/adapters/codebase/run_options.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/scripting.rb +39 -0
- data/lib/hecks/hecks/adapters/codebase/search.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +79 -51
- data/lib/hecks/hecks/adapters/codebase/spec_files/report.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/spec_files/shared_run.rb +73 -0
- data/lib/hecks/hecks/adapters/codebase/spec_files.rb +95 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +53 -4
- data/lib/hecks/hecks/adapters/codebase/test_scope.rb +105 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +48 -34
- data/lib/hecks/hecks/adapters/console_capture.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +9 -1
- data/lib/hecks/hecks/adapters/dispatching/brief_text.rb +51 -0
- data/lib/hecks/hecks/adapters/dispatching/launcher.rb +97 -0
- data/lib/hecks/hecks/adapters/dispatching/plan.rb +82 -0
- data/lib/hecks/hecks/adapters/dispatching/rows.rb +66 -0
- data/lib/hecks/hecks/adapters/dispatching/settings.rb +81 -0
- data/lib/hecks/hecks/adapters/dispatching/settler.rb +40 -0
- data/lib/hecks/hecks/adapters/dispatching.adapter +7 -0
- data/lib/hecks/hecks/adapters/dispatching.rb +93 -0
- data/lib/hecks/hecks/adapters/doctor_reading/report.rb +27 -0
- data/lib/hecks/hecks/adapters/doctor_reading.adapter +7 -0
- data/lib/hecks/hecks/adapters/doctor_reading.rb +161 -0
- data/lib/hecks/hecks/adapters/git_checkout.rb +1 -1
- data/lib/hecks/hecks/adapters/git_commit.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_commit.rb +61 -0
- data/lib/hecks/hecks/adapters/git_landing/finisher.rb +82 -0
- data/lib/hecks/hecks/adapters/git_landing/foreign_hold.rb +111 -0
- data/lib/hecks/hecks/adapters/git_landing/line.rb +171 -0
- data/lib/hecks/hecks/adapters/git_landing/lineup.rb +98 -0
- data/lib/hecks/hecks/adapters/git_landing/merger.rb +1 -1
- data/lib/hecks/hecks/adapters/git_landing/numbering.rb +54 -0
- data/lib/hecks/hecks/adapters/git_landing/rivals.rb +59 -0
- data/lib/hecks/hecks/adapters/git_landing/ticket.rb +156 -0
- data/lib/hecks/hecks/adapters/git_landing/tidy.rb +147 -0
- data/lib/hecks/hecks/adapters/git_landing/train.rb +104 -0
- data/lib/hecks/hecks/adapters/git_landing/verdict.rb +64 -0
- data/lib/hecks/hecks/adapters/git_landing/waiting.rb +120 -0
- data/lib/hecks/hecks/adapters/git_landing.rb +106 -20
- data/lib/hecks/hecks/adapters/git_main_pull.rb +99 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/bench.rb +82 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/due.rb +84 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/impacted/skips.rb +47 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/impacted/spec_run.rb +75 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/impacted.rb +181 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/line.rb +76 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/reading.rb +24 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/record.rb +146 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/spec_map/corpus.rb +25 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/spec_map/needles.rb +61 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/spec_map.rb +105 -0
- data/lib/hecks/hecks/adapters/git_rehearsal/stages.rb +118 -0
- data/lib/hecks/hecks/adapters/git_rehearsal.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_rehearsal.rb +141 -0
- data/lib/hecks/hecks/adapters/git_train/bench.rb +15 -15
- data/lib/hecks/hecks/adapters/git_train/branch_guard.rb +62 -0
- data/lib/hecks/hecks/adapters/git_train/carriage.rb +27 -28
- data/lib/hecks/hecks/adapters/git_train/launcher_shape.rb +75 -0
- data/lib/hecks/hecks/adapters/git_train/mint_step.rb +47 -0
- data/lib/hecks/hecks/adapters/git_train/settlement.rb +167 -0
- data/lib/hecks/hecks/adapters/git_train.rb +73 -15
- data/lib/hecks/hecks/adapters/git_worktree_add.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_worktree_add.rb +68 -0
- data/lib/hecks/hecks/adapters/github_pull_requests.adapter +7 -0
- data/lib/hecks/hecks/adapters/github_pull_requests.rb +109 -0
- data/lib/hecks/hecks/adapters/host_http.rb +6 -6
- data/lib/hecks/hecks/adapters/in_process_boot.rb +10 -8
- data/lib/hecks/hecks/adapters/in_process_operations.rb +11 -17
- data/lib/hecks/hecks/adapters/in_process_undo.rb +17 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +13 -4
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +28 -15
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +17 -1
- data/lib/hecks/hecks/adapters/journal_store/held_domain/renaming.rb +45 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +31 -5
- data/lib/hecks/hecks/adapters/journal_store/held_mint.rb +128 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/attestation.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/readings/boot_status.rb +58 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/composition.rb +39 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/outcomes.rb +76 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/postgres_reader.rb +44 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/reader.rb +64 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/report.rb +102 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/sqlite_reader.rb +43 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/stores.rb +80 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/subscribers.rb +72 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace/summary.rb +39 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/event_trace.rb +39 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/command_line.rb +57 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/edge_guards.rb +107 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/inspection.rb +94 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/plan.rb +66 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/report.rb +72 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/store.rb +34 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/watermark_guards.rb +86 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan/world.rb +66 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/mint_plan.rb +36 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/projection_audit.rb +64 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/scaffolding.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/readings/sqlite_status/database_reading.rb +113 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/sqlite_status/read_only.rb +33 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/sqlite_status.rb +32 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +13 -1
- data/lib/hecks/hecks/adapters/journal_store/reconcile_report.rb +60 -0
- data/lib/hecks/hecks/adapters/journal_store/reconciliation.rb +121 -0
- data/lib/hecks/hecks/adapters/journal_store/translation_write/shape_edge.rb +59 -0
- data/lib/hecks/hecks/adapters/journal_store/translation_write.rb +123 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +16 -0
- data/lib/hecks/hecks/adapters/landed_work.adapter +7 -0
- data/lib/hecks/hecks/adapters/landed_work.rb +52 -0
- data/lib/hecks/hecks/adapters/local_files.rb +6 -6
- data/lib/hecks/hecks/adapters/process_pool.rb +3 -2
- data/lib/hecks/hecks/adapters/push_size.rb +57 -0
- data/lib/hecks/hecks/adapters/qa_quiescing.adapter +7 -0
- data/lib/hecks/hecks/adapters/qa_quiescing.rb +57 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +30 -3
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +63 -3
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +29 -6
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +11 -5
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +10 -1
- data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
- data/lib/hecks/hecks/adapters/settings_reading/disposable_ledger.rb +32 -0
- data/lib/hecks/hecks/adapters/settings_reading/push_nudge.rb +26 -0
- data/lib/hecks/hecks/adapters/settings_reading/stores.rb +76 -0
- data/lib/hecks/hecks/adapters/settings_reading/test_scope.rb +28 -0
- data/lib/hecks/hecks/adapters/settings_reading.rb +28 -2
- data/lib/hecks/hecks/adapters/started_work.rb +42 -0
- data/lib/hecks/hecks/adapters/terminal.rb +12 -10
- data/lib/hecks/hecks/adapters/where_reading.adapter +7 -0
- data/lib/hecks/hecks/adapters/where_reading.rb +64 -0
- data/lib/hecks/hecks/adapters/work_areas.rb +57 -0
- data/lib/hecks/hecks/adapters/work_limits.rb +57 -0
- data/lib/hecks/hecks/build/build.bluebook +576 -0
- data/lib/hecks/hecks/check/check.bluebook +966 -0
- data/lib/hecks/hecks/codebase.behaviors +61 -5
- data/lib/hecks/hecks/composer.behaviors +1 -1
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +33 -7
- data/lib/hecks/hecks/delegate/delegate.bluebook +355 -0
- data/lib/hecks/hecks/deliver/deliver.bluebook +945 -0
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/evolve/evolve.bluebook +788 -0
- data/lib/hecks/hecks/find/find.bluebook +81 -0
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.bluebook +0 -133
- data/lib/hecks/hecks/hecks.contextmap +2 -0
- data/lib/hecks/hecks/hecks.hecksagon +185 -29
- data/lib/hecks/hecks/hecks.world +35 -9
- data/lib/hecks/hecks/implicit_moves.json +24 -0
- data/lib/hecks/hecks/migrate/migrate.bluebook +1066 -0
- data/lib/hecks/hecks/operate/operate.bluebook +695 -0
- data/lib/hecks/hecks/quiesce/quiesce.bluebook +117 -0
- data/lib/hecks/hecks/rehearse/rehearse.bluebook +133 -0
- data/lib/hecks/hecks/release/release.bluebook +916 -0
- data/lib/hecks/hecks/serve/serve.bluebook +134 -0
- data/lib/hecks/hecks/shapes.json +1 -15
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/start/start.bluebook +194 -0
- data/lib/hecks/hecks/test/test.bluebook +638 -0
- data/lib/hecks/hecks/tickets.world +14 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +22 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +34 -0
- data/lib/hecks/language/bluebook/command.bluebook +35 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +14 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +174 -54
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +14 -0
- data/lib/hecks/language/kinds/run.kind +120 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +24 -0
- data/lib/hecks/module_folders.rb +31 -0
- data/lib/hecks/ports/key_vault.rb +1 -1
- data/lib/hecks/ports/persistence/append_only.rb +26 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +3 -1
- data/lib/hecks/ports/persistence/codec_boundary.rb +1 -1
- data/lib/hecks/ports/persistence/mint_door.rb +74 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +6 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +5 -1
- data/lib/hecks/ports/persistence/plugins/era/era_status.rb +48 -0
- data/lib/hecks/ports/persistence/plugins/era/projected_bluebook.rb +127 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/additive_shape.rb +107 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/compose.rb +155 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/compose_render.rb +79 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/edge_files.rb +49 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/edge_route.rb +61 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/implicit_edge.rb +67 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/reconcile.rb +130 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/reconcile_files.rb +35 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +13 -5
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +5 -0
- data/lib/hecks/ports/persistence/plugins/era.rb +1 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +11 -3
- data/lib/hecks/ports/persistence/stored_name_guard.rb +36 -0
- data/lib/hecks/ports/persistence.rb +2 -0
- data/lib/hecks/projections/ci_pipeline/table/checks.rb +137 -0
- data/lib/hecks/projections/ci_pipeline/table/lookup.rb +59 -0
- data/lib/hecks/projections/ci_pipeline/table/refusals.rb +58 -0
- data/lib/hecks/projections/ci_pipeline/table/rows.rb +119 -0
- data/lib/hecks/projections/ci_pipeline/table/tree.rb +30 -0
- data/lib/hecks/projections/ci_pipeline/table/typing.rb +92 -0
- data/lib/hecks/projections/ci_pipeline/table.rb +112 -0
- data/lib/hecks/projections/ci_pipeline/workflows.rb +162 -0
- data/lib/hecks/projections/ci_pipeline/yaml_text.rb +104 -0
- data/lib/hecks/projections/deploy/box/hosting.rb +1 -1
- data/lib/hecks/projections/deploy/box/roll_recipe.rb +1 -1
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +1 -1
- data/lib/hecks/projections/deploy/box.rb +1 -1
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +1 -1
- data/lib/hecks/projections/deploy/templates/lambda/Makefile.tmpl +3 -3
- data/lib/hecks/projections/undo.rb +46 -0
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/projector/cli_projector/claims.rb +11 -0
- data/lib/hecks/projector/cli_projector/command_help.rb +6 -1
- data/lib/hecks/projector/cli_projector/command_specs.rb +11 -1
- data/lib/hecks/projector/cli_projector/completion.rb +87 -0
- data/lib/hecks/projector/cli_projector/everyday.rb +40 -0
- data/lib/hecks/projector/cli_projector/json_listing.rb +77 -0
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/modules.rb +39 -0
- data/lib/hecks/projector/cli_projector/query_specs.rb +1 -0
- data/lib/hecks/projector/cli_projector/renames.rb +58 -0
- data/lib/hecks/projector/cli_projector/search.rb +74 -0
- data/lib/hecks/projector/cli_projector/usage.rb +23 -6
- data/lib/hecks/projector/cli_projector.rb +24 -1
- data/lib/hecks/quality_control/adapters/ci_head.adapter +8 -0
- data/lib/hecks/quality_control/adapters/image_roll.adapter +9 -0
- data/lib/hecks/quality_control/adapters/image_roll.rb +158 -0
- data/lib/hecks/quality_control/adapters/main_maintenance.adapter +8 -0
- data/lib/hecks/quality_control/adapters/main_maintenance.rb +275 -0
- data/lib/hecks/quality_control/adapters/pump_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/pump_tools.rb +18 -0
- data/lib/hecks/quality_control/adapters/push_due_reader.adapter +8 -0
- data/lib/hecks/quality_control/adapters/push_due_reader.rb +99 -0
- data/lib/hecks/quality_control/adapters/qa_auto_mint.rb +183 -0
- data/lib/hecks/quality_control/adapters/qa_era_mint.rb +94 -0
- data/lib/hecks/quality_control/adapters/qa_quiesce_marker.rb +126 -0
- data/lib/hecks/quality_control/adapters/run_keys.rb +36 -0
- data/lib/hecks/quality_control/adapters/run_reader.adapter +7 -0
- data/lib/hecks/quality_control/adapters/run_reader.rb +188 -0
- data/lib/hecks/quality_control/cli/child.rb +1 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains/scanning.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_event_pump/attached.rb +67 -0
- data/lib/hecks/quality_control/cli/qa_event_pump/consumer.rb +103 -0
- data/lib/hecks/quality_control/cli/qa_event_pump/cursors.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_event_pump/reactions.rb +46 -0
- data/lib/hecks/quality_control/cli/qa_event_pump/reader.rb +30 -0
- data/lib/hecks/quality_control/cli/qa_event_pump.rb +76 -0
- data/lib/hecks/quality_control/cli/qa_fix_main_bug/publishing.rb +79 -0
- data/lib/hecks/quality_control/cli/qa_fix_main_bug.rb +167 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains/evaluation.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_main_gate/gate_run.rb +52 -0
- data/lib/hecks/quality_control/cli/qa_main_gate/logging.rb +70 -0
- data/lib/hecks/quality_control/cli/qa_main_gate/shell.rb +47 -0
- data/lib/hecks/quality_control/cli/qa_main_gate/sweeping.rb +59 -0
- data/lib/hecks/quality_control/cli/qa_main_gate/worktree.rb +62 -0
- data/lib/hecks/quality_control/cli/qa_main_gate.rb +135 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_pr_check/checking.rb +2 -3
- data/lib/hecks/quality_control/cli/qa_push_nudge.rb +48 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles/seed.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/seat.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_tick/event_pump_step.rb +34 -0
- data/lib/hecks/quality_control/cli/qa_tick/output.rb +16 -1
- data/lib/hecks/quality_control/cli/qa_tick/push_nudge_step.rb +28 -0
- data/lib/hecks/quality_control/cli/qa_tick/quiesce_guard.rb +51 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +36 -8
- data/lib/hecks/quality_control/quality_control.behaviors +65 -0
- data/lib/hecks/quality_control/quality_control.bluebook +1131 -11
- data/lib/hecks/quality_control/quality_control.hecksagon +11 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +138 -0
- data/lib/hecks/query_ir/touchpoints.rb +5 -7
- data/lib/hecks/refused.rb +21 -0
- data/lib/hecks/release/approval.rb +6 -3
- data/lib/hecks/release/conditions.rb +46 -0
- data/lib/hecks/release/runner/gem_publisher.rb +4 -4
- data/lib/hecks/release/version_commit.rb +48 -0
- data/lib/hecks/runtime/command_interpreter/hydration.rb +5 -4
- data/lib/hecks/runtime/command_interpreter/persisting.rb +11 -3
- data/lib/hecks/runtime/command_interpreter.rb +2 -2
- data/lib/hecks/runtime/command_rules/admissibility.rb +0 -2
- data/lib/hecks/runtime/command_rules/emission.rb +0 -1
- data/lib/hecks/runtime/dependency_planning/expression_reads.rb +0 -1
- data/lib/hecks/runtime/dispatcher/planning.rb +24 -0
- data/lib/hecks/runtime/dispatcher/routing.rb +6 -0
- data/lib/hecks/runtime/dispatcher/value_routing.rb +54 -0
- data/lib/hecks/runtime/dispatcher.rb +7 -1
- data/lib/hecks/runtime/dry_run_plan.rb +60 -0
- data/lib/hecks/runtime/entity_element/location.rb +0 -1
- data/lib/hecks/runtime/entity_element/mutations.rb +0 -1
- data/lib/hecks/runtime/entity_interpreter/locating.rb +5 -0
- data/lib/hecks/runtime/entity_interpreter.rb +0 -1
- data/lib/hecks/runtime/errors.rb +26 -0
- data/lib/hecks/runtime/instance.rb +0 -1
- data/lib/hecks/runtime/invocation_builder.rb +0 -1
- data/lib/hecks/runtime/outbox/consumer_runner.rb +9 -1
- data/lib/hecks/runtime/outbox/relay.rb +25 -4
- data/lib/hecks/runtime/outbox/runnable.rb +3 -1
- data/lib/hecks/runtime/outbox.rb +6 -2
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +18 -2
- data/lib/hecks/runtime/policy_interpreter.rb +17 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +21 -7
- data/lib/hecks/runtime/query_interpreter/arguments.rb +6 -5
- data/lib/hecks/runtime/reaction_invocation.rb +0 -1
- data/lib/hecks/runtime/refusal_wording.rb +12 -1
- data/lib/hecks/runtime/registry/context_maps.rb +1 -5
- data/lib/hecks/runtime/registry/declarations.rb +34 -4
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +15 -1
- data/lib/hecks/runtime/registry/runtime_state.rb +10 -0
- data/lib/hecks/runtime/registry/world_defaults.rb +24 -2
- data/lib/hecks/runtime/registry/world_names.rb +99 -0
- data/lib/hecks/runtime/registry.rb +33 -27
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +10 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +29 -3
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +0 -1
- data/lib/hecks/runtime/saga_interpreter/lifecycle.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +46 -2
- data/lib/hecks/runtime/value/coercion.rb +36 -8
- data/lib/hecks/rust_build/conformance.rb +16 -5
- data/lib/hecks/rust_build/parser.rb +25 -0
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +45 -12
- data/lib/hecks/rust_build/project_rust/declared.rb +80 -0
- data/lib/hecks/rust_build/project_rust/features_header.rb +22 -0
- data/lib/hecks/rust_build/project_rust/manifest.rb +96 -0
- data/lib/hecks/rust_build/project_rust/meta.rb +36 -0
- data/lib/hecks/rust_build/project_rust.rb +28 -90
- data/lib/hecks/rust_build/write_if_changed.rb +19 -4
- data/lib/hecks/rust_build.rb +2 -1
- data/lib/hecks/rust_format.rb +32 -2
- data/lib/hecks/secrets.rb +111 -0
- data/lib/hecks/shape_ledger/edge_hygiene.rb +170 -0
- data/lib/hecks/shape_ledger/scope.rb +51 -0
- data/lib/hecks/shape_ledger/status.rb +89 -0
- data/lib/hecks/shape_ledger.rb +78 -33
- data/lib/hecks/site/bluebook/site.bluebook +1 -0
- data/lib/hecks/storehouse/audit.rb +34 -4
- data/lib/hecks/storehouse/callers.rb +8 -0
- data/lib/hecks/storehouse/dispatching.rb +14 -3
- data/lib/hecks/storehouse/identity.rb +41 -0
- data/lib/hecks/storehouse/querying.rb +7 -7
- data/lib/hecks/storehouse.rb +28 -10
- data/lib/hecks/tickets/bluebook/adapters/work_areas.adapter +7 -0
- data/lib/hecks/tickets/bluebook/adapters/work_limits.adapter +7 -0
- data/lib/hecks/tickets/bluebook/tickets.behaviors +70 -0
- data/lib/hecks/tickets/bluebook/tickets.bluebook +323 -28
- data/lib/hecks/tickets/bluebook/tickets.hecksagon +7 -0
- data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +17 -0
- data/lib/hecks/{cli → tools}/behaviors.rb +1 -1
- data/lib/hecks/{cli → tools}/canonicalise.rb +1 -1
- data/lib/hecks/{cli → tools}/check_engine_agreement.rb +1 -1
- data/lib/hecks/{cli → tools}/check_era.rb +6 -6
- data/lib/hecks/tools/ci_gates.rb +8 -2
- data/lib/hecks/tools/ci_workflows.rb +98 -0
- data/lib/hecks/{cli → tools}/console.rb +1 -1
- data/lib/hecks/{cli → tools}/deploy_template_diff.rb +1 -1
- data/lib/hecks/{cli → tools}/doc_coverage.rb +2 -2
- data/lib/hecks/{cli → tools}/document.rb +1 -1
- data/lib/hecks/{cli → tools}/domain_stub.rb +1 -1
- data/lib/hecks/{cli → tools}/domain_writer.rb +1 -1
- data/lib/hecks/tools/downstream_model_check.rb +122 -0
- data/lib/hecks/tools/evolve_run.rb +2 -2
- data/lib/hecks/{cli → tools}/expected_era.rb +1 -1
- data/lib/hecks/{cli → tools}/expression_projection.rb +1 -1
- data/lib/hecks/{cli → tools}/follow.rb +1 -1
- data/lib/hecks/tools/fuzz_sweep/options.rb +40 -5
- data/lib/hecks/tools/fuzz_sweep/pool.rb +12 -1
- data/lib/hecks/tools/fuzz_sweep/shards.rb +155 -0
- data/lib/hecks/tools/fuzz_sweep.rb +11 -5
- data/lib/hecks/tools/gate.rb +38 -29
- data/lib/hecks/tools/gate_memo/local_files.rb +33 -0
- data/lib/hecks/tools/gate_memo/rehearsal.rb +54 -0
- data/lib/hecks/tools/gate_memo/ruby_files.rb +152 -0
- data/lib/hecks/tools/gate_memo/session.rb +114 -0
- data/lib/hecks/tools/gate_memo/standalone.rb +67 -0
- data/lib/hecks/tools/gate_memo.rb +196 -0
- data/lib/hecks/tools/gate_report.rb +56 -0
- data/lib/hecks/{cli → tools}/history.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_agent.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_aggregate.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_draft.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_record.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_run.rb +2 -2
- data/lib/hecks/{cli → tools}/interview_session/agent_state.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_session/catalog.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_session/manual_findings.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_session/proposals.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_session.rb +2 -2
- data/lib/hecks/{cli → tools}/interview_shape/lifecycle.rb +1 -1
- data/lib/hecks/{cli → tools}/interview_shape.rb +1 -1
- data/lib/hecks/{cli → tools}/ir.rb +1 -1
- data/lib/hecks/tools/lanes/files.rb +1 -1
- data/lib/hecks/tools/lanes/watch_workflow.rb +6 -8
- data/lib/hecks/tools/lanes/workflow.rb +1 -1
- data/lib/hecks/tools/lanes.rb +1 -1
- data/lib/hecks/{cli → tools}/mcp.rb +1 -1
- data/lib/hecks/{cli → tools}/model_check/examination.rb +1 -1
- data/lib/hecks/{cli → tools}/model_check/sources.rb +1 -1
- data/lib/hecks/{cli → tools}/model_check.rb +1 -1
- data/lib/hecks/tools/move_disables/reasons.rb +35 -0
- data/lib/hecks/tools/move_disables.rb +211 -0
- data/lib/hecks/tools/move_former_name.rb +105 -0
- data/lib/hecks/{cli → tools}/pattern_cases.rb +1 -1
- data/lib/hecks/{cli → tools}/persistence_notice.rb +1 -1
- data/lib/hecks/{cli → tools}/present.rb +1 -1
- data/lib/hecks/{cli → tools}/project_cli/launcher_source.rb +32 -22
- data/lib/hecks/{cli → tools}/project_cli/templates.rb +22 -15
- data/lib/hecks/{cli → tools}/project_cli.rb +11 -7
- data/lib/hecks/{cli → tools}/project_diagrams.rb +3 -3
- data/lib/hecks/{cli → tools}/project_glossary.rb +2 -2
- data/lib/hecks/{cli → tools}/reference.rb +2 -2
- data/lib/hecks/{cli → tools}/refresh_projections.rb +1 -1
- data/lib/hecks/{cli → tools}/refresh_rspec_runtime_baseline.rb +1 -1
- data/lib/hecks/tools/regeneration_run/pool.rb +148 -0
- data/lib/hecks/tools/regeneration_run/scratch.rb +76 -0
- data/lib/hecks/tools/regeneration_run.rb +125 -103
- data/lib/hecks/{cli → tools}/release.rb +1 -1
- data/lib/hecks/{cli → tools}/release_gem.rb +1 -1
- data/lib/hecks/{cli → tools}/rspec_io_parallel_files.rb +1 -1
- data/lib/hecks/{cli → tools}/rspec_shard_files.rb +1 -1
- data/lib/hecks/{cli → tools}/run.rb +9 -2
- data/lib/hecks/{cli → tools}/run_expectations.rb +1 -1
- data/lib/hecks/{cli → tools}/seed_semantics_corpus.rb +4 -4
- data/lib/hecks/{cli → tools}/serve_query_ir_mcp.rb +1 -1
- data/lib/hecks/{cli → tools}/shape.rb +3 -2
- data/lib/hecks/{cli → tools}/smoke_http/checks.rb +1 -1
- data/lib/hecks/{cli → tools}/smoke_http/settings.rb +1 -1
- data/lib/hecks/{cli → tools}/smoke_http/transport.rb +1 -1
- data/lib/hecks/{cli → tools}/smoke_http.rb +1 -1
- data/lib/hecks/{cli → tools}/smoke_test.rb +1 -1
- data/lib/hecks/{cli → tools}/spec_example.rb +1 -1
- data/lib/hecks/tools/stage_progress.rb +54 -0
- data/lib/hecks/{cli → tools}/statements.rb +1 -1
- data/lib/hecks/{cli → tools}/stores.rb +1 -1
- data/lib/hecks/{cli → tools}/stress_concurrency_specs/reporting.rb +1 -1
- data/lib/hecks/{cli → tools}/stress_concurrency_specs/usage.rb +4 -4
- data/lib/hecks/{cli → tools}/stress_concurrency_specs.rb +3 -3
- data/lib/hecks/tools/tenant_provisioning.rb +23 -0
- data/lib/hecks/tools/tools_doc.rb +10 -8
- data/lib/hecks/tools/translation_audit/edges.rb +21 -16
- data/lib/hecks/tools/translation_audit/verdicts.rb +1 -1
- data/lib/hecks/tools/translation_audit.rb +1 -1
- data/lib/hecks/tools/translation_scaffold.rb +1 -1
- data/lib/hecks/tools.rb +4 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +61 -31
- data/lib/hecks/workflow_pagination/judge.rb +48 -0
- data/lib/hecks/workflow_pagination/statement.rb +50 -0
- data/lib/hecks/workflow_pagination.rb +58 -0
- data/lib/hecks.rb +2 -0
- data/lib/rubocop/cop/hecks/bare_event_and_query_names.rb +58 -0
- data/lib/rubocop/cop/hecks/correlates_by_aggregate.rb +39 -0
- data/lib/rubocop/cop/hecks/creating_command_sets.rb +75 -0
- data/lib/rubocop/cop/hecks/invariant_expression_language.rb +47 -0
- data/lib/rubocop/cop/hecks/lifecycle_needs_attribute.rb +58 -0
- data/lib/rubocop/cop/hecks/no_id_string_attribute.rb +49 -0
- data/lib/rubocop/cop/hecks/one_terminal_mark_per_lifecycle.rb +47 -0
- data/lib/rubocop/cop/hecks/value_object_shorthand.rb +56 -0
- data/prebuilt/13c04bdc5970295b310f0a980d397ba722232c8da99cc1a0cdb383249921e871.marshal +0 -0
- data/prebuilt/e3857b77f47d9ecc1787b2e1f5136364f3cb3444ef4aea5479f3b98167affea2.marshal +0 -0
- data/qa/settings.yml +23 -0
- data/rust/.gitignore +0 -6
- data/rust/Cargo.toml +2 -1
- data/rust/build/Cargo.toml +1 -1
- data/rust/build/src/binding_pass.rs +57 -4
- data/rust/build/src/pipeline.rs +6 -4
- data/rust/build/src/resolve.rs +1 -0
- data/rust/codegen/Cargo.toml +1 -1
- data/rust/codegen/src/domain_generator.rs +3 -0
- data/rust/codegen/src/json_codec.rs +182 -23
- data/rust/codegen/src/one_of_references.rs +1 -0
- data/rust/codegen/src/queries.rs +20 -2
- data/rust/codegen/src/registry.rs +21 -5
- data/rust/expr/Cargo.toml +1 -1
- data/rust/host/Cargo.toml +1 -1
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/core/Cargo.toml +1 -1
- data/rust/host/src/bin/mint_harness.rs +1 -0
- data/rust/host/src/boot.rs +1 -0
- data/rust/host/src/store/mint.rs +40 -6
- data/rust/lsp/Cargo.toml +1 -1
- data/rust/parser/Cargo.toml +1 -1
- data/rust/parser/src/build/correlation_keys.rs +125 -0
- data/rust/parser/src/build/mod.rs +1 -0
- data/rust/parser/src/emit.rs +55 -3
- data/rust/parser/src/ir.rs +33 -1
- data/rust/parser/src/keywords.rs +289 -1
- data/rust/parser/src/main.rs +2 -0
- data/rust/parser/src/parse/aggregate.rs +10 -1
- data/rust/parser/src/parse/audience.rs +65 -0
- data/rust/parser/src/parse/command.rs +14 -0
- data/rust/parser/src/parse/declaration.rs +97 -0
- data/rust/parser/src/parse/domain_port.rs +6 -0
- data/rust/parser/src/parse/mod.rs +1 -0
- data/rust/parser/src/parse/process_manager.rs +17 -10
- data/rust/parser/src/parse/world.rs +12 -8
- data/rust/parser/src/render_ruby.rs +9 -0
- data/rust/parser/src/sentence/format.rs +3 -0
- data/rust/parser/src/sentence/process.rs +6 -2
- data/rust/src/exemplar/json.rs +17 -0
- data/rust/src/kernel/cli.rs +27 -3
- data/rust/src/kernel/dispatch.rs +4 -2
- data/rust/src/kernel/json.rs +62 -0
- data/rust/src/kernel/orchestrate.rs +28 -3
- data/rust/src/kernel/orchestrate_guard_tests.rs +12 -12
- data/rust/web/Cargo.toml +1 -1
- metadata +389 -96
- data/lib/hecks/cli/command_table.rb +0 -72
- data/lib/hecks/cli/launcher_words.rb +0 -168
- data/lib/hecks/cli.rb +0 -133
- data/lib/hecks/hecks/agents.bluebook +0 -246
- data/lib/hecks/hecks/codebase.bluebook +0 -3209
- data/lib/hecks/hecks/custodian.bluebook +0 -1300
- data/lib/hecks/hecks/ledger.bluebook +0 -190
- data/lib/hecks/hecks/rehearsal.bluebook +0 -437
- data/lib/hecks/hecks/tooling.bluebook +0 -1234
- data/lib/hecks/hecks/translations/2-148748.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-15a2e4.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-1b447c.bluebook +0 -5
- data/lib/hecks/hecks/translations/2-240474.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-44446d.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-46ca26.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-473eab.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-75cc28.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-781c07.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-84c18b.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-aa5fff.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-bfdb48.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-cd96d8.bluebook +0 -10
- data/lib/hecks/hecks/translations/2-d43f71.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-eea577.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-f53248.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-fbb677.bluebook +0 -2
- data/lib/hecks/hecks/translations/2-ffdf12.bluebook +0 -6
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +0 -36
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +0 -56
- data/prebuilt/31cf81e12f16236ad26249be590cfea3799c9db67432e6a4ef54b801c355b6ae.marshal +0 -0
- data/prebuilt/68ecb2bd3abb1a221007db4f2b0375834c13aaccac8f2b99778e8517f11aa113.marshal +0 -0
- data/prebuilt/verdicts-0328c2403383837d780ad5ed81d1da351c56436b894cf9015b5d56b8f1f1bf10.json +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require "delegate"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
# The values a world hands an adapter under a `secret` declaration, held by name so any text a
|
|
5
|
+
# launcher prints can be scrubbed of them: a refusal, a boot note, a settings line, a result or
|
|
6
|
+
# an exception's message. The adapter that declared the secret gets the value; whatever a person
|
|
7
|
+
# or a log sees gets `«secret:NAME»`. Redaction follows the declaration, not a guess at a
|
|
8
|
+
# value's shape, with one addition: the password inside a `database` URL, which the URL
|
|
9
|
+
# carries without any adapter declaring it.
|
|
10
|
+
module Secrets
|
|
11
|
+
MASK = "«secret:%s»".freeze
|
|
12
|
+
PASSWORD = %r{://[^:/@\s]+:([^@\s]+)@}
|
|
13
|
+
|
|
14
|
+
@held = {}
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Holds one secret value under its name. An empty or non-String value is nothing to hide.
|
|
20
|
+
#
|
|
21
|
+
# @param name [#to_s] the declared name
|
|
22
|
+
# @param value [Object] the value the world supplied
|
|
23
|
+
# @return [void]
|
|
24
|
+
def register(name, value)
|
|
25
|
+
return unless value.is_a?(String) && !value.empty?
|
|
26
|
+
|
|
27
|
+
@mutex.synchronize { @held[value] = name.to_s }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Holds every secret an adapter declares out of the settings it is built with, and the
|
|
31
|
+
# password of its `database` URL when the URL carries one.
|
|
32
|
+
#
|
|
33
|
+
# @param adapter [Bluebook::Adapter, nil] the adapter's declaration; nil holds nothing
|
|
34
|
+
# @param settings [Hash] the settings the world supplied for it
|
|
35
|
+
# @return [void]
|
|
36
|
+
def register_settings(adapter, settings)
|
|
37
|
+
Array(adapter&.secrets).each { |secret| register(secret, settings[secret.to_sym]) }
|
|
38
|
+
register_database(settings[:database])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Holds the password a `scheme://user:password@host` URL carries, under a fixed name.
|
|
42
|
+
#
|
|
43
|
+
# @param url [Object] the database setting; anything but a URL with a password holds nothing
|
|
44
|
+
# @return [void]
|
|
45
|
+
def register_database(url)
|
|
46
|
+
return unless url.is_a?(String) && (found = url.match(PASSWORD))
|
|
47
|
+
|
|
48
|
+
register("database password", found[1])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Replaces every held value in the text with its mask, longest value first so a value that
|
|
52
|
+
# contains another is masked whole.
|
|
53
|
+
#
|
|
54
|
+
# @param text [Object] what is about to be printed; a non-String is returned as it is
|
|
55
|
+
# @return [Object] the text with each held value masked
|
|
56
|
+
def redact(text)
|
|
57
|
+
return text unless text.is_a?(String)
|
|
58
|
+
|
|
59
|
+
held = @mutex.synchronize { @held.dup }
|
|
60
|
+
held.keys.sort_by { |value| -value.length }
|
|
61
|
+
.reduce(text) { |scrubbed, value| scrubbed.gsub(value) { format(MASK, held[value]) } }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Forgets every held value, for a process that boots another domain.
|
|
65
|
+
# @return [void]
|
|
66
|
+
def clear! = @mutex.synchronize { @held.clear }
|
|
67
|
+
|
|
68
|
+
# Whether any value is held.
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def any? = @mutex.synchronize { !@held.empty? }
|
|
71
|
+
|
|
72
|
+
# The stream, writing through `redact`; a stream already guarded is returned as it is.
|
|
73
|
+
#
|
|
74
|
+
# @param io [IO, #write] the stream to guard
|
|
75
|
+
# @return [Guarded] the guarded stream
|
|
76
|
+
def guarded(io) = io.is_a?(Guarded) ? io : Guarded.new(io)
|
|
77
|
+
|
|
78
|
+
# Guards the process's own output streams, so a `puts`, `warn` or `abort` anywhere in it
|
|
79
|
+
# prints no held value. A launcher calls it once, right after it loads the gem.
|
|
80
|
+
#
|
|
81
|
+
# @return [void]
|
|
82
|
+
def guard!
|
|
83
|
+
$stdout = guarded($stdout)
|
|
84
|
+
$stderr = guarded($stderr)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# A stream whose every write passes through `Secrets.redact`. A value split across two
|
|
88
|
+
# writes is not caught, which the one-call writes `puts`, `warn` and `abort` make never
|
|
89
|
+
# happens.
|
|
90
|
+
class Guarded < SimpleDelegator
|
|
91
|
+
# @param parts [Array<#to_s>] what is written
|
|
92
|
+
# @return [Integer] the bytes written
|
|
93
|
+
def write(*parts) = __getobj__.write(*parts.map { |part| Secrets.redact(part.to_s) })
|
|
94
|
+
|
|
95
|
+
# @param parts [Array<#to_s>] the lines; an Array prints one line per element
|
|
96
|
+
# @return [nil]
|
|
97
|
+
def puts(*parts) = __getobj__.puts(*parts.flatten.map { |part| Secrets.redact(part.to_s) })
|
|
98
|
+
|
|
99
|
+
# @param parts [Array<#to_s>] what is printed
|
|
100
|
+
# @return [nil]
|
|
101
|
+
def print(*parts) = __getobj__.print(*parts.map { |part| Secrets.redact(part.to_s) })
|
|
102
|
+
|
|
103
|
+
# @param part [#to_s] what is appended
|
|
104
|
+
# @return [Guarded] self
|
|
105
|
+
def <<(part)
|
|
106
|
+
write(part)
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module ShapeLedger
|
|
7
|
+
# Finds the translation edges that no longer earn their file; it deletes nothing. The graph is
|
|
8
|
+
# a bluebook's edge files plus its recorded implicit moves, aimed at the current shape. A file is:
|
|
9
|
+
#
|
|
10
|
+
# - a **self-loop** when its `from` equals its `to`.
|
|
11
|
+
# - a **fork** branch when two edges leave a label and both reach the current shape; the branch
|
|
12
|
+
# with the fewest steps is kept.
|
|
13
|
+
# - an **orphan** when it lies on no path from a held era (the ledger label, an implicit move's
|
|
14
|
+
# start, or the start of a chain) to the current shape. A sole route is never flagged.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# EdgeHygiene.findings(dir, ledger: {}, shapes: { "Shop" => "bbbbbb1" }, moves: {})
|
|
18
|
+
module EdgeHygiene
|
|
19
|
+
# One edge: the file that declares it (nil for a recorded implicit move) and its two labels.
|
|
20
|
+
Edge = Struct.new(:path, :from, :to)
|
|
21
|
+
|
|
22
|
+
# The list beside the ledger of stale edges already known, each a finding key. A listed edge is
|
|
23
|
+
# reported as a warning and does not fail the check; any other stale edge fails it.
|
|
24
|
+
KNOWN_FILE = "known_stale_edges.json"
|
|
25
|
+
|
|
26
|
+
# One problem: its kind, the bluebook, the file to remove, a stable key naming the edge, and what
|
|
27
|
+
# to do.
|
|
28
|
+
Finding = Struct.new(:kind, :domain, :path, :key, :message, keyword_init: true)
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# Raises on each stale edge the known list does not name, and warns of those it does.
|
|
33
|
+
#
|
|
34
|
+
# @param directory [String] the domain directory holding `translations/`
|
|
35
|
+
# @return [String] a warning line for each known stale edge, or empty
|
|
36
|
+
# @raise [ShapeLedger::Failure] naming each other stale edge and the `git rm` that clears it
|
|
37
|
+
def report(directory, **world)
|
|
38
|
+
known = known_stale(File.join(directory, KNOWN_FILE))
|
|
39
|
+
listed, fresh = findings(directory, **world).partition { |finding| known.include?(finding.key) }
|
|
40
|
+
raise Failure, lines(fresh).join("\n") unless fresh.empty?
|
|
41
|
+
|
|
42
|
+
listed.map { |finding| "\nwarning, known stale edge (#{KNOWN_FILE}) #{lines([finding]).first}" }.join
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Array<String>] the finding keys of the known stale edges, empty when there is no list
|
|
46
|
+
def known_stale(path) = File.exist?(path) ? JSON.parse(File.read(path)) : []
|
|
47
|
+
|
|
48
|
+
# @param directory [String] the domain directory holding `translations/`
|
|
49
|
+
# @param ledger [Hash{String => String}] the committed shape ledger
|
|
50
|
+
# @param shapes [Hash{String => String}] each bluebook's current storage-shape hash
|
|
51
|
+
# @param moves [Hash{String => Array<Hash>}] the recorded implicit moves
|
|
52
|
+
# @return [Array<Finding>] every stale edge file, by bluebook
|
|
53
|
+
def findings(directory, ledger:, shapes:, moves:)
|
|
54
|
+
files = Translation::EdgeFiles.scan(directory)
|
|
55
|
+
shapes.flat_map do |name, hash|
|
|
56
|
+
recorded = moves.fetch(name, [])
|
|
57
|
+
domain_findings(name, edges_of(files, name) + implicit_edges(recorded), ShapeLedger.label(hash),
|
|
58
|
+
held_labels(ledger[name], recorded))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def edges_of(files, name)
|
|
63
|
+
files.select { |found| found.domain == name }.map { |f| Edge.new(f.path, f.from, f.to) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [Array<String>] one report line per finding, each telling what to do
|
|
67
|
+
def lines(findings) = findings.map { |finding| "#{finding.domain}: #{finding.message}" }
|
|
68
|
+
|
|
69
|
+
# @return [String] the stable name of the edge a finding is about, as `kind:Bluebook:from>to`
|
|
70
|
+
def key(kind, name, edge) = "#{kind}:#{name}:#{edge.from}>#{edge.to}"
|
|
71
|
+
|
|
72
|
+
def implicit_edges(moves) = moves.map { |move| Edge.new(nil, move["from"], move["to"]) }
|
|
73
|
+
|
|
74
|
+
def held_labels(held, moves)
|
|
75
|
+
[(ShapeLedger.label(held) if held), *moves.map { |move| move["from"] }].compact
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def domain_findings(name, all, current, held)
|
|
79
|
+
loops, graph = all.partition { |edge| edge.from == edge.to && edge.path }
|
|
80
|
+
forks = fork_findings(name, graph, current)
|
|
81
|
+
route = held + entry_labels(graph)
|
|
82
|
+
loops.map { |edge| loop_finding(name, edge) } + forks +
|
|
83
|
+
orphan_findings(name, graph, current, route, forks.map(&:path))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def loop_finding(name, edge)
|
|
87
|
+
Finding.new(kind: :self_loop, domain: name, path: edge.path, key: key(:self_loop, name, edge),
|
|
88
|
+
message: "self-loop edge #{edge.from} -> #{edge.to} in #{edge.path}; remove it with " \
|
|
89
|
+
"`git rm #{edge.path}`")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def fork_findings(name, graph, current)
|
|
93
|
+
graph.group_by(&:from).flat_map do |label, leaving|
|
|
94
|
+
live = leaving.select { |edge| leads_to?(edge.to, current, graph) }
|
|
95
|
+
live.size > 1 ? fork_branch_findings(name, label, live, graph, current) : []
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def fork_branch_findings(name, label, live, graph, current)
|
|
100
|
+
keep = live.min_by { |edge| [steps(edge.to, current, graph), edge.path ? 0 : 1] }
|
|
101
|
+
(live - [keep]).map do |branch|
|
|
102
|
+
files = branch_files(branch, keep, graph)
|
|
103
|
+
Finding.new(kind: :fork, domain: name, path: files.first, key: key(:fork, name, branch),
|
|
104
|
+
message: fork_message(label, live, keep, branch, files))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def fork_message(label, live, keep, branch, files)
|
|
109
|
+
leaving = live.map { |edge| "#{edge.from} -> #{edge.to}" }.join(", ")
|
|
110
|
+
"fork at #{label}: #{live.size} edges leave it and each reaches the current shape (#{leaving}); " \
|
|
111
|
+
"keep #{keep.from} -> #{keep.to} and #{removal(branch, files)}, or compose the branch into one edge"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def removal(branch, files)
|
|
115
|
+
return "remove the recorded implicit move #{branch.from} -> #{branch.to} from implicit_moves.json" if files.empty?
|
|
116
|
+
|
|
117
|
+
"remove the branch with `git rm #{files.join(" ")}`"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The files only the branch carries: edges leaving a label the branch reaches that the kept
|
|
121
|
+
# route does not also reach.
|
|
122
|
+
def branch_files(branch, keep, graph)
|
|
123
|
+
shared = reached_from(keep.to, graph)
|
|
124
|
+
own = reached_from(branch.to, graph) - shared
|
|
125
|
+
[branch, *graph.select { |edge| own.include?(edge.from) }].filter_map(&:path).uniq
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def orphan_findings(name, graph, current, starts, flagged)
|
|
129
|
+
carried = starts.flat_map { |label| reached_from(label, graph) }
|
|
130
|
+
graph.select(&:path).reject { |edge| flagged.include?(edge.path) || carried_to?(edge, carried, current, graph) }
|
|
131
|
+
.map { |edge| orphan_finding(name, edge, current) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def carried_to?(edge, carried, current, graph) = carried.include?(edge.from) && leads_to?(edge.to, current, graph)
|
|
135
|
+
|
|
136
|
+
def orphan_finding(name, edge, current)
|
|
137
|
+
Finding.new(kind: :orphan, domain: name, path: edge.path, key: key(:orphan, name, edge),
|
|
138
|
+
message: "orphan edge #{edge.from} -> #{edge.to} in #{edge.path} lies on no route from a " \
|
|
139
|
+
"held era to the current shape #{current}; remove it with `git rm #{edge.path}`")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @return [Array<String>] labels no edge leads into: the first shape of each chain
|
|
143
|
+
def entry_labels(graph) = graph.map(&:from).uniq - graph.map(&:to)
|
|
144
|
+
|
|
145
|
+
def leads_to?(label, current, graph) = label == current || reached_from(label, graph).include?(current)
|
|
146
|
+
|
|
147
|
+
# @return [Array<String>] every label a chain of edges leads to from one label, itself included
|
|
148
|
+
def reached_from(label, graph)
|
|
149
|
+
seen = [label]
|
|
150
|
+
seen.each { |at| graph.each { |edge| seen << edge.to if edge.from == at && !seen.include?(edge.to) } }
|
|
151
|
+
seen
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @return [Integer, Float] the fewest edges from one label to another, infinite when none
|
|
155
|
+
def steps(label, current, graph)
|
|
156
|
+
distance = { label => 0 }
|
|
157
|
+
graph.size.times { widen(distance, graph) }
|
|
158
|
+
distance.fetch(current, Float::INFINITY)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def widen(distance, graph)
|
|
162
|
+
graph.each do |edge|
|
|
163
|
+
next unless distance.key?(edge.from)
|
|
164
|
+
|
|
165
|
+
distance[edge.to] = [distance[edge.to], distance[edge.from] + 1].compact.min
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module ShapeLedger
|
|
5
|
+
# Which bluebooks the ledger holds. A bluebook is held when its committed persistence is an
|
|
6
|
+
# era-managed adapter, or when it is a framework member such as Governance, which clients
|
|
7
|
+
# persist. Every other bluebook is tooling, Memory by its committed world (ADR 0136), with no
|
|
8
|
+
# database whose eras a shape change could strand.
|
|
9
|
+
module Scope
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# @param directory [String] the domain directory
|
|
13
|
+
# @return [Runtime::Registry] its bluebooks as committed: no local or environment overlay
|
|
14
|
+
def committed_registry(directory)
|
|
15
|
+
Adapters::FolderOverlay.committed_only { Hecks.describe(directory, environment: nil).registry }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param registry [Runtime::Registry] the committed registry
|
|
19
|
+
# @return [Array(Array<String>, Hash{String => String})] the names the ledger holds, and each
|
|
20
|
+
# skipped bluebook's name mapped to its committed adapter
|
|
21
|
+
def of(registry)
|
|
22
|
+
held, skipped = registry.bluebooks.partition { |name, bluebook| held?(registry, name, bluebook) }
|
|
23
|
+
[held.map(&:first), skipped.to_h { |name, bluebook| [name, adapter_of(registry, name, bluebook)] }]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Array(Hash, Hash, Hash)] the shapes and the ledger entries of the bluebooks held,
|
|
27
|
+
# and the bluebooks skipped
|
|
28
|
+
def view(registry, path)
|
|
29
|
+
skipped = of(registry).last
|
|
30
|
+
[ShapeLedger.shapes_of(registry), ShapeLedger.read(path)].map { |held| held.except(*skipped.keys) } << skipped
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [String] the line naming the skipped bluebooks, empty when there are none
|
|
34
|
+
def note(skipped)
|
|
35
|
+
return "" if skipped.empty?
|
|
36
|
+
|
|
37
|
+
"\nskipped #{skipped.size} tooling bluebooks (committed persistence: #{skipped.values.uniq.join(", ")}): " \
|
|
38
|
+
"#{skipped.keys.join(", ")}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def held?(registry, name, bluebook)
|
|
42
|
+
Framework.members.key?(name) || Runtime::EraCheck.lineage_capable?(registry, adapter_of(registry, name, bluebook))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def adapter_of(registry, name, bluebook)
|
|
46
|
+
first = bluebook.aggregates.first
|
|
47
|
+
first ? Runtime::EraCheck.adapter_for(registry, name, first) : Ports::Persistence::DEFAULT_ADAPTER
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module ShapeLedger
|
|
5
|
+
# The ledger status of each bluebook under a directory, read from the committed ledger and its
|
|
6
|
+
# edges, never from a database. A status is one of:
|
|
7
|
+
#
|
|
8
|
+
# - `current`: the ledger holds the shape the bluebook now has.
|
|
9
|
+
# - `behind`: the shape the ledger holds leads to the current one. `move` names the implicit
|
|
10
|
+
# move the ledger records for that step, or is nil when no such move is recorded.
|
|
11
|
+
# - `superseded`: the current shape is an older one than the ledger holds.
|
|
12
|
+
# - `unminted`: the ledger has no entry for the bluebook, or no chain of edges or recorded moves
|
|
13
|
+
# leads from what it holds to the current shape.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# Status.of("lib/hecks/hecks")["bluebooks"]["Governance"]["status"] # => "current"
|
|
17
|
+
module Status
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param directory [String] the domain directory whose bluebooks the ledger records
|
|
21
|
+
# @param registry [Runtime::Registry] the domain's loaded registry; loaded here when not given
|
|
22
|
+
# @return [Hash] `domain`, and `bluebooks`: each bluebook's status row, by name
|
|
23
|
+
def of(directory, registry: Hecks.describe(directory).registry)
|
|
24
|
+
path = File.join(directory, FILE)
|
|
25
|
+
ledger = ShapeLedger.read(path)
|
|
26
|
+
moves = ShapeLedger.implicit_moves(ShapeLedger.implicit_path(path))
|
|
27
|
+
edges = ShapeLedger.with_implicit(ShapeLedger.edges_of(registry), moves)
|
|
28
|
+
bluebooks = registry.bluebooks.slice(*Scope.of(Scope.committed_registry(directory)).first).to_h do |name, bluebook|
|
|
29
|
+
[name, classify(name, ledger[name], Runtime::StorageShape.mint_hash(bluebook), moves, edges)]
|
|
30
|
+
end
|
|
31
|
+
{ "domain" => directory, "bluebooks" => bluebooks }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param name [String] the bluebook's name
|
|
35
|
+
# @param held [String, nil] the storage-shape hash the ledger holds, nil when it has none
|
|
36
|
+
# @param now [String] the storage-shape hash of the bluebook as it is now
|
|
37
|
+
# @param moves [Hash{String => Array<Hash>}] each bluebook's recorded implicit moves
|
|
38
|
+
# @param edges [Hash{String => Array<Array(String, String)>}] each bluebook's edges and moves
|
|
39
|
+
# @return [Hash] the bluebook's status row
|
|
40
|
+
def classify(name, held, now, moves, edges)
|
|
41
|
+
return unminted(name, now) unless held
|
|
42
|
+
|
|
43
|
+
held_label = ShapeLedger.label(held)
|
|
44
|
+
now_label = ShapeLedger.label(now)
|
|
45
|
+
return row("current", held_label, now_label) if held_label == now_label
|
|
46
|
+
|
|
47
|
+
ahead_of(held_label, now_label, moves.fetch(name, []), edges.fetch(name, []))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Hash] a row for a bluebook the ledger holds no shape for
|
|
51
|
+
def unminted(name, now)
|
|
52
|
+
row("unminted", nil, ShapeLedger.label(now), reason: "the ledger holds no shape for #{name}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Places the current shape against the held one when they differ.
|
|
56
|
+
#
|
|
57
|
+
# @return [Hash] a `superseded`, `behind` or `unminted` row
|
|
58
|
+
def ahead_of(held, now, recorded, edges)
|
|
59
|
+
return superseded(held, now) if ShapeLedger.reachable?(now, held, edges)
|
|
60
|
+
return behind(held, now, recorded) if ShapeLedger.reachable?(held, now, edges)
|
|
61
|
+
|
|
62
|
+
row("unminted", held, now, reason: "no translation edge or recorded implicit move leads from #{held} to #{now}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Hash] a row for a shape older than the one the ledger holds
|
|
66
|
+
def superseded(held, now)
|
|
67
|
+
row("superseded", held, now, reason: "the current shape #{now} is older than the ledger's #{held}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @param held [String] the label the ledger holds
|
|
71
|
+
# @param now [String] the label the bluebook now has
|
|
72
|
+
# @param recorded [Array<Hash>] the bluebook's recorded implicit moves
|
|
73
|
+
# @return [Hash] a row for a shape one recorded move or edge ahead of the ledger
|
|
74
|
+
def behind(held, now, recorded)
|
|
75
|
+
move = recorded.find { |entry| entry["from"] == held && entry["to"] == now }
|
|
76
|
+
return row("behind", held, now, move: move) if move
|
|
77
|
+
|
|
78
|
+
row("behind", held, now, reason: "no implicit move is recorded from #{held} to #{now}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [Hash] one bluebook's status, with every field present; `source` says it was read
|
|
82
|
+
# from the ledger
|
|
83
|
+
def row(status, held, now, move: nil, reason: nil)
|
|
84
|
+
{ "status" => status, "held" => held, "current" => now, "move" => move, "reason" => reason,
|
|
85
|
+
"source" => "ledger" }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
data/lib/hecks/shape_ledger.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
1
|
require "json"
|
|
3
2
|
require "hecks"
|
|
3
|
+
require_relative "ports/persistence/plugins/era/era_check"
|
|
4
|
+
require_relative "shape_ledger/scope"
|
|
5
|
+
require_relative "shape_ledger/status"
|
|
6
|
+
require_relative "shape_ledger/edge_hygiene"
|
|
4
7
|
|
|
5
8
|
module Hecks
|
|
6
9
|
# The committed record of each bluebook's storage shape, held against the shapes the bluebooks
|
|
@@ -10,17 +13,24 @@ module Hecks
|
|
|
10
13
|
# that at a boot only runs against a real database, which specs and gates do not use, so this one
|
|
11
14
|
# reads nothing but the loaded bluebooks and the committed edges: it needs no database.
|
|
12
15
|
#
|
|
16
|
+
# It holds only a bluebook whose committed persistence is an era adapter, or a framework member
|
|
17
|
+
# that clients persist (see `Scope`); tooling is Memory and needs no entry.
|
|
18
|
+
#
|
|
13
19
|
# @example
|
|
14
|
-
# ShapeLedger.check("lib/hecks/hecks") # => "
|
|
20
|
+
# ShapeLedger.check("lib/hecks/hecks") # => "1 bluebook shapes, each ..."
|
|
15
21
|
module ShapeLedger
|
|
16
22
|
# The ledger file, beside the bluebooks it records.
|
|
17
23
|
FILE = "shapes.json".freeze
|
|
18
24
|
|
|
25
|
+
# The record beside the ledger of each implicit move: a shape change that carried no edge file
|
|
26
|
+
# and was settled with `--confirm --additive`. Each bluebook maps to its `{from, to}` moves.
|
|
27
|
+
IMPLICIT_FILE = "implicit_moves.json".freeze
|
|
28
|
+
|
|
19
29
|
# The length of the label a translation edge names a shape by.
|
|
20
30
|
LABEL = 6
|
|
21
31
|
|
|
22
32
|
# What a problem line tells the reader to do.
|
|
23
|
-
REWRITE = "rewrite the ledger with `hecks conformance_run.check_shape_ledger --confirm --wait`".freeze
|
|
33
|
+
REWRITE = "rewrite the ledger with `hecks conformance_run.check_shape_ledger! --confirm --wait`".freeze
|
|
24
34
|
|
|
25
35
|
# Raised with every problem found, or when the ledger cannot be rewritten.
|
|
26
36
|
class Failure < StandardError; end
|
|
@@ -31,17 +41,22 @@ module Hecks
|
|
|
31
41
|
#
|
|
32
42
|
# @param directory [String] the domain directory whose bluebooks the ledger records
|
|
33
43
|
# @param confirm [Boolean] rewrite the ledger when every moved shape has an edge
|
|
34
|
-
# @param additive [Boolean] with `confirm`,
|
|
35
|
-
#
|
|
36
|
-
# the
|
|
44
|
+
# @param additive [Boolean] with `confirm`, move the ledger for each shape that moved with no
|
|
45
|
+
# edge, on the operator's word that the change only adds. No edge file is written: the boot
|
|
46
|
+
# and the mint treat such a move as an implicit empty edge, and the mint refuses any change
|
|
47
|
+
# the scaffold does not find to be additive
|
|
37
48
|
# @return [String] the clean verdict, or what was written
|
|
38
49
|
# @raise [Failure] with each problem
|
|
39
50
|
def check(directory, confirm: false, additive: false)
|
|
40
|
-
registry =
|
|
41
|
-
shapes = shapes_of(registry)
|
|
42
|
-
edges = edges_of(registry)
|
|
51
|
+
registry = Scope.committed_registry(directory)
|
|
43
52
|
path = File.join(directory, FILE)
|
|
44
|
-
|
|
53
|
+
shapes, ledger, skipped = Scope.view(registry, path)
|
|
54
|
+
moves = implicit_moves(implicit_path(path))
|
|
55
|
+
edges = with_implicit(edges_of(registry), moves)
|
|
56
|
+
return rewrite(path, ledger, shapes, edges, additive) if confirm
|
|
57
|
+
|
|
58
|
+
verdict(ledger, shapes, edges) + Scope.note(skipped) +
|
|
59
|
+
EdgeHygiene.report(directory, ledger: ledger, shapes: shapes, moves: moves)
|
|
45
60
|
end
|
|
46
61
|
|
|
47
62
|
# @param registry [Runtime::Registry] the loaded bluebooks
|
|
@@ -58,6 +73,18 @@ module Hecks
|
|
|
58
73
|
|
|
59
74
|
def pairs(edges) = edges.map { |edge| [edge.from, edge.to] }
|
|
60
75
|
|
|
76
|
+
# @return [Hash{String => Array<Array(String, String)>}] `edges` with each recorded implicit
|
|
77
|
+
# move added as an edge: the boot and the mint read a recorded move as an empty edge
|
|
78
|
+
def with_implicit(edges, record)
|
|
79
|
+
record.each_with_object(edges.dup) do |(name, moves), merged|
|
|
80
|
+
merged[name] = merged.fetch(name, []) + pairs_of(moves)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @param moves [Array<Hash>] recorded `{"from" => ..., "to" => ...}` moves
|
|
85
|
+
# @return [Array<Array(String, String)>] the moves as `[from, to]`
|
|
86
|
+
def pairs_of(moves) = moves.map { |move| [move["from"], move["to"]] }
|
|
87
|
+
|
|
61
88
|
def label(hash) = hash.to_s[0, LABEL]
|
|
62
89
|
|
|
63
90
|
# @return [Boolean] whether a chain of edges leads from one label to another
|
|
@@ -74,8 +101,8 @@ module Hecks
|
|
|
74
101
|
fresh
|
|
75
102
|
end
|
|
76
103
|
|
|
77
|
-
def verdict(
|
|
78
|
-
problems = problems(
|
|
104
|
+
def verdict(ledger, shapes, edges)
|
|
105
|
+
problems = problems(ledger, shapes, edges)
|
|
79
106
|
raise Failure, problems.join("\n") unless problems.empty?
|
|
80
107
|
|
|
81
108
|
"#{shapes.size} bluebook shapes, each the one the ledger holds"
|
|
@@ -100,25 +127,49 @@ module Hecks
|
|
|
100
127
|
# What to commit: the empty edge covers a change that only adds, and a change that renames or
|
|
101
128
|
# reshapes data needs the rules the scaffold writes from the database that holds the eras.
|
|
102
129
|
def edge_advice(name, from, now)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"Data that is renamed or reshaped
|
|
106
|
-
"domain.value=<directory>` writes them, reading
|
|
107
|
-
"
|
|
108
|
-
"by `hecks conformance_run.check_shape_ledger --confirm --additive --wait`"
|
|
130
|
+
"#{name} moved with no edge. A change that only adds needs no edge file: settle it in one step " \
|
|
131
|
+
"with `hecks conformance_run.check_shape_ledger! --confirm --additive --wait`, which the boot and " \
|
|
132
|
+
"the mint treat as an implicit empty edge from #{from} to #{now}. Data that is renamed or reshaped " \
|
|
133
|
+
"needs rules: `hecks query era.scaffold_translation domain.value=<directory>` writes them, reading " \
|
|
134
|
+
"the eras from the database the domain's own world names."
|
|
109
135
|
end
|
|
110
136
|
|
|
111
|
-
def rewrite(path, shapes, edges,
|
|
112
|
-
|
|
113
|
-
written = additive ? write_edges(directory, uncovered_moves(ledger, shapes, edges)) : []
|
|
137
|
+
def rewrite(path, ledger, shapes, edges, additive)
|
|
138
|
+
implicit = additive ? uncovered_moves(ledger, shapes, edges) : []
|
|
114
139
|
uncovered = additive ? [] : problems(ledger, shapes, edges).reject { |line| line.include?(REWRITE) }
|
|
115
140
|
raise Failure, uncovered.join("\n") unless uncovered.empty?
|
|
116
141
|
|
|
117
142
|
File.write(path, "#{JSON.pretty_generate(shapes.sort.to_h)}\n")
|
|
118
|
-
|
|
143
|
+
record_implicit(implicit_path(path), implicit)
|
|
144
|
+
"wrote #{shapes.size} bluebook shapes to #{path}#{implicit_note(implicit)}"
|
|
119
145
|
end
|
|
120
146
|
|
|
121
|
-
|
|
147
|
+
# Appends each implicit move to the record, once: a move the record already holds is skipped.
|
|
148
|
+
#
|
|
149
|
+
# @param path [String] the record file beside the ledger
|
|
150
|
+
# @param moves [Array<Array(String, String, String)>] `[bluebook, from, to]` for each move
|
|
151
|
+
# @return [void]
|
|
152
|
+
def record_implicit(path, moves)
|
|
153
|
+
return if moves.empty?
|
|
154
|
+
|
|
155
|
+
record = implicit_moves(path)
|
|
156
|
+
moves.each { |name, from, now| append_move(record, name, { "from" => from, "to" => now }) }
|
|
157
|
+
File.write(path, "#{JSON.pretty_generate(record.sort.to_h)}\n")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def append_move(record, name, move)
|
|
161
|
+
entries = record.fetch(name, [])
|
|
162
|
+
record[name] = entries.include?(move) ? entries : entries + [move]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def implicit_path(path) = File.join(File.dirname(path), IMPLICIT_FILE)
|
|
166
|
+
|
|
167
|
+
def implicit_note(moves)
|
|
168
|
+
return "" if moves.empty?
|
|
169
|
+
|
|
170
|
+
named = moves.map { |name, from, now| "#{name} #{from} -> #{now}" }
|
|
171
|
+
" and #{moves.size} implicit move(s), no edge file: #{named.join(", ")}"
|
|
172
|
+
end
|
|
122
173
|
|
|
123
174
|
# @return [Array<Array(String, String, String)>] `[bluebook, from, to]` for each shape that
|
|
124
175
|
# moved
|
|
@@ -133,17 +184,11 @@ module Hecks
|
|
|
133
184
|
end
|
|
134
185
|
end
|
|
135
186
|
|
|
136
|
-
# @return [Array<String>] the paths of the empty edges written
|
|
137
|
-
def write_edges(directory, moves)
|
|
138
|
-
FileUtils.mkdir_p(File.join(directory, "translations"))
|
|
139
|
-
moves.map do |name, from, now|
|
|
140
|
-
path = File.join(directory, "translations", "2-#{now}.bluebook")
|
|
141
|
-
File.write(path, %(Hecks.data_translation "#{name}", from: "#{from}", to: "#{now}" do\nend\n))
|
|
142
|
-
path
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
187
|
# @return [Hash{String => String}] the ledger, empty when there is none yet
|
|
147
188
|
def read(path) = File.exist?(path) ? JSON.parse(File.read(path)) : {}
|
|
189
|
+
|
|
190
|
+
# @param path [String] the implicit-move record
|
|
191
|
+
# @return [Hash{String => Array<Hash>}] each bluebook's recorded moves, empty when there is none
|
|
192
|
+
def implicit_moves(path) = File.exist?(path) ? JSON.parse(File.read(path)) : {}
|
|
148
193
|
end
|
|
149
194
|
end
|
|
@@ -23,6 +23,7 @@ Hecks.bluebook "Site" do
|
|
|
23
23
|
|
|
24
24
|
aggregate "Route" do
|
|
25
25
|
description "The shape of one row of a site's route table: where a path is served from, who may reach it, how it is cached, and where it appears in the navigation."
|
|
26
|
+
acknowledges :no_command_surface, "a route row is declared data: the table is what a site's bluebook says, and no command changes a row"
|
|
26
27
|
|
|
27
28
|
identified_by :path
|
|
28
29
|
|
|
@@ -4,11 +4,18 @@ module Hecks
|
|
|
4
4
|
# reading it back as a tail or as the events it announced. Extended onto `Storehouse`.
|
|
5
5
|
module Audit
|
|
6
6
|
# The caller-supplied fields of an audit line, in the order the line spells them.
|
|
7
|
-
AUDIT_FIELDS = %i[verb summary source role actor_id].freeze
|
|
7
|
+
AUDIT_FIELDS = %i[verb summary source role actor_id surface].freeze
|
|
8
8
|
|
|
9
9
|
# A character a log file's name may not carry.
|
|
10
10
|
UNSAFE_NAME_CHARACTER = /[^A-Za-z0-9_-]/
|
|
11
11
|
|
|
12
|
+
# The boundary between a lowercase or digit character and an uppercase one, where a
|
|
13
|
+
# CamelCase word takes an underscore.
|
|
14
|
+
CAMEL_CASE_BOUNDARY = /([a-z\d])([A-Z])/
|
|
15
|
+
|
|
16
|
+
# The last segment of a qualified constant name, such as `TypeMismatch` in `Runtime::TypeMismatch`.
|
|
17
|
+
UNQUALIFIED_NAME = /[^:]+\z/
|
|
18
|
+
|
|
12
19
|
# Kept under Hecks::CacheDir, not the domain directory (part of the domain's
|
|
13
20
|
# own state) or the gem directory (read-only once installed).
|
|
14
21
|
# :nodoc:
|
|
@@ -27,14 +34,37 @@ module Hecks
|
|
|
27
34
|
def record!(domain_name, tool:, outcome:, **entry)
|
|
28
35
|
return unless domain_name
|
|
29
36
|
|
|
30
|
-
line = { time: Time.now.utc.iso8601, tool: tool }.merge(AUDIT_FIELDS.to_h { |field| [field, entry[field]] })
|
|
31
|
-
.merge(outcome.slice(:ok, :id, :error, :events)).compact
|
|
32
37
|
FileUtils.mkdir_p(log_root)
|
|
33
|
-
File.open(log_path(domain_name), "a") { |f| f.puts(JSON.generate(
|
|
38
|
+
File.open(log_path(domain_name), "a") { |f| f.puts(JSON.generate(audit_line(tool, outcome, entry))) }
|
|
34
39
|
rescue StandardError
|
|
35
40
|
nil
|
|
36
41
|
end
|
|
37
42
|
|
|
43
|
+
# One audit line as a hash: when, the tool, the caller's fields, the outcome's fields, then
|
|
44
|
+
# the call's duration and refusal kind, with absent values left out.
|
|
45
|
+
# :nodoc:
|
|
46
|
+
def audit_line(tool, outcome, entry)
|
|
47
|
+
fields = AUDIT_FIELDS.to_h { |field| [field, entry[field]] }
|
|
48
|
+
{ time: Time.now.utc.iso8601, tool: tool, **fields, **outcome.slice(:ok, :id, :error, :events),
|
|
49
|
+
**entry.slice(:duration_ms, :refusal) }.compact
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Whole milliseconds since `started`, a CLOCK_MONOTONIC reading.
|
|
53
|
+
# :nodoc:
|
|
54
|
+
def elapsed_ms(started)
|
|
55
|
+
((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The refusal's class as a snake_case kind, such as `type_mismatch`.
|
|
59
|
+
# :nodoc:
|
|
60
|
+
def refusal_kind(error)
|
|
61
|
+
snake_case(error.class.name[UNQUALIFIED_NAME])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The word with an underscore at each CamelCase boundary, in lowercase.
|
|
65
|
+
# :nodoc:
|
|
66
|
+
def snake_case(word) = word.gsub(CAMEL_CASE_BOUNDARY, '\\1_\\2').downcase
|
|
67
|
+
|
|
38
68
|
# Tails this bus's own dispatch/query/state audit log — a pull-based
|
|
39
69
|
# substitute for a push subscription, since a stdio server answers one
|
|
40
70
|
# request at a time with no channel to push through.
|