feat(policy): PolicyDialect — the engine's chain vocabulary behind the module (#45, C.2) - #69
Merged
Merged
Conversation
…e module (#45, C.2) The policy engine's Antelope-specific vocabulary leaves the core. A new PolicyDialect (core/policy/dialect.ts) carries exactly what varies per chain; the skeleton — deny-first control flow, JCS canonicalization, limits model, fail-closed pipeline — stays chain-agnostic: - match-path resolution (authorization.actor|permission mapping, data.* traversal) moves from engine.resolvePath to dialect.resolvePath; - asset extraction (data.quantity = {amount,symbol,precision}) moves from engine.actionAsset to dialect.assetOf; limit bounds parse through dialect.parseAssetLimit ("1.0000 XPR" grammar); - the quota recipient convention (data.to) becomes dialect.recipientOf; - the provider query→read-call mapping (xpr.rpc.tableRow → get_table_rows) moves from daemon/providerResolver.resolveOne to dialect.resolveProviderQuery — the resolver keeps only the generic timeout + fail-closed wrapper; - the validator's per-chain patterns (match paths, chain id, select fields, provider namespace) come from the dialect: validatePolicy / verifyStoredPolicy take it as a parameter, with one compiled Ajv validator memoized per dialect. TableRowProvider.provider widens to string (the namespace is enforced per-dialect at load time). The XPR dialect (chains/xpr/dialect.ts) is the moved code, verbatim in behavior, reading its patterns from the SAME vocabulary module the web editor imports (C.1) — daemon, validator and editor stay in lockstep. ChainModule gains `dialect`; the daemon (DaemonDependencies), the policy cache, the CLI explain and the MCP explain all receive it through the registry. EvaluationContext gains `dialect` (required — a policy is always evaluated in ITS chain's dialect, INV-013). Load-bearing acceptance verified: all 241 tests green (same decision assertions, ctx plumbing only), and the LIVE testnet policies (funagent v7, fuckagent v2) still pass verifyStoredPolicy byte-identically through the dialect path. vitest testTimeout raised to 60s — the Argon2id MODERATE suites (256 MiB by design) overran the 5s default under full-suite parallelism. Part of #45 — with C.1 this completes the Phase-A dialect work; the engine no longer hardcodes any chain vocabulary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RJ7hqFnC9Nnyf2Db2h3WTb
This was referenced Aug 6, 2026
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.
PR C.2 of the implementation plan (Wave 1, Track C) — with C.1 (#68) this completes the Phase-A dialect work of #45: the core engine no longer hardcodes any chain vocabulary.
What moved where
chains/xpr/dialect.ts)engine.resolvePath—authorization.actor|permission,data.*dialect.resolvePathengine.actionAsset—data.quantity {amount,symbol,precision}dialect.assetOfparseAsseton limit bounds —"1.0000 XPR"grammardialect.parseAssetLimitdata["to"]quota-recipient conventiondialect.recipientOfproviderResolver.resolveOne—xpr.rpc.tableRow→get_table_rowsdialect.resolveProviderQuery(resolver keeps the generic timeout + fail-closed wrapper)validatePolicy(input, dialect)/verifyStoredPolicy(json, hash, dialect), one Ajv validator memoized per dialectChain-agnostic skeleton untouched: deny-first control flow, JCS canonicalization + sha256, limits model, quota demands, fail-closed mapping. The multi-authorization refusal stays in core — it's the
DecodedActioncontract (MVP invariant), not vocabulary.Plumbing
ChainModulegainsdialect(#44's sketch completed);DaemonDependencies,PolicyCache, CLIexplain, MCPexplainreceive it through the registry.EvaluationContext.dialectis required — a policy is always validated/evaluated in ITS chain's dialect (INV-013). The XPR dialect reads its patterns from the samevocabulary.tsthe web editor imports (C.1) — daemon, validator, editor in lockstep.Load-bearing acceptance (issue #45)
authorization.*/data.quantity/data.to/get_table_rows/asset-grammar reference left incore/policy/engine.ts,schema.tsorproviderResolver.ts(docs comments aside).Also
vitest testTimeout→ 60s: the Argon2id MODERATE keystore suites (256 MiB per derivation, by design) overran the 5s default under full-suite parallelism — the earlier intermittent single-test failures were exactly this.A new chain's policy support is now: one dialect + one registry contract, zero core-engine edits (Phase B).
🤖 Generated with Claude Code
https://claude.ai/code/session_01RJ7hqFnC9Nnyf2Db2h3WTb