hecks 3.1.3 → 3.2.1

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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
  3. data/lib/hecks/cli/check_era.rb +3 -39
  4. data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
  5. data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
  6. data/lib/hecks/deploy/oidc.json +15 -0
  7. data/lib/hecks/doors/mcp_door.rb +16 -2
  8. data/lib/hecks/doors/mcp_door_scope.rb +43 -4
  9. data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
  10. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
  11. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
  12. data/lib/hecks/hecks/adapters/cost_explorer.adapter +7 -0
  13. data/lib/hecks/hecks/adapters/cost_explorer.rb +102 -0
  14. data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
  15. data/lib/hecks/hecks/adapters/site_toolchain.rb +21 -11
  16. data/lib/hecks/hecks/hecks.hecksagon +8 -0
  17. data/lib/hecks/language/bluebook/vocabulary.bluebook +7 -7
  18. data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
  19. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
  20. data/lib/hecks/projections/deploy/box/settings.rb +23 -2
  21. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +3 -1
  22. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
  23. data/lib/hecks/projections/deploy/box.rb +13 -2
  24. data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
  25. data/lib/hecks/projections/site/edge/checks.rb +31 -5
  26. data/lib/hecks/projections/site/edge.rb +25 -11
  27. data/lib/hecks/projections/site/routes_ts.rb +50 -27
  28. data/lib/hecks/projections/site/site_cdn.rb +7 -4
  29. data/lib/hecks/projections/site/table/checks.rb +53 -7
  30. data/lib/hecks/projections/site/table/link_builder.rb +64 -0
  31. data/lib/hecks/projections/site/table/row_builder.rb +21 -6
  32. data/lib/hecks/projections/site/table.rb +16 -5
  33. data/lib/hecks/rust_build/kernel_input.rb +18 -6
  34. data/lib/hecks/rust_build/project_rust.rb +6 -132
  35. data/lib/hecks/site/bluebook/site.bluebook +27 -13
  36. data/lib/hecks/storehouse.rb +35 -1
  37. data/lib/hecks/tools/comment_style.rb +4 -2
  38. data/lib/hecks/tools/deploy_recipe.rb +1 -0
  39. data/lib/hecks/tools/rust_comment_style.rb +1 -1
  40. data/lib/hecks/tools/site_routes.rb +69 -28
  41. data/lib/hecks/version.rb +1 -1
  42. data/lib/hecks/vocabulary.rb +1 -1
  43. data/lib/hecks.rb +1 -0
  44. data/rust/build/src/lineage_pass.rs +1 -1
  45. data/rust/build/src/pipeline.rs +1 -1
  46. data/rust/codegen/src/attr.rs +1 -1
  47. data/rust/codegen/src/bridging.rs +1 -1
  48. data/rust/codegen/src/commands.rs +1 -1
  49. data/rust/codegen/src/constraints.rs +1 -1
  50. data/rust/codegen/src/dependency_planning.rs +1 -1
  51. data/rust/codegen/src/domain_generator.rs +2 -2
  52. data/rust/codegen/src/exemplar.rs +1 -1
  53. data/rust/codegen/src/expr_emitter.rs +1 -1
  54. data/rust/codegen/src/fielded.rs +1 -1
  55. data/rust/codegen/src/json_codec.rs +1 -1
  56. data/rust/codegen/src/main.rs +3 -4
  57. data/rust/codegen/src/mutations.rs +1 -1
  58. data/rust/codegen/src/naming.rs +1 -1
  59. data/rust/codegen/src/ports.rs +1 -1
  60. data/rust/codegen/src/queries.rs +3 -3
  61. data/rust/codegen/src/reactions.rs +1 -1
  62. data/rust/codegen/src/read_models.rs +2 -2
  63. data/rust/codegen/src/reference_specs.rs +1 -1
  64. data/rust/codegen/src/registry.rs +4 -4
  65. data/rust/codegen/src/shared.rs +1 -1
  66. data/rust/codegen/src/skip_reason.rs +1 -1
  67. data/rust/codegen/src/types.rs +4 -4
  68. data/rust/host/HECKS_RELEASE +1 -1
  69. data/rust/host/src/auth.rs +25 -1
  70. data/rust/host/src/needs.rs +94 -27
  71. data/rust/host/src/web.rs +9 -5
  72. data/rust/src/exemplar/commands.rs +1 -1
  73. data/rust/src/exemplar/constraints.rs +1 -1
  74. data/rust/src/exemplar/fielded.rs +1 -1
  75. data/rust/src/exemplar/json.rs +1 -1
  76. data/rust/src/exemplar/mod.rs +1 -1
  77. data/rust/src/exemplar/mutations.rs +1 -1
  78. data/rust/src/exemplar/queries.rs +1 -1
  79. data/rust/src/exemplar/reactions.rs +1 -1
  80. data/rust/src/exemplar/read_models.rs +1 -1
  81. data/rust/src/exemplar/registry.rs +1 -1
  82. data/rust/src/exemplar/types.rs +1 -1
  83. data/rust/src/kernel/cli.rs +2 -2
  84. data/rust/src/kernel/read_model.rs +1 -1
  85. data/rust/src/lib.rs +1 -1
  86. metadata +5 -23
  87. data/rust/project/bridging.rb +0 -257
  88. data/rust/project/commands.rb +0 -877
  89. data/rust/project/constraints.rb +0 -86
  90. data/rust/project/dependency_planning.rb +0 -178
  91. data/rust/project/domain_generator.rb +0 -983
  92. data/rust/project/exemplar.rb +0 -148
  93. data/rust/project/expr_emitter.rb +0 -72
  94. data/rust/project/fielded.rb +0 -141
  95. data/rust/project/json_codec.rb +0 -641
  96. data/rust/project/mutations.rb +0 -695
  97. data/rust/project/naming.rb +0 -223
  98. data/rust/project/ports.rb +0 -65
  99. data/rust/project/queries.rb +0 -493
  100. data/rust/project/reactions.rb +0 -317
  101. data/rust/project/read_models.rb +0 -690
  102. data/rust/project/reference_specs.rb +0 -27
  103. data/rust/project/registry.rb +0 -396
  104. data/rust/project/skip_reason.rb +0 -29
  105. data/rust/project/types.rb +0 -225
  106. data/rust/project/write_if_changed.rb +0 -7
  107. data/rust/project.rb +0 -30
  108. data/rust/project_rust_pipeline.rb +0 -391
@@ -1,86 +0,0 @@
1
- module RustProjection
2
- module Projector
3
- module_function
4
-
5
- # Scalar that `admits:`/`pattern:` checks operate on: the value itself for a `String`, or
6
- # the sole field of a single-field value object. `nil` for any other shape.
7
- def scalar_field_expr(value_expr, attr_type, value_objects_by_name)
8
- return value_expr if attr_type == "String"
9
-
10
- vo = value_objects_by_name[attr_type]
11
- return nil unless vo && vo[:attributes].size == 1
12
-
13
- "#{value_expr}.#{rust_ident_field(vo[:attributes].first[:name])}"
14
- end
15
-
16
- # An optional attribute is `Option<T>`, so the check must sit inside an `if let Some(..)`.
17
- # Returns `[scalar_expr, nil]` when required, `[expr_over_binding, option_source]` when not.
18
- def optional_scalar_expr(value_expr, attr, value_objects_by_name)
19
- return [scalar_field_expr(value_expr, attr[:type], value_objects_by_name), nil] unless attr[:optional]
20
-
21
- [scalar_field_expr(OPTIONAL_BINDING, attr[:type], value_objects_by_name), value_expr]
22
- end
23
-
24
- # Binds by reference into the `Option`; a `None` field skips the check, as in Ruby.
25
- OPTIONAL_BINDING = "__optional_value"
26
-
27
- def wrap_if_optional(check, optional_source)
28
- return check unless optional_source
29
-
30
- "if let Some(#{OPTIONAL_BINDING}) = &#{optional_source} { #{check} }"
31
- end
32
-
33
- # Members of the closed set named by `"Aggregate::SetName"`, resolved against the full domain.
34
- # `nil` when the target is not a closed-set value object: codegen skips the check.
35
- def admitted_set_members(admits, aggregates_by_name)
36
- aggregate_name, set_name = admits.to_s.split("::", 2)
37
- aggregate = aggregates_by_name[aggregate_name]
38
- return nil unless aggregate && set_name
39
-
40
- vo = aggregate[:value_objects].find { |v| v[:name] == set_name }
41
- return nil unless vo && vo[:closed_set]
42
-
43
- vo[:members].map { |row| row.first[1] }
44
- end
45
-
46
- # Emits the `admits_declared_set` check. The member list is resolved at codegen time and passed
47
- # as a `&[&str]` literal; the refusal wording lives only in Vocabulary::RefusalTemplate.
48
- def emit_admits_check(value_expr, attr, aggregates_by_name, value_objects_by_name)
49
- return nil unless attr[:admits]
50
-
51
- members = admitted_set_members(attr[:admits], aggregates_by_name)
52
- return nil unless members
53
-
54
- scalar, optional_source = optional_scalar_expr(value_expr, attr, value_objects_by_name)
55
- return nil unless scalar
56
-
57
- members_array = "[#{members.map(&:inspect).join(', ')}]"
58
- check = Exemplar.render(
59
- "admits_check",
60
- '["tmpl_member_a", "tmpl_member_b"]' => members_array,
61
- "tmpl_scalar" => scalar,
62
- '"tmpl_admits_name"' => attr[:name].to_s.inspect,
63
- '"tmpl_admits_target"' => attr[:admits].to_s.inspect
64
- )
65
- wrap_if_optional(check, optional_source)
66
- end
67
-
68
- # Emits the `pattern_mismatch` check. The regex goes over as its raw source string, unescaped,
69
- # matching Ruby's verbatim interpolation.
70
- def emit_pattern_check(value_expr, attr, owner_type_name, value_objects_by_name)
71
- return nil unless attr[:pattern]
72
-
73
- scalar, optional_source = optional_scalar_expr(value_expr, attr, value_objects_by_name)
74
- return nil unless scalar
75
-
76
- check = Exemplar.render(
77
- "pattern_check",
78
- '"tmpl_pattern_text"' => attr[:pattern].inspect,
79
- "tmpl_scalar" => scalar,
80
- '"tmpl_pattern_owner"' => owner_type_name.to_s.inspect,
81
- '"tmpl_pattern_field"' => attr[:name].to_s.inspect
82
- )
83
- wrap_if_optional(check, optional_source)
84
- end
85
- end
86
- end
@@ -1,178 +0,0 @@
1
- module RustProjection
2
- module Projector
3
- module_function
4
-
5
- # Whether a creating command's state is fully determined by its payload and declared defaults.
6
- # Port of `Runtime::DependencyPlanning::Analyzer`'s `complete_state?`, over the exported IR
7
- # hash so codegen never needs live Bluebook objects or a call into Ruby.
8
- # Only meaningful for a command `creates_owner?` already accepted.
9
- def complete_state_creation?(aggregate, command, value_objects_by_name)
10
- owner_fields = creation_owner_fields(aggregate)
11
- payload_fields = command[:attributes].to_set { |a| a[:name].to_s }
12
-
13
- known_writes, disqualified = creation_known_writes(aggregate, command, owner_fields, payload_fields, value_objects_by_name)
14
- !disqualified && owner_fields.subset?(known_writes)
15
- end
16
-
17
- # Port of `complete_state? && state_independent?`: when true, the `AlreadyExists` check is
18
- # deferred until after givens, mutations, ensures and invariants have run.
19
- # Only meaningful for a command `creates_owner?` already accepted.
20
- def state_independent_creation?(aggregate, command, value_objects_by_name)
21
- return false unless complete_state_creation?(aggregate, command, value_objects_by_name)
22
-
23
- owner_fields = creation_owner_fields(aggregate)
24
- payload_fields = command[:attributes].to_set { |a| a[:name].to_s }
25
-
26
- rules = command[:givens].map { |rule| [rule, :before] } +
27
- command[:ensures].map { |rule| [rule, :after] } +
28
- aggregate[:invariants].map { |rule| [rule, :after] }
29
-
30
- rules.all? do |rule, phase|
31
- creation_rule_state_independent?(rule[:ast], phase, payload_fields, owner_fields)
32
- end
33
- end
34
-
35
- # Every field a fresh instance carries: declared attributes, the lifecycle field, and projected
36
- # fields (seeded outside this analysis, so they count as owner state).
37
- def creation_owner_fields(aggregate)
38
- fields = aggregate[:attributes].to_set { |a| a[:name].to_s }
39
- fields << aggregate[:lifecycle][:field].to_s if aggregate[:lifecycle]
40
- Array(aggregate[:projected_fields]).each { |field| fields << field[:name].to_s }
41
- fields
42
- end
43
-
44
- # Ports `analyze_initial_state`, `analyze_mutations` and `analyze_lifecycle`, in that order.
45
- # Returns `[known_writes, disqualified]`; the Analyzer's `unresolved` folds into one boolean.
46
- def creation_known_writes(aggregate, command, owner_fields, payload_fields, value_objects_by_name)
47
- known = Set.new
48
- aggregate[:attributes].each do |attr|
49
- known << attr[:name].to_s if creation_deterministic_initial_value?(attr, value_objects_by_name)
50
- end
51
- known << aggregate[:lifecycle][:field].to_s if aggregate[:lifecycle]
52
-
53
- # A creating command guarded by a lifecycle `from:` state reads prior state that is absent.
54
- disqualified = command[:from] && aggregate[:lifecycle] ? true : false
55
-
56
- command[:mutations].each do |mutation|
57
- target = mutation[:target].to_s
58
- unless owner_fields.include?(target)
59
- disqualified = true
60
- next
61
- end
62
-
63
- case creation_mutation_outcome(mutation, payload_fields, owner_fields)
64
- when :known then known << target
65
- when :unresolved then disqualified = true
66
- end
67
- end
68
-
69
- [known, disqualified]
70
- end
71
-
72
- # One mutation's outcome: `:known` (adds to known writes), `:unresolved` (disqualifies), or
73
- # `:state_read` (neither; stateful ops simply never enter known writes).
74
- def creation_mutation_outcome(mutation, payload_fields, owner_fields)
75
- case mutation[:op].to_s
76
- when "set"
77
- creation_classify_source(mutation[:source], payload_fields, owner_fields)
78
- when "append"
79
- # Stateful, so never a known write; only an undeclared source name in its fields
80
- # disqualifies.
81
- creation_append_outcome(mutation[:fields], payload_fields, owner_fields)
82
- when "increment", "decrement", "multiply", "clamp", "remove"
83
- # Stateful like `append`; no creating command in the corpus declares one.
84
- :state_read
85
- else
86
- :unresolved
87
- end
88
- end
89
-
90
- def creation_append_outcome(fields, payload_fields, owner_fields)
91
- fields.each_value do |wire_value|
92
- parsed = append_field_source(wire_value)
93
- next unless parsed.is_a?(Symbol)
94
-
95
- return :unresolved if creation_classify_symbol(parsed.to_s, payload_fields, owner_fields) == :unresolved
96
- end
97
-
98
- :state_read
99
- end
100
-
101
- # True for a list or optional attribute, or one with a non-nil default; otherwise true only
102
- # when its type is a value object whose every attribute has a non-nil default.
103
- def creation_deterministic_initial_value?(attr, value_objects_by_name)
104
- return true if [attr[:list], attr[:optional], !attr[:default].nil?].any?
105
-
106
- vo = value_objects_by_name[attr[:type]]
107
- return false unless vo
108
-
109
- vo[:attributes].all? { |field| !field[:default].nil? }
110
- end
111
-
112
- # Classifies a name as `:known` (payload argument), `:state_read` (owner field), or
113
- # `:unresolved`.
114
- def creation_classify_symbol(name, payload_fields, owner_fields)
115
- return :known if payload_fields.include?(name)
116
- return :state_read if owner_fields.include?(name)
117
-
118
- :unresolved
119
- end
120
-
121
- # Classifies a mutation's top-level `source:` (`{kind:, name:/value:}`).
122
- # A `"state"` source is `:known`, matching the live Analyzer, which has no StateRef branch;
123
- # "fixing" it would make this port disagree with the Analyzer it mirrors.
124
- def creation_classify_source(source, payload_fields, owner_fields)
125
- return creation_classify_symbol(source[:name].to_s, payload_fields, owner_fields) if source[:kind] == "argument"
126
-
127
- :known
128
- end
129
-
130
- # True when one rule's exported `ast` reads nothing that disqualifies state independence
131
- # (an unresolved path or a prior-state read).
132
- def creation_rule_state_independent?(ast, phase, payload_fields, owner_fields)
133
- creation_paths(ast, Set.new).all? do |path|
134
- creation_path_state_independent?(path, phase, payload_fields, owner_fields)
135
- end
136
- end
137
-
138
- # Collects dotted lookup paths from the JSON `ast`, mirroring `ExpressionReads.collect`.
139
- # Paths rooted at a name bound by a `block_predicate` param are skipped.
140
- def creation_paths(node, bound_names)
141
- case node
142
- when ::Hash
143
- case node["op"]
144
- when "lookup"
145
- root = node["path"].first.to_s
146
- bound_names.include?(root) ? [] : [node["path"].join(".")]
147
- when "block_predicate"
148
- creation_paths(node["receiver"], bound_names) +
149
- creation_paths(node["predicate"], bound_names | [node["param"].to_s])
150
- else
151
- node.each_value.flat_map { |value| creation_paths(value, bound_names) }
152
- end
153
- when ::Array
154
- node.flat_map { |value| creation_paths(value, bound_names) }
155
- else
156
- []
157
- end
158
- end
159
-
160
- # Port of `classify_path`. A `parent`/`old` head always disqualifies. A payload read is fine.
161
- # An owner-field read is fine only after mutation (`:after`): every owner field is a known
162
- # write by then, so a given (`:before`) is the one disqualifying case.
163
- # Unknown names disqualify.
164
- def creation_path_state_independent?(path, phase, payload_fields, owner_fields)
165
- head, = path.split(".", 2)
166
-
167
- case head
168
- when "parent", "old"
169
- false
170
- else
171
- return true if payload_fields.include?(head)
172
- return false unless owner_fields.include?(head)
173
-
174
- phase != :before
175
- end
176
- end
177
- end
178
- end