What it is
Two separate, deliberately independent checks that run beforePolicyEngine.evaluate ever
sees a request’s signals:
- Signal-Intent Binding (
SignalIntentBinder,packages/policy/src/SignalIntentBinder.ts), proves a policy’s declared signals describe the same action as the Intent that will actually execute if the transaction is approved. - Signal-State Verification (
SignalStateVerifierport,packages/policy/src/types/SignalStateVerifier.ts, composed viaCompositeSignalStateVerifier), proves those signals are true, not merely consistent.
Why it was built
PolicyEngine evaluates whatever PolicySignals it’s handed. Nothing about PolicyEngine
itself prevents a caller from declaring {"managerApproved": true} when no manager ever
approved anything, or from declaring a small, fully-verified signals payload while the real
Intent (the thing that actually gets signed and executed) targets something entirely
different. SignalIntentBinder’s own doc comment states the risk directly
(Policy.ts:238-249): without it, “a caller could otherwise declare a small, fully-verified
signals payload while Intent silently targets something else entirely, and receive a signed
APPROVED trust record for it.” SignalStateVerifier’s doc comment
(SignalStateVerifier.ts:24-33) draws the companion distinction explicitly: “SignalIntentBinder
proves a policy’s signals describe the same action as Intent; it never proves those signals
are true.” Two different failure modes, two different, additive mechanisms.
How it works
Signal-Intent Binding
A policy’sboundSignals field (Policy.ts:266, e.g. {"paymentAmount": "parameters.amount"}) declares that a given signal key must equal the value found at a
specific dot-path into the real Intent, an IntentSnapshot of { target, parameters }
(SignalIntentBinder.ts:9-12).
SignalIntentBinder.findViolations(policy, signals, intent)
(SignalIntentBinder.ts:67-95) walks every boundSignals entry, resolves the dot-path against
the real Intent via resolveIntentPath (SignalIntentBinder.ts:32-44, returns undefined
for any missing path segment rather than throwing, so a missing Intent field is reported as a
mismatch, not a crash), and compares it by strict equality (!==) against the caller-declared
signal value. Any mismatch becomes a SignalIntentBindingViolation, the empty array means
either the policy declares no boundSignals at all, or every declared binding held.
Only signals with a genuine Intent-side equivalent belong in boundSignals, an amount, a
target/vendor identifier. A signal representing an external fact with no Intent-side
equivalent (vendorVerified, riskScore) cannot be expressed this way and is deliberately
left out, acknowledged instead in unboundSignalReasons (Chapter 4 covers the validator’s
enforcement of this).
Signal-State Verification
TheSignalStateVerifier interface (SignalStateVerifier.ts:42-47) is a single async method:
findViolations(request, signals) => Promise<readonly SignalStateViolation[]>. A request
carries action, businessTransactionId, and intentParameters, enough for an
implementation to decide whether, and how, to independently re-derive the relevant facts from a
real external source and compare them against what the caller declared.
The interface is explicitly optional and capability-scoped: an implementation that doesn’t
recognize the given action returns an empty array, the same “nothing to check” shape
SignalIntentBinder uses when a policy has no boundSignals.
CompositeSignalStateVerifier (packages/policy/src/CompositeSignalStateVerifier.ts) combines
multiple capability-scoped verifiers into one: it queries each in the supplied order and
returns the first non-empty result (CompositeSignalStateVerifier.ts:23-35). This lets
RuntimeEngine, which accepts exactly one SignalStateVerifier, be wired with as many
capability-specific verifiers as production needs, each responsible for only the action(s) it
knows how to check.
A concrete production implementation: createHubSpotSignalStateVerifier
(packages/api/src/bootstrap/createHubSpotSignalStateVerifier.ts) builds a
HubSpotSignalStateVerifier (from @parmana/connector-hubspot) wired with the real signing key
(FileKeyProvider, DEFAULT_KEY_ID), the real execution gateway, and an ApprovalVerifier for
independently checking preAuthorizedForAmountChange, a signal that, without this, would be
nothing but a caller’s unverified claim. Its own doc comment notes approvalVerifier is always
supplied in production, never omitted; only direct unit tests construct the verifier without
it.
Where both run
Both checks run inside the runtime pipeline (Chapter 8 covers the full order) beforePolicyEngine.evaluate. A violation from either becomes an ordinary policy REJECT, no
authorization is ever generated for a request whose declared signals don’t match the real
Intent, or don’t match independently verified reality.
How it enables things, with a concrete example
- Tutorial 62 (
examples/tutorials/62-signal-intent-binding/run.ts) exercisesSignalIntentBinderdirectly, including a deliberate mismatch between declared signals and real Intent parameters. - Tutorial 71 (
examples/tutorials/71-hubspot-signal-state-verification/run.ts) exercises the realHubSpotSignalStateVerifierimplementation described above. - Tutorial 82 (
examples/tutorials/82-composite-signal-state-verification/run.ts) demonstratesCompositeSignalStateVerifiercombining multiple capability-scoped verifiers. - Tutorial 98 (
examples/tutorials/98-signal-freshness-enforcement/run.ts) covers a related freshness concern at the execution boundary, worth reading alongside this chapter, though it’s a distinct mechanism.
How to validate this yourself
packages/policy/src/SignalIntentBinder.ts(96 lines) andpackages/policy/tests/unit/SignalIntentBinder.test.ts.packages/policy/src/types/SignalStateVerifier.tsandpackages/policy/src/CompositeSignalStateVerifier.tsfor the port and its composition.packages/api/src/bootstrap/createHubSpotSignalStateVerifier.tsfor a real, wired production implementation, and its corresponding package underpackages/connector-hubspot/(or wherever@parmana/connector-hubspotactually lives in this checkout) for the verifier’s own logic.
Integration requirements
SignalIntentBinder needs nothing beyond the policy and the real Intent, both already present
in any request. A capability-specific SignalStateVerifier (like the HubSpot one) needs
whatever real credentials that connector needs to make its own independent verification calls
(a HubSpot private app token, in that case), see the connectors chapter for the full
credential list per connector.