hecks 1.2.0 → 1.3.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/heki/journal.rb +57 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/expectations.rb +32 -4
- data/lib/hecks/bluebook/behaviour/domain_port.rb +24 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +25 -3
- data/lib/hecks/bluebook/model_check.rb +148 -17
- data/lib/hecks/forms/field_shape.rb +5 -3
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +266 -0
- data/lib/hecks/fuzzing/era_boundary.rb +105 -0
- data/lib/hecks/fuzzing/form_census.rb +184 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +198 -10
- data/lib/hecks/fuzzing/persistence_parity.rb +163 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +188 -13
- data/lib/hecks/fuzzing/properties/guards.rb +103 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +34 -1
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties.rb +23 -2
- data/lib/hecks/fuzzing/replay.rb +130 -20
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/self_consistency.rb +647 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +90 -26
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +50 -4
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +11 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +73 -9
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -14
- data/lib/hecks/fuzzing/structural_skips.rb +146 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +149 -0
- data/lib/hecks/fuzzing/value_generator.rb +55 -3
- data/lib/hecks/fuzzing.rb +6 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +17 -2
- data/lib/hecks/naming.rb +70 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +50 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +31 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -5
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projector/narrate_projector.rb +4 -11
- data/lib/hecks/query_specification/common/comparison.rb +27 -1
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +26 -8
- data/lib/hecks/runtime/command_rules/references.rb +75 -0
- data/lib/hecks/runtime/entity_element.rb +168 -16
- data/lib/hecks/runtime/entity_interpreter.rb +68 -3
- data/lib/hecks/runtime/query_interpreter.rb +66 -2
- data/lib/hecks/runtime/reaction_invocation.rb +70 -3
- data/lib/hecks/runtime/refusal_wording.rb +5 -2
- data/lib/hecks/runtime/registry.rb +12 -0
- data/lib/hecks/runtime/routing.rb +67 -2
- data/lib/hecks/runtime/saga_interpreter.rb +38 -1
- data/lib/hecks/runtime/value/coercion.rb +77 -115
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +7 -2
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- metadata +23 -2
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
require_relative "../errors"
|
|
2
|
+
require_relative "../refusal_wording"
|
|
3
|
+
require_relative "../../rendering"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Runtime
|
|
7
|
+
class Value
|
|
8
|
+
# HOW A `list_of` ATTRIBUTE'S OWN ELEMENTS GET HYDRATED — entity-typed
|
|
9
|
+
# and value-object-typed alike — split out of `Coercion` (this file's
|
|
10
|
+
# sibling, extended into `Value` alongside it exactly the way
|
|
11
|
+
# `Admission` already is) once `Coercion` itself grew past
|
|
12
|
+
# `Metrics/ModuleLength`'s budget from two, unrelated fixes landing in
|
|
13
|
+
# the same method the same week (BUG#32's own `hydrate_entity_identity`
|
|
14
|
+
# and BUG#33's own `check_entity_list_identities` — see each method's
|
|
15
|
+
# own header, and this repo's `.rubocop_todo.yml` for the merge that
|
|
16
|
+
# made the split necessary). Cross-calls into `Coercion`'s own
|
|
17
|
+
# `for_attribute`/`build`/`fields_for`/`value_object_for`/
|
|
18
|
+
# `trusting_stored_state?` work unqualified here exactly as they did
|
|
19
|
+
# before the split, because both modules land on the SAME `Value`
|
|
20
|
+
# singleton class once extended — `self` never has to know which file
|
|
21
|
+
# a sibling method actually lives in.
|
|
22
|
+
module EntityListCoercion
|
|
23
|
+
# S17, ADR 0026 — SEARCHES THE WHOLE ENTITY TREE, not only the
|
|
24
|
+
# root's own direct children. `aggregate` here is always the
|
|
25
|
+
# ROOT aggregate — `for_attribute`'s own `aggregate` argument is
|
|
26
|
+
# never reassigned as hydration recurses into a nested element,
|
|
27
|
+
# because coercion has to resolve value objects, and only the
|
|
28
|
+
# root answers `.value_object` at all (Entity's own header
|
|
29
|
+
# comment: an entity must NOT answer to it, or `Value.
|
|
30
|
+
# for_attribute` could no longer tell a piece from a head). So
|
|
31
|
+
# a NESTED entity — Dispatch, inside Handler — is not a direct
|
|
32
|
+
# child of the root the way Handler itself is, and a plain
|
|
33
|
+
# `aggregate.entities.find` stops one level short of it.
|
|
34
|
+
def find_entity(construct, name)
|
|
35
|
+
construct.entities.each do |candidate|
|
|
36
|
+
return candidate if candidate.hecks_name == name
|
|
37
|
+
|
|
38
|
+
found = find_entity(candidate, name)
|
|
39
|
+
return found if found
|
|
40
|
+
end
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Frozen through: a list read back out of the store is an answer,
|
|
45
|
+
# not a handle on what is stored.
|
|
46
|
+
#
|
|
47
|
+
# ADR 0047 — this used to bail (`return value unless entity`) the
|
|
48
|
+
# moment `attribute.type` named a value object rather than an
|
|
49
|
+
# entity, handing back the raw, un-hydrated argument untouched.
|
|
50
|
+
# A `sets :field` mutation sourced from a whole-array argument (as
|
|
51
|
+
# opposed to element-by-element `append:`) went straight through
|
|
52
|
+
# `for_attribute`'s `:list` branch, so `Banking::CardPayment.
|
|
53
|
+
# Authorize`'s own `sets :tags` (`list_of(Tag)`) stored plain
|
|
54
|
+
# Ruby Hashes as its `tags` elements forever — never a real
|
|
55
|
+
# `Value`, never through `Tag`'s own `pattern:`/`invariant`
|
|
56
|
+
# checks. `remove:`'s `==` comparison (a real `Value` against a
|
|
57
|
+
# raw `Hash`) then always failed, since `Hash#==` refuses anything
|
|
58
|
+
# that isn't itself a compatible Hash — the bug ADR 0047 traces in
|
|
59
|
+
# full. Delegating to `hydrate_value_object_list` below closes
|
|
60
|
+
# that gap the same way the ENTITY branch already worked: build a
|
|
61
|
+
# real, validated `Value` per element, reusing `for_attribute`'s
|
|
62
|
+
# own composite-construction path rather than inventing a second
|
|
63
|
+
# one.
|
|
64
|
+
# BUG#32 (QualityControl ledger) — `remove:`'s own single-target
|
|
65
|
+
# value used to fall straight into `Array(value).map { ... }`
|
|
66
|
+
# below UNGUARDED, unlike this method's own delegated
|
|
67
|
+
# `hydrate_value_object_list` sibling, whose `value.is_a?(Array)`
|
|
68
|
+
# check exists for exactly this shape (see that method's own
|
|
69
|
+
# comment). For an ENTITY-typed list, `remove:`'s scalar target
|
|
70
|
+
# (`Ledger.Void`'s `sequence: EntrySequence`) is never a Hash, so
|
|
71
|
+
# `Array(2)` merely wrapped it as `[2]` rather than shredding it —
|
|
72
|
+
# but the wrapping itself was still wrong: `MutationApplier#
|
|
73
|
+
# removed`'s `element == value` then compared a stored `Entry`
|
|
74
|
+
# Hash against a one-element Array, which can never be `==` a
|
|
75
|
+
# Hash, so nothing was ever removed and nothing ever refused
|
|
76
|
+
# either. Routed to `hydrate_entity_identity` instead, below.
|
|
77
|
+
def hydrate_entity_list(aggregate, attribute, value)
|
|
78
|
+
entity = find_entity(aggregate, attribute.type.to_s)
|
|
79
|
+
return hydrate_value_object_list(aggregate, attribute, value) unless entity
|
|
80
|
+
|
|
81
|
+
return hydrate_entity_identity(aggregate, entity, value) unless value.is_a?(Array)
|
|
82
|
+
|
|
83
|
+
hydrated = value.map do |element|
|
|
84
|
+
next element unless element.is_a?(Hash)
|
|
85
|
+
|
|
86
|
+
element.each_with_object({}) do |(name, field_value), acc|
|
|
87
|
+
key = name.to_sym
|
|
88
|
+
field = entity.attribute(key)
|
|
89
|
+
acc[key] = field ? for_attribute(aggregate, field, field_value) : field_value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
# BUG#33 — only a genuine WHOLE-LIST offering (`value.is_a?(Array)`)
|
|
93
|
+
# is a caller naming every element's own identity at once; the
|
|
94
|
+
# single-target shape this same method also hydrates (a `remove:`
|
|
95
|
+
# target, wrapped one level up by `Array()`) never reaches this
|
|
96
|
+
# check, and never should — see `check_entity_list_identities`'s
|
|
97
|
+
# own comment.
|
|
98
|
+
check_entity_list_identities(aggregate, entity, hydrated) if value.is_a?(Array)
|
|
99
|
+
Freezer.deep(hydrated)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# `MutationApplier#check_entity_collision`'s own guard
|
|
103
|
+
# (mutation_applier.rb), extended from a single caller-supplied
|
|
104
|
+
# APPEND (BUG#13) to a whole-list REPLACE — `sets :entries` bare
|
|
105
|
+
# (`Ledger.ReplaceEntries`, the corpus's first `list_of(ENTITY)`
|
|
106
|
+
# command argument/mutation, qa/stress_domains/corrections). The
|
|
107
|
+
# array branch above rebuilds each offered element's own declared
|
|
108
|
+
# fields but never checked the OFFERED LIST ITSELF for either way
|
|
109
|
+
# it can misname its own entities:
|
|
110
|
+
#
|
|
111
|
+
# - two elements sharing one identity — the same silent-duplicate
|
|
112
|
+
# hazard BUG#13's own comment describes: `EntityInterpreter#
|
|
113
|
+
# element_of`'s own `find_index` always matches the FIRST
|
|
114
|
+
# match, so the second becomes permanently unaddressable by any
|
|
115
|
+
# later command.
|
|
116
|
+
# - an element missing its identity altogether — there is no
|
|
117
|
+
# auto-mint for a whole-list replace the way `MutationApplier#
|
|
118
|
+
# entity_element` mints one for a single append (which element
|
|
119
|
+
# would it mint for? every element here is the caller's own
|
|
120
|
+
# whole state, offered at once), so an absent identity is
|
|
121
|
+
# refused rather than guessed.
|
|
122
|
+
#
|
|
123
|
+
# NON-COMPOSITE identities only, exactly BUG#13's own scope
|
|
124
|
+
# (`entity.identified_by` answers a single Symbol only when
|
|
125
|
+
# `identity_heads.size == 1`, `Behaviour::Traits#derive_identity`) —
|
|
126
|
+
# a composite identity's own duplicate/missing question is the
|
|
127
|
+
# same pre-existing, narrower gap BUG#13 documented and left open,
|
|
128
|
+
# not widened here.
|
|
129
|
+
#
|
|
130
|
+
# SKIPPED under `trusting_stored_state?`, the same guard `validate!`
|
|
131
|
+
# already gives every value object (C6.3): a record already
|
|
132
|
+
# written is trusted as it was, so tightening this check can never
|
|
133
|
+
# make an old, already-persisted record unreadable.
|
|
134
|
+
def check_entity_list_identities(aggregate, entity, elements)
|
|
135
|
+
identity = entity.identified_by
|
|
136
|
+
return unless identity
|
|
137
|
+
return if trusting_stored_state?
|
|
138
|
+
|
|
139
|
+
field = entity.attribute(identity)
|
|
140
|
+
seen = []
|
|
141
|
+
elements.each do |fields|
|
|
142
|
+
next unless fields.is_a?(Hash)
|
|
143
|
+
|
|
144
|
+
offered = fields[identity]
|
|
145
|
+
if offered.nil?
|
|
146
|
+
raise TypeMismatch,
|
|
147
|
+
RefusalWording.render("TypeMismatch", "numeric_field",
|
|
148
|
+
type: entity.hecks_name, field: identity,
|
|
149
|
+
expected: field&.type, offered: "nil")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if seen.include?(offered)
|
|
153
|
+
raise AlreadyExists,
|
|
154
|
+
RefusalWording.render("AlreadyExists", "entity_duplicate",
|
|
155
|
+
entity: entity.hecks_name, aggregate: aggregate.hecks_name,
|
|
156
|
+
identity: entity.identity_paths.join(", "),
|
|
157
|
+
offered: Rendering.describe(offered))
|
|
158
|
+
end
|
|
159
|
+
seen << offered
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# BUG#32 — `remove:`'s own single-target value against an
|
|
164
|
+
# ENTITY-typed list. An entity is never offered to `remove:`
|
|
165
|
+
# WHOLE the way a value object is (`hydrate_value_object_list`'s
|
|
166
|
+
# own `element == value` full-value-equality shape) — an entity
|
|
167
|
+
# must never answer `.value_object` at all (`Entity`'s own header
|
|
168
|
+
# comment), so there is no whole-value shape here to rebuild in
|
|
169
|
+
# the first place, only the ONE field a caller would otherwise
|
|
170
|
+
# have to NAME to address that element any other way
|
|
171
|
+
# (`EntityElement#element_of`'s own `wants`). So this coerces the
|
|
172
|
+
# offered scalar against the entity's OWN identity field's
|
|
173
|
+
# declared type — not the entity's full shape — and hands back a
|
|
174
|
+
# real, correctly-typed `Value` ready to compare against each
|
|
175
|
+
# stored element's own identity field
|
|
176
|
+
# (`EntityElement.list_element_match?`, the shared rule both
|
|
177
|
+
# `MutationApplier#removed` and `EntityElement#
|
|
178
|
+
# removed_from_element` match against).
|
|
179
|
+
#
|
|
180
|
+
# A COMPOSITE identity (more than one head) has no single field a
|
|
181
|
+
# bare `remove:` target could mean, so this passes the value
|
|
182
|
+
# through UNCOERCED rather than guessing which head — the same
|
|
183
|
+
# "nothing in this corpus needs it yet" boundary
|
|
184
|
+
# `MutationApplier#check_entity_collision`'s own header already
|
|
185
|
+
# draws for entity identity elsewhere in this runtime.
|
|
186
|
+
# `list_element_match?` treats an uncoerced value the same way
|
|
187
|
+
# it always treated the pre-fix wrapped Array: never a match, a
|
|
188
|
+
# documented no-op rather than a crash.
|
|
189
|
+
def hydrate_entity_identity(aggregate, entity, value)
|
|
190
|
+
return value if value.is_a?(self)
|
|
191
|
+
|
|
192
|
+
heads = entity.identity_heads
|
|
193
|
+
return value unless heads.one?
|
|
194
|
+
|
|
195
|
+
field = entity.attribute(heads.first)
|
|
196
|
+
return value unless field
|
|
197
|
+
|
|
198
|
+
for_attribute(aggregate, field, value)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The value-object sibling of the entity branch above: an element
|
|
202
|
+
# already shaped like the target `Value` (or a `Hash`/scalar that
|
|
203
|
+
# `fields_for` can still open) is rebuilt through the SAME `build`
|
|
204
|
+
# a scalar composite attribute already uses (`for_attribute`'s own
|
|
205
|
+
# `coerced = ... build(value_object, fields_for(...), aggregate)`
|
|
206
|
+
# line) — same defaults, same `pattern:`/`admits:`/invariant
|
|
207
|
+
# checks, same `trusting_stored_state?` bypass on a trusted load.
|
|
208
|
+
# `attribute.type` naming neither an entity nor a value object
|
|
209
|
+
# (a `list_of(String)`, say) has no shape to rebuild into, so the
|
|
210
|
+
# element passes through unchanged, exactly as the entity branch's
|
|
211
|
+
# own non-Hash elements do.
|
|
212
|
+
#
|
|
213
|
+
# NOT `Array(value).map` (unlike the entity branch above) — `value`
|
|
214
|
+
# here is not always genuinely list-shaped. `MutationApplier#
|
|
215
|
+
# removed`'s own `Value.for_attribute(aggregate, attribute, value)`
|
|
216
|
+
# call (`attribute` = the LIST attribute, `mutation.target`; `value`
|
|
217
|
+
# = the single REMOVE-target argument, already a real `Value` by
|
|
218
|
+
# the time it gets here) reuses this exact branch — for `remove:`,
|
|
219
|
+
# not for a whole-list `sets`. `Array(a_real_Value)` alone would be
|
|
220
|
+
# harmless (`Value` defines neither `to_a` nor `to_ary`, so Kernel
|
|
221
|
+
# wraps it `[value]`), but `Array(a_Hash)` is NOT harmless: Ruby's
|
|
222
|
+
# `Array()` opens a bare Hash into its own `[[k, v], ...]` pairs,
|
|
223
|
+
# not `[hash]` — silently shredding a single-element Hash-shaped
|
|
224
|
+
# target into garbage instead of hydrating it. Branching on
|
|
225
|
+
# `value.is_a?(Array)` up front (true only for a genuine whole-list
|
|
226
|
+
# `sets`/hydrate load) keeps the single-target shape a single
|
|
227
|
+
# target, hydrated the same way, never listified.
|
|
228
|
+
def hydrate_value_object_list(aggregate, attribute, value)
|
|
229
|
+
return value unless aggregate.respond_to?(:value_object)
|
|
230
|
+
|
|
231
|
+
value_object = value_object_for(aggregate, attribute.type)
|
|
232
|
+
return value unless value_object
|
|
233
|
+
|
|
234
|
+
return hydrate_value_object_element(aggregate, attribute, value_object, value) unless value.is_a?(Array)
|
|
235
|
+
|
|
236
|
+
hydrated = value.map { |element| hydrate_value_object_element(aggregate, attribute, value_object, element) }
|
|
237
|
+
Freezer.deep(hydrated)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def hydrate_value_object_element(aggregate, attribute, value_object, element)
|
|
241
|
+
return element if element.is_a?(self) && element.type_name == value_object.hecks_name
|
|
242
|
+
|
|
243
|
+
build(value_object, fields_for(value_object, attribute.name, element), aggregate)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
data/lib/hecks/runtime/value.rb
CHANGED
|
@@ -2,6 +2,7 @@ require "json"
|
|
|
2
2
|
require_relative "../rendering"
|
|
3
3
|
require_relative "value/invariant_violation"
|
|
4
4
|
require_relative "value/coercion"
|
|
5
|
+
require_relative "value/entity_list_coercion"
|
|
5
6
|
require_relative "value/admission"
|
|
6
7
|
|
|
7
8
|
module Hecks
|
|
@@ -9,10 +10,14 @@ module Hecks
|
|
|
9
10
|
# A typed value object in hand: frozen fields, read by name. How one is
|
|
10
11
|
# MADE — coerced from a raw argument, checked against its declared
|
|
11
12
|
# numeric types, patterns and closed sets — is the class-side engine in
|
|
12
|
-
# value/coercion.rb
|
|
13
|
-
#
|
|
13
|
+
# value/coercion.rb, value/entity_list_coercion.rb (a `list_of`
|
|
14
|
+
# attribute's own elements — split out once growing Coercion tripped
|
|
15
|
+
# Metrics/ModuleLength, see that file's own header), and
|
|
16
|
+
# value/admission.rb, extended here so the door stays where it always
|
|
17
|
+
# was: `Value.for`, `Value.build`.
|
|
14
18
|
class Value
|
|
15
19
|
extend Coercion
|
|
20
|
+
extend EntityListCoercion
|
|
16
21
|
extend Admission
|
|
17
22
|
|
|
18
23
|
attr_reader :value_object
|
data/lib/hecks/version.rb
CHANGED
data/lib/hecks/vocabulary.rb
CHANGED
|
@@ -148,7 +148,7 @@ module Hecks
|
|
|
148
148
|
{"refusal"=>"UnknownVerb", "site"=>"no_aggregate", "template"=>"{domain} has no aggregate {aggregate}"}.freeze,
|
|
149
149
|
{"refusal"=>"LifecycleRefused", "site"=>"transition_blocked", "template"=>"{command} refused — {field} is {current}, and {command} moves it only from {allowed}"}.freeze,
|
|
150
150
|
{"refusal"=>"TypeMismatch", "site"=>"value_object_shape", "template"=>"{name} is a {type} — pass its fields as an object, not {offered}"}.freeze,
|
|
151
|
-
{"refusal"=>"TypeMismatch", "site"=>"
|
|
151
|
+
{"refusal"=>"TypeMismatch", "site"=>"reference_wrong_shape", "template"=>"{command} refused — a reference is an id, and {attribute} arrived as {offered}{known_by}"}.freeze,
|
|
152
152
|
{"refusal"=>"TypeMismatch", "site"=>"multi_field_scalar", "template"=>"{type} has multiple fields and cannot stand in for a scalar"}.freeze,
|
|
153
153
|
{"refusal"=>"TypeMismatch", "site"=>"composite_identity", "template"=>"{type} is a composite identity — an identity must have exactly one field"}.freeze,
|
|
154
154
|
{"refusal"=>"TypeMismatch", "site"=>"numeric_field", "template"=>"{type}.{field} expects {expected}, got {offered}"}.freeze,
|
|
@@ -166,6 +166,7 @@ module Hecks
|
|
|
166
166
|
{"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "template"=>"{name} admits {admits}, which this chapter does not declare — a closed set is named Aggregate::SetName, and it must be one the bluebook actually holds"}.freeze,
|
|
167
167
|
{"refusal"=>"Unauthorized", "site"=>"tenant_required", "template"=>"{query} declares authorize with tenant: {field} — pass {field}: to name which {field} this ask is scoped to"}.freeze,
|
|
168
168
|
{"refusal"=>"Unauthorized", "site"=>"role_mismatch", "template"=>"{command} refused — role: {role}, and the caller stated {caller_role}"}.freeze,
|
|
169
|
+
{"refusal"=>"Unauthorized", "site"=>"cross_tenant_reference", "template"=>"{aggregate} {field} is {tenant}, but {attribute} names a {target} whose own {target_field} is {other} — a cross-tenant reference"}.freeze,
|
|
169
170
|
{"refusal"=>"AttributeAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is absent on this record — declared, not optional, and added since it was written. Backfill it in a translation (backfill :{field}, default: ...), or declare it optional: true"}.freeze,
|
|
170
171
|
{"refusal"=>"ProjectionAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is not yet projected on this record — declared via projects :{field}, but no rebuild sweep has populated it. Run the sweep, or read {reference}.{remote_field} directly if this rule cannot wait"}.freeze
|
|
171
172
|
].freeze,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: prism
|
|
@@ -83,6 +83,7 @@ files:
|
|
|
83
83
|
- lib/hecks/adapters/driven/sqlite/schema_builder.rb
|
|
84
84
|
- lib/hecks/adapters/driven/system_clock.adapter
|
|
85
85
|
- lib/hecks/adapters/driven/system_clock.rb
|
|
86
|
+
- lib/hecks/adapters/driving/github_webhook.rb
|
|
86
87
|
- lib/hecks/behaviors.rb
|
|
87
88
|
- lib/hecks/behaviors/dsl.rb
|
|
88
89
|
- lib/hecks/behaviors/expectations.rb
|
|
@@ -223,20 +224,32 @@ files:
|
|
|
223
224
|
- lib/hecks/freezer.rb
|
|
224
225
|
- lib/hecks/fuzzing.rb
|
|
225
226
|
- lib/hecks/fuzzing/bounded_exhaustive_expressions.rb
|
|
227
|
+
- lib/hecks/fuzzing/concurrent_dispatch.rb
|
|
228
|
+
- lib/hecks/fuzzing/era_boundary.rb
|
|
229
|
+
- lib/hecks/fuzzing/form_census.rb
|
|
226
230
|
- lib/hecks/fuzzing/invalid_value_generator.rb
|
|
227
231
|
- lib/hecks/fuzzing/isolated_boot.rb
|
|
232
|
+
- lib/hecks/fuzzing/persistence_parity.rb
|
|
228
233
|
- lib/hecks/fuzzing/properties.rb
|
|
234
|
+
- lib/hecks/fuzzing/properties/corrections.rb
|
|
229
235
|
- lib/hecks/fuzzing/properties/dispatch_and_mutations.rb
|
|
230
236
|
- lib/hecks/fuzzing/properties/guards.rb
|
|
231
237
|
- lib/hecks/fuzzing/properties/invariants_and_aggregation.rb
|
|
232
238
|
- lib/hecks/fuzzing/properties/lifecycle_and_replay.rb
|
|
239
|
+
- lib/hecks/fuzzing/properties/outbox.rb
|
|
233
240
|
- lib/hecks/fuzzing/properties/querying.rb
|
|
234
241
|
- lib/hecks/fuzzing/replay.rb
|
|
242
|
+
- lib/hecks/fuzzing/rotation_priority.rb
|
|
243
|
+
- lib/hecks/fuzzing/self_consistency.rb
|
|
235
244
|
- lib/hecks/fuzzing/sequence_generator.rb
|
|
245
|
+
- lib/hecks/fuzzing/sequence_generator/adversary.rb
|
|
236
246
|
- lib/hecks/fuzzing/sequence_generator/catalog.rb
|
|
237
247
|
- lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb
|
|
238
248
|
- lib/hecks/fuzzing/sequence_generator/picker.rb
|
|
239
249
|
- lib/hecks/fuzzing/sequence_generator/step_builder.rb
|
|
250
|
+
- lib/hecks/fuzzing/structural_skips.rb
|
|
251
|
+
- lib/hecks/fuzzing/sweep_depth.rb
|
|
252
|
+
- lib/hecks/fuzzing/target_capabilities.rb
|
|
240
253
|
- lib/hecks/fuzzing/value_generator.rb
|
|
241
254
|
- lib/hecks/grammar.rb
|
|
242
255
|
- lib/hecks/grammar/evolve.rb
|
|
@@ -345,6 +358,13 @@ files:
|
|
|
345
358
|
- lib/hecks/projections.rb
|
|
346
359
|
- lib/hecks/projections/diagrams.rb
|
|
347
360
|
- lib/hecks/projections/glossary.rb
|
|
361
|
+
- lib/hecks/projections/glossary/html.rb
|
|
362
|
+
- lib/hecks/projections/glossary/markdown.rb
|
|
363
|
+
- lib/hecks/projections/glossary/mermaid.rb
|
|
364
|
+
- lib/hecks/projections/glossary/page.css
|
|
365
|
+
- lib/hecks/projections/glossary/page.js
|
|
366
|
+
- lib/hecks/projections/glossary/sections.rb
|
|
367
|
+
- lib/hecks/projections/glossary/sentences.rb
|
|
348
368
|
- lib/hecks/projections/ir.rb
|
|
349
369
|
- lib/hecks/projections/model.rb
|
|
350
370
|
- lib/hecks/projections/model/deviations.rb
|
|
@@ -429,6 +449,7 @@ files:
|
|
|
429
449
|
- lib/hecks/runtime/value.rb
|
|
430
450
|
- lib/hecks/runtime/value/admission.rb
|
|
431
451
|
- lib/hecks/runtime/value/coercion.rb
|
|
452
|
+
- lib/hecks/runtime/value/entity_list_coercion.rb
|
|
432
453
|
- lib/hecks/runtime/value/invariant_violation.rb
|
|
433
454
|
- lib/hecks/storehouse.rb
|
|
434
455
|
- lib/hecks/version.rb
|