feat(policy): add RulePolicy, the rule-shaped definition as a TransactionPolicy - #89
Draft
AlonzoRicardo wants to merge 1 commit into
Draft
AlonzoRicardo wants to merge 1 commit into
AlonzoRicardo wants to merge 1 commit into
Conversation
…tionPolicy Bridges rule-shaped policy definitions (the object registerPolicy accepts) onto the TransactionPolicy contract so both kinds of policy can compose under one engine path. Built from the engine's own parts — validatePolicy, PolicyRegistry, buildContext, evaluate — so within a single policy the semantics are identical to today's, not reimplemented. Verdict mapping from the evaluator: ALLOW -> allow (reason 'override' preserved), a matching DENY rule -> deny with the rule's reason, rules that addressed the operation but whose conditions did not hold -> abstain. Within one policy that is "no opinion"; the engine's cross-policy default-deny then yields the same decision as today. match() honours wallet, path and index bindings as well as rule operations, so an instance is safe to consult unfiltered. commit and rollback are no-ops; state and onSuccess remain reserved. DEFAULT_CONDITION_TIMEOUT_MS moves from policy-engine.js to constants.js so the engine and RulePolicy read one value. No behaviour change. Tests assert behaviour directly and, table-driven, verdict equivalence against evaluate() on the same definition and record. Claude-Session: https://claude.ai/code/session_01EU4Ntwfs7hKAB9mmdyxYtC
AlonzoRicardo
force-pushed
the
feat/policy-transaction-policy-base
branch
from
September 7, 2026 20:09
4e8bd4e to
8ba432f
Compare
AlonzoRicardo
force-pushed
the
feat/policy-rule-policy
branch
from
September 7, 2026 20:09
846ed0f to
44f9f8a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
RulePolicy: a rule-shaped policy definition (the objectregisterPolicyaccepts today) implemented as aTransactionPolicy. Stacked on the base-classes PR; nothing is wired into the proxy yet.This is the adapter that lets rule-shaped and code-defined policies compose under one contract, and it is deliberately built out of the engine's own parts —
validatePolicy,PolicyRegistry,buildContext,evaluate— so that within a single policy the semantics are identical to today's engine rather than a reimplementation of them.Verdict mapping
RulePolicy.evaluateALLOW(reasonmatched){ outcome: 'allow' }ALLOW(reasonoverride){ outcome: 'allow', reason: 'override' }BLOCKfrom a matching DENY rule{ outcome: 'deny', reason }(rule reason, or name, or the condition-error/timeout message)BLOCKbecause rules addressed the operation but no conditions held{ outcome: 'abstain' }Within one policy, "my rules addressed it but none fired" is no opinion; the engine's cross-policy default-deny then produces the same final decision today's engine does. Cross-policy composition (DENY-wins across policies, account-scope override of project DENYs) stays in the engine.
matchhonours the definition's wallet and account bindings (path and index) as well as the rules'operationfields, so an instance is safe to consult even unfiltered.commit/rollbackare no-ops;stateandonSuccessremain reserved.What this PR surfaced
Writing
RulePolicyshowed thatOperationRawneededpathandindexalongside the account view — read-only views don't expose either, and account-scope bindings match on them. That was folded into the base-classes PR before it was committed, which is exactly why this step runs before the engine switch.DEFAULT_CONDITION_TIMEOUT_MSmoved from a module-private constant inpolicy-engine.jstoconstants.jsso the engine andRulePolicyread one value. No behaviour change.Tests
tests/rule-policy.test.js(25 tests): construction reusesregisterPolicyvalidation;matchon literal / wildcard / array operations and on wallet, path and index bindings;evaluatefor ALLOW, DENY with and without reason, DENY-wins within a policy, throwing conditions (fail-closed on DENY, open-as-no-match on ALLOW), timeouts, the frozen snapshottedPolicyContextconditions receive, andoverride_broader_scope; plus a table-driven equivalence check that runs the same definition and record throughevaluate()directly and asserts the mapping above.https://claude.ai/code/session_01EU4Ntwfs7hKAB9mmdyxYtC