@crediolabs/policy-synth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/oz/adapter.d.ts +20 -0
- package/dist/adapters/oz/adapter.js +282 -0
- package/dist/adapters/oz/index.d.ts +1 -0
- package/dist/adapters/oz/index.js +2 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.js +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/ir/index.d.ts +1 -0
- package/dist/ir/index.js +2 -0
- package/dist/ir/types.d.ts +97 -0
- package/dist/ir/types.js +11 -0
- package/dist/mandate/index.d.ts +2 -0
- package/dist/mandate/index.js +2 -0
- package/dist/mandate/to-ir.d.ts +3 -0
- package/dist/mandate/to-ir.js +60 -0
- package/dist/mandate/types.d.ts +20 -0
- package/dist/mandate/types.js +8 -0
- package/dist/record/decode.d.ts +76 -0
- package/dist/record/decode.js +372 -0
- package/dist/record/freshness.d.ts +17 -0
- package/dist/record/freshness.js +50 -0
- package/dist/record/index.d.ts +21 -0
- package/dist/record/index.js +163 -0
- package/dist/record/movements.d.ts +20 -0
- package/dist/record/movements.js +187 -0
- package/dist/record/rpc.d.ts +22 -0
- package/dist/record/rpc.js +70 -0
- package/dist/record/validate.d.ts +22 -0
- package/dist/record/validate.js +60 -0
- package/dist/registry/identify.d.ts +11 -0
- package/dist/registry/identify.js +84 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/known-addresses.d.ts +16 -0
- package/dist/registry/known-addresses.js +49 -0
- package/dist/registry/protocols.d.ts +38 -0
- package/dist/registry/protocols.js +149 -0
- package/dist/seams/index.d.ts +1 -0
- package/dist/seams/index.js +2 -0
- package/dist/seams/types.d.ts +66 -0
- package/dist/seams/types.js +11 -0
- package/dist/synth/compose-from-recording.d.ts +36 -0
- package/dist/synth/compose-from-recording.js +162 -0
- package/dist/synth/index.d.ts +5 -0
- package/dist/synth/index.js +6 -0
- package/dist/synth/lower.d.ts +23 -0
- package/dist/synth/lower.js +116 -0
- package/dist/synth/scope.d.ts +26 -0
- package/dist/synth/scope.js +77 -0
- package/dist/synth/synthesize-from-mandate.d.ts +5 -0
- package/dist/synth/synthesize-from-mandate.js +34 -0
- package/dist/synth/synthesize-from-recording.d.ts +17 -0
- package/dist/synth/synthesize-from-recording.js +178 -0
- package/dist/types.d.ts +249 -0
- package/dist/types.js +35 -0
- package/package.json +37 -0
- package/src/adapters/oz/adapter.ts +363 -0
- package/src/adapters/oz/index.ts +9 -0
- package/src/errors.ts +79 -0
- package/src/index.ts +9 -0
- package/src/ir/index.ts +13 -0
- package/src/ir/types.ts +94 -0
- package/src/mandate/index.ts +4 -0
- package/src/mandate/to-ir.ts +71 -0
- package/src/mandate/types.ts +21 -0
- package/src/record/decode.ts +500 -0
- package/src/record/freshness.ts +63 -0
- package/src/record/index.ts +224 -0
- package/src/record/movements.ts +188 -0
- package/src/record/rpc.ts +88 -0
- package/src/record/validate.ts +75 -0
- package/src/registry/identify.ts +99 -0
- package/src/registry/index.ts +24 -0
- package/src/registry/known-addresses.ts +71 -0
- package/src/registry/protocols.ts +176 -0
- package/src/seams/index.ts +11 -0
- package/src/seams/types.ts +81 -0
- package/src/synth/compose-from-recording.ts +226 -0
- package/src/synth/index.ts +19 -0
- package/src/synth/lower.ts +144 -0
- package/src/synth/scope.ts +108 -0
- package/src/synth/synthesize-from-mandate.ts +46 -0
- package/src/synth/synthesize-from-recording.ts +226 -0
- package/src/types.ts +218 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
|
|
3
|
+
export type ErrorCode =
|
|
4
|
+
// --- recorder ---
|
|
5
|
+
| 'RECORDING_FAILED'
|
|
6
|
+
| 'RECORDING_VALIDATION_FAILED'
|
|
7
|
+
// --- synthesis ---
|
|
8
|
+
| 'SCOPE_UNRESOLVED'
|
|
9
|
+
| 'SYNTHESIS_ERROR'
|
|
10
|
+
| 'MALFORMED_PREDICATE'
|
|
11
|
+
// --- simulate / verify ---
|
|
12
|
+
| 'SIMULATION_ERROR'
|
|
13
|
+
| 'VERIFICATION_FAILED'
|
|
14
|
+
| 'DENY_CASE_FAILURE'
|
|
15
|
+
| 'PERMIT_CASE_FAILED'
|
|
16
|
+
| 'SUMMARY_DRIFT'
|
|
17
|
+
// --- install / revoke ---
|
|
18
|
+
| 'INSTALL_BUILD_FAILED'
|
|
19
|
+
| 'INSTALL_CONFIRM_MISSING'
|
|
20
|
+
| 'INSTALL_CONFIRM_EXPIRED'
|
|
21
|
+
| 'REVOKE_BUILD_FAILED'
|
|
22
|
+
| 'REVOKE_CONFIRM_MISSING'
|
|
23
|
+
| 'USER_REJECTED_SIGN'
|
|
24
|
+
| 'WALLET_TIMEOUT'
|
|
25
|
+
| 'WALLET_UNAVAILABLE'
|
|
26
|
+
// --- predicate caps (fail-closed at install) ---
|
|
27
|
+
| 'PREDICATE_TOO_LARGE'
|
|
28
|
+
| 'PREDICATE_TOO_DEEP'
|
|
29
|
+
| 'PREDICATE_ORACLE_OVER_LIMIT'
|
|
30
|
+
| 'POLICY_CAP_EXCEEDED'
|
|
31
|
+
| 'WASM_TOO_LARGE'
|
|
32
|
+
// --- interpreter-side denies (canonical names; matches Rust DenyCode) ---
|
|
33
|
+
| 'MASTER_AUTH_REQUIRED'
|
|
34
|
+
| 'NONCE_REPLAY'
|
|
35
|
+
| 'VERSION_MISMATCH'
|
|
36
|
+
| 'ARITHMETIC_OVERFLOW'
|
|
37
|
+
| 'AMOUNT_OVERFLOW'
|
|
38
|
+
| 'RULE_SIGNERS_CHANGED'
|
|
39
|
+
| 'SCOPE_SELF_CALL'
|
|
40
|
+
// --- oracle denies (fatal evaluator errors) ---
|
|
41
|
+
| 'ORACLE_STALE'
|
|
42
|
+
| 'ORACLE_MISSING'
|
|
43
|
+
| 'ORACLE_DEVIATION_EXCEEDED'
|
|
44
|
+
| 'ORACLE_MALFORMED_HISTORY'
|
|
45
|
+
| 'ORACLE_FINGERPRINT_DRIFT'
|
|
46
|
+
| 'ORACLE_PAUSED'
|
|
47
|
+
| 'ORACLE_LEAF_INVALID_POSITION'
|
|
48
|
+
| 'ORACLE_PARAMS_OUT_OF_RANGE'
|
|
49
|
+
| 'ORACLE_DECIMALS_MISMATCH'
|
|
50
|
+
// --- escape-hatch compile gate ---
|
|
51
|
+
| 'COMPILE_OK'
|
|
52
|
+
| 'COMPILE_GATE_FAILED'
|
|
53
|
+
|
|
54
|
+
export interface ToolError {
|
|
55
|
+
code: ErrorCode
|
|
56
|
+
message: string
|
|
57
|
+
/** LLM-actionable hint for the agent skill. Per-code severity is pinned
|
|
58
|
+
* in CODE_SEVERITY below; this field is the resolved value at runtime. */
|
|
59
|
+
severity: 'info' | 'warning' | 'error' | 'fatal'
|
|
60
|
+
retryable: boolean
|
|
61
|
+
remediation?: {
|
|
62
|
+
toolCall?: { name: string; args: Record<string, unknown> }
|
|
63
|
+
userQuestion?: { code: string; question: string }
|
|
64
|
+
docsUrl?: string
|
|
65
|
+
}
|
|
66
|
+
/** Precedence rule for an LLM agent that sees BOTH toolCall AND userQuestion:
|
|
67
|
+
* - toolCall + userQuestion together = userQuestion wins (the human decides).
|
|
68
|
+
* - toolCall alone = agent proceeds.
|
|
69
|
+
* - userQuestion alone = agent stops and asks.
|
|
70
|
+
* No silent ambiguity. */
|
|
71
|
+
details?: unknown
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type ToolResponse<T> = { ok: true; data: T } | { ok: false; error: ToolError }
|
|
75
|
+
|
|
76
|
+
/** Unsigned Soroban transaction envelope, base64-encoded XDR (Soroban SDK convention).
|
|
77
|
+
* Pinned for CLI + MCP + WalletAdapter compatibility. Hex / raw-bytes / TS objects
|
|
78
|
+
* are NOT accepted - the contract is base64 string, full stop. */
|
|
79
|
+
export type UnsignedXdrB64 = string
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './adapters/oz/index.ts'
|
|
2
|
+
export * from './errors.ts'
|
|
3
|
+
export * from './ir/index.ts'
|
|
4
|
+
export * from './mandate/index.ts'
|
|
5
|
+
export * from './record/index.ts'
|
|
6
|
+
export * from './registry/index.ts'
|
|
7
|
+
export * from './seams/index.ts'
|
|
8
|
+
export * from './synth/index.ts'
|
|
9
|
+
export * from './types.ts'
|
package/src/ir/index.ts
ADDED
package/src/ir/types.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/ir/types.ts - the PolicyIR ("Policy Tree").
|
|
2
|
+
//
|
|
3
|
+
// `PolicyIR` is the custody-agnostic "Policy Tree" hub (the diagram's Policy
|
|
4
|
+
// Tree) that every CustodyAdapter compiles FROM. It generalizes the NEAR-V2
|
|
5
|
+
// policy schema (roles / scope filter / guard / constraint / comparison leaves /
|
|
6
|
+
// default behaviour) into one chain-neutral shape and extends it with the
|
|
7
|
+
// OZ-only selectors (spend window, oracle price, invocation count, time) so a
|
|
8
|
+
// single IR can serve every backend. NEAR-V2 has no stateful spend window,
|
|
9
|
+
// oracle, time/expiry, or invocation-count; those are marked as OZ extensions
|
|
10
|
+
// below and are only lowered by adapters that declare support for them.
|
|
11
|
+
|
|
12
|
+
/** Comparison operator. Mirrors NEAR-V2 `CompOp`. */
|
|
13
|
+
export type IRCompOp = 'eq' | 'lt' | 'lte' | 'gt' | 'gte'
|
|
14
|
+
|
|
15
|
+
/** Boolean combinator. Mirrors NEAR-V2 `LogicOp`. */
|
|
16
|
+
export type IRLogic = 'and' | 'or'
|
|
17
|
+
|
|
18
|
+
/** Vector match mode for a repeated arg. Mirrors NEAR-V2 `VecMode`. */
|
|
19
|
+
export type IRVecMode = 'all' | 'any'
|
|
20
|
+
|
|
21
|
+
/** Scalar value type. Mirrors NEAR-V2 `ScValType`, superset for OZ. */
|
|
22
|
+
export type IRScalarType =
|
|
23
|
+
| 'address'
|
|
24
|
+
| 'i128'
|
|
25
|
+
| 'u128'
|
|
26
|
+
| 'u32'
|
|
27
|
+
| 'u64'
|
|
28
|
+
| 'i64'
|
|
29
|
+
| 'symbol'
|
|
30
|
+
| 'bytes'
|
|
31
|
+
| 'bool'
|
|
32
|
+
|
|
33
|
+
/** WHERE in the authorized call a value is read. NEAR-V2 selectors first, OZ
|
|
34
|
+
* extensions after (an adapter that cannot express an extension flags it). */
|
|
35
|
+
export type IRSelector =
|
|
36
|
+
// --- NEAR-V2 selectors ---
|
|
37
|
+
| {
|
|
38
|
+
kind: 'arg'
|
|
39
|
+
argIndex: number
|
|
40
|
+
fieldIndex?: number
|
|
41
|
+
vecMode?: IRVecMode
|
|
42
|
+
scalarType: IRScalarType
|
|
43
|
+
} // Stellar StellarComp
|
|
44
|
+
| { kind: 'calldata'; offset: number; length: number } // EVM ByteComp
|
|
45
|
+
| { kind: 'value' } // EVM tx.value
|
|
46
|
+
// --- OZ extensions (not in NEAR-V2; lowered only by adapters that support them) ---
|
|
47
|
+
| { kind: 'amount'; token: string } // token amount this call moves
|
|
48
|
+
| { kind: 'window_spent'; token: string; windowSeconds: number } // stateful cumulative spend
|
|
49
|
+
| { kind: 'invocation_count'; windowSeconds: number }
|
|
50
|
+
| { kind: 'now' }
|
|
51
|
+
| { kind: 'valid_until' }
|
|
52
|
+
| { kind: 'oracle_price'; asset: string }
|
|
53
|
+
|
|
54
|
+
/** A single comparison leaf. `value` is a decimal/hex string (i128-safe;
|
|
55
|
+
* never a JS number). */
|
|
56
|
+
export interface IRCompare {
|
|
57
|
+
selector: IRSelector
|
|
58
|
+
operator: IRCompOp
|
|
59
|
+
value: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Condition tree. NEAR-V2 guard/constraint are flat And/Or; the IR allows
|
|
63
|
+
* nesting + `not` + `in` so the same IR can later lower to the OZ predicate
|
|
64
|
+
* DSL. Week-1 adapters only lower the flat supported subset and flag the rest. */
|
|
65
|
+
export type IRCondition =
|
|
66
|
+
| { op: 'and' | 'or'; children: IRCondition[] }
|
|
67
|
+
| { op: 'not'; child: IRCondition }
|
|
68
|
+
| { op: 'compare'; compare: IRCompare }
|
|
69
|
+
| { op: 'in'; selector: IRSelector; values: string[] }
|
|
70
|
+
|
|
71
|
+
export interface IRPolicyRule {
|
|
72
|
+
/** NEAR-V2 roles whitelist (empty = any; owner exempt). */
|
|
73
|
+
roles: string[]
|
|
74
|
+
/** NEAR-V2 scope filter; each field optional (absent = wildcard). */
|
|
75
|
+
scope: { chainId?: number; contract?: string; method?: string }
|
|
76
|
+
/** NEAR-V2 guard (applicability; skip the rule when it fails). */
|
|
77
|
+
guard?: IRCondition
|
|
78
|
+
/** NEAR-V2 constraint (AND; reject the transaction when any fails). */
|
|
79
|
+
constraints: IRCondition[]
|
|
80
|
+
// --- OZ extensions ---
|
|
81
|
+
/** M-of-N approval (OZ threshold primitives). */
|
|
82
|
+
approval?: { kind: 'threshold'; threshold: number; weights?: Record<string, number> }
|
|
83
|
+
/** OZ context-rule expiry. */
|
|
84
|
+
expiry?: { validUntilLedger?: number; validUntilUnixSeconds?: number }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PolicyIR {
|
|
88
|
+
/** NEAR-V2 PolicyChain. */
|
|
89
|
+
chain: 'stellar' | 'evm'
|
|
90
|
+
/** NEAR-V2 DefaultBehavior. OZ context rules are deny-by-default, so
|
|
91
|
+
* `deny_all` is the OZ default fallback when no rule matches. */
|
|
92
|
+
defaultBehavior: 'allow_all' | 'deny_all'
|
|
93
|
+
rules: IRPolicyRule[]
|
|
94
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/mandate/to-ir.ts - deterministic lowering of a MandateSpec to a PolicyIR.
|
|
2
|
+
//
|
|
3
|
+
// `mandateToPolicyIR` is pure and total: the same spec always lowers to a
|
|
4
|
+
// byte-identical PolicyIR. No decoding, no inference, no clock. Each mandate
|
|
5
|
+
// field maps to exactly one IR construct:
|
|
6
|
+
// contract/method -> rule.scope
|
|
7
|
+
// spendingLimit -> a `window_spent(token,window) <= limit` compare
|
|
8
|
+
// approvalThreshold-> rule.approval.threshold
|
|
9
|
+
// recipients -> an `in` condition on the recipient arg (Path-B flagged
|
|
10
|
+
// by the OZ adapter; that is expected)
|
|
11
|
+
// expiry -> rule.expiry
|
|
12
|
+
// The top-level default is `deny_all` (OZ context rules are deny-by-default).
|
|
13
|
+
|
|
14
|
+
import type { IRCondition, IRPolicyRule, IRSelector, PolicyIR } from '../ir/types.ts'
|
|
15
|
+
import type { MandateSpec } from './types.ts'
|
|
16
|
+
|
|
17
|
+
/** Arg index the recipient allowlist constrains. Pinned to the SEP-41
|
|
18
|
+
* `transfer(from, to, amount)` convention where `to` is arg 1. This condition
|
|
19
|
+
* is flagged Path-B by the OZ adapter (no built-in primitive expresses an arg
|
|
20
|
+
* allowlist), so the index only needs to be deterministic in week-1. */
|
|
21
|
+
const RECIPIENT_ARG_INDEX = 1
|
|
22
|
+
|
|
23
|
+
export function mandateToPolicyIR(spec: MandateSpec): PolicyIR {
|
|
24
|
+
const scope: IRPolicyRule['scope'] = { contract: spec.contract }
|
|
25
|
+
if (spec.method !== undefined) scope.method = spec.method
|
|
26
|
+
|
|
27
|
+
const constraints: IRCondition[] = []
|
|
28
|
+
|
|
29
|
+
if (spec.spendingLimit) {
|
|
30
|
+
constraints.push({
|
|
31
|
+
op: 'compare',
|
|
32
|
+
compare: {
|
|
33
|
+
selector: {
|
|
34
|
+
kind: 'window_spent',
|
|
35
|
+
token: spec.spendingLimit.token,
|
|
36
|
+
windowSeconds: spec.spendingLimit.windowSeconds,
|
|
37
|
+
},
|
|
38
|
+
operator: 'lte',
|
|
39
|
+
value: spec.spendingLimit.limit,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (spec.recipients && spec.recipients.length > 0) {
|
|
45
|
+
const selector: IRSelector = {
|
|
46
|
+
kind: 'arg',
|
|
47
|
+
argIndex: RECIPIENT_ARG_INDEX,
|
|
48
|
+
scalarType: 'address',
|
|
49
|
+
}
|
|
50
|
+
constraints.push({ op: 'in', selector, values: [...spec.recipients] })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const rule: IRPolicyRule = { roles: [], scope, constraints }
|
|
54
|
+
|
|
55
|
+
if (spec.approvalThreshold !== undefined) {
|
|
56
|
+
rule.approval = { kind: 'threshold', threshold: spec.approvalThreshold }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (spec.expiry) {
|
|
60
|
+
const expiry: NonNullable<IRPolicyRule['expiry']> = {}
|
|
61
|
+
if (spec.expiry.validUntilLedger !== undefined) {
|
|
62
|
+
expiry.validUntilLedger = spec.expiry.validUntilLedger
|
|
63
|
+
}
|
|
64
|
+
if (spec.expiry.validUntilUnixSeconds !== undefined) {
|
|
65
|
+
expiry.validUntilUnixSeconds = spec.expiry.validUntilUnixSeconds
|
|
66
|
+
}
|
|
67
|
+
rule.expiry = expiry
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { chain: spec.chain, defaultBehavior: 'deny_all', rules: [rule] }
|
|
71
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/mandate/types.ts - the deterministic Mandate source.
|
|
2
|
+
//
|
|
3
|
+
// A MandateSpec is a declarative policy statement: "this account may call this
|
|
4
|
+
// contract, spending at most L per window, requiring M approvals, to these
|
|
5
|
+
// recipients, until this expiry". It carries NO transaction to decode, NO
|
|
6
|
+
// parseConfidence, and NO inference - it lowers deterministically to a
|
|
7
|
+
// PolicyIR. It is the clean end-to-end demo path, co-equal with the recorder.
|
|
8
|
+
|
|
9
|
+
export interface MandateSpec {
|
|
10
|
+
chain: 'stellar'
|
|
11
|
+
contract: string
|
|
12
|
+
method?: string
|
|
13
|
+
/** -> OZ `spending_limit` primitive. `limit` is an i128 decimal string. */
|
|
14
|
+
spendingLimit?: { token: string; limit: string; windowSeconds: number }
|
|
15
|
+
/** -> OZ `simple_threshold` / `weighted_threshold` primitive. */
|
|
16
|
+
approvalThreshold?: number
|
|
17
|
+
/** Recipient allowlist -> an `in` arg condition (flagged Path-B in week-1). */
|
|
18
|
+
recipients?: string[]
|
|
19
|
+
/** -> OZ context-rule expiry. */
|
|
20
|
+
expiry?: { validUntilLedger?: number; validUntilUnixSeconds?: number }
|
|
21
|
+
}
|