hecks 7.0.0 → 7.1.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/lib/hecks/adapters/driven/postgres_era/lineage/chain_sql.rb +2 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +4 -2
- data/lib/hecks/adapters/driven/postgres_era/lineage/split_sql.rb +16 -8
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_sql.rb +7 -3
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/mint_request.rb +2 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/refusals.rb +2 -1
- data/lib/hecks/adapters/driving/cli_runner/bang_rule.rb +3 -0
- data/lib/hecks/adapters/driving/cli_runner/completion_words.rb +2 -0
- data/lib/hecks/adapters/driving/cli_runner/read_context.rb +6 -3
- data/lib/hecks/adapters/driving/cli_runner/renamed_names.rb +2 -1
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +2 -2
- data/lib/hecks/adapters/driving/cli_runner/resume_words.rb +1 -0
- data/lib/hecks/adapters/driving/cli_runner/run_reference.rb +6 -3
- data/lib/hecks/adapters/driving/cli_runner/search_words.rb +2 -0
- data/lib/hecks/adapters/driving/cli_runner/tour_words.rb +1 -0
- data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +2 -1
- data/lib/hecks/adapters/driving/cli_runner/verbosity.rb +2 -0
- data/lib/hecks/adapters/driving/cli_runner/word_help.rb +40 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +2 -0
- data/lib/hecks/adapters/driving/mcp/progress_sink.rb +4 -2
- data/lib/hecks/adapters/driving/mcp.rb +2 -1
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +2 -1
- data/lib/hecks/adapters/driving/ruby/aggregate_module/hooks.rb +28 -14
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +4 -2
- data/lib/hecks/assembly.rb +18 -4
- data/lib/hecks/behaviors/expectations/adapter_selection.rb +82 -0
- data/lib/hecks/behaviors/expectations/boot.rb +12 -5
- data/lib/hecks/bluebook/acknowledgements.rb +1 -1
- data/lib/hecks/bluebook/command.rb +3 -2
- data/lib/hecks/bluebook/declaration.rb +2 -1
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +9 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder/metadata.rb +6 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/modules.rb +6 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +2 -1
- data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +4 -2
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +2 -1
- data/lib/hecks/bluebook/hecksagon.rb +6 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +9 -2
- data/lib/hecks/bluebook/meta_validator.rb +1 -1
- data/lib/hecks/bluebook/model_check/aggregate_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/classification_checks.rb +51 -0
- data/lib/hecks/bluebook/model_check/context_checks.rb +2 -0
- data/lib/hecks/bluebook/model_check/redundant_relationships.rb +2 -1
- data/lib/hecks/ci_speed/adapters/shard_partitioner.adapter +7 -0
- data/lib/hecks/ci_speed/adapters/shard_partitioner.rb +66 -0
- data/lib/hecks/ci_speed/adapters/timings_source.adapter +7 -0
- data/lib/hecks/ci_speed/adapters/timings_source.rb +68 -0
- data/lib/hecks/ci_speed/ci_speed.bluebook +111 -0
- data/lib/hecks/ci_speed/ci_speed.ports.hecksagon +22 -0
- data/lib/hecks/ci_workflows/ci_workflows.bluebook +50 -6
- data/lib/hecks/coder/coder.bluebook +3 -1
- data/lib/hecks/command_names/index.rb +1 -1
- data/lib/hecks/command_names/resolution.rb +15 -5
- data/lib/hecks/composer/toolbox.rb +2 -1
- data/lib/hecks/corpus.rb +4 -0
- data/lib/hecks/doc/reference/readme.rb +1 -1
- data/lib/hecks/framework_bluebooks.rb +3 -2
- data/lib/hecks/fuzzing/differential.rb +1 -1
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +1 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +1 -1
- data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
- data/lib/hecks/hecks/adapters/ci_runs.adapter +7 -0
- data/lib/hecks/hecks/adapters/ci_runs.rb +80 -0
- data/lib/hecks/hecks/adapters/claude_agent_list.rb +43 -0
- data/lib/hecks/hecks/adapters/claude_agents.rb +50 -19
- data/lib/hecks/hecks/adapters/claude_jobs.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_queries.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/minitest_runs.rb +4 -2
- data/lib/hecks/hecks/adapters/codebase/run_options.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +3 -19
- data/lib/hecks/hecks/adapters/dispatching/brief_text.rb +5 -3
- data/lib/hecks/hecks/adapters/dispatching/launcher.rb +2 -1
- data/lib/hecks/hecks/adapters/dispatching/plan.rb +8 -4
- data/lib/hecks/hecks/adapters/dispatching/settings.rb +2 -1
- data/lib/hecks/hecks/adapters/dispatching/settler.rb +4 -2
- data/lib/hecks/hecks/adapters/dispatching.rb +6 -3
- data/lib/hecks/hecks/adapters/doctor_reading.rb +4 -2
- data/lib/hecks/hecks/adapters/git_landing/carried.rb +42 -0
- data/lib/hecks/hecks/adapters/git_landing/finisher.rb +2 -1
- data/lib/hecks/hecks/adapters/git_landing/flagged.rb +32 -0
- data/lib/hecks/hecks/adapters/git_landing/tidy.rb +27 -14
- data/lib/hecks/hecks/adapters/git_landing.rb +30 -13
- data/lib/hecks/hecks/adapters/git_leftovers.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_leftovers.rb +49 -0
- data/lib/hecks/hecks/adapters/git_main_pull.rb +2 -2
- data/lib/hecks/hecks/adapters/git_train.rb +9 -5
- data/lib/hecks/hecks/adapters/git_worktree_add.rb +8 -4
- data/lib/hecks/hecks/adapters/github_pull_requests.rb +2 -1
- data/lib/hecks/hecks/adapters/in_process_operations.rb +2 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/readings/composition.rb +2 -1
- data/lib/hecks/hecks/adapters/journal_store/translation_write/shape_edge.rb +4 -2
- data/lib/hecks/hecks/adapters/journal_store/translation_write.rb +4 -2
- data/lib/hecks/hecks/adapters/landed_branches.rb +38 -0
- data/lib/hecks/hecks/adapters/main_boarding.adapter +7 -0
- data/lib/hecks/hecks/adapters/main_boarding.rb +85 -0
- data/lib/hecks/hecks/adapters/main_integration/checks.rb +63 -0
- data/lib/hecks/hecks/adapters/main_integration/command_runner.rb +32 -0
- data/lib/hecks/hecks/adapters/main_integration/range_guard.rb +49 -0
- data/lib/hecks/hecks/adapters/main_integration/repository.rb +83 -0
- data/lib/hecks/hecks/adapters/main_integration/runs.rb +51 -0
- data/lib/hecks/hecks/adapters/main_integration/train.rb +74 -0
- data/lib/hecks/hecks/adapters/main_integration.adapter +7 -0
- data/lib/hecks/hecks/adapters/main_integration.rb +77 -0
- data/lib/hecks/hecks/adapters/main_verification.adapter +7 -0
- data/lib/hecks/hecks/adapters/main_verification.rb +89 -0
- data/lib/hecks/hecks/adapters/push_recorder.adapter +7 -0
- data/lib/hecks/hecks/adapters/push_recorder.rb +78 -0
- data/lib/hecks/hecks/adapters/red_flag_guard.rb +62 -0
- data/lib/hecks/hecks/adapters/red_flag_marker.rb +101 -0
- data/lib/hecks/hecks/adapters/red_flag_report.rb +59 -0
- data/lib/hecks/hecks/adapters/red_flag_survey.rb +65 -0
- data/lib/hecks/hecks/adapters/red_flagging.adapter +7 -0
- data/lib/hecks/hecks/adapters/red_flagging.rb +98 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +6 -1
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +6 -3
- data/lib/hecks/hecks/adapters/settings_reading/event_tap_row.rb +33 -0
- data/lib/hecks/hecks/adapters/settings_reading.rb +3 -1
- data/lib/hecks/hecks/codebase.behaviors +0 -6
- data/lib/hecks/hecks/custodian.behaviors +22 -1
- data/lib/hecks/hecks/delegate/delegate.bluebook +128 -15
- data/lib/hecks/hecks/deliver/deliver.bluebook +450 -2
- data/lib/hecks/hecks/environments/memory.world +5 -0
- data/lib/hecks/hecks/hecks.contextmap +8 -0
- data/lib/hecks/hecks/hecks.hecksagon +106 -6
- data/lib/hecks/hecks/hecks.world +9 -1
- data/lib/hecks/hecks/migrate/migrate.bluebook +6 -1
- data/lib/hecks/hecks/redflag/redflag.bluebook +249 -0
- data/lib/hecks/hecks/test/test.bluebook +0 -19
- data/lib/hecks/language/adapter.bluebook +13 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +8 -9
- data/lib/hecks/language/sentence/sentence.bluebook +2 -0
- data/lib/hecks/pipeline/pipeline.bluebook +251 -0
- data/lib/hecks/pipeline/pipeline.hecksagon +17 -0
- data/lib/hecks/pipeline_board/adapters/cache.rb +43 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_fill.rb +56 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_git.rb +82 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_github/runs.rb +66 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_github.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_github.rb +70 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_jobs.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_jobs.rb +87 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal/chain.rb +40 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal/tap.rb +75 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal.rb +83 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/ci_tiles.rb +85 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/fanout.rb +73 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/gather.rb +57 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/metrics.rb +36 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/strip.rb +102 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/tile_kit.rb +57 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/work_tiles.rb +59 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages.rb +84 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_tickets.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_tickets.rb +77 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/page.html +292 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/sequence.html +190 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/server.rb +105 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/snapshot.rb +57 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/stream.rb +59 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/wiring.html +536 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web.rb +80 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring/details.rb +85 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring/graph.rb +120 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring.rb +36 -0
- data/lib/hecks/pipeline_board/adapters/probe.rb +48 -0
- data/lib/hecks/pipeline_board/pipeline_board.bluebook +144 -0
- data/lib/hecks/pipeline_board/pipeline_board.hecksagon +12 -0
- data/lib/hecks/pipeline_board/pipeline_board.ports.hecksagon +47 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +2 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/compose.rb +4 -3
- data/lib/hecks/ports/persistence/repository_factory.rb +2 -1
- data/lib/hecks/projections/ci_pipeline/table.rb +2 -1
- data/lib/hecks/projections/deploy/lambda/stack/recipes.rb +2 -2
- data/lib/hecks/projections/undo.rb +2 -1
- data/lib/hecks/projector/cli_projector/command_help.rb +5 -4
- data/lib/hecks/projector/cli_projector/renames.rb +4 -2
- data/lib/hecks/projector/cli_projector/usage.rb +4 -3
- data/lib/hecks/projector/exporter/adapters.rb +3 -2
- data/lib/hecks/projector/narrate_projector/use_cases.rb +74 -0
- data/lib/hecks/projector/narrate_projector.rb +10 -3
- data/lib/hecks/quality_control/adapters/main_maintenance.rb +9 -5
- data/lib/hecks/quality_control/adapters/qa_auto_mint.rb +21 -2
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +2 -1
- data/lib/hecks/quality_control/cli/qa_fix_main_bug/publishing.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_fix_main_bug.rb +10 -8
- data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_main_gate/gate_run.rb +6 -3
- data/lib/hecks/quality_control/cli/qa_main_gate/logging.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_main_gate/shell.rb +4 -2
- data/lib/hecks/quality_control/cli/qa_main_gate/sweeping.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_main_gate/worktree.rb +3 -2
- data/lib/hecks/quality_control/cli/qa_main_gate.rb +10 -5
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode/children.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_tick.rb +6 -3
- data/lib/hecks/quality_control/quality_control.bluebook +11 -11
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +6 -3
- data/lib/hecks/query_ir/touchpoints.rb +2 -1
- data/lib/hecks/refused.rb +2 -1
- data/lib/hecks/release/approval.rb +4 -2
- data/lib/hecks/runtime/event_log.rb +27 -0
- data/lib/hecks/runtime/event_tap.rb +106 -0
- data/lib/hecks/runtime/loader/boot_steps.rb +1 -1
- data/lib/hecks/runtime/loader.rb +35 -19
- data/lib/hecks/runtime/registry/durability_warnings.rb +12 -5
- data/lib/hecks/runtime/registry/runtime_state.rb +4 -1
- data/lib/hecks/runtime/registry/saga_anchor.rb +54 -0
- data/lib/hecks/runtime/registry/saga_durability_rule.rb +66 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +58 -12
- data/lib/hecks/runtime/registry/verification.rb +7 -3
- data/lib/hecks/runtime/saga_interpreter/lifecycle.rb +2 -1
- data/lib/hecks/runtime/saga_interpreter.rb +1 -1
- data/lib/hecks/rust_format.rb +2 -1
- data/lib/hecks/shape_ledger/edge_hygiene.rb +10 -5
- data/lib/hecks/shape_ledger.rb +1 -1
- data/lib/hecks/storehouse/audit.rb +2 -1
- data/lib/hecks/storehouse/identity.rb +6 -3
- data/lib/hecks/storehouse.rb +2 -1
- data/lib/hecks/tickets/bluebook/tickets.bluebook +5 -4
- data/lib/hecks/tools/ci_workflows.rb +2 -1
- data/lib/hecks/tools/document.rb +12 -1
- data/lib/hecks/tools/downstream_model_check.rb +4 -2
- data/lib/hecks/tools/fuzz_sweep/shards.rb +9 -30
- data/lib/hecks/tools/model_check/examination.rb +25 -2
- data/lib/hecks/tools/move_disables.rb +4 -2
- data/lib/hecks/tools/regeneration_run/scratch.rb +2 -1
- data/lib/hecks/tools/rspec_shard_files.rb +12 -15
- data/lib/hecks/tools/sequence_script.rb +1 -1
- data/lib/hecks/tools/shard_planner.rb +65 -0
- data/lib/hecks/tools/stage_progress.rb +2 -1
- data/lib/hecks/tools/stress_concurrency_specs.rb +2 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +8 -9
- data/lib/hecks.rb +5 -2
- data/prebuilt/{13c04bdc5970295b310f0a980d397ba722232c8da99cc1a0cdb383249921e871.marshal → 4b81646f769ae5b1e7457f9b11c48d8bc1093e96a1dd6fb61864065121ca6f01.marshal} +0 -0
- data/prebuilt/{e3857b77f47d9ecc1787b2e1f5136364f3cb3444ef4aea5479f3b98167affea2.marshal → 681890632e3f4a99646623dcae44af36b1bad66c515f5abf67a2460372b9c6fd.marshal} +0 -0
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/parser/src/emit.rs +5 -1
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +12 -0
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/adapter.rs +1 -0
- data/rust/parser/src/phrases.rs +16 -0
- data/rust/parser/src/sentence/examples.rs +93 -0
- data/rust/parser/src/sentence/mod.rs +1 -0
- metadata +89 -5
- data/lib/hecks/tools/refresh_rspec_runtime_baseline.rb +0 -166
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module PipelineBoard
|
|
6
|
+
# What a node declares, for the sidebar: an aggregate's description, commands with their goals
|
|
7
|
+
# and events, lifecycle and queries; a port's operations; an adapter's port. Read from the IR
|
|
8
|
+
# and never from a store.
|
|
9
|
+
class Details
|
|
10
|
+
# @param registry [Runtime::Registry] a booted registry
|
|
11
|
+
def initialize(registry)
|
|
12
|
+
@registry = registry
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param node [Hash] a graph node
|
|
16
|
+
# @return [Hash] the node with its `details`
|
|
17
|
+
def enrich(node)
|
|
18
|
+
node.merge(details: send(node[:kind], node))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def aggregate(node)
|
|
24
|
+
book = @registry.bluebooks[node[:domain]]
|
|
25
|
+
held = book&.aggregates&.find { |candidate| candidate.hecks_name == node[:label] }
|
|
26
|
+
return {} unless held
|
|
27
|
+
|
|
28
|
+
{ description: held.description.to_s, vision: vision(book, node), commands: commands(held),
|
|
29
|
+
lifecycle: lifecycle(held), queries: queries(held) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def vision(book, node)
|
|
33
|
+
return "" unless node[:context] && book.respond_to?(:modules)
|
|
34
|
+
|
|
35
|
+
row = book.modules.find { |held| held.name.to_s == node[:context] }
|
|
36
|
+
row ? row.vision.to_s : ""
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def commands(held)
|
|
40
|
+
held.commands.map do |command|
|
|
41
|
+
{ name: name_of(command), goal: command.goal.to_s, emits: Array(command.emits).map(&:to_s) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def lifecycle(held)
|
|
46
|
+
cycle = held.lifecycle
|
|
47
|
+
return nil unless cycle
|
|
48
|
+
|
|
49
|
+
{ field: cycle.field.to_s, default: cycle.default.to_s, states: Array(cycle.states).map(&:to_s) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def queries(held) = held.queries.map { |query| { name: name_of(query), description: query.description.to_s } }
|
|
53
|
+
|
|
54
|
+
def port(node)
|
|
55
|
+
declared = declared_ports.find { |held| held.name.to_s == node[:label] }
|
|
56
|
+
return {} unless declared
|
|
57
|
+
|
|
58
|
+
{ operations: declared.operations.map { |operation| operation_of(operation) },
|
|
59
|
+
answers: Array(declared.answered_queries).map(&:to_s) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Every operation port an aggregate of any booted bluebook declares.
|
|
63
|
+
def declared_ports
|
|
64
|
+
aggregates = @registry.bluebooks.each_value.flat_map(&:aggregates)
|
|
65
|
+
aggregates.flat_map { |held| held.respond_to?(:ports) ? held.ports : [] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def operation_of(operation)
|
|
69
|
+
{ name: name_of(operation), direction: operation.direction.to_s, answers: operation.answers.to_s,
|
|
70
|
+
refuses: operation.refuses.to_s, emits: Array(operation.emits).map(&:to_s) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def adapter(node)
|
|
74
|
+
held = @registry.adapters[node[:label]]
|
|
75
|
+
return {} unless held
|
|
76
|
+
|
|
77
|
+
{ port: (held.respond_to?(:port_name) ? held.port_name : held.port).to_s,
|
|
78
|
+
fields: (held.respond_to?(:fields) ? Array(held.fields) : []).map(&:to_s) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def name_of(item) = (item.respond_to?(:hecks_name) ? item.hecks_name : item.name).to_s
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module PipelineBoard
|
|
6
|
+
# The wiring of a booted registry as a graph: an aggregate is a node, a policy is an edge from
|
|
7
|
+
# the aggregate whose event it hears to the aggregate whose command it reaches, an `ask` goes
|
|
8
|
+
# to a port node, and a port goes to the adapter bound to it. Only what is wired is drawn, so
|
|
9
|
+
# an aggregate no policy or port touches is left out.
|
|
10
|
+
class Graph
|
|
11
|
+
# @param registry [Runtime::Registry] a booted registry
|
|
12
|
+
def initialize(registry)
|
|
13
|
+
@registry = registry
|
|
14
|
+
@nodes = {}
|
|
15
|
+
@edges = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Hash] `nodes` and `edges`, each a Hash of plain strings
|
|
19
|
+
def to_h
|
|
20
|
+
policy_edges
|
|
21
|
+
adapter_edges
|
|
22
|
+
details = Details.new(@registry)
|
|
23
|
+
{ nodes: @nodes.values.map { |node| details.enrich(node) }, edges: @edges.uniq }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def policy_edges
|
|
29
|
+
@registry.bluebooks.each_value do |bluebook|
|
|
30
|
+
bluebook.policies.each { |policy| edge_for(bluebook.name.to_s, policy) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def edge_for(domain, policy)
|
|
35
|
+
from = aggregate_id(domain, policy.event_qualifier.to_s)
|
|
36
|
+
target = policy.reaches.to_s
|
|
37
|
+
return if from.empty? || target.empty?
|
|
38
|
+
|
|
39
|
+
to = target.start_with?("ask :") ? nil : destination(domain, target)
|
|
40
|
+
link(from, to, policy.name.to_s, to&.start_with?("port:") ? "ask" : "policy")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The node a reach lands on: the port when the verb is a port operation, else the aggregate.
|
|
44
|
+
def destination(domain, verb)
|
|
45
|
+
parts = path_of(verb)
|
|
46
|
+
return aggregate_id(domain, parts.first) if parts.size == 1
|
|
47
|
+
return "port:#{parts.last}" if port?(parts.last)
|
|
48
|
+
return aggregate_id(parts.first, parts[1]) if @registry.bluebooks.key?(parts.first)
|
|
49
|
+
|
|
50
|
+
aggregate_id(domain, parts.first)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The `Domain::Aggregate::Part` segments of a verb, without its command.
|
|
54
|
+
def path_of(verb)
|
|
55
|
+
owner = verb.split(".").first
|
|
56
|
+
owner.to_s.split("::")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def port?(name) = port_names.include?(name.to_s)
|
|
60
|
+
|
|
61
|
+
# Every port name: the plain ports the registry holds and the operation ports each
|
|
62
|
+
# aggregate declares.
|
|
63
|
+
def port_names
|
|
64
|
+
@port_names ||= @registry.ports.keys.map(&:to_s) + @registry.bluebooks.each_value.flat_map { |book| owned_ports(book) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def owned_ports(bluebook)
|
|
68
|
+
bluebook.aggregates.flat_map { |aggregate| declared_ports(aggregate) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def declared_ports(aggregate)
|
|
72
|
+
aggregate.respond_to?(:ports) ? aggregate.ports.map { |port| port.name.to_s } : []
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def aggregate_id(domain, aggregate) = aggregate.to_s.empty? ? "" : "#{domain}::#{aggregate.split("::").last}"
|
|
76
|
+
|
|
77
|
+
def link(from, to, label, kind)
|
|
78
|
+
return unless to
|
|
79
|
+
|
|
80
|
+
origin = node(from, nil)
|
|
81
|
+
node(to, origin)
|
|
82
|
+
@edges << { from: from, to: to, label: label, kind: kind }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# An aggregate belongs to its own domain and to the module its bluebook declares it in,
|
|
86
|
+
# when there is one; a port to the domain and module of the aggregate that asks through it.
|
|
87
|
+
def node(id, asking)
|
|
88
|
+
port = id.start_with?("port:")
|
|
89
|
+
label = id.split("::").last.sub("port:", "")
|
|
90
|
+
domain = port ? asking[:domain] : id.split("::").first
|
|
91
|
+
context = port ? asking[:context] : module_of(domain, label)
|
|
92
|
+
@nodes[id] ||= { id: id, kind: port ? "port" : "aggregate", domain: domain, label: label, context: context }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def module_of(domain, aggregate)
|
|
96
|
+
book = @registry.bluebooks[domain]
|
|
97
|
+
return nil unless book.respond_to?(:modules)
|
|
98
|
+
|
|
99
|
+
row = book.modules.find { |held| held.aggregate.to_s == aggregate }
|
|
100
|
+
row && row.name.to_s
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def adapter_edges
|
|
104
|
+
@registry.adapters.each_value do |adapter|
|
|
105
|
+
port = adapter.respond_to?(:port_name) ? adapter.port_name : adapter.port
|
|
106
|
+
next unless @nodes.key?("port:#{port}")
|
|
107
|
+
|
|
108
|
+
bind(port, adapter.name.to_s)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def bind(port, adapter)
|
|
113
|
+
id = "adapter:#{adapter}"
|
|
114
|
+
@nodes[id] = { id: id, kind: "adapter", domain: "", label: adapter }
|
|
115
|
+
@edges << { from: "port:#{port}", to: id, label: "", kind: "adapter" }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Binds the `Wiring` port to `PipelineWiring`, which answers `Wiring` from the booted registry.
|
|
2
|
+
# Required by name, not relative, so a copy of this directory shares the one implementation loaded.
|
|
3
|
+
require "hecks/pipeline_board/adapters/pipeline_wiring"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "PipelineWiring" do
|
|
6
|
+
port "Wiring"
|
|
7
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "probe"
|
|
5
|
+
require_relative "pipeline_wiring/details"
|
|
6
|
+
require_relative "pipeline_wiring/graph"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Adapters
|
|
10
|
+
# The `Wiring` port's adapter: answers `Wiring` with what the loaded bluebooks wire together,
|
|
11
|
+
# for the diagram: which aggregate's events a policy listens to, which aggregate's command it
|
|
12
|
+
# reaches, which port an `ask` goes out through, and which adapter answers each port. It reads
|
|
13
|
+
# the booted registry and nothing else, and changes nothing.
|
|
14
|
+
class PipelineWiring
|
|
15
|
+
# @param registry [Runtime::Registry, nil] the booted registry whose bluebooks are read
|
|
16
|
+
# @param aggregate [Object, nil] unused
|
|
17
|
+
# @param settings [Hash] unused
|
|
18
|
+
# @param root [String, nil] unused
|
|
19
|
+
def initialize(registry: nil, aggregate: nil, settings: {}, root: nil)
|
|
20
|
+
@registry = registry
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Hash{Symbol => String}] `text:` the graph as JSON: `nodes` and `edges`
|
|
24
|
+
# @raise [PipelineBoard::Unavailable] when no registry is booted here
|
|
25
|
+
def wiring = { text: JSON.generate(graph) }
|
|
26
|
+
|
|
27
|
+
# @return [Hash] `nodes` (aggregates, ports, adapters) and `edges` (policy, ask, adapter)
|
|
28
|
+
# @raise [PipelineBoard::Unavailable] when no registry is booted here
|
|
29
|
+
def graph
|
|
30
|
+
raise PipelineBoard::Unavailable, "no domain is booted here to read the wiring from" unless @registry
|
|
31
|
+
|
|
32
|
+
@graph ||= PipelineBoard::Graph.new(@registry).to_h
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "timeout"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Adapters
|
|
9
|
+
# What the PipelineBoard readers share: the reason a read failed, the outside command they run,
|
|
10
|
+
# and the short cache that keeps a polling page from hammering it.
|
|
11
|
+
module PipelineBoard
|
|
12
|
+
# A read that could not be answered. The message is the reason a stage shows after `unknown:`.
|
|
13
|
+
class Unavailable < StandardError; end
|
|
14
|
+
|
|
15
|
+
# Runs one outside command for a reader and answers its standard output.
|
|
16
|
+
module Probe
|
|
17
|
+
TIMEOUT_SECONDS = 25
|
|
18
|
+
|
|
19
|
+
# @param command [Array<String>] the program and its arguments, never a shell string
|
|
20
|
+
# @param chdir [String, nil] where it runs
|
|
21
|
+
# @return [String] standard output
|
|
22
|
+
# @raise [Unavailable] when the program is missing, times out or exits non-zero
|
|
23
|
+
def self.run(*command, chdir: nil, seconds: TIMEOUT_SECONDS)
|
|
24
|
+
out, err, status = Timeout.timeout(seconds) { Open3.capture3(*command, chdir: chdir || Dir.pwd) }
|
|
25
|
+
raise Unavailable, "#{command.first} #{command[1]}: #{reason(err)}" unless status.success?
|
|
26
|
+
|
|
27
|
+
out
|
|
28
|
+
rescue Errno::ENOENT then raise Unavailable, "#{command.first} is not installed"
|
|
29
|
+
rescue Timeout::Error then raise Unavailable, "#{command.first} did not answer in #{seconds}s"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [String] the first line the command wrote to standard error
|
|
33
|
+
def self.reason(err)
|
|
34
|
+
first = err.lines.first
|
|
35
|
+
first.to_s.strip
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Object] the JSON the command printed
|
|
39
|
+
# @raise [Unavailable] when the output is not JSON
|
|
40
|
+
def self.json(*command, chdir: nil)
|
|
41
|
+
JSON.parse(run(*command, chdir: chdir))
|
|
42
|
+
rescue JSON::ParserError => e
|
|
43
|
+
raise Unavailable, "#{command.first} printed no JSON: #{e.message[0, 60]}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# PipelineBoard: the pipeline from coding to release, read live and shown on one local page. The
|
|
2
|
+
# read side is six queries, each answered by an adapter behind a port: GitHub for the CI runs, Git
|
|
3
|
+
# for local main and the train, Tickets for the Work items, Jobs for the sessions, Journal for the
|
|
4
|
+
# events the tooling recorded. The bluebook names the questions and never fetches, shells out or
|
|
5
|
+
# opens a file.
|
|
6
|
+
#
|
|
7
|
+
# The one command is `Serve`. It opens the page on 127.0.0.1 and answers `GET /` and
|
|
8
|
+
# `GET /api/state` until it is stopped; no command is reachable from the page, so the board only
|
|
9
|
+
# reads. A read that fails answers `unknown: <reason>` for its stage and leaves the others alone.
|
|
10
|
+
#
|
|
11
|
+
# exe/hecks PipelineBoard.serve! port.value=4747 --wait
|
|
12
|
+
|
|
13
|
+
Hecks.bluebook "PipelineBoard" do
|
|
14
|
+
vision "The pipeline from a Work row to a release is read live, stage by stage, and shown with the real events it emitted."
|
|
15
|
+
|
|
16
|
+
supporting
|
|
17
|
+
|
|
18
|
+
# The board. One run of it is one server opened on a local port, until it is stopped.
|
|
19
|
+
aggregate "PipelineBoard" do
|
|
20
|
+
description "The pipeline's stages, CI runs, work, sessions and events as they stand, and one server that shows them."
|
|
21
|
+
|
|
22
|
+
identified_by :run
|
|
23
|
+
|
|
24
|
+
attribute :run, RunKey
|
|
25
|
+
attribute :port, Port, optional: true
|
|
26
|
+
attribute :last, Reason, optional: true
|
|
27
|
+
|
|
28
|
+
value_object "RunKey" do
|
|
29
|
+
attribute :value, String, matches: :present
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# A local port the page is served on.
|
|
33
|
+
value_object "Port" do
|
|
34
|
+
attribute :value, Integer, default: 4747
|
|
35
|
+
|
|
36
|
+
invariant("a port is 1024 to 65535") { value >= 1024 && value <= 65535 }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Why a server ended or was refused.
|
|
40
|
+
value_object "Reason" do
|
|
41
|
+
attribute :value, String
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A JSON document: the results of a query, or `unknown: <reason>` for a read that failed.
|
|
45
|
+
value_object "Document" do
|
|
46
|
+
attribute :text, String
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# How many results a query answers, at most.
|
|
50
|
+
value_object "Limit" do
|
|
51
|
+
attribute :value, Integer, default: 20
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
lifecycle :status, default: "requested" do
|
|
55
|
+
mark :failure, "refused"
|
|
56
|
+
mark :terminal, "closed", "refused"
|
|
57
|
+
transition "Close" => "closed", from: "requested"
|
|
58
|
+
transition "Refuse" => "refused", from: "requested"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
command "Serve" do
|
|
62
|
+
role "Maintainer"
|
|
63
|
+
goal "Show the pipeline on a page at 127.0.0.1 until stopped; the page reads and writes nothing"
|
|
64
|
+
|
|
65
|
+
attribute :run, RunKey, sets: true
|
|
66
|
+
attribute :port, Port, default: 4747, sets: true
|
|
67
|
+
|
|
68
|
+
emits ServeRequested
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
command "Close" do
|
|
72
|
+
role "System"
|
|
73
|
+
goal "Record that the server ended"
|
|
74
|
+
|
|
75
|
+
reference_to PipelineBoard
|
|
76
|
+
attribute :last, Reason, sets: true
|
|
77
|
+
|
|
78
|
+
emits BoardClosed
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
command "Refuse" do
|
|
82
|
+
role "System"
|
|
83
|
+
goal "Record that the server could not open, and why"
|
|
84
|
+
|
|
85
|
+
reference_to PipelineBoard
|
|
86
|
+
attribute :last, Reason, sets: true
|
|
87
|
+
|
|
88
|
+
emits BoardRefused
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
query "Stages" do
|
|
92
|
+
description "One entry per stage, left to right: Work items, Sessions, Local main, Train, CI, Promote, Release, each with its state, count and a one-line detail."
|
|
93
|
+
returns Document
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
query "CiRuns" do
|
|
97
|
+
description "The last CI runs on main, oldest first: sha, started, wall minutes, conclusion and how many jobs failed."
|
|
98
|
+
attribute :limit, Limit, optional: true
|
|
99
|
+
returns Document
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
query "SlowestJobs" do
|
|
103
|
+
description "The jobs of the last completed CI run, slowest first, in minutes."
|
|
104
|
+
attribute :limit, Limit, optional: true
|
|
105
|
+
returns Document
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
query "OpenWork" do
|
|
109
|
+
description "The Work items that are not done: slug, status and area."
|
|
110
|
+
returns Document
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
query "Sessions" do
|
|
114
|
+
description "The busy and the recent dispatched sessions, with the job's own state."
|
|
115
|
+
returns Document
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
query "Wiring" do
|
|
119
|
+
description "What the loaded bluebooks wire together: aggregates, the policies between them, the ports an ask goes out through and the adapters that answer them."
|
|
120
|
+
returns Document
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
query "Events" do
|
|
124
|
+
description "The most recent events the tooling recorded, newest first: time, aggregate, event, and the policy that reacted where the journal holds one."
|
|
125
|
+
attribute :limit, Limit, optional: true
|
|
126
|
+
returns Document
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
policy "ListenWhenServed" do
|
|
131
|
+
on "PipelineBoard.ServeRequested"
|
|
132
|
+
ask :listen, with: { port: :port }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
policy "CloseWhenEnded" do
|
|
136
|
+
on "PipelineBoard.ServerEnded"
|
|
137
|
+
trigger PipelineBoard::Close, with: { last: :last }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
policy "RefuseWhenRefused" do
|
|
141
|
+
on "PipelineBoard.ServerRefused"
|
|
142
|
+
trigger PipelineBoard::Refuse, with: { last: :last }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# The PipelineBoard bluebook, bound so it boots standing alone (a behaviors or model_check boot of
|
|
2
|
+
# this directory). The ports it shares with every attaching hecksagon are in
|
|
3
|
+
# pipeline_board.ports.hecksagon.
|
|
4
|
+
Hecks.hecksagon "PipelineBoard" do
|
|
5
|
+
attaches "Governance"
|
|
6
|
+
persisted_by "Memory"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# The anti-corruption layer for Governance, which the bluebook's roles are checked against.
|
|
10
|
+
Hecks.hecksagon "Governance" do
|
|
11
|
+
persisted_by "Memory"
|
|
12
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# The ports PipelineBoard declares, for every hecksagon that attaches the bluebook. Each names the
|
|
2
|
+
# question it asks of the outside and never says how it is answered; an adapter binds each. Every
|
|
3
|
+
# port but `Web` only reads.
|
|
4
|
+
Hecks.hecksagon "PipelineBoard" do
|
|
5
|
+
attaches "Governance"
|
|
6
|
+
|
|
7
|
+
# CI as GitHub reports it, read with the `gh` CLI.
|
|
8
|
+
PipelineBoard::PipelineBoard.port "GitHub" do
|
|
9
|
+
answers_query "CiRuns"
|
|
10
|
+
answers_query "SlowestJobs"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The Work items, read through the Tickets domain.
|
|
14
|
+
PipelineBoard::PipelineBoard.port "Tickets" do
|
|
15
|
+
answers_query "OpenWork"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# The dispatched sessions: each job's state file and `claude agents --json`.
|
|
19
|
+
PipelineBoard::PipelineBoard.port "Jobs" do
|
|
20
|
+
answers_query "Sessions"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The events the tooling recorded, read from the journal store without writing to it.
|
|
24
|
+
PipelineBoard::PipelineBoard.port "Journal" do
|
|
25
|
+
answers_query "Events"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The wiring of the booted domains, read from the registry, for the diagram.
|
|
29
|
+
PipelineBoard::PipelineBoard.port "Wiring" do
|
|
30
|
+
answers_query "Wiring"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Every stage, composed from the readings above and from this checkout's git state.
|
|
34
|
+
PipelineBoard::PipelineBoard.port "Pipeline" do
|
|
35
|
+
answers_query "Stages"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The page. The adapter opens a server on 127.0.0.1 and answers `GET /` and `GET /api/state`;
|
|
39
|
+
# it is the only effect the board has.
|
|
40
|
+
PipelineBoard::PipelineBoard.port "Web" do
|
|
41
|
+
asks "Listen", to: PipelineBoard do
|
|
42
|
+
attribute :port, Port
|
|
43
|
+
answers "ServerEnded"
|
|
44
|
+
refuses "ServerRefused"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -80,7 +80,8 @@ module Hecks
|
|
|
80
80
|
# @return [Bluebook::Bind] the authoritative bind
|
|
81
81
|
# @raise [Runtime::WiringError] if the roleless count is not one, or a role is present
|
|
82
82
|
def authoritative_among(bindings, domain, aggregate)
|
|
83
|
-
# The same adapter declared again, by a hecksagon that another domain also attaches, is
|
|
83
|
+
# The same adapter declared again, by a hecksagon that another domain also attaches, is
|
|
84
|
+
# one
|
|
84
85
|
# binding, not an ambiguous second one.
|
|
85
86
|
authoritative = bindings.select { |bind| bind.role.nil? || bind.role.empty? }.uniq(&:adapter)
|
|
86
87
|
raise ambiguous_binding(domain, aggregate, authoritative) unless authoritative.size == 1
|
|
@@ -32,7 +32,6 @@ module Hecks
|
|
|
32
32
|
# its one step is the edge from that label to itself, when such an edge exists.
|
|
33
33
|
#
|
|
34
34
|
# @return [Array<Bluebook::Translation>] the edges in order; empty when none leads `from` to `to`
|
|
35
|
-
# and `from` is `to`
|
|
36
35
|
# @raise [Runtime::WiringError] when a step has no edge, has two, or the walk loops
|
|
37
36
|
def chain(translations, domain:, from:, to:)
|
|
38
37
|
return self_edges(translations, domain, from) if from == to
|
|
@@ -48,7 +47,8 @@ module Hecks
|
|
|
48
47
|
steps
|
|
49
48
|
end
|
|
50
49
|
|
|
51
|
-
# @return [Bluebook::Translation] the one edge from the first step's origin to the last step's
|
|
50
|
+
# @return [Bluebook::Translation] the one edge from the first step's origin to the last step's
|
|
51
|
+
# target
|
|
52
52
|
# @raise [Runtime::WiringError] when a step's rules cannot be composed into one edge
|
|
53
53
|
def compose(domain:, from:, to:, chain:)
|
|
54
54
|
refuse_unrenderable!(chain)
|
|
@@ -102,7 +102,8 @@ module Hecks
|
|
|
102
102
|
# Follows each aggregate from its held name to its current one, and finds the one step that
|
|
103
103
|
# carries field rules for each origin.
|
|
104
104
|
#
|
|
105
|
-
# @return [Array(Hash, Array<String>, Hash)] origins keyed by current name, every retired
|
|
105
|
+
# @return [Array(Hash, Array<String>, Hash)] origins keyed by current name, every retired
|
|
106
|
+
# name,
|
|
106
107
|
# and the aggregate carrying field rules for each origin
|
|
107
108
|
def track_chain(chain)
|
|
108
109
|
state = { origins: {}, retired: [], ruled: {} }
|
|
@@ -55,7 +55,8 @@ module Hecks
|
|
|
55
55
|
settings.merge(boot_marks(registry, domain))
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
# The per-domain marks the boot gate left on the registry, merged into an adapter's
|
|
58
|
+
# The per-domain marks the boot gate left on the registry, merged into an adapter's
|
|
59
|
+
# settings.
|
|
59
60
|
#
|
|
60
61
|
# @return [Hash] the domain, resolved era, superseding era and whether the shape is unminted
|
|
61
62
|
def boot_marks(registry, domain)
|
|
@@ -41,7 +41,8 @@ module Hecks
|
|
|
41
41
|
"#{claimed.map(&:name).join(", ")}; a project has one set of workflow rows"
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# @return [Array<Bluebook::Declaration>] the project's bluebooks that declare `Workflow`
|
|
44
|
+
# @return [Array<Bluebook::Declaration>] the project's bluebooks that declare `Workflow`
|
|
45
|
+
# rows
|
|
45
46
|
def self.holders(registry)
|
|
46
47
|
clients = registry.bluebooks.values.reject { |bluebook| bluebook.name == BLUEBOOK }
|
|
47
48
|
clients.select { |bluebook| rows_of(bluebook, MARKER).any? }
|
|
@@ -26,11 +26,11 @@ module Hecks
|
|
|
26
26
|
|
|
27
27
|
# `hecks ask` answers the scaffold as text, and refuses (exit 1) where the script
|
|
28
28
|
# aborted.
|
|
29
|
-
SCAFFOLD = Translation.new(verb: "scaffold-translation", script: "exe/hecks
|
|
29
|
+
SCAFFOLD = Translation.new(verb: "scaffold-translation", script: "exe/hecks era.scaffold_translation",
|
|
30
30
|
extra_args: "", cwd: "$(ROOT)", run_prefix: "HECKS_ENVIRONMENT=memory ruby",
|
|
31
31
|
db_env_blind: true, domain_arg: "domain=$(DOMAIN) ")
|
|
32
32
|
# A refused audit is a refused query: `hecks ask` exits 1, so the target fails too.
|
|
33
|
-
AUDIT = Translation.new(verb: "translation-audit", script: "exe/hecks
|
|
33
|
+
AUDIT = Translation.new(verb: "translation-audit", script: "exe/hecks era.audit_translation",
|
|
34
34
|
extra_args: "", cwd: "$(ROOT)", run_prefix: "HECKS_ENVIRONMENT=memory ruby",
|
|
35
35
|
db_env_blind: true, domain_arg: "domain=$(DOMAIN) ")
|
|
36
36
|
# An app-owned one-time migration script instead of a hecks tool:
|
|
@@ -15,7 +15,8 @@ module Hecks
|
|
|
15
15
|
module_function
|
|
16
16
|
|
|
17
17
|
# @param bluebook [Bluebook::Declaration] the bluebook holding the process managers
|
|
18
|
-
# @param options [Hash{Symbol => Object}] `command:`, the command's verb form, e.g.
|
|
18
|
+
# @param options [Hash{Symbol => Object}] `command:`, the command's verb form, e.g.
|
|
19
|
+
# "Account.Debit"
|
|
19
20
|
# @return [Array<String>] one sentence: the undoing command, or that none is declared
|
|
20
21
|
def call(bluebook:, options: {})
|
|
21
22
|
command = options.fetch(:command)
|
|
@@ -5,9 +5,9 @@ module Hecks
|
|
|
5
5
|
module CommandHelp
|
|
6
6
|
# Four independent blocks, concatenated in fixed order: meta, invocation,
|
|
7
7
|
# arguments, refusals.
|
|
8
|
-
def command_help(program, name, spec, ask: false)
|
|
8
|
+
def command_help(program, name, spec, ask: false, qualified: false)
|
|
9
9
|
out = command_help_meta_lines(name, spec)
|
|
10
|
-
out.concat(command_help_invocation_lines(program, name, spec, ask))
|
|
10
|
+
out.concat(command_help_invocation_lines(program, name, spec, [ask, qualified]))
|
|
11
11
|
out.concat(command_help_argument_lines(spec))
|
|
12
12
|
out.concat(command_help_refusal_lines(spec))
|
|
13
13
|
out.join("\n")
|
|
@@ -28,8 +28,9 @@ module Hecks
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# The example call with every argument as `path=…`.
|
|
31
|
-
def command_help_invocation_lines(program, name, spec,
|
|
32
|
-
|
|
31
|
+
def command_help_invocation_lines(program, name, spec, asking)
|
|
32
|
+
ask, qualified = asking
|
|
33
|
+
invocation = ask ? [program, ("query" if qualified), name].compact.join(" ") : "#{program} #{name}!"
|
|
33
34
|
["", " #{invocation}#{spec[:arguments].map { |a| " #{a[:path]}=…" }.join}", ""]
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -3,7 +3,8 @@ module Hecks
|
|
|
3
3
|
module CliProjector
|
|
4
4
|
# The launcher's rename table: the old command names a bluebook's `launcher` setting lists as
|
|
5
5
|
# `renamed`, each mapped to the command it became. An old spelling resolves to the new command
|
|
6
|
-
# and the launcher prints the new spelling once; an entry may also translate old argument
|
|
6
|
+
# and the launcher prints the new spelling once; an entry may also translate old argument
|
|
7
|
+
# names.
|
|
7
8
|
#
|
|
8
9
|
# The table resolves through 8.0; its removal is recorded in docs/compatibility.md.
|
|
9
10
|
module Renames
|
|
@@ -13,7 +14,8 @@ module Hecks
|
|
|
13
14
|
# @param commands [Hash{String => Hash}] the projected commands, keyed by snake-form name
|
|
14
15
|
# @param renamed [Hash{String => String, Hash}, nil] old name to new name, or to a hash
|
|
15
16
|
# with `to:` and `arguments:` (old argument name to new)
|
|
16
|
-
# @return [Hash{Symbol => Hash{String => String}}] `:command` and `:question`, each mapping
|
|
17
|
+
# @return [Hash{Symbol => Hash{String => String}}] `:command` and `:question`, each mapping
|
|
18
|
+
# an
|
|
17
19
|
# old spelling to the name it became
|
|
18
20
|
# @raise [Bluebook::DSL::Malformed] if an entry names no projected command
|
|
19
21
|
def renamed_aliases(commands, questions, renamed)
|
|
@@ -25,7 +25,7 @@ module Hecks
|
|
|
25
25
|
return unless only
|
|
26
26
|
|
|
27
27
|
spec = help_spec(only, commands, questions, options[:ask])
|
|
28
|
-
command_help(program, verb_form(spec, spec[:short]), spec, ask: options[:ask]) if spec
|
|
28
|
+
command_help(program, verb_form(spec, spec[:short]), spec, ask: options[:ask], qualified: options[:query_word]) if spec
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# `ask` picks the namespace when both hold the name; without it a
|
|
@@ -58,11 +58,12 @@ module Hecks
|
|
|
58
58
|
module_name ? module_header(bluebook, module_name, program) : usage_header(bluebook, program)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
# The bluebook's vision, the release of the gem this launcher runs, and the two ways to call
|
|
61
|
+
# The bluebook's vision, the release of the gem this launcher runs, and the two ways to call
|
|
62
|
+
# it.
|
|
62
63
|
def usage_header(bluebook, program)
|
|
63
64
|
["#{bluebook.name} — #{bluebook.vision}", " hecks #{Hecks::VERSION}", "",
|
|
64
65
|
" #{program} <command>! [name=value …] do something",
|
|
65
|
-
" #{program}
|
|
66
|
+
" #{program} <query> [name=value …] read something, written bare", ""]
|
|
66
67
|
end
|
|
67
68
|
|
|
68
69
|
# The pointers after the tables: `--help`, `--all`, `--maintainer` and an example call.
|