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,107 @@
|
|
|
1
|
+
require_relative "../storage_shape"
|
|
2
|
+
require_relative "../projected_bluebook"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Translation
|
|
6
|
+
# The one rule for whether a change to an aggregate's storage shape only adds: its identity and
|
|
7
|
+
# lifecycle field are as they were, every attribute it held is held exactly as before (same
|
|
8
|
+
# name, same list or scalar form, same type with the same members), and each attribute it gained
|
|
9
|
+
# is optional. Anything else is breaking, and a caller that cannot tell treats it as breaking.
|
|
10
|
+
#
|
|
11
|
+
# The implicit edge and the disposable ledger both ask it, so the two never disagree.
|
|
12
|
+
#
|
|
13
|
+
# **Known limits.** A stored projection leaves out `optional` and a lifecycle's states, so an
|
|
14
|
+
# attribute that became required, or a state that was removed, is seen only when the held side
|
|
15
|
+
# is a full declaration; between two projections it cannot be told from an unchanged shape.
|
|
16
|
+
module AdditiveShape
|
|
17
|
+
# The parts of a projected aggregate besides its attributes that its stored rows depend on.
|
|
18
|
+
KEYS = %w[identity lifecycle_field].freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# @param held [Hash] the held aggregate's projection, as `StorageShape.project` builds it
|
|
23
|
+
# @param current [Hash] the booting aggregate's projection
|
|
24
|
+
# @param optional [#call] answers, given a name, whether the booting aggregate declares
|
|
25
|
+
# that attribute optional
|
|
26
|
+
# @param was_optional [#call, nil] answers whether the held aggregate declared an attribute
|
|
27
|
+
# optional, or nil when the held side is a projection that does not say
|
|
28
|
+
# @return [Boolean] whether `current` only adds to `held`
|
|
29
|
+
def widened?(held, current, optional:, was_optional: nil)
|
|
30
|
+
return false unless held.values_at(*KEYS) == current.values_at(*KEYS)
|
|
31
|
+
return false unless (held["attributes"] - current["attributes"]).empty?
|
|
32
|
+
return false if tightened?(held, optional, was_optional)
|
|
33
|
+
|
|
34
|
+
(names(current) - names(held)).all? { |name| optional.call(name) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Whether an attribute the held declaration marked optional is required now.
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def tightened?(held, optional, was_optional)
|
|
41
|
+
return false unless was_optional
|
|
42
|
+
|
|
43
|
+
names(held).any? { |name| was_optional.call(name) && !optional.call(name) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Array<String>] the attribute names of a projected aggregate
|
|
47
|
+
def names(projected) = projected["attributes"].map { |attribute| attribute["name"] }
|
|
48
|
+
|
|
49
|
+
# Whether every aggregate present in both shapes only adds. A shared aggregate that is
|
|
50
|
+
# unchanged counts; one the current side lacks or the held side lacks is not this check's
|
|
51
|
+
# business (the scaffold diff names renames, retirements and new aggregates).
|
|
52
|
+
#
|
|
53
|
+
# @param held [Hash, Runtime::ProjectedBluebook, Bluebook::Declaration] the held side
|
|
54
|
+
# @param current [Bluebook::Declaration] the booting bluebook
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def additive?(held, current)
|
|
57
|
+
held_by = by_name(shape_of(held))
|
|
58
|
+
by_name(Runtime::StorageShape.project(current)).all? do |name, projected|
|
|
59
|
+
held_by[name].nil? || aggregate_additive?(held, held_by[name], projected, current.aggregate(name))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Boolean] whether one aggregate present in both shapes only adds
|
|
64
|
+
def aggregate_additive?(held, before, after, aggregate)
|
|
65
|
+
states_kept?(held, aggregate) &&
|
|
66
|
+
widened?(before, after, optional: optional_in(aggregate), was_optional: was_optional_in(held, aggregate.name))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Whether every lifecycle state the held declaration had is still declared. A held side that
|
|
70
|
+
# is a projection records no states, so it answers true.
|
|
71
|
+
#
|
|
72
|
+
# @return [Boolean]
|
|
73
|
+
def states_kept?(held, aggregate)
|
|
74
|
+
return true if held.is_a?(Hash) || held.is_a?(Runtime::ProjectedBluebook)
|
|
75
|
+
|
|
76
|
+
before = states_of(held.aggregate(aggregate.name))
|
|
77
|
+
before.nil? || ((before - Array(states_of(aggregate))).empty? && !states_of(aggregate).nil?)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Array<String>, nil] the lifecycle states an aggregate declares; nil without one
|
|
81
|
+
def states_of(aggregate)
|
|
82
|
+
lifecycle = aggregate&.lifecycle
|
|
83
|
+
lifecycle&.states
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [Hash] the storage shape of any held side
|
|
87
|
+
def shape_of(held)
|
|
88
|
+
held = Runtime::ProjectedBluebook.wrap(held)
|
|
89
|
+
held.is_a?(Runtime::ProjectedBluebook) ? held.projection : Runtime::StorageShape.project(held)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Hash{String => Hash}] each projected aggregate by name
|
|
93
|
+
def by_name(shape) = shape["aggregates"].to_h { |aggregate| [aggregate["name"], aggregate] }
|
|
94
|
+
|
|
95
|
+
# @return [#call] whether the aggregate declares the named attribute optional
|
|
96
|
+
def optional_in(aggregate) = ->(name) { aggregate&.attribute(name)&.optional? || false }
|
|
97
|
+
|
|
98
|
+
# @return [#call, nil] whether the held declaration's aggregate declared the named attribute
|
|
99
|
+
# optional; nil for a projection, which does not record it
|
|
100
|
+
def was_optional_in(held, name)
|
|
101
|
+
return nil if held.is_a?(Hash) || held.is_a?(Runtime::ProjectedBluebook)
|
|
102
|
+
|
|
103
|
+
optional_in(held.aggregate(name))
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../../../bluebook/translation"
|
|
4
|
+
require_relative "compose_render"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Translation
|
|
8
|
+
# Composes the chain of translation edges from a held era to the current shape into the one
|
|
9
|
+
# edge that leads straight there. Rendering the edge as `.bluebook` text is `ComposeRender`.
|
|
10
|
+
#
|
|
11
|
+
# Composition is exact only where the chain is unambiguous. An aggregate's field rules (renames,
|
|
12
|
+
# drops, derives and the rest) may appear in one step of the chain; two steps' rules on the same
|
|
13
|
+
# aggregate would need their order and names reconciled, and that is a decision for a person.
|
|
14
|
+
# Aggregate names are followed through every step, so a rename in any step survives into the
|
|
15
|
+
# composed edge as `was:`.
|
|
16
|
+
module Compose
|
|
17
|
+
# The field-level rule lists a `Bluebook::TranslationAggregate` carries.
|
|
18
|
+
FIELD_RULES = %i[renames moves converts drops retypes derives rekeys backfills splits].freeze
|
|
19
|
+
|
|
20
|
+
# The rules no rendering covers yet; a chain that carries one is refused, not guessed at.
|
|
21
|
+
UNRENDERABLE = %i[rekeys splits].freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Walks the edges that lead one shape label to the next until the current shape.
|
|
26
|
+
#
|
|
27
|
+
# @param translations [Array<Bluebook::Translation>] every loaded edge, any domain
|
|
28
|
+
# @param domain [String] the domain whose edges count
|
|
29
|
+
# @param from [String] the held era's shape label
|
|
30
|
+
# @param to [String] the current shape label
|
|
31
|
+
# A held era whose label is the current one but whose projection differs has no chain to walk:
|
|
32
|
+
# its one step is the edge from that label to itself, when such an edge exists.
|
|
33
|
+
#
|
|
34
|
+
# @return [Array<Bluebook::Translation>] the edges in order; empty when none leads `from` to `to`
|
|
35
|
+
# and `from` is `to`
|
|
36
|
+
# @raise [Runtime::WiringError] when a step has no edge, has two, or the walk loops
|
|
37
|
+
def chain(translations, domain:, from:, to:)
|
|
38
|
+
return self_edges(translations, domain, from) if from == to
|
|
39
|
+
|
|
40
|
+
steps = []
|
|
41
|
+
label = from
|
|
42
|
+
until label == to
|
|
43
|
+
step = next_step(translations, domain, label, to)
|
|
44
|
+
raise_loop!(domain, step.to) if [from, *steps.map(&:to)].include?(step.to)
|
|
45
|
+
steps << step
|
|
46
|
+
label = step.to
|
|
47
|
+
end
|
|
48
|
+
steps
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Bluebook::Translation] the one edge from the first step's origin to the last step's target
|
|
52
|
+
# @raise [Runtime::WiringError] when a step's rules cannot be composed into one edge
|
|
53
|
+
def compose(domain:, from:, to:, chain:)
|
|
54
|
+
refuse_unrenderable!(chain)
|
|
55
|
+
origins, retired, ruled = track_chain(chain)
|
|
56
|
+
Bluebook::Translation.new(domain: domain, from: from, to: to,
|
|
57
|
+
aggregates: composed_aggregates(origins, ruled),
|
|
58
|
+
retired: retired.reject { |name| origins.key?(name) })
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self_edges(translations, domain, label)
|
|
62
|
+
translations.select { |edge| edge.domain == domain && edge.from == label && edge.to == label }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Bluebook::Translation] the step leaving `label`, the only one that does
|
|
66
|
+
# @raise [Runtime::WiringError] when none leaves it or two do
|
|
67
|
+
def next_step(translations, domain, label, to)
|
|
68
|
+
leaving = translations.select { |edge| edge.domain == domain && edge.from == label }
|
|
69
|
+
refuse_broken!(domain, label, to) if leaving.empty?
|
|
70
|
+
refuse_forked!(domain, label, leaving) if leaving.size > 1
|
|
71
|
+
leaving.first
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def refuse_broken!(domain, label, to)
|
|
75
|
+
raise Runtime::WiringError,
|
|
76
|
+
"cannot compose #{domain}: the edge chain is broken at #{label} — no translation leads " \
|
|
77
|
+
"#{label} onward to #{to}; restore translations/ or write the missing edge"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def refuse_forked!(domain, label, leaving)
|
|
81
|
+
raise Runtime::WiringError,
|
|
82
|
+
"cannot compose #{domain}: #{leaving.size} translations leave #{label} " \
|
|
83
|
+
"(#{leaving.map(&:to).join(", ")}); one must go before the chain can compose"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def raise_loop!(domain, label)
|
|
87
|
+
raise Runtime::WiringError, "cannot compose #{domain}: the edge chain loops back to #{label}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def refuse_unrenderable!(chain)
|
|
91
|
+
chain.each do |step|
|
|
92
|
+
step.aggregates.each do |aggregate|
|
|
93
|
+
next if UNRENDERABLE.all? { |rule| aggregate.public_send(rule).empty? }
|
|
94
|
+
|
|
95
|
+
raise Runtime::WiringError,
|
|
96
|
+
"cannot compose #{step.domain}: #{aggregate.name} carries a rekey or split, which " \
|
|
97
|
+
"composes by hand; write the composed edge yourself"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Follows each aggregate from its held name to its current one, and finds the one step that
|
|
103
|
+
# carries field rules for each origin.
|
|
104
|
+
#
|
|
105
|
+
# @return [Array(Hash, Array<String>, Hash)] origins keyed by current name, every retired name,
|
|
106
|
+
# and the aggregate carrying field rules for each origin
|
|
107
|
+
def track_chain(chain)
|
|
108
|
+
state = { origins: {}, retired: [], ruled: {} }
|
|
109
|
+
chain.each_with_index { |step, index| track_step(state, step, index) }
|
|
110
|
+
[state[:origins], state[:retired].uniq, state[:ruled].transform_values(&:last)]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def track_step(state, step, index)
|
|
114
|
+
step.aggregates.each { |aggregate| track_aggregate(state, step.domain, aggregate, index) }
|
|
115
|
+
state[:retired].concat(step.retired)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def track_aggregate(state, domain, aggregate, index)
|
|
119
|
+
origin = track(state[:origins], aggregate)
|
|
120
|
+
return if FIELD_RULES.all? { |rule| aggregate.public_send(rule).empty? }
|
|
121
|
+
|
|
122
|
+
refuse_two_steps!(domain, aggregate.name, state[:ruled][origin], index) if state[:ruled][origin]
|
|
123
|
+
state[:ruled][origin] = [index, aggregate]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Records an aggregate under its current name, keeping the name it was held under.
|
|
127
|
+
def track(origins, aggregate)
|
|
128
|
+
previous = aggregate.was || aggregate.name
|
|
129
|
+
origin = origins.delete(previous) || previous
|
|
130
|
+
origins[aggregate.name] = origin
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Builds the composed aggregates: one per aggregate that was renamed or carries field rules.
|
|
134
|
+
def composed_aggregates(origins, ruled)
|
|
135
|
+
origins.filter_map do |name, origin|
|
|
136
|
+
source = ruled[origin]
|
|
137
|
+
next if source.nil? && origin == name
|
|
138
|
+
|
|
139
|
+
composed_aggregate(name, origin, source)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def refuse_two_steps!(domain, name, earlier, index)
|
|
144
|
+
raise Runtime::WiringError,
|
|
145
|
+
"cannot compose #{domain}: #{name} has field rules in steps #{earlier.first + 1} and " \
|
|
146
|
+
"#{index + 1} of the chain; compose those by hand"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def composed_aggregate(name, origin, source)
|
|
150
|
+
rules = source ? FIELD_RULES.to_h { |rule| [rule, source.public_send(rule)] } : {}
|
|
151
|
+
Bluebook::TranslationAggregate.new(name: name, was: (origin == name ? nil : origin), **rules)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Translation
|
|
5
|
+
# Renders a composed edge as `.bluebook` text, the form the era gate and the audit read back.
|
|
6
|
+
module ComposeRender
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# The `.bluebook` text of one edge, with a comment naming the chain it composes.
|
|
10
|
+
#
|
|
11
|
+
# @param edge [Bluebook::Translation] the composed edge
|
|
12
|
+
# @param labels [Array<String>] the shape labels the chain passes through, in order
|
|
13
|
+
# @return [String] the text, ending in a newline
|
|
14
|
+
def render(edge, labels: [])
|
|
15
|
+
lines = [*comment_lines(labels), header(edge)]
|
|
16
|
+
edge.aggregates.each { |aggregate| lines.concat(aggregate_lines(aggregate)) }
|
|
17
|
+
edge.retired.each { |name| lines << " retired #{name.inspect}" }
|
|
18
|
+
lines << "end"
|
|
19
|
+
"#{lines.join("\n")}\n"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def header(edge)
|
|
23
|
+
"Hecks.data_translation #{edge.domain.inspect}, from: #{edge.from.inspect}, to: #{edge.to.inspect} do"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def comment_lines(labels)
|
|
27
|
+
return [] if labels.empty?
|
|
28
|
+
|
|
29
|
+
["# Composed from the chain #{labels.join(" -> ")}: the rules of each step, carried into one edge."]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def aggregate_lines(aggregate)
|
|
33
|
+
header = " aggregate #{aggregate.name.inspect}"
|
|
34
|
+
header += ", was: #{aggregate.was.inspect}" if aggregate.was
|
|
35
|
+
["#{header} do", *rule_lines(aggregate).map { |line| " #{line}" }, " end"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Each field rule as the line that declares it, in the order the bluebook reads.
|
|
39
|
+
def rule_lines(aggregate)
|
|
40
|
+
renames(aggregate) + moves(aggregate) + converts(aggregate) + drops(aggregate) +
|
|
41
|
+
retypes(aggregate) + derives(aggregate) + backfills(aggregate)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def renames(aggregate)
|
|
45
|
+
aggregate.renames.map { |old_name, new_name| "rename #{symbol(old_name)}, to: #{symbol(new_name)}" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def moves(aggregate)
|
|
49
|
+
aggregate.moves.map { |move| "move #{move.from.inspect}, to: #{move.to.inspect}" }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def converts(aggregate)
|
|
53
|
+
aggregate.converts.map do |convert|
|
|
54
|
+
"convert #{convert.from.inspect}, to: #{convert.to.inspect}, values: #{convert.values.inspect}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def drops(aggregate) = aggregate.drops.map { |path| "drop #{path.to_s.inspect}" }
|
|
59
|
+
|
|
60
|
+
def retypes(aggregate)
|
|
61
|
+
aggregate.retypes.map { |retype| "retype #{retype.from.inspect}, to: #{retype.to.inspect}" }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def derives(aggregate)
|
|
65
|
+
aggregate.derives.map do |derive|
|
|
66
|
+
"derive #{symbol(derive.to)}, as: #{derive.expression.to_s.inspect}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def backfills(aggregate)
|
|
71
|
+
aggregate.backfills.map do |backfill|
|
|
72
|
+
"backfill #{symbol(backfill.name)}, default: #{backfill.default.inspect}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def symbol(name) = ":#{name}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Translation
|
|
5
|
+
# The edge files of a domain's `translations/` directory, read by their headers.
|
|
6
|
+
#
|
|
7
|
+
# A header names the domain and the two shape labels an edge leads between, so a file is found
|
|
8
|
+
# without loading it: the writer needs to know which files a chain uses before it changes any.
|
|
9
|
+
module EdgeFiles
|
|
10
|
+
# One edge file: where it is and the header it declares.
|
|
11
|
+
Found = Struct.new(:path, :domain, :from, :to, keyword_init: true)
|
|
12
|
+
|
|
13
|
+
HEADER = /^Hecks\.data_translation\s+"(?<domain>[^"]+)",\s*from:\s*"(?<from>[^"]+)",\s*to:\s*"(?<to>[^"]+)"/
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# @param directory [String] the domain's bluebook directory
|
|
18
|
+
# @return [Array<Found>] every `translations/*.bluebook` file whose header parses, by path
|
|
19
|
+
def scan(directory)
|
|
20
|
+
Dir[File.join(directory, "translations", "*.bluebook")].filter_map do |path|
|
|
21
|
+
match = HEADER.match(File.read(path))
|
|
22
|
+
Found.new(path: path, domain: match[:domain], from: match[:from], to: match[:to]) if match
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The files of the edges a composed chain replaces: each leads to a shape the chain passes
|
|
27
|
+
# through before its last step, so only the chain used it.
|
|
28
|
+
#
|
|
29
|
+
# @param directory [String] the domain's bluebook directory
|
|
30
|
+
# @param domain [String] the domain whose edges count
|
|
31
|
+
# @param chain [Array<Bluebook::Translation>] the chain being composed, in order
|
|
32
|
+
# @return [Array<String>] the paths to delete
|
|
33
|
+
def stale(directory, domain:, chain:)
|
|
34
|
+
intermediates = chain[0...-1].map(&:to)
|
|
35
|
+
scan(directory).filter_map do |found|
|
|
36
|
+
found.path if found.domain == domain && intermediates.include?(found.to)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param directory [String] the domain's bluebook directory
|
|
41
|
+
# @param from [String] the shape label the composed edge leaves
|
|
42
|
+
# @param to [String] the shape label the composed edge reaches
|
|
43
|
+
# @return [String] the path the composed edge is written to
|
|
44
|
+
def path_for(directory, from:, to:)
|
|
45
|
+
File.join(directory, "translations", "2-#{from}-to-#{to}.bluebook")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Translation
|
|
5
|
+
# Finds the run of translation edges that leads one shape label to another. A run is found by
|
|
6
|
+
# search rather than by walking every label's edges, so an edge that leaves a label off the run
|
|
7
|
+
# (a fork branch) does not stop the route.
|
|
8
|
+
module EdgeRoute
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# The shortest run of edges from one label to another.
|
|
12
|
+
#
|
|
13
|
+
# @return [Array<Bluebook::Translation>] the edges in order; when `from` is `to`, the self
|
|
14
|
+
# edge if one exists, else empty
|
|
15
|
+
# @raise [Runtime::WiringError] when no run of edges leads `from` to `to`
|
|
16
|
+
def route(edges, from, to)
|
|
17
|
+
return edges.select { |edge| edge.from == from && edge.to == from }.first(1) if from == to
|
|
18
|
+
|
|
19
|
+
walk(edges, from, to).fetch(to) { raise Runtime::WiringError, "no translation edge leads #{from} to #{to}" }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Breadth-first over labels, keeping the first run found to each one.
|
|
23
|
+
#
|
|
24
|
+
# @return [Hash{String => Array<Bluebook::Translation>}] the run to each reached label
|
|
25
|
+
def walk(edges, from, to)
|
|
26
|
+
paths = { from => [] }
|
|
27
|
+
frontier = [from]
|
|
28
|
+
until frontier.empty? || paths.key?(to)
|
|
29
|
+
label = frontier.shift
|
|
30
|
+
extend_runs(paths, frontier, edges, label)
|
|
31
|
+
end
|
|
32
|
+
paths
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def extend_runs(paths, frontier, edges, label)
|
|
36
|
+
edges.each do |edge|
|
|
37
|
+
next unless edge.from == label && !paths.key?(edge.to)
|
|
38
|
+
|
|
39
|
+
paths[edge.to] = [*paths[label], edge]
|
|
40
|
+
frontier << edge.to
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Boolean] whether a run of edges leads from one label to another
|
|
45
|
+
def reachable?(from, to, edges) = reached(from, edges).include?(to)
|
|
46
|
+
|
|
47
|
+
# @return [Array<String>] every label reachable from `from`, `from` included
|
|
48
|
+
def reached(from, edges)
|
|
49
|
+
seen = [from]
|
|
50
|
+
frontier = [from]
|
|
51
|
+
until frontier.empty?
|
|
52
|
+
label = frontier.shift
|
|
53
|
+
successors = edges.filter_map { |edge| edge.to if edge.from == label && !seen.include?(edge.to) }
|
|
54
|
+
seen.concat(successors)
|
|
55
|
+
frontier.concat(successors)
|
|
56
|
+
end
|
|
57
|
+
seen
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require_relative "../projected_bluebook"
|
|
2
|
+
require_relative "additive_shape"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Translation
|
|
6
|
+
# The empty edge a shape change needs when no edge file is written.
|
|
7
|
+
#
|
|
8
|
+
# A change that scaffolds to no rule, no unresolved line and no retirement, and whose storage
|
|
9
|
+
# change only adds, carries every row unchanged, so no file has to say so. The boot and the mint
|
|
10
|
+
# treat it as an empty edge from the held shape to the current one, and the journal records the
|
|
11
|
+
# era it minted as implicit. Every other change still needs a real edge file.
|
|
12
|
+
class ImplicitEdge
|
|
13
|
+
# What the journal stores for an era minted under an implicit edge. A later boot reads
|
|
14
|
+
# it back, so an audit can tell the edge was never written by hand.
|
|
15
|
+
RECORD = { kind: "implicit", scaffolded: true }.freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :domain, :from, :to
|
|
18
|
+
|
|
19
|
+
# @param domain [String] the domain the edge belongs to
|
|
20
|
+
# @param from [String] the shape label the edge leaves
|
|
21
|
+
# @param to [String] the shape label the edge reaches
|
|
22
|
+
def initialize(domain:, from:, to:)
|
|
23
|
+
@domain = domain
|
|
24
|
+
@from = from
|
|
25
|
+
@to = to
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# An implicit edge declares no aggregate, so it renames, moves, converts and drops nothing.
|
|
29
|
+
#
|
|
30
|
+
# @return [Array] always empty
|
|
31
|
+
def aggregates = []
|
|
32
|
+
|
|
33
|
+
# An implicit edge retires no aggregate, as a written edge's `retired` lists none it drops.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array] always empty
|
|
36
|
+
def retired = []
|
|
37
|
+
|
|
38
|
+
# @param _name [String, Symbol] an aggregate name
|
|
39
|
+
# @return [nil] an implicit edge has no rules for any aggregate
|
|
40
|
+
def for_aggregate(_name) = nil
|
|
41
|
+
|
|
42
|
+
# @return [Boolean] always true
|
|
43
|
+
def implicit? = true
|
|
44
|
+
|
|
45
|
+
# The era record a mint writes for this edge, keyed by the label of the era it reaches.
|
|
46
|
+
#
|
|
47
|
+
# @return [Hash] `label` and `edge`, as the journal stores them
|
|
48
|
+
def record = { label: to, edge: RECORD }
|
|
49
|
+
|
|
50
|
+
# Whether a storage change needs no edge file: the scaffold finds no rule, no retirement and
|
|
51
|
+
# no unresolved name, and every aggregate both shapes have only adds: same identity and
|
|
52
|
+
# lifecycle field, every held attribute held exactly as before (list or scalar included),
|
|
53
|
+
# and every attribute gained optional. Anything this cannot tell is breaking.
|
|
54
|
+
#
|
|
55
|
+
# @param diff [Hash] the scaffold's diff of the held shape against the current one
|
|
56
|
+
# @param held [Hash, Runtime::ProjectedBluebook, Bluebook::Declaration] the held era's storage
|
|
57
|
+
# shape, a view over it, or a declared bluebook
|
|
58
|
+
# @param current [Bluebook::Declaration] the current bluebook, which says which of its
|
|
59
|
+
# attributes are optional
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def self.qualifies?(diff, held, current)
|
|
62
|
+
diff[:aggregates].empty? && diff[:retired].empty? && diff[:unclaimed].empty? &&
|
|
63
|
+
AdditiveShape.additive?(held, current)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "compose"
|
|
4
|
+
require_relative "edge_route"
|
|
5
|
+
require_relative "compose_render"
|
|
6
|
+
require_relative "edge_files"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Translation
|
|
10
|
+
# Plans `Era.Reconcile` for one domain: the one edge that leads its latest held era to the
|
|
11
|
+
# current shape, the edge files that edge supersedes, and the named reason a plan is refused.
|
|
12
|
+
#
|
|
13
|
+
# Planning reads the held labels, the loaded edges and the edge files, and writes nothing;
|
|
14
|
+
# `ReconcileFiles` carries out an admitted plan.
|
|
15
|
+
module Reconcile
|
|
16
|
+
# What a plan is made from: the domain's name, its held eras (`ordinal:` and `label:`, in
|
|
17
|
+
# order), the current shape's label, its loaded edges, the edge files of its translations
|
|
18
|
+
# directory and the directory a composed edge is written under.
|
|
19
|
+
Scope = Struct.new(:domain, :held, :current, :edges, :files, :directory, keyword_init: true)
|
|
20
|
+
|
|
21
|
+
# One domain's plan. `status` is `:no_era`, `:clean`, `:write` or `:refused`; `latest` is the
|
|
22
|
+
# latest held era (`ordinal:`, `label:`); `edge` is the composed edge when one is needed, and
|
|
23
|
+
# `removals` the edge files the plan deletes; `refusal` names why a plan is refused.
|
|
24
|
+
Plan = Struct.new(:domain, :status, :latest, :current, :labels, :edge, :write_path, :text,
|
|
25
|
+
:removals, :mints, :refusal, keyword_init: true)
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# @param scope [Scope] what the plan is made from
|
|
30
|
+
# @return [Plan] the plan; `status` is `:refused` with `refusal` naming the reason
|
|
31
|
+
def plan(scope)
|
|
32
|
+
return Plan.new(domain: scope.domain, status: :no_era, current: scope.current) if scope.held.empty?
|
|
33
|
+
|
|
34
|
+
latest = scope.held.last
|
|
35
|
+
decide(scope, latest, composition(scope, latest))
|
|
36
|
+
rescue Runtime::WiringError => e
|
|
37
|
+
Plan.new(domain: scope.domain, status: :refused, latest: scope.held.last, current: scope.current,
|
|
38
|
+
refusal: e.message)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Hash] `chain:` the edges from the latest era, `edge:` the composed edge when the
|
|
42
|
+
# chain has more than one step, and `kept:` the edge that already leads to the current shape
|
|
43
|
+
def composition(scope, latest)
|
|
44
|
+
chain = EdgeRoute.route(scope.edges, latest[:label], scope.current)
|
|
45
|
+
return { chain: chain, edge: nil, kept: chain.first } if chain.size <= 1
|
|
46
|
+
|
|
47
|
+
edge = Compose.compose(domain: scope.domain, from: latest[:label], to: scope.current, chain: chain)
|
|
48
|
+
{ chain: chain, edge: edge, kept: edge }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def decide(scope, latest, composition)
|
|
52
|
+
removed = superseded(composition, scope.edges, latest)
|
|
53
|
+
refusal = refusal_for(scope, removed, composition)
|
|
54
|
+
return refused(scope, latest, refusal) if refusal
|
|
55
|
+
|
|
56
|
+
verdict(scope, latest, composition, removed.map { |edge| file_for!(scope, edge) })
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The edges a composed plan replaces: every chain step when the chain is composed, and every
|
|
60
|
+
# other edge leaving the latest held era.
|
|
61
|
+
def superseded(composition, edges, latest)
|
|
62
|
+
steps = composition[:chain].size > 1 ? composition[:chain] : []
|
|
63
|
+
branches = edges.select { |edge| edge.from == latest[:label] && !same?(edge, composition[:kept]) }
|
|
64
|
+
(steps + branches).uniq { |edge| key(edge) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The named reason the change is refused: an earlier era still using an edge the change
|
|
68
|
+
# deletes, or an earlier era the change would leave with no way to the current shape.
|
|
69
|
+
def refusal_for(scope, removed, composition)
|
|
70
|
+
after = scope.edges.reject { |edge| removed.any? { |gone| same?(gone, edge) } }
|
|
71
|
+
after += [composition[:edge]] if composition[:edge]
|
|
72
|
+
earlier = scope.held[0...-1]
|
|
73
|
+
still_used(scope.domain, earlier, removed) || uncovered(scope, earlier, after)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def still_used(domain, earlier, removed)
|
|
77
|
+
removed.each do |edge|
|
|
78
|
+
era = earlier.find { |held| held[:label] == edge.from }
|
|
79
|
+
next unless era
|
|
80
|
+
|
|
81
|
+
return "#{domain}: era #{era[:ordinal]} (#{era[:label]}) still uses the edge #{edge.from} -> " \
|
|
82
|
+
"#{edge.to}, which the change would delete"
|
|
83
|
+
end
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def uncovered(scope, earlier, after)
|
|
88
|
+
era = earlier.find { |held| !EdgeRoute.reachable?(held[:label], scope.current, after) }
|
|
89
|
+
return nil unless era
|
|
90
|
+
|
|
91
|
+
verb = EdgeRoute.reachable?(era[:label], scope.current, scope.edges) ? "the change would leave" : "no edge leads"
|
|
92
|
+
"#{scope.domain}: #{verb} era #{era[:ordinal]} (#{era[:label]}) a way to #{scope.current}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def verdict(scope, latest, composition, removal_paths)
|
|
96
|
+
labels = [latest[:label], *composition[:chain].map(&:to)]
|
|
97
|
+
edge = composition[:edge]
|
|
98
|
+
status = edge || removal_paths.any? ? :write : :clean
|
|
99
|
+
Plan.new(domain: scope.domain, status: status, latest: latest, current: scope.current, labels: labels,
|
|
100
|
+
edge: edge, write_path: write_path_for(scope, latest, edge),
|
|
101
|
+
text: edge && ComposeRender.render(edge, labels: labels), removals: removal_paths,
|
|
102
|
+
mints: latest[:label] != scope.current)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def write_path_for(scope, latest, edge)
|
|
106
|
+
return nil unless edge
|
|
107
|
+
|
|
108
|
+
EdgeFiles.path_for(scope.directory, from: latest[:label], to: scope.current)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def refused(scope, latest, refusal)
|
|
112
|
+
Plan.new(domain: scope.domain, status: :refused, latest: latest, current: scope.current, refusal: refusal)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [String] the path of the edge file that carries `edge`
|
|
116
|
+
# @raise [Runtime::WiringError] when no file under the translations directory holds it
|
|
117
|
+
def file_for!(scope, edge)
|
|
118
|
+
found = scope.files.find { |file| file.domain == scope.domain && file.from == edge.from && file.to == edge.to }
|
|
119
|
+
return found.path if found
|
|
120
|
+
|
|
121
|
+
raise Runtime::WiringError,
|
|
122
|
+
"#{scope.domain}: no edge file holds #{edge.from} -> #{edge.to}, so it cannot be removed"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def same?(left, right) = !right.nil? && key(left) == key(right)
|
|
126
|
+
|
|
127
|
+
def key(edge) = [edge.from, edge.to]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|