Skip to main content

What it is

The Runtime Pipeline is the ordered sequence of checks and steps a BusinessTransaction passes through, from arrival to a signed ExecutionTrustRecord. Its center is RuntimeEngine.execute() (packages/runtime/src/RuntimeEngine.ts), a single method that loads a policy, runs every configured pre-authorization protection in a fixed order, evaluates the policy, builds a Decision, signs an ExecutionAuthorizationPayload if approved, and hands the result to two further pipelines (RuntimePipeline for execution, BusinessTrustPipeline for the final trust record). Everything downstream of a request being accepted runs through this one method.

Why it was built

A system that authorizes real-world actions needs one place where every protection is guaranteed to run, in a guaranteed order, with no way for a caller or a misconfigured deployment to skip a step silently. RuntimeEngine is that place. Its own class doc comment states its responsibilities directly: load the requested policy, evaluate it deterministically, create the Decision artifact, create the initial Execution artifact, execute the Runtime Pipeline, and produce the ExecutionTrustRecord. Most of the protections wired into it (signal-intent binding, capability/policy binding, signal-state verification, policy governance verification) were added incrementally, each as an optional, trailing constructor parameter, specifically so every pre-existing call site keeps compiling and behaving identically when a new protection is introduced. This is a deliberate, repeated pattern in this codebase, not an accident of growth.

How it works

RuntimeEngine’s constructor takes ten required dependencies (RuntimePipeline, PolicyRouter, PolicyEngine, SignalIntentBinder, DecisionBuilder, ExecutionGate, ExecutionBuilder, BusinessTrustPipeline, RuntimeAuthorizationSigner, authorizationTtlSeconds) and seven optional trailing ones (hooks, refusalRecordBuilder, refusalRecordRepository, signalStateVerifier, capabilityPolicyBinder, policyExecutionVerifier, policyGovernanceAnchorResolver). At construction, it logs which optional protections are actually configured (runtime_engine_constructed), which is how an operator can confirm, from log output alone, exactly which protections are active for a given deployment. execute(transaction) runs these steps, in this exact order (comments quoted verbatim from RuntimeEngine.ts):
  1. Policy load. policyRouter.load(transaction.policy.name, transaction.policy.version). beforePolicyLoad/afterPolicyLoad hooks fire around this.
  2. Policy content hash (G-24). policyContentHash = policyContentHasher.hash(policy) , computed from the actually loaded policy document, never from what the caller declared, using the same TrustRecordHasher (canonicalize, then SHA-256) every other content hash in this codebase uses.
  3. Policy Governance evidence anchor (G-45). If policyGovernanceAnchorResolver is configured, it resolves whether this policy has a valid, matching approval record. Purely evidentiary, a resolver error is caught and logged, never allowed to affect the real outcome.
  4. Policy Governance execution-time verification. If policyExecutionVerifier is configured, it checks the same approval-record question, but this one can reject: “a policy with no approval record, an approval record whose signature does not verify, or live content that no longer matches its approval record is rejected before PolicyEngine ever evaluates a single rule in it.”
  5. Capability/Policy binding (TD-22). If capabilityPolicyBinder is configured and step 4 found no violation, it checks whether the invoked capability has a canonical policy binding and, if so, whether the declared policy matches it. Runs before signal-intent binding “for the same reason capabilityPolicyBinder runs before signalIntentBinder: checking a narrower guarantee against an already-wrong policy is meaningless.”
  6. Signal-intent binding. If steps 4 and 5 found no violation, signalIntentBinder.findViolations(policy, signals, {target, parameters}) checks that every boundSignals entry actually matches the real Intent’s target/parameters.
  7. Policy evaluation. If none of steps 4 through 6 rejected, policyEngine.evaluate(policy, signals) runs the real rules. Any rejection from steps 4 through 6 becomes a synthetic PolicyDecision with outcome: REJECT, evaluatedRules: 0, no rule is ever evaluated for a request that fails an earlier check.
  8. Signal-state verification (G-24 residual closure, RFC-0022). Only runs if the provisional decision is APPROVE, “a request already rejected… needs no independent re-fetch of real state.” If signalStateVerifier is configured, it independently re-derives the declared facts from a real external source; a mismatch overrides the decision to REJECT.
  9. Decision. decisionBuilder.build(transaction, policyDecision).
  10. Refusal Record (RFC-0021). If the decision is not APPROVED, a RefusalRecord is built and persisted, but this write is explicitly never allowed to affect or block anything downstream; a failure here is only logged (refusal_record_write_failed), the method quote: “The refusal itself must never depend on its own evidence being writable.”
  11. Enforce. executionGate.enforce(decision), this is the actual gate; a REJECT throws here and nothing past this point runs for a rejected transaction.
  12. Authorization. Only reached on APPROVE. signalsHash is computed, then authorizationSigner.sign(...) produces the SignedExecutionAuthorization (Chapter 9 covers this envelope’s exact fields).
  13. Execution + Runtime Context. executionBuilder.build(...) and the RuntimeContext are assembled, the context carries a copy of transaction.policy augmented with contentHash and, if resolved, governanceAnchor; the original caller-submitted transaction (already persisted before this method ever ran) is never mutated.
  14. Runtime Pipeline, then Business Trust Pipeline. pipeline.execute(context) runs the actual execution stages (Chapter 10 covers ExecutionGateway, one implementation of the ExecutionSystem interface this pipeline calls into); trustPipeline.execute(...) produces the final, signed ExecutionTrustRecord.
RuntimeFactory.create() (packages/runtime/src/RuntimeFactory.ts) is the composition root that assembles a fully wired RuntimeEngine (via RuntimeBuilder) plus the surrounding ExecutionTrustApplication (transaction/execution/verification/receipt services). It takes the same optional protections as trailing parameters and only wires them into RuntimeBuilder when supplied (if (signalStateVerifier) { builder.withSignalStateVerifier(...) }), the same “absent means unconfigured, not broken” discipline as the constructor itself.

How it enables things, with examples

  • examples/tutorials/03-runtime-execution, the baseline: a transaction through RuntimeEngine.execute() to a trust record, no optional protections.
  • examples/tutorials/16-runtime-pipeline, the pipeline stages themselves.
  • examples/tutorials/18-runtime-hooks, the RuntimeHook interface (beforePolicyLoad, afterDecision, etc.) that lets an integrator observe or extend the pipeline without modifying RuntimeEngine itself.
  • examples/tutorials/19-runtime-composition, composing multiple pipeline stages.
  • examples/tutorials/15-custom-runtime-component, writing a custom pipeline stage.

How to validate this yourself

  • packages/runtime/src/RuntimeEngine.ts, the method itself; every ordering claim above is a direct comment in this file.
  • packages/runtime/src/RuntimeFactory.ts, RuntimeBuilder.ts, how a RuntimeEngine actually gets constructed for a real deployment.
  • packages/runtime/tests/e2e/runtime.e2e.test.ts, end-to-end proof, including the G-24 content-hash-at-decision-time assertion against real on-disk policy content.
  • packages/runtime/tests/unit/optional-protections-logging.test.ts, proves the construction-time log line accurately reflects what’s wired.
  • packages/runtime/tests/integration/runtime.integration.test.ts, the fuller integration surface.

Integration requirements

None beyond what Chapter 2 (Configuration and Bootstrapping) already covers, RuntimeEngine itself takes no environment variables directly; every dependency it needs is constructed and passed in by RuntimeFactory/application.ts. The optional protections (signalStateVerifier, capabilityPolicyBinder, policyExecutionVerifier, policyGovernanceAnchorResolver) each have their own configuration surface, covered in the chapters specific to them.