hecks 1.4.0 → 1.5.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/claude_code.rb +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +29 -5
- data/lib/hecks/projector.rb +74 -6
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +11 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
|
@@ -17,25 +17,29 @@ module Hecks
|
|
|
17
17
|
# "no, that's wrong". A banker, a support rep, the CEO and an engineer
|
|
18
18
|
# read the same page.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
21
|
-
# term is a term, not "an Aggregate" or "a Value Object", and every
|
|
22
|
-
# identifier is spelled as a person says it (`Naming.words`: `ATMCard`
|
|
23
|
-
# is "ATM card"). Second, nothing invented: every sentence is either
|
|
24
|
-
# the domain author's own words (a `description`, a `goal`, an
|
|
25
|
-
# invariant) or built mechanically from a declared fact ("Recorded
|
|
26
|
-
# after Freeze account") — the same discipline `DocsProjector` holds
|
|
27
|
-
# to; a wrong sentence is worse than a missing one. Third, grouped
|
|
28
|
-
# under the aggregate each term belongs to, A to Z within it — a
|
|
29
|
-
# reader thinks "what does Account mean" before "what starts with A",
|
|
30
|
-
# and once there reads the way a dictionary reads.
|
|
20
|
+
# ## Three things follow from that audience
|
|
31
21
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
22
|
+
# First, no type jargon: a term is a term, not "an Aggregate" or "a
|
|
23
|
+
# Value Object", and every identifier is spelled as a person says it
|
|
24
|
+
# (`Naming.words`: `ATMCard` is "ATM card"). Second, nothing invented:
|
|
25
|
+
# every sentence is either the domain author's own words (a
|
|
26
|
+
# `description`, a `goal`, an invariant) or built mechanically from a
|
|
27
|
+
# declared fact ("Recorded after Freeze account") — the same
|
|
28
|
+
# discipline `DocsProjector` holds to; a wrong sentence is worse than
|
|
29
|
+
# a missing one. Third, grouped under the aggregate each term belongs
|
|
30
|
+
# to, A to Z within it — a reader thinks "what does Account mean"
|
|
31
|
+
# before "what starts with A", and once there reads the way a
|
|
32
|
+
# dictionary reads.
|
|
33
|
+
#
|
|
34
|
+
# ## Two files from one source
|
|
35
|
+
#
|
|
36
|
+
# `glossary.md` is the document — every construct in it renders on
|
|
37
|
+
# GitHub as-is (headings, blockquotes, ```mermaid fences, lists,
|
|
38
|
+
# in-page links). `html/index.html` is that exact Markdown string
|
|
39
|
+
# rendered into a page with a navigation rail; it is built from the
|
|
40
|
+
# Markdown, not beside it, so the two cannot drift. `bin/project_glossary`
|
|
41
|
+
# writes both to `<domain>/glossary/` beside the bluebook (examples/banking,
|
|
42
|
+
# examples/pizzas, and the QA ledger in qa/).
|
|
39
43
|
#
|
|
40
44
|
# Projector.call(:glossary, bluebook: <the Bluebook chapter>)
|
|
41
45
|
# # => { "glossary.md" => "...", "html/index.html" => "..." }
|
|
@@ -59,6 +63,14 @@ module Hecks
|
|
|
59
63
|
|
|
60
64
|
module_function
|
|
61
65
|
|
|
66
|
+
# Projects `bluebook`'s glossary as a Markdown document and the HTML
|
|
67
|
+
# page built from it.
|
|
68
|
+
#
|
|
69
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to render a
|
|
70
|
+
# glossary for
|
|
71
|
+
# @param options [Hash] unused; accepted to satisfy the registry's call shape
|
|
72
|
+
# @return [Hash{String => String}] `"glossary.md"` and `"html/index.html"`,
|
|
73
|
+
# each mapped to its rendered content
|
|
62
74
|
def call(bluebook:, options: {})
|
|
63
75
|
markdown = Markdown.render(document(bluebook))
|
|
64
76
|
{ "glossary.md" => markdown, "html/index.html" => Html.render(markdown) }
|
|
@@ -66,6 +78,11 @@ module Hecks
|
|
|
66
78
|
|
|
67
79
|
# ── the document ─────────────────────────────────────────────────
|
|
68
80
|
|
|
81
|
+
# Gathers every term, groups it into sections, assigns headings and
|
|
82
|
+
# slugs, and builds the index links resolve through.
|
|
83
|
+
#
|
|
84
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to render
|
|
85
|
+
# @return [Document] the fully assembled document, ready for `Markdown.render`
|
|
69
86
|
def document(bluebook)
|
|
70
87
|
sections = sections(bluebook, entries(bluebook))
|
|
71
88
|
Slugs.assign!(bluebook, sections)
|
|
@@ -74,6 +91,12 @@ module Hecks
|
|
|
74
91
|
|
|
75
92
|
# Aggregates first, a to Z by their spoken name ("Account" before
|
|
76
93
|
# "ATM card"), then the three groups nothing homes to one aggregate.
|
|
94
|
+
#
|
|
95
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
96
|
+
# aggregates to group by
|
|
97
|
+
# @param entries [Array<Entry>] every gathered term
|
|
98
|
+
# @return [Array<Section>] one section per aggregate, sorted, followed by
|
|
99
|
+
# any of the Roles/Read models/Reactions sections that have entries
|
|
77
100
|
def sections(bluebook, entries)
|
|
78
101
|
grouped = entries.group_by(&:section)
|
|
79
102
|
list = bluebook.aggregates.sort_by { |aggregate| Naming.words(aggregate.hecks_name).downcase }.map do |aggregate|
|
|
@@ -87,10 +110,16 @@ module Hecks
|
|
|
87
110
|
list
|
|
88
111
|
end
|
|
89
112
|
|
|
90
|
-
# A
|
|
113
|
+
# A headword is qualified only when it would repeat within its own
|
|
91
114
|
# section — "Open" the action and "Open (the list)" the question;
|
|
92
115
|
# "Return (key issuance)" beside another Return — never numbered
|
|
93
116
|
# (Chicago 18.9, MDN's disambiguation pages: qualify the headword).
|
|
117
|
+
#
|
|
118
|
+
# @param entries [Array<Entry>] the section's own entries
|
|
119
|
+
# @param section_name [String] the entries' own section name, for telling
|
|
120
|
+
# an entry's own section apart from a borrowed one when qualifying
|
|
121
|
+
# @return [Array<Entry>] `entries`, headword-assigned, sorted by headword
|
|
122
|
+
# then kind
|
|
94
123
|
def with_headwords(entries, section_name)
|
|
95
124
|
entries.each { |entry| entry.headword = Naming.words(entry.name) }
|
|
96
125
|
entries.group_by(&:headword).each_value do |group|
|
|
@@ -101,6 +130,14 @@ module Hecks
|
|
|
101
130
|
entries.sort_by { |entry| [entry.headword.downcase, entry.kind.to_s] }
|
|
102
131
|
end
|
|
103
132
|
|
|
133
|
+
# Disambiguates one entry's headword from the rest of its group.
|
|
134
|
+
#
|
|
135
|
+
# @param entry [Entry] the entry to qualify
|
|
136
|
+
# @param group [Array<Entry>] every entry sharing `entry`'s plain headword
|
|
137
|
+
# @param section_name [String] the section `entry` and `group` are being
|
|
138
|
+
# rendered under
|
|
139
|
+
# @return [String] `entry`'s headword, qualified with "(the list)" or its
|
|
140
|
+
# own holder's name when needed to tell it apart from the rest of `group`
|
|
104
141
|
def qualified(entry, group, section_name)
|
|
105
142
|
base = Naming.words(entry.name)
|
|
106
143
|
return "#{base} (the list)" if entry.kind == :query && group.any? { |other| other.kind == :command }
|
|
@@ -114,6 +151,10 @@ module Hecks
|
|
|
114
151
|
# An entity can carry its own commands and queries too — walked the
|
|
115
152
|
# same one level down `DocsProjector` and `Projections::Diagrams`
|
|
116
153
|
# already walk it.
|
|
154
|
+
#
|
|
155
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
|
|
156
|
+
# @return [Array<Bluebook::Aggregate, Bluebook::Entity>] every aggregate, each
|
|
157
|
+
# immediately followed by its own nested entities
|
|
117
158
|
def holders(bluebook)
|
|
118
159
|
bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
|
|
119
160
|
end
|
|
@@ -121,6 +162,10 @@ module Hecks
|
|
|
121
162
|
# Every holder's own aggregate, one hop or zero — an aggregate maps
|
|
122
163
|
# to itself, an entity to whichever aggregate declared it. The one
|
|
123
164
|
# fact the grouping is built on.
|
|
165
|
+
#
|
|
166
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
|
|
167
|
+
# @return [Hash{String => String}] every aggregate and entity name, mapped to
|
|
168
|
+
# its own owning aggregate's name
|
|
124
169
|
def holder_aggregate(bluebook)
|
|
125
170
|
bluebook.aggregates.each_with_object({}) do |aggregate, map|
|
|
126
171
|
map[aggregate.hecks_name] = aggregate.hecks_name
|
|
@@ -131,14 +176,29 @@ module Hecks
|
|
|
131
176
|
# Every event's raisers — an event is never declared, only emitted,
|
|
132
177
|
# so its home is whichever aggregate the first command that raises
|
|
133
178
|
# it belongs to; a policy or saga reacting to it inherits that home.
|
|
179
|
+
#
|
|
180
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
|
|
181
|
+
# @return [Hash{String => Array<Array(Bluebook::Aggregate, Bluebook::Command)>}]
|
|
182
|
+
# every emitted event name, mapped to `[holder, command]` pairs for every
|
|
183
|
+
# command that raises it; a name absent from the Hash raises nothing
|
|
134
184
|
def event_raisers(bluebook)
|
|
135
185
|
holders(bluebook).each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |holder, map|
|
|
136
186
|
holder.commands.each { |command| command.emits.each { |event| map[event] << [holder, command] } }
|
|
137
187
|
end
|
|
138
188
|
end
|
|
139
189
|
|
|
190
|
+
# Strips a dotted name down to its last segment.
|
|
191
|
+
#
|
|
192
|
+
# @param qualified [String, Symbol] a dotted or bare name, such as
|
|
193
|
+
# `"Handler.Dispatch"` or `"Freeze"`
|
|
194
|
+
# @return [String] the name after the last `.`, or the whole name if it has none
|
|
140
195
|
def bare(qualified) = qualified.to_s.split(".").last
|
|
141
196
|
|
|
197
|
+
# Gathers every term the glossary carries, ungrouped and unordered.
|
|
198
|
+
#
|
|
199
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather terms from
|
|
200
|
+
# @return [Array<Entry>] one entry per entity, value object, command, query,
|
|
201
|
+
# event, policy, saga, role, and read model
|
|
142
202
|
def entries(bluebook)
|
|
143
203
|
homes = holder_aggregate(bluebook)
|
|
144
204
|
raisers = event_raisers(bluebook)
|
|
@@ -156,6 +216,10 @@ module Hecks
|
|
|
156
216
|
entries
|
|
157
217
|
end
|
|
158
218
|
|
|
219
|
+
# Gathers one entry per entity.
|
|
220
|
+
#
|
|
221
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather entities from
|
|
222
|
+
# @return [Array<Entry>] one `:entity`-kind entry per entity
|
|
159
223
|
def entity_entries(bluebook)
|
|
160
224
|
bluebook.aggregates.flat_map do |aggregate|
|
|
161
225
|
aggregate.entities.map do |entity|
|
|
@@ -167,6 +231,11 @@ module Hecks
|
|
|
167
231
|
|
|
168
232
|
# Aggregates only — an entity declares no value objects of its own
|
|
169
233
|
# (`Bluebook::Entity` deliberately does not answer `value_objects`).
|
|
234
|
+
#
|
|
235
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather value
|
|
236
|
+
# objects from
|
|
237
|
+
# @return [Array<Entry>] one `:value_object`-kind entry per aggregate-declared
|
|
238
|
+
# value object
|
|
170
239
|
def value_object_entries(bluebook)
|
|
171
240
|
bluebook.aggregates.flat_map do |aggregate|
|
|
172
241
|
aggregate.value_objects.map do |value_object|
|
|
@@ -176,6 +245,14 @@ module Hecks
|
|
|
176
245
|
end
|
|
177
246
|
end
|
|
178
247
|
|
|
248
|
+
# Gathers one entry per command and query.
|
|
249
|
+
#
|
|
250
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather commands
|
|
251
|
+
# and queries from
|
|
252
|
+
# @param homes [Hash{String => String}] every holder's name, mapped to its
|
|
253
|
+
# owning aggregate's name (as `holder_aggregate` builds)
|
|
254
|
+
# @return [Array<Entry>] one `:command`-kind or `:query`-kind entry per command
|
|
255
|
+
# and query, commands first
|
|
179
256
|
def verb_entries(bluebook, homes)
|
|
180
257
|
holders(bluebook).flat_map do |holder|
|
|
181
258
|
commands = holder.commands.map do |command|
|
|
@@ -190,6 +267,15 @@ module Hecks
|
|
|
190
267
|
end
|
|
191
268
|
end
|
|
192
269
|
|
|
270
|
+
# Gathers one entry per raised event.
|
|
271
|
+
#
|
|
272
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
273
|
+
# policies checked for reactions
|
|
274
|
+
# @param raisers [Hash{String => Array<Array(Bluebook::Aggregate, Bluebook::Command)>}]
|
|
275
|
+
# every event name, mapped to its raising `[holder, command]` pairs (as
|
|
276
|
+
# `event_raisers` builds)
|
|
277
|
+
# @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
|
|
278
|
+
# @return [Array<Entry>] one `:event`-kind entry per raised event
|
|
193
279
|
def event_entries(bluebook, raisers, home_of)
|
|
194
280
|
raisers.map do |event, raised_by|
|
|
195
281
|
reactions = bluebook.policies.select { |policy| bare(policy.on_event) == event }
|
|
@@ -198,6 +284,11 @@ module Hecks
|
|
|
198
284
|
end
|
|
199
285
|
end
|
|
200
286
|
|
|
287
|
+
# Gathers one entry per declared policy.
|
|
288
|
+
#
|
|
289
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather policies from
|
|
290
|
+
# @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
|
|
291
|
+
# @return [Array<Entry>] one `:policy`-kind entry per declared policy
|
|
201
292
|
def policy_entries(bluebook, home_of)
|
|
202
293
|
bluebook.policies.map do |policy|
|
|
203
294
|
Entry.new(name: policy.name, kind: :policy, section: home_of.call(bare(policy.on_event)),
|
|
@@ -205,6 +296,11 @@ module Hecks
|
|
|
205
296
|
end
|
|
206
297
|
end
|
|
207
298
|
|
|
299
|
+
# Gathers one entry per declared process manager.
|
|
300
|
+
#
|
|
301
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather sagas from
|
|
302
|
+
# @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
|
|
303
|
+
# @return [Array<Entry>] one `:saga`-kind entry per declared process manager
|
|
208
304
|
def saga_entries(bluebook, home_of)
|
|
209
305
|
bluebook.process_managers.map do |saga|
|
|
210
306
|
shape = saga.to_h
|
|
@@ -216,6 +312,9 @@ module Hecks
|
|
|
216
312
|
# Cross-cutting by nature — `System` and `Customer` issue commands
|
|
217
313
|
# across half the aggregates here — so a role belongs to no single
|
|
218
314
|
# one and gets its own section.
|
|
315
|
+
#
|
|
316
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather roles from
|
|
317
|
+
# @return [Array<Entry>] one `:role`-kind entry per distinct role named by a command
|
|
219
318
|
def role_entries(bluebook)
|
|
220
319
|
by_role = Hash.new { |hash, key| hash[key] = [] }
|
|
221
320
|
holders(bluebook).each do |holder|
|
|
@@ -228,6 +327,10 @@ module Hecks
|
|
|
228
327
|
|
|
229
328
|
# A read model joins heads from more than one aggregate — its own
|
|
230
329
|
# header says so — so it belongs to none of them.
|
|
330
|
+
#
|
|
331
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather read
|
|
332
|
+
# models from
|
|
333
|
+
# @return [Array<Entry>] one `:read_model`-kind entry per declared read model
|
|
231
334
|
def read_model_entries(bluebook)
|
|
232
335
|
bluebook.read_models.map do |read_model|
|
|
233
336
|
Entry.new(name: read_model.name, kind: :read_model, section: READ_MODELS, facts: { read_model: read_model })
|
|
@@ -244,8 +347,20 @@ module Hecks
|
|
|
244
347
|
module Slugs
|
|
245
348
|
module_function
|
|
246
349
|
|
|
350
|
+
# Reproduces GitHub's own heading-to-slug transform.
|
|
351
|
+
#
|
|
352
|
+
# @param text [String] the heading text to slugify
|
|
353
|
+
# @return [String] `text` lowercased, stripped of punctuation, spaces turned
|
|
354
|
+
# to hyphens
|
|
247
355
|
def github(text) = text.to_s.downcase.gsub(/[^\p{Word}\- ]/, "").tr(" ", "-")
|
|
248
356
|
|
|
357
|
+
# Assigns every section's and every entry's own slug, in document order.
|
|
358
|
+
#
|
|
359
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter the document
|
|
360
|
+
# titles itself after
|
|
361
|
+
# @param sections [Array<Section>] the sections to assign slugs to, in the
|
|
362
|
+
# order they render
|
|
363
|
+
# @return [void]
|
|
249
364
|
def assign!(bluebook, sections)
|
|
250
365
|
seen = Hash.new(0)
|
|
251
366
|
take = lambda do |text|
|
|
@@ -267,6 +382,10 @@ module Hecks
|
|
|
267
382
|
# (a cross-domain command like `Notifications.Send`) answers nil,
|
|
268
383
|
# and the sentence says it in words with no link.
|
|
269
384
|
class Index
|
|
385
|
+
# Builds the lookup table every `link` call resolves through.
|
|
386
|
+
#
|
|
387
|
+
# @param sections [Array<Section>] the document's own sections, already
|
|
388
|
+
# built and slugged
|
|
270
389
|
def initialize(sections)
|
|
271
390
|
@by_key = {}
|
|
272
391
|
sections.each do |section|
|
|
@@ -275,6 +394,12 @@ module Hecks
|
|
|
275
394
|
end
|
|
276
395
|
end
|
|
277
396
|
|
|
397
|
+
# Builds one entry's lookup key.
|
|
398
|
+
#
|
|
399
|
+
# @param entry [Entry] the entry to key
|
|
400
|
+
# @return [Array(Symbol, String, String), Array(Symbol, String)] `[kind, within,
|
|
401
|
+
# name]` for a command, query, or value object (which can share a bare name
|
|
402
|
+
# across holders); `[kind, name]` otherwise
|
|
278
403
|
def key_of(entry)
|
|
279
404
|
case entry.kind
|
|
280
405
|
when :command, :query, :value_object then [entry.kind, entry.within, entry.name]
|
|
@@ -282,6 +407,14 @@ module Hecks
|
|
|
282
407
|
end
|
|
283
408
|
end
|
|
284
409
|
|
|
410
|
+
# Looks up one entry or aggregate section by its key.
|
|
411
|
+
#
|
|
412
|
+
# @param kind [Symbol] the entry's kind, or `:aggregate` for a section
|
|
413
|
+
# @param name [String] the entry's or aggregate's name
|
|
414
|
+
# @param within [String, nil] the holder's name, for a command, query, or
|
|
415
|
+
# value object; nil otherwise
|
|
416
|
+
# @return [Entry, Section, nil] the matching entry or aggregate section, or
|
|
417
|
+
# nil if the chapter declares none
|
|
285
418
|
def [](kind, name, within: nil)
|
|
286
419
|
@by_key[within ? [kind, within, name] : [kind, name]]
|
|
287
420
|
end
|
|
@@ -289,6 +422,15 @@ module Hecks
|
|
|
289
422
|
# A Markdown link to a term's own heading, or the plain words when
|
|
290
423
|
# the chapter declares no such term. `label:` overrides the link
|
|
291
424
|
# text for a sentence that has to tell two same-named terms apart.
|
|
425
|
+
#
|
|
426
|
+
# @param kind [Symbol] the target's kind, or `:aggregate` for a section
|
|
427
|
+
# @param name [String] the target's or aggregate's name
|
|
428
|
+
# @param within [String, nil] the holder's name, for a command, query, or
|
|
429
|
+
# value object; nil otherwise
|
|
430
|
+
# @param label [String, nil] link text to use instead of the target's own
|
|
431
|
+
# headword or title
|
|
432
|
+
# @return [String] a `[text](#slug)` Markdown link, or the plain words when
|
|
433
|
+
# the chapter declares no such term
|
|
292
434
|
def link(kind, name, within: nil, label: nil)
|
|
293
435
|
target = self[kind, name, within: within]
|
|
294
436
|
return label || Naming.words(name) unless target
|
|
@@ -80,19 +80,63 @@ module Hecks
|
|
|
80
80
|
# first. Entity spells its own (separate, non-colliding) text twin
|
|
81
81
|
# of the parent link `owner`. Not restated here: the one list is
|
|
82
82
|
# `Assembly::PARENT_POINTERS`, which the assembly gate reads too.
|
|
83
|
+
# Tells whether `field` is a parent pointer, minted bare rather than
|
|
84
|
+
# declared like an ordinary field.
|
|
85
|
+
#
|
|
86
|
+
# @param field [String, Symbol] the field name to check
|
|
87
|
+
# @return [Boolean] true if `field` is a parent pointer (`Assembly::PARENT_POINTERS`,
|
|
88
|
+
# or any name ending `_id`)
|
|
83
89
|
def parent_ref?(field) = Hecks::Bluebook::Assembly.parent_pointer?(field)
|
|
84
90
|
|
|
85
91
|
# The judge's own fields, never the model's — read off the
|
|
86
92
|
# category's contract (`derived: { position: :walk }`), not restated.
|
|
93
|
+
#
|
|
94
|
+
# @param name [String] the construct's name, an `Assembly::CONTRACTS` key
|
|
95
|
+
# @return [Array<Symbol>] fields the assembly judge derives by walking, for this construct
|
|
96
|
+
# @raise [KeyError] if `name` has no assembly contract
|
|
87
97
|
def judge_only(name) = Hecks::Bluebook::Assembly.contract(name).walked
|
|
88
98
|
|
|
89
99
|
# The tables that carry a reason answer with names only when the
|
|
90
100
|
# caller wants the set rather than the explanations.
|
|
101
|
+
#
|
|
102
|
+
# @param name [String] the construct's name, an `OFF_THE_WIRE` key
|
|
103
|
+
# @return [Array<Symbol>] fields declared but deliberately not emitted for `name`;
|
|
104
|
+
# empty if `name` has none
|
|
91
105
|
def off_the_wire(name) = OFF_THE_WIRE.fetch(name, {}).keys
|
|
106
|
+
|
|
107
|
+
# Names `name`'s model-only fields.
|
|
108
|
+
#
|
|
109
|
+
# @param name [String] the construct's name, a `COMPUTED` key
|
|
110
|
+
# @return [Array<Symbol>] model-only fields computed rather than declared for
|
|
111
|
+
# `name`; empty if `name` has none
|
|
92
112
|
def computed(name) = COMPUTED.fetch(name, {}).keys
|
|
113
|
+
|
|
114
|
+
# Names `name`'s containment edges.
|
|
115
|
+
#
|
|
116
|
+
# @param name [String] the construct's name, a `CONTAINED` key
|
|
117
|
+
# @return [Array<Symbol>] fields the model holds that the grammar declares as
|
|
118
|
+
# containment edges instead, for `name`; empty if `name` has none
|
|
93
119
|
def contained(name) = CONTAINED.fetch(name, [])
|
|
120
|
+
|
|
121
|
+
# Names `name`'s folded fields and what each gathers.
|
|
122
|
+
#
|
|
123
|
+
# @param name [String] the construct's name, a `FOLDED` key
|
|
124
|
+
# @return [Hash{Symbol => Array<Symbol>}] each folded field for `name`, mapped to
|
|
125
|
+
# the declared fields it gathers; empty if `name` has none
|
|
94
126
|
def folded(name) = FOLDED.fetch(name, {})
|
|
127
|
+
|
|
128
|
+
# Names `name`'s unpacked fields and what each opens into.
|
|
129
|
+
#
|
|
130
|
+
# @param name [String] the construct's name, an `UNPACKED` key
|
|
131
|
+
# @return [Hash{Symbol => Array<Symbol>}] each declared field for `name` that opens
|
|
132
|
+
# into several model fields, mapped to those fields; empty if `name` has none
|
|
95
133
|
def unpacked(name) = UNPACKED.fetch(name, {})
|
|
134
|
+
|
|
135
|
+
# Names `name`'s dynamic-tail fields.
|
|
136
|
+
#
|
|
137
|
+
# @param name [String] the construct's name, a `DYNAMIC_TAIL` key
|
|
138
|
+
# @return [Array<Symbol>] fields for `name` emitted by `to_h`'s own merge rather
|
|
139
|
+
# than by `emits_ir`; empty if `name` has none
|
|
96
140
|
def dynamic_tail(name) = DYNAMIC_TAIL.fetch(name, [])
|
|
97
141
|
end
|
|
98
142
|
end
|
|
@@ -17,7 +17,7 @@ module Hecks
|
|
|
17
17
|
# written — the chapter looked generatable and was not, until its
|
|
18
18
|
# `@hecks_root`, ports table and child stamping moved behind `settle`.
|
|
19
19
|
#
|
|
20
|
-
# The HOST manifest is the honest part. The grammar states the fields;
|
|
20
|
+
# The `HOST` manifest is the honest part. The grammar states the fields;
|
|
21
21
|
# it does not state which constructs are Ruby classes rather than
|
|
22
22
|
# instances, what a constructor's defaults are, or how a value is
|
|
23
23
|
# coerced on the way in. Those are facts about Ruby, not about
|
|
@@ -65,10 +65,26 @@ module Hecks
|
|
|
65
65
|
|
|
66
66
|
module_function
|
|
67
67
|
|
|
68
|
+
# Renders every `HOST`-listed construct's holding half.
|
|
69
|
+
#
|
|
70
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter itself (the language
|
|
71
|
+
# describing its own constructs), not a domain being projected
|
|
72
|
+
# @param options [Hash{Symbol => Object}] ignored; present to satisfy the
|
|
73
|
+
# `Projector::Target` calling convention
|
|
74
|
+
# @return [Hash{String => String}] each construct's output filename mapped to its
|
|
75
|
+
# rendered Ruby source
|
|
68
76
|
def call(bluebook:, options: {})
|
|
69
77
|
HOST.to_h { |name, host| [host.fetch(:file), render(bluebook, name, host)] }
|
|
70
78
|
end
|
|
71
79
|
|
|
80
|
+
# Renders one construct's class: emission, readers and constructor, wrapped in
|
|
81
|
+
# the generated-file header and namespace.
|
|
82
|
+
#
|
|
83
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
|
|
84
|
+
# attributes are read from
|
|
85
|
+
# @param name [String] the construct's name, a key into `HOST` (`"Policy"`, ...)
|
|
86
|
+
# @param host [Hash{Symbol => Object}] `name`'s `HOST` entry
|
|
87
|
+
# @return [String] the rendered class source, ready to write to `host[:file]`
|
|
72
88
|
def render(bluebook, name, host)
|
|
73
89
|
<<~RUBY
|
|
74
90
|
# Generated — projected from the language's own #{name} aggregate.
|
|
@@ -98,6 +114,11 @@ module Hecks
|
|
|
98
114
|
# A computed field (Deviations::COMPUTED) is emitted too — it is
|
|
99
115
|
# model-only by definition, so it rides after the declared fields
|
|
100
116
|
# and must have a `renders` entry, there being nothing to `send`.
|
|
117
|
+
#
|
|
118
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
|
|
119
|
+
# attributes are read from
|
|
120
|
+
# @param name [String] the construct's name, a key into `HOST`
|
|
121
|
+
# @return [String] the rendered `emits_ir(...)` call, one field per line
|
|
101
122
|
def emits(bluebook, name)
|
|
102
123
|
fields = emitted_fields(bluebook, name) + Deviations.computed(name)
|
|
103
124
|
renders = HOST.fetch(name).fetch(:renders, {})
|
|
@@ -110,6 +131,13 @@ module Hecks
|
|
|
110
131
|
# deviation the tables account for. The generator and
|
|
111
132
|
# spec/model_shape_conformance_spec compute this the same way, from
|
|
112
133
|
# the same tables, which is the point of the tables being in lib.
|
|
134
|
+
#
|
|
135
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
|
|
136
|
+
# attributes are read from
|
|
137
|
+
# @param name [String] the construct's name, an aggregate on `bluebook`
|
|
138
|
+
# @return [Array<Symbol>] the declared attribute names, minus every field
|
|
139
|
+
# `Deviations` marks as parent-ref, judge-only, off-the-wire, dynamic-tail,
|
|
140
|
+
# folded, or unpacked
|
|
113
141
|
def emitted_fields(bluebook, name)
|
|
114
142
|
bluebook.aggregate(name).attributes.map(&:name)
|
|
115
143
|
.reject { |f| Deviations.parent_ref?(f) } -
|
|
@@ -120,6 +148,11 @@ module Hecks
|
|
|
120
148
|
Deviations.unpacked(name).keys
|
|
121
149
|
end
|
|
122
150
|
|
|
151
|
+
# Renders the `attr_reader` line for every emitted field, plus an `attr_accessor`
|
|
152
|
+
# for each field the model deliberately keeps off the wire.
|
|
153
|
+
#
|
|
154
|
+
# @param host [Hash{Symbol => Object}] the construct's `HOST` entry
|
|
155
|
+
# @return [String] the rendered reader/accessor lines, one construct's worth
|
|
123
156
|
def readers(host)
|
|
124
157
|
lines = ["attr_reader #{host.fetch(:readers).map { |r| ":#{r}" }.join(', ')}"]
|
|
125
158
|
accessors = host.fetch(:accessors, [])
|
|
@@ -137,6 +170,11 @@ module Hecks
|
|
|
137
170
|
end).join("\n")
|
|
138
171
|
end
|
|
139
172
|
|
|
173
|
+
# Renders the `initialize` that assigns every declared field, coerced as `HOST`
|
|
174
|
+
# states, and calls `settle` unless the construct opts out.
|
|
175
|
+
#
|
|
176
|
+
# @param host [Hash{Symbol => Object}] the construct's `HOST` entry
|
|
177
|
+
# @return [String] the rendered `def initialize ... end` block
|
|
140
178
|
def constructor(host)
|
|
141
179
|
args = host.fetch(:defaults).map { |f, d| d ? "#{f}: #{d}" : "#{f}:" }.join(", ")
|
|
142
180
|
body = host.fetch(:defaults).keys.map do |f|
|
|
@@ -147,7 +185,19 @@ module Hecks
|
|
|
147
185
|
"def initialize(#{args})\n#{body.join("\n")}\nend"
|
|
148
186
|
end
|
|
149
187
|
|
|
188
|
+
# Indents every non-blank line of a rendered block, for nesting it inside the
|
|
189
|
+
# class body.
|
|
190
|
+
#
|
|
191
|
+
# @param text [String] the block to indent
|
|
192
|
+
# @param by [Integer] how many spaces to prefix each non-blank line with
|
|
193
|
+
# @return [String] the indented text
|
|
150
194
|
def indent(text, by) = text.lines.map { |l| l.strip.empty? ? l : (" " * by) + l }.join
|
|
195
|
+
|
|
196
|
+
# Wraps prose into `# `-prefixed comment lines, for a reader-declared reason
|
|
197
|
+
# rendered back into the generated file.
|
|
198
|
+
#
|
|
199
|
+
# @param text [String] the prose to wrap
|
|
200
|
+
# @return [String] the wrapped text, its lines joined by `"\n# "`
|
|
151
201
|
def wrap(text) = text.scan(/.{1,62}(?:\s|$)/).map(&:strip).join("\n# ")
|
|
152
202
|
end
|
|
153
203
|
end
|
|
@@ -7,7 +7,8 @@ module Hecks
|
|
|
7
7
|
# an identity provider has to know about this domain before it can
|
|
8
8
|
# issue a token that means anything here.
|
|
9
9
|
#
|
|
10
|
-
# The artifact half of something already half-built
|
|
10
|
+
# ## The artifact half of something already half-built
|
|
11
|
+
#
|
|
11
12
|
# `spec/oidc_projection_spec.rb` covers the integration half — verified
|
|
12
13
|
# claims in, `IdentityResolution.resolve` → `Authorization.holds_role?`
|
|
13
14
|
# → a dispatch scoped by `Hecks.as_caller(role:)`. That half enforces
|
|
@@ -24,8 +25,9 @@ module Hecks
|
|
|
24
25
|
# `Ports::Authorization.holds_role?` compares against a real
|
|
25
26
|
# `Governance::RoleAssignment`.
|
|
26
27
|
#
|
|
27
|
-
# Roles come from the commands, not from governance
|
|
28
|
-
#
|
|
28
|
+
# ## Roles come from the commands, not from governance
|
|
29
|
+
#
|
|
30
|
+
# A command's own `role "Compliance officer"` is in the bluebook IR
|
|
29
31
|
# (`Command#role`), whereas `uses_framework "Governance"` is
|
|
30
32
|
# declared in the `.hecksagon` — which `call(bluebook:, options:)`
|
|
31
33
|
# cannot see at all. Reading the commands is both the only thing
|
|
@@ -47,6 +49,13 @@ module Hecks
|
|
|
47
49
|
# `audience:` overrides the domain name, for the ordinary case
|
|
48
50
|
# where the IdP's registered audience is a URL rather than a bare
|
|
49
51
|
# chapter name.
|
|
52
|
+
#
|
|
53
|
+
# @param bluebook [Bluebook::Chapter] the chapter being projected
|
|
54
|
+
# @param options [Hash{Symbol => Object}] `:audience` (String, Symbol, nil)
|
|
55
|
+
# overrides the manifest's `"audience"` value
|
|
56
|
+
# @return [Hash{String => Object}] `"audience"` (String), `"scopes"` (see
|
|
57
|
+
# `scopes_for`) and `"roles"` (`Array<String>`, every distinct declared role,
|
|
58
|
+
# sorted)
|
|
50
59
|
def call(bluebook:, options: {})
|
|
51
60
|
scopes = scopes_for(bluebook)
|
|
52
61
|
|
|
@@ -67,11 +76,22 @@ module Hecks
|
|
|
67
76
|
# through `Dispatcher#dispatch`'s dotted `Entity.Command` routing
|
|
68
77
|
# is real and callable, so a manifest that never names it can
|
|
69
78
|
# never grant a client a scope for it either.
|
|
79
|
+
#
|
|
80
|
+
# @param bluebook [Bluebook::Chapter] the chapter being projected
|
|
81
|
+
# @return [Array<Hash{String => Object}>] every command's scope entry (see
|
|
82
|
+
# `command_scopes`), sorted by `"scope"`
|
|
70
83
|
def scopes_for(bluebook)
|
|
71
84
|
bluebook.aggregates.flat_map { |aggregate| aggregate_scopes(bluebook, aggregate) }
|
|
72
85
|
.sort_by { |scope| scope["scope"] }
|
|
73
86
|
end
|
|
74
87
|
|
|
88
|
+
# Projects one aggregate's own commands and every entity nested inside it.
|
|
89
|
+
#
|
|
90
|
+
# @param bluebook [Bluebook::Chapter] the aggregate's owning chapter, for the
|
|
91
|
+
# verb and scope prefixes
|
|
92
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate being projected
|
|
93
|
+
# @return [Array<Hash{String => Object}>] the aggregate's and its entities'
|
|
94
|
+
# scope entries (see `command_scopes`)
|
|
75
95
|
def aggregate_scopes(bluebook, aggregate)
|
|
76
96
|
verb_prefix = "#{bluebook.name}::#{aggregate.hecks_name}"
|
|
77
97
|
scope_prefix = "#{Naming.snake(bluebook.name)}:#{Naming.snake(aggregate.hecks_name)}"
|
|
@@ -84,6 +104,14 @@ module Hecks
|
|
|
84
104
|
# inside `Handler`), so this recurses the same way `Chapter#verbs`
|
|
85
105
|
# now does. The verb and scope prefixes grow in lockstep, each
|
|
86
106
|
# `.`-joined the same way its own kind already was.
|
|
107
|
+
#
|
|
108
|
+
# @param entity [Bluebook::Entity] the entity being projected
|
|
109
|
+
# @param verb_prefix [String] the enclosing aggregate or entity's own verb
|
|
110
|
+
# prefix, extended with this entity's name
|
|
111
|
+
# @param scope_prefix [String] the enclosing aggregate or entity's own scope
|
|
112
|
+
# prefix, extended with this entity's snake-cased name
|
|
113
|
+
# @return [Array<Hash{String => Object}>] this entity's and its nested entities'
|
|
114
|
+
# scope entries (see `command_scopes`)
|
|
87
115
|
def entity_scopes(entity, verb_prefix, scope_prefix)
|
|
88
116
|
verb_prefix = "#{verb_prefix}.#{entity.hecks_name}"
|
|
89
117
|
scope_prefix = "#{scope_prefix}.#{Naming.snake(entity.hecks_name)}"
|
|
@@ -96,6 +124,15 @@ module Hecks
|
|
|
96
124
|
# spelled in, and snake_cased through the same `Naming.snake` the
|
|
97
125
|
# facade uses to name a command's own door method, so a scope and
|
|
98
126
|
# the Ruby call that satisfies it cannot drift apart.
|
|
127
|
+
#
|
|
128
|
+
# @param commands [Array<Bluebook::Command>] the commands to project
|
|
129
|
+
# @param verb_prefix [String] the owning aggregate or entity's fully-qualified
|
|
130
|
+
# verb prefix
|
|
131
|
+
# @param scope_prefix [String] the owning aggregate or entity's snake-cased
|
|
132
|
+
# scope prefix
|
|
133
|
+
# @return [Array<Hash{String => Object}>] one entry per command: `"scope"`
|
|
134
|
+
# (String), `"verb"` (String), `"role"` (String, `nil` if the command
|
|
135
|
+
# declares none)
|
|
99
136
|
def command_scopes(commands, verb_prefix, scope_prefix)
|
|
100
137
|
commands.map do |command|
|
|
101
138
|
{
|