Module: Hecks::Runtime
- Defined in:
- lib/hecks/runtime.rb,
lib/hecks/runtime/event.rb,
lib/hecks/runtime/value.rb,
lib/hecks/runtime/caller.rb,
lib/hecks/runtime/errors.rb,
lib/hecks/runtime/loader.rb,
lib/hecks/runtime/routing.rb,
lib/hecks/runtime/identity.rb,
lib/hecks/runtime/instance.rb,
lib/hecks/runtime/registry.rb,
lib/hecks/runtime/boot_gates.rb,
lib/hecks/runtime/dispatcher.rb,
lib/hecks/runtime/interpreting.rb,
lib/hecks/runtime/tenant_check.rb,
lib/hecks/runtime/tenant_scope.rb,
lib/hecks/runtime/command_rules.rb,
lib/hecks/runtime/rebuild_sweep.rb,
lib/hecks/runtime/reference_hop.rb,
lib/hecks/runtime/aggregate_lock.rb,
lib/hecks/runtime/entity_element.rb,
lib/hecks/runtime/value/coercion.rb,
lib/hecks/runtime/refusal_wording.rb,
lib/hecks/runtime/value/admission.rb,
lib/hecks/runtime/capability_graph.rb,
lib/hecks/runtime/saga_interpreter.rb,
lib/hecks/runtime/query_interpreter.rb,
lib/hecks/runtime/remote_dispatcher.rb,
lib/hecks/runtime/entity_interpreter.rb,
lib/hecks/runtime/policy_interpreter.rb,
lib/hecks/runtime/command_interpreter.rb,
lib/hecks/runtime/dependency_planning.rb,
lib/hecks/runtime/reaction_invocation.rb,
lib/hecks/runtime/registry/verification.rb,
lib/hecks/runtime/saga_pending_dispatch.rb,
lib/hecks/runtime/command_rules/emission.rb,
lib/hecks/runtime/read_model_interpreter.rb,
lib/hecks/runtime/command_rules/arithmetic.rb,
lib/hecks/runtime/command_rules/references.rb,
lib/hecks/runtime/registry/saga_persistence.rb,
lib/hecks/runtime/value/invariant_violation.rb,
lib/hecks/runtime/port_operation_interpreter.rb,
lib/hecks/runtime/command_rules/admissibility.rb,
lib/hecks/runtime/command_rules/authorization.rb,
lib/hecks/runtime/saga_interpreter/correlation.rb,
lib/hecks/ports/persistence/plugins/era/era_check.rb,
lib/hecks/ports/persistence/plugins/era/era_guard.rb,
lib/hecks/ports/persistence/plugins/era/era_tamper.rb,
lib/hecks/runtime/command_interpreter/argument_gate.rb,
lib/hecks/ports/persistence/plugins/era/storage_shape.rb,
lib/hecks/runtime/command_interpreter/mutation_applier.rb,
lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb
Defined Under Namespace
Modules: AggregateLock, Caller, DependencyPlanning, EntityElement, EraCheck, EraGuard, EraTamper, Identity, Interpreting, ReactionInvocation, RebuildSweep, ReferenceHop, RefusalWording, Routing, StorageShape, TenantCheck, TenantScope Classes: AbsentArgument, AlreadyExists, AttributeAbsent, BootGates, CapabilityGraph, CommandInterpreter, CommandRules, Dispatcher, EnsuresNotMet, EntityInterpreter, Event, GivenNotMet, Instance, InvariantViolation, LifecycleRefused, Loader, NotFound, NothingToCorrect, PolicyInterpreter, PortOperationInterpreter, ProjectionAbsent, QueryInterpreter, ReadModelInterpreter, Registry, RemoteDispatcher, RemoteRefusal, SagaInterpreter, StaleWrite, TypeMismatch, Unauthorized, UnknownArgument, UnknownVerb, Value, WiringError
Constant Summary collapse
- DOMAIN_REFUSALS =
The domain saying NO — the errors a reaction may legitimately meet and record as an undelivered outcome. A policy whose target refuses is a fact about the domain ; the originating command still stands.
Everything ELSE is a defect : a NoMethodError in an interpreter, a NameError from a missing constant, a TypeError from a bad assumption. A blanket
rescue StandardErrorused to fold both into one line —delivered: false, reason: "..."— so a crash in the runtime was indistinguishable from a rule doing its job, and read as normal operation in the log.UnknownVerb IS one of these, and deliberately : a cross-domain policy (
across "Notifications") fires in deployments where that domain is not loaded, and recording the undelivered reaction rather than raising is the design — spec/policy_spec states it in so many words, "records a reaction it cannot deliver rather than swallowing it". InvariantViolation belongs here and was missing. A value object refusing its own rule is the domain saying no as plainly as a given is — but the class is declared over in value.rb and never made the list, so the policy and saga interpreters, which rescue exactly these, would let it propagate as though the RUNTIME had broken. A reaction whose target violates an invariant is declined, not crashed. Found by spec/domain_refusal_spec on its first run : every corpus refusal must be a class named here, and 23 of banking's were InvariantViolation. THE NAMES COME FROM THE LANGUAGE, the classes from this module.DomainRefusaldeclares WHICH refusals are the domain's own — a rule the caller broke — as against a runtime fault. Resolving each name here means a refusal declared but never defined fails at load with a NameError, rather than being quietly absent from a list nothing re-checks. Hecks::Vocabulary.fetch("DomainRefusal").map { |name| const_get(name) }.freeze
- SAGA_PENDING_DISPATCH_KEY =
THE ONE SHARED CONSTANT between
SagaInterpreter(the writer) andRegistry::SagaPersistence#rehydrate_sagas!(the reader) for a scoped, minimal answer to the saga-durability review's item 8 (a durable outbox): a marker that survives exactly the window a crash inadvance_saga/unwindcan otherwise hide.THE PROBLEM THIS CLOSES —
checkpointpersists a saga's new state BEFORE the leg that justifies it (handler.dispatches) runs, and deliberately so: the mutex it holds is not reentrant, and a dispatch can re-enter this same interpreter. If the process dies in that window, the store says the transition happened and there is no record that its dispatch(es) never ran — not a refusal (the domain never got asked), not a defect (nothing raised), just silence indistinguishable from a leg that finished cleanly.THE FIX —
checkpointnow writes this key into the SAME already- durablememoryblob (no new column, no adapter/schema change:memoryis already an opaque, adapter-agnostic JSON blob everysave_sagaimplementation round-trips verbatim) whenever it checkpoints a state a dispatch cascade hasn't run for YET, and clears it (a second checkpoint,pending: nil) once that cascade — success, refusal-compensated, defect-compensated, or ceiling- compensated — has actually run. A crash between those two writes leaves the marker standing;rehydrate_sagas!strips it back out of the LIVE instance's own:memory(so no dispatch/given/ fuzzer/doc consumer of a saga's memory ever sees this key — it exists only in the persisted blob) and surfaces it loudly instead.WHAT THIS DELIBERATELY DOES NOT DO — auto-redrive the pending leg. Redelivering a dispatch whose outcome is genuinely unknown is only safe with idempotent delivery (the downstream command recognizing and no-op'ing a duplicate), which hecks's command/event pipeline has no mechanism for today. Blindly re-dispatching without that is how a stalled transfer becomes a DOUBLE-CREDITED one — a strictly worse defect than the stall it would replace. So this is real, durable, crash-surviving VISIBILITY into exactly what a stalled saga was doing when the process died — the missing half of "no reconciliation pass exists" — not the full pending → claimed → delivered outbox
future-features.mdstill lists as unbuilt, and not a substitute for it. :__hecks_saga_pending_dispatch__
Class Attribute Summary collapse
-
.current_registry ⇒ Object
readonly
The registry declarations are currently landing in, or nil outside a boot.
Class Method Summary collapse
-
.as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &block) ⇒ Object
Bind the ambient caller (see Runtime::Caller) for the duration of the block — who a command's declared
role, if any, is checked against. -
.boot(path, shared: nil, install_facade: true, environment: nil) ⇒ Object
Load a bluebook directory and return the Dispatcher bound to it.
-
.boot_files(paths, shared: nil, install_facade: true, environment: nil) ⇒ Object
pathsform — see Loader.boot_files. -
.with_registry(registry) ⇒ Object
Bind the ambient registry for the duration of the block, restoring whatever was there before.
Class Attribute Details
.current_registry ⇒ Object (readonly)
The registry declarations are currently landing in, or nil outside a boot. Read by the DSL collectors on the top-level module and by the extraction port.
62 63 64 |
# File 'lib/hecks/runtime.rb', line 62 def current_registry @current_registry end |
Class Method Details
.as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &block) ⇒ Object
Bind the ambient caller (see Runtime::Caller) for the duration of
the block — who a command's declared role, if any, is checked
against.
as_of: is OPTIONAL, same opt-in shape as actor_id: — a caller
that wants a Governance RoleAssignment's starts_at enforced
passes as_of: Ports::Clock.now(registry) here, at the door,
exactly where cli_runner.rb already merges Clock.now into a
command's own args. Nothing on the dispatch path calls the clock
itself — see Ports::Clock's own header for why — so an unbound
as_of leaves starts_at unchecked, exactly as before.
scope: is OPTIONAL too — a caller that states which scope it is
acting in gets that scope checked against the matching
RoleAssignment's own scope, not just its role_name. See
Runtime::Caller::Current's own header for why this lives here
rather than as a command-level DSL construct.
102 103 104 |
# File 'lib/hecks/runtime.rb', line 102 def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &block) Caller.as(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &block) end |
.boot(path, shared: nil, install_facade: true, environment: nil) ⇒ Object
Load a bluebook directory and return the Dispatcher bound to it.
install_facade:, environment: — see Loader.boot.
66 67 68 |
# File 'lib/hecks/runtime.rb', line 66 def boot(path, shared: nil, install_facade: true, environment: nil) Loader.boot(path, shared: shared, install_facade: install_facade, environment: environment) end |
.boot_files(paths, shared: nil, install_facade: true, environment: nil) ⇒ Object
paths form — see Loader.boot_files.
71 72 73 |
# File 'lib/hecks/runtime.rb', line 71 def boot_files(paths, shared: nil, install_facade: true, environment: nil) Loader.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment) end |
.with_registry(registry) ⇒ Object
Bind the ambient registry for the duration of the block, restoring whatever was there before. Nesting is safe ; a raise still restores.
77 78 79 80 81 82 83 |
# File 'lib/hecks/runtime.rb', line 77 def with_registry(registry) previous = @current_registry @current_registry = registry yield ensure @current_registry = previous end |