Skip to content

Policy dialects: chain-agnostic core + per-chain vocabulary and registry contract #45

Description

@SuperstrongBE

Context

The policy engine splits cleanly into a portable core and an Antelope-flavoured vocabulary, but the two are currently fused:

Portable as-is — document skeleton (schemaVersion, default: "deny", chain{name,chainId}, rules[], maxActionsPerTransaction), RuleLimits, match operators (lte/gte/eq/in/notIn), JCS canonicalization + sha256 (src/core/canonical/jcs.ts), the verifyStoredPolicy gate (src/core/policy/onchain.ts), PolicyCache anti-rollback, QuotaJournal, and the pure evaluatePolicy control flow (deny-wins, first-match allow, fail closed).

Antelope-specific, baked into the core today:

  • MATCH_PATH_PATTERN in src/core/policy/schema.ts hard-codes authorization.(actor|permission) — the Antelope auth model.
  • chainId pattern ^[0-9a-f]{64}$ (Antelope 64-hex).
  • Asset grammar "1.0000 XPR" in src/core/asset.ts ([A-Z]{1,7} symbol, int64 bound).
  • Engine conventions in src/core/policy/engine.ts: actionAsset() reads data.quantity, quota-recipient logic reads data.toeosio.token shapes.
  • The only provider is xpr.rpc.tableRow, resolved through Antelope get_table_rows (src/daemon/providerResolver.ts).
  • Policy storage is the AssemblyScript signbox contract (contract/assembly/), keyed on Antelope Name (agent.N).

Coupling debt that multiplies per chain: the web editor hand-duplicates the schema — regex copies in web/src/editor/compile.ts, field sets in decompile.ts, plus a second canonicalize implementation. Parity is only enforced at the boundary (the daemon rejects not_canonical), not by construction.

Proposed design

Hard invariant: the document skeleton, JCS canonicalization, sha256 policyhash, limits model, and the fail-closed pipeline stay chain-agnostic and byte-identical in behaviour on every chain. Only the vocabulary varies.

PolicyDialect (a member of ChainModule — see the chain-modules issue):

interface PolicyDialect {
  matchPathPattern: RegExp;               // per-chain closed match vocabulary
  resolveAuthPath(action, path): unknown; // authorization.actor vs SUI sender, etc.
  asset: { parse(s): AssetAmount; format(a): string };  // per-chain grammar/bounds
  amountPath: string;                     // which data path is "the amount" (quotas)
  recipientPath: string;                  // which data path is "the recipient" (cooldowns)
  providers: { namespace: string; schema: JSONSchema; resolver: ProviderResolver };
}
  • The core schema validates the skeleton; the dialect validates the chain parts. evaluatePolicy receives the dialect instead of hardcoding actionAsset/data.to.
  • Per-chain policy registry contract (decided): each chain deploys its own registry implementing the same logical contract — row (agent, authority, version, policyhash, policyjson, enabled), monotonic version (anti-rollback), integrity sha256(policyjson) == policyhash, authority-only writes, kill-switch. verifyStoredPolicy (JCS + hash + schema) remains the single chain-agnostic gate on the daemon side.
  • Shared policy-core package (schema + canonicalize + dialect descriptors as data) consumed by BOTH the daemon and web/ — the editor's hand-kept regex/field-set/canonicalize copies disappear, and the editor learns a new chain by loading its dialect descriptor instead of a code fork.

Tasks

Phase A — dialect extraction (XPR only, no behavior change)

  • Define PolicyDialect; extract the Antelope vocabulary from schema.ts / engine.ts / asset.ts into the XPR dialect.
  • evaluatePolicy + validatePolicy consume the dialect (core keeps the skeleton).
  • Extract the shared policy-core package; port the web editor onto it (delete the duplicated regexes/canonicalize).

Phase B — follow-up passes (separate issues when Phase A lands)

  • SUI policy registry contract (Move shared object) + SUI dialect.
  • Metal policy registry contract (EVM/Solidity) + Metal dialect.

Invariants to preserve

INV-001 (deny by default), INV-004 (policy not agent-modifiable), INV-008-A (providers deterministic & narrowing-only), INV-010 (fail closed), INV-013 (explicit chain identity — a policy stays bound to one chainId).

Acceptance criteria

  • XPR policies (bytes, hashes, decisions) are unchanged after the dialect extraction — existing on-chain policies keep verifying.
  • Daemon and web editor validate/canonicalize through the same package (no hand-kept copies left).
  • A new chain's policy support = one dialect descriptor + one registry contract, zero core-engine edits.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RJ7hqFnC9Nnyf2Db2h3WTb

Metadata

Metadata

Assignees

Labels

ImprovementArchitecture / design improvement

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions