@crediolabs/policy-synth 0.2.0 → 0.3.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/README.md +4 -4
- package/dist/adapters/interpreter/adapter.d.ts +19 -25
- package/dist/adapters/interpreter/adapter.js +54 -487
- package/dist/adapters/interpreter/index.d.ts +1 -1
- package/dist/adapters/interpreter/index.js +1 -1
- package/dist/adapters/oz/adapter.js +1 -15
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -4
- package/dist/install/build-add-context-rule.d.ts +3 -19
- package/dist/install/build-add-context-rule.js +3 -98
- package/dist/install/build-install-policy.d.ts +4 -53
- package/dist/install/build-install-policy.js +44 -123
- package/dist/install/index.d.ts +0 -2
- package/dist/install/index.js +0 -7
- package/dist/ir/index.d.ts +1 -1
- package/dist/ir/types.d.ts +7 -76
- package/dist/ir/types.js +0 -2
- package/dist/predicate/decode.d.ts +1 -1
- package/dist/predicate/decode.js +2 -70
- package/dist/predicate/encode.js +45 -229
- package/dist/predicate/from-json.js +1 -42
- package/dist/record/decode.js +3 -8
- package/dist/review-card/builder.d.ts +4 -3
- package/dist/review-card/builder.js +18 -162
- package/dist/review-card/cross-check.js +2 -105
- package/dist/review-card/render-leaf.d.ts +4 -0
- package/dist/review-card/render-leaf.js +47 -0
- package/dist/run/index.d.ts +36 -139
- package/dist/run/index.js +125 -437
- package/dist/run/schemas.d.ts +168 -1929
- package/dist/run/schemas.js +40 -269
- package/dist/seams/types.d.ts +0 -2
- package/dist/simulate/deny-cases.d.ts +26 -0
- package/dist/simulate/deny-cases.js +282 -0
- package/dist/simulate/evaluate.d.ts +18 -0
- package/dist/simulate/evaluate.js +271 -0
- package/dist/simulate/index.d.ts +4 -0
- package/dist/simulate/index.js +8 -0
- package/dist/synth/compose-from-recording.d.ts +24 -51
- package/dist/synth/compose-from-recording.js +120 -282
- package/dist/synth/deny-cases.d.ts +5 -7
- package/dist/synth/deny-cases.js +23 -373
- package/dist/synth/evaluate.d.ts +0 -21
- package/dist/synth/evaluate.js +56 -336
- package/dist/synth/index.d.ts +0 -5
- package/dist/synth/index.js +0 -5
- package/dist/synth/permit-context.d.ts +2 -9
- package/dist/synth/permit-context.js +1 -82
- package/dist/synth/synthesize-from-recording.d.ts +1 -29
- package/dist/synth/synthesize-from-recording.js +41 -356
- package/dist/types.d.ts +8 -60
- package/dist/types.js +5 -2
- package/dist/verify/simulate.d.ts +1 -14
- package/dist/verify/simulate.js +2 -88
- package/dist/verify/verify.d.ts +1 -7
- package/dist/verify/verify.js +0 -8
- package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
- package/dist-cjs/adapters/interpreter/adapter.js +56 -489
- package/dist-cjs/adapters/interpreter/index.d.ts +1 -1
- package/dist-cjs/adapters/interpreter/index.js +1 -2
- package/dist-cjs/adapters/oz/adapter.js +1 -15
- package/dist-cjs/errors.d.ts +1 -1
- package/dist-cjs/index.d.ts +1 -4
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/install/build-add-context-rule.d.ts +3 -19
- package/dist-cjs/install/build-add-context-rule.js +1 -97
- package/dist-cjs/install/build-install-policy.d.ts +4 -53
- package/dist-cjs/install/build-install-policy.js +44 -122
- package/dist-cjs/install/index.d.ts +0 -2
- package/dist-cjs/install/index.js +2 -23
- package/dist-cjs/ir/index.d.ts +1 -1
- package/dist-cjs/ir/types.d.ts +7 -76
- package/dist-cjs/ir/types.js +0 -2
- package/dist-cjs/predicate/decode.d.ts +1 -1
- package/dist-cjs/predicate/decode.js +2 -70
- package/dist-cjs/predicate/encode.js +45 -229
- package/dist-cjs/predicate/from-json.js +1 -42
- package/dist-cjs/record/decode.js +3 -8
- package/dist-cjs/review-card/builder.d.ts +4 -3
- package/dist-cjs/review-card/builder.js +23 -167
- package/dist-cjs/review-card/cross-check.js +7 -110
- package/dist-cjs/review-card/render-leaf.d.ts +4 -0
- package/dist-cjs/review-card/render-leaf.js +52 -0
- package/dist-cjs/run/index.d.ts +36 -139
- package/dist-cjs/run/index.js +125 -444
- package/dist-cjs/run/schemas.d.ts +168 -1929
- package/dist-cjs/run/schemas.js +41 -270
- package/dist-cjs/seams/types.d.ts +0 -2
- package/dist-cjs/simulate/deny-cases.d.ts +26 -0
- package/dist-cjs/simulate/deny-cases.js +286 -0
- package/dist-cjs/simulate/evaluate.d.ts +18 -0
- package/dist-cjs/simulate/evaluate.js +274 -0
- package/dist-cjs/simulate/index.d.ts +4 -0
- package/dist-cjs/simulate/index.js +13 -0
- package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
- package/dist-cjs/synth/compose-from-recording.js +120 -282
- package/dist-cjs/synth/deny-cases.d.ts +5 -7
- package/dist-cjs/synth/deny-cases.js +23 -374
- package/dist-cjs/synth/evaluate.d.ts +0 -21
- package/dist-cjs/synth/evaluate.js +57 -337
- package/dist-cjs/synth/index.d.ts +0 -5
- package/dist-cjs/synth/index.js +1 -11
- package/dist-cjs/synth/permit-context.d.ts +2 -9
- package/dist-cjs/synth/permit-context.js +1 -82
- package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
- package/dist-cjs/synth/synthesize-from-recording.js +39 -354
- package/dist-cjs/types.d.ts +8 -60
- package/dist-cjs/types.js +6 -3
- package/dist-cjs/verify/simulate.d.ts +1 -14
- package/dist-cjs/verify/simulate.js +2 -88
- package/dist-cjs/verify/verify.d.ts +1 -7
- package/dist-cjs/verify/verify.js +0 -8
- package/package.json +4 -4
- package/src/adapters/interpreter/adapter.ts +76 -572
- package/src/errors.ts +0 -19
- package/src/index.ts +1 -4
- package/src/install/build-add-context-rule.ts +5 -139
- package/src/install/build-install-policy.ts +93 -214
- package/src/install/index.ts +0 -34
- package/src/predicate/decode.ts +2 -70
- package/src/predicate/encode.ts +49 -261
- package/src/predicate/from-json.ts +1 -43
- package/src/record/decode.ts +3 -8
- package/src/review-card/builder.ts +19 -168
- package/src/review-card/cross-check.ts +3 -105
- package/src/review-card/render-leaf.ts +48 -0
- package/src/run/index.ts +144 -572
- package/src/run/schemas.ts +42 -291
- package/src/simulate/deny-cases.ts +334 -0
- package/src/simulate/evaluate.ts +284 -0
- package/src/simulate/index.ts +11 -0
- package/src/synth/compose-from-recording.ts +148 -347
- package/src/synth/index.ts +0 -13
- package/src/synth/synthesize-from-recording.ts +43 -456
- package/src/types.ts +13 -49
- package/dist/install/authority-overlap.d.ts +0 -134
- package/dist/install/authority-overlap.js +0 -0
- package/dist/install/build-install-predicate.d.ts +0 -96
- package/dist/install/build-install-predicate.js +0 -444
- package/dist/install/build-merge-policy.d.ts +0 -70
- package/dist/install/build-merge-policy.js +0 -130
- package/dist/install/plan-merge-policy.d.ts +0 -49
- package/dist/install/plan-merge-policy.js +0 -86
- package/dist/install/read-account-rules.d.ts +0 -100
- package/dist/install/read-account-rules.js +0 -283
- package/dist-cjs/install/authority-overlap.d.ts +0 -134
- package/dist-cjs/install/authority-overlap.js +0 -0
- package/dist-cjs/install/build-install-predicate.d.ts +0 -96
- package/dist-cjs/install/build-install-predicate.js +0 -479
- package/dist-cjs/install/build-merge-policy.d.ts +0 -70
- package/dist-cjs/install/build-merge-policy.js +0 -134
- package/dist-cjs/install/plan-merge-policy.d.ts +0 -49
- package/dist-cjs/install/plan-merge-policy.js +0 -90
- package/dist-cjs/install/read-account-rules.d.ts +0 -100
- package/dist-cjs/install/read-account-rules.js +0 -296
- package/src/adapters/interpreter/index.ts +0 -8
- package/src/adapters/oz/adapter.ts +0 -376
- package/src/adapters/oz/index.ts +0 -9
- package/src/codegen/compile-gate.ts +0 -167
- package/src/codegen/index.ts +0 -17
- package/src/codegen/template.ts +0 -165
- package/src/install/authority-overlap.ts +0 -0
- package/src/install/build-merge-policy.ts +0 -219
- package/src/install/plan-merge-policy.ts +0 -133
- package/src/install/read-account-rules.ts +0 -376
- package/src/ir/index.ts +0 -13
- package/src/ir/types.ts +0 -132
- package/src/mandate/index.ts +0 -4
- package/src/mandate/to-ir.ts +0 -71
- package/src/mandate/types.ts +0 -21
- package/src/seams/index.ts +0 -11
- package/src/seams/types.ts +0 -81
- package/src/synth/deny-cases.ts +0 -663
- package/src/synth/evaluate.ts +0 -613
- package/src/synth/harness.ts +0 -68
- package/src/synth/minimize.ts +0 -48
- package/src/synth/permit-context.ts +0 -136
- package/src/synth/predicate-literals.ts +0 -27
- package/src/synth/synthesize-from-mandate.ts +0 -82
- package/src/verify/envelope.ts +0 -28
- package/src/verify/index.ts +0 -5
- package/src/verify/simulate.ts +0 -311
- package/src/verify/verify.ts +0 -243
|
@@ -7,23 +7,13 @@
|
|
|
7
7
|
// summary being REPRODUCIBLE + TESTABLE + NON-HALLUCINABLE, so this module is
|
|
8
8
|
// pure: same inputs -> byte-identical output, no clock, no randomness, no I/O.
|
|
9
9
|
//
|
|
10
|
-
// The builder walks
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// 2. The interpreter `PredicateNode`. One string per constraint leaf,
|
|
19
|
-
// rendered by enclosing-comparison kind. Templates (Task 7b):
|
|
20
|
-
// - invocation_count_in_window < N -> At most N calls per <window> seconds
|
|
21
|
-
// - call_arg[i] in [list] -> Recipient/arg must be one of [list]
|
|
22
|
-
// - eq(call_arg[i], literal_vec[...]) -> Path must be exactly [list]
|
|
23
|
-
// - oracle_price(asset) OP price -> Only when oracle_price(asset) OP price
|
|
24
|
-
// - call_fn == x -> Function must be x
|
|
25
|
-
// - call_contract == c -> Contract must be c
|
|
26
|
-
// - amount <= v -> Amount <= v
|
|
10
|
+
// The builder walks the interpreter `PredicateNode` and emits ONE constraint
|
|
11
|
+
// string per leaf, in a fixed deterministic order. Templates (Task 7b):
|
|
12
|
+
// - call_arg[i] in [list] -> Recipient/arg must be one of [list]
|
|
13
|
+
// - eq(call_arg[i], literal_vec[...]) -> Path must be exactly [list]
|
|
14
|
+
// - call_fn == x -> Function must be x
|
|
15
|
+
// - call_contract == c -> Contract must be c
|
|
16
|
+
// - amount <= v -> Amount <= v
|
|
27
17
|
//
|
|
28
18
|
// The content hash is a stable sha256 hex of a canonical JSON of
|
|
29
19
|
// { ruleName, plainEnglish, constraints, expiry, backend } - identical
|
|
@@ -33,15 +23,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
33
23
|
exports.buildReviewCardSummary = buildReviewCardSummary;
|
|
34
24
|
exports.describePredicate = describePredicate;
|
|
35
25
|
const node_crypto_1 = require("node:crypto");
|
|
26
|
+
const render_leaf_ts_1 = require("./render-leaf.js");
|
|
36
27
|
/** Build a deterministic review-card summary from a policy + context rule +
|
|
37
28
|
* simulation result. Pure: same inputs -> byte-identical output. */
|
|
38
|
-
function buildReviewCardSummary(predicate,
|
|
29
|
+
function buildReviewCardSummary(predicate, contextRule,
|
|
30
|
+
/** Evaluator identity folded into the content hash. */
|
|
31
|
+
backend) {
|
|
39
32
|
const constraints = [];
|
|
40
|
-
for (const ref of policyRefs) {
|
|
41
|
-
const line = renderOzPrimitive(ref);
|
|
42
|
-
if (line !== null)
|
|
43
|
-
constraints.push(line);
|
|
44
|
-
}
|
|
45
33
|
if (predicate !== null) {
|
|
46
34
|
walkPredicate(predicate, (node) => {
|
|
47
35
|
const line = renderConstraint(node);
|
|
@@ -59,7 +47,6 @@ function buildReviewCardSummary(predicate, policyRefs, contextRule, simulation)
|
|
|
59
47
|
const signerNote = contextRule.signers.length >= 2
|
|
60
48
|
? 'any ONE signer may authorise a permitted op under this rule (OZ any-of-N semantic)'
|
|
61
49
|
: null;
|
|
62
|
-
const backend = simulation.backend;
|
|
63
50
|
const contentHash = computeContentHash({
|
|
64
51
|
ruleName,
|
|
65
52
|
plainEnglish,
|
|
@@ -70,25 +57,6 @@ function buildReviewCardSummary(predicate, policyRefs, contextRule, simulation)
|
|
|
70
57
|
});
|
|
71
58
|
return { ruleName, plainEnglish, constraints, expiry, signerNote, backend, contentHash };
|
|
72
59
|
}
|
|
73
|
-
/** Render the OZ built-in primitive summary line. Only `spending_limit` is
|
|
74
|
-
* quoted by the review card (it is the only primitive that defines a
|
|
75
|
-
* transactional bound). Other primitives (threshold) are signer-config
|
|
76
|
-
* concerns handled by the OZ adapter's own `uncovered` machinery.
|
|
77
|
-
* Spending_limit takes `period_ledgers` on-chain (~5s/ledger); the card
|
|
78
|
-
* states the window in seconds so the user reads it consistently with the
|
|
79
|
-
* interpreter templates. */
|
|
80
|
-
function renderOzPrimitive(ref) {
|
|
81
|
-
if (ref.kind !== 'oz_builtin')
|
|
82
|
-
return null;
|
|
83
|
-
const primitive = ref.primitive;
|
|
84
|
-
if (primitive.primitive !== 'spending_limit')
|
|
85
|
-
return null;
|
|
86
|
-
const params = primitive.params;
|
|
87
|
-
const limit = params.spending_limit ?? '0';
|
|
88
|
-
const periodLedgers = params.period_ledgers ?? 0;
|
|
89
|
-
const windowSecs = periodLedgers * 5;
|
|
90
|
-
return `spending_limit(${limit}, ${windowSecs})`;
|
|
91
|
-
}
|
|
92
60
|
/** Walk every comparison / membership node of the predicate and invoke
|
|
93
61
|
* `visit` on each. The walk is depth-first, left-to-right, so the
|
|
94
62
|
* constraint list is stable across runs. Pure boolean nodes contribute no
|
|
@@ -113,21 +81,14 @@ function describePredicate(predicate) {
|
|
|
113
81
|
function walkPredicate(node, visit) {
|
|
114
82
|
switch (node.op) {
|
|
115
83
|
case 'and':
|
|
116
|
-
case 'or':
|
|
117
84
|
for (const child of node.children)
|
|
118
85
|
walkPredicate(child, visit);
|
|
119
86
|
return;
|
|
120
|
-
case 'not':
|
|
121
|
-
walkPredicate(node.child, visit);
|
|
122
|
-
return;
|
|
123
87
|
case 'in':
|
|
124
88
|
visit(node);
|
|
125
89
|
return;
|
|
126
90
|
case 'eq':
|
|
127
|
-
case 'lt':
|
|
128
91
|
case 'lte':
|
|
129
|
-
case 'gt':
|
|
130
|
-
case 'gte':
|
|
131
92
|
visit(node);
|
|
132
93
|
return;
|
|
133
94
|
}
|
|
@@ -135,18 +96,13 @@ function walkPredicate(node, visit) {
|
|
|
135
96
|
/** Render ONE constraint sentence for ONE interpreter predicate node. The
|
|
136
97
|
* shape of the output is pinned by Task 7b so the test suite can assert
|
|
137
98
|
* byte-for-byte equality. Returns `null` when the node is a structural
|
|
138
|
-
* boolean (`and`
|
|
99
|
+
* boolean (`and`) - that is not a constraint leaf. */
|
|
139
100
|
function renderConstraint(node) {
|
|
140
101
|
switch (node.op) {
|
|
141
102
|
case 'and':
|
|
142
|
-
case 'or':
|
|
143
|
-
case 'not':
|
|
144
103
|
return null;
|
|
145
104
|
case 'eq':
|
|
146
|
-
case 'lt':
|
|
147
105
|
case 'lte':
|
|
148
|
-
case 'gt':
|
|
149
|
-
case 'gte':
|
|
150
106
|
return renderComparison(node);
|
|
151
107
|
case 'in':
|
|
152
108
|
return renderMembership(node);
|
|
@@ -165,7 +121,7 @@ function renderComparison(node) {
|
|
|
165
121
|
}
|
|
166
122
|
// eq(call_arg[i], literal_vec) -> Path must be exactly [list]
|
|
167
123
|
if (left.kind === 'call_arg' && node.op === 'eq' && right.kind === 'literal_vec') {
|
|
168
|
-
return `Path must be exactly [${right.elements.map(renderVecElement).join(', ')}]`;
|
|
124
|
+
return `Path must be exactly [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`;
|
|
169
125
|
}
|
|
170
126
|
// eq(call_arg_len[i], literal_u32) -> Length of arg[i] is K
|
|
171
127
|
// Binds the OUTER vec length so a caller cannot append an element to
|
|
@@ -176,73 +132,41 @@ function renderComparison(node) {
|
|
|
176
132
|
}
|
|
177
133
|
// eq/lte(call_arg_field[i, el, field], <literal>) -> arg[i] element[el].field = <value>
|
|
178
134
|
// The structured-argument bind pins a scalar field inside a vec element.
|
|
179
|
-
// For
|
|
180
|
-
//
|
|
135
|
+
// For lte the line reads "OP <value>" so the comparison kind is visible;
|
|
136
|
+
// eq reads "= <value>".
|
|
181
137
|
if (left.kind === 'call_arg_field') {
|
|
182
138
|
const head = `arg[${left.index}] element[${left.element}].${left.field}`;
|
|
183
|
-
const sep = node.op === 'eq' ? '=' : comparisonOpText(node.op);
|
|
139
|
+
const sep = node.op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(node.op);
|
|
184
140
|
if (right.kind === 'literal_address')
|
|
185
141
|
return `${head} ${sep} ${right.value}`;
|
|
186
142
|
if (right.kind === 'literal_symbol')
|
|
187
143
|
return `${head} ${sep} ${right.value}`;
|
|
188
|
-
if (right.kind === 'literal_bytes')
|
|
189
|
-
return `${head} ${sep} ${right.value}`;
|
|
190
|
-
if (right.kind === 'literal_u64')
|
|
191
|
-
return `${head} ${sep} ${right.value}`;
|
|
192
144
|
if (right.kind === 'literal_i128')
|
|
193
145
|
return `${head} ${sep} ${right.value}`;
|
|
194
146
|
if (right.kind === 'literal_u32')
|
|
195
147
|
return `${head} ${sep} ${right.value}`;
|
|
196
148
|
if (right.kind === 'literal_vec') {
|
|
197
|
-
return `${head} ${sep} [${right.elements.map(renderVecElement).join(', ')}]`;
|
|
149
|
+
return `${head} ${sep} [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`;
|
|
198
150
|
}
|
|
199
151
|
}
|
|
200
|
-
// The
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
const allowed = node.op === 'lt' ? right.value : right.value + 1;
|
|
205
|
-
return `At most ${allowed} calls per ${left.windowSecs} seconds`;
|
|
206
|
-
}
|
|
207
|
-
// OP(call_arg[i], <scalar literal>) -> arg[i] OP <value>
|
|
208
|
-
//
|
|
209
|
-
// The per-call cap. It has to be here because the `amount` template below
|
|
210
|
-
// is unreachable for an interpreter policy: `amount` is deliberately not in
|
|
211
|
-
// the contract's grammar (dsl.rs - the interpreter sees one authorized
|
|
212
|
-
// call, not the transaction's token movements), so a predicate using it is
|
|
213
|
-
// refused at install. A bound on the call's own amount ARGUMENT is how a
|
|
214
|
-
// cap is actually written, and it was rendering nothing at all - the card
|
|
215
|
-
// silently understated the policy.
|
|
152
|
+
// The per-call cap: OP(call_arg[i], <scalar literal>) -> arg[i] OP <value>.
|
|
153
|
+
// The interpreter is passed one authorized call, not the transaction's token
|
|
154
|
+
// movements, so a bound on the call's own amount ARGUMENT is how a cap is
|
|
155
|
+
// written.
|
|
216
156
|
//
|
|
217
157
|
// Placed after the literal_vec case above so an exact-sequence `eq` still
|
|
218
158
|
// reads as a path rather than as a comparison.
|
|
219
159
|
if (left.kind === 'call_arg') {
|
|
220
160
|
const head = `arg[${left.index}]`;
|
|
221
|
-
const sep = node.op === 'eq' ? '=' : comparisonOpText(node.op);
|
|
161
|
+
const sep = node.op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(node.op);
|
|
222
162
|
if (right.kind === 'literal_i128')
|
|
223
163
|
return `${head} ${sep} ${right.value}`;
|
|
224
|
-
if (right.kind === 'literal_u64')
|
|
225
|
-
return `${head} ${sep} ${right.value}`;
|
|
226
164
|
if (right.kind === 'literal_u32')
|
|
227
165
|
return `${head} ${sep} ${right.value}`;
|
|
228
166
|
if (right.kind === 'literal_address')
|
|
229
167
|
return `${head} ${sep} ${right.value}`;
|
|
230
168
|
if (right.kind === 'literal_symbol')
|
|
231
169
|
return `${head} ${sep} ${right.value}`;
|
|
232
|
-
if (right.kind === 'literal_bytes')
|
|
233
|
-
return `${head} ${sep} ${right.value}`;
|
|
234
|
-
// call_arg_scaled is the slippage floor and reads as itself.
|
|
235
|
-
if (right.kind === 'call_arg_scaled') {
|
|
236
|
-
return `${head} >= arg[${right.index}] * ${right.num}/${right.den}`;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
// amount <= v -> Amount <= v
|
|
240
|
-
if (left.kind === 'amount' && right.kind === 'literal_i128') {
|
|
241
|
-
return `Amount <= ${right.value}`;
|
|
242
|
-
}
|
|
243
|
-
// oracle_price(asset) OP price -> Only when oracle_price(asset) OP price
|
|
244
|
-
if (left.kind === 'oracle_price' && right.kind === 'oracle_threshold') {
|
|
245
|
-
return `Only when oracle_price(${left.asset}) ${comparisonOpText(node.op)} ${right.value} (${right.decimals} dp)`;
|
|
246
170
|
}
|
|
247
171
|
// Any other comparison shape is a structural fail-closed: do not surface
|
|
248
172
|
// a misleading line. Cross-check still requires every leaf produce a
|
|
@@ -254,79 +178,11 @@ function renderComparison(node) {
|
|
|
254
178
|
function renderMembership(node) {
|
|
255
179
|
// call_arg[i] in [list] -> Recipient/arg must be one of [list]
|
|
256
180
|
if (node.needle.kind === 'call_arg') {
|
|
257
|
-
const list = node.haystack.map(renderHaystackElement).join(', ');
|
|
181
|
+
const list = node.haystack.map(render_leaf_ts_1.renderHaystackElement).join(', ');
|
|
258
182
|
return `Recipient/arg must be one of [${list}]`;
|
|
259
183
|
}
|
|
260
184
|
return null;
|
|
261
185
|
}
|
|
262
|
-
function renderVecElement(leaf) {
|
|
263
|
-
switch (leaf.kind) {
|
|
264
|
-
case 'literal_address':
|
|
265
|
-
return leaf.value;
|
|
266
|
-
case 'literal_i128':
|
|
267
|
-
return leaf.value;
|
|
268
|
-
case 'literal_symbol':
|
|
269
|
-
return leaf.value;
|
|
270
|
-
case 'literal_u32':
|
|
271
|
-
return String(leaf.value);
|
|
272
|
-
case 'literal_u64':
|
|
273
|
-
return leaf.value;
|
|
274
|
-
case 'literal_bytes':
|
|
275
|
-
return leaf.value;
|
|
276
|
-
case 'literal_vec':
|
|
277
|
-
return `[${leaf.elements.map(renderVecElement).join(', ')}]`;
|
|
278
|
-
case 'call_contract':
|
|
279
|
-
case 'call_fn':
|
|
280
|
-
case 'call_arg':
|
|
281
|
-
case 'call_arg_len':
|
|
282
|
-
case 'call_arg_field':
|
|
283
|
-
case 'call_arg_scaled':
|
|
284
|
-
case 'amount':
|
|
285
|
-
case 'window_spent':
|
|
286
|
-
case 'now':
|
|
287
|
-
case 'valid_until':
|
|
288
|
-
case 'invocation_count_in_window':
|
|
289
|
-
case 'oracle_price':
|
|
290
|
-
return `<${leaf.kind}>`;
|
|
291
|
-
// Show the declared basis, not just the digits. A threshold on the wrong
|
|
292
|
-
// basis is the one policy error the contract cannot detect, so the review
|
|
293
|
-
// card is where a human has to be able to see it.
|
|
294
|
-
case 'oracle_threshold':
|
|
295
|
-
return `${leaf.value} (${leaf.decimals} dp)`;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function renderHaystackElement(leaf) {
|
|
299
|
-
if (leaf.kind === 'literal_address')
|
|
300
|
-
return leaf.value;
|
|
301
|
-
if (leaf.kind === 'literal_i128')
|
|
302
|
-
return leaf.value;
|
|
303
|
-
if (leaf.kind === 'literal_symbol')
|
|
304
|
-
return leaf.value;
|
|
305
|
-
if (leaf.kind === 'literal_u32')
|
|
306
|
-
return String(leaf.value);
|
|
307
|
-
if (leaf.kind === 'literal_u64')
|
|
308
|
-
return leaf.value;
|
|
309
|
-
if (leaf.kind === 'literal_bytes')
|
|
310
|
-
return leaf.value;
|
|
311
|
-
if (leaf.kind === 'literal_vec') {
|
|
312
|
-
return `[${leaf.elements.map(renderHaystackElement).join(', ')}]`;
|
|
313
|
-
}
|
|
314
|
-
return `<${leaf.kind}>`;
|
|
315
|
-
}
|
|
316
|
-
function comparisonOpText(op) {
|
|
317
|
-
switch (op) {
|
|
318
|
-
case 'lt':
|
|
319
|
-
return '<';
|
|
320
|
-
case 'lte':
|
|
321
|
-
return '<=';
|
|
322
|
-
case 'gt':
|
|
323
|
-
return '>';
|
|
324
|
-
case 'gte':
|
|
325
|
-
return '>=';
|
|
326
|
-
case 'eq':
|
|
327
|
-
return '==';
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
186
|
/** Render the plain-English one-liner. Format: `<ruleName>: <constraints>`,
|
|
331
187
|
* joined by `; ` so the user reads one sentence per constraint. */
|
|
332
188
|
function renderPlainEnglish(ruleName, constraints) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// guard.
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.summaryCrossCheck = summaryCrossCheck;
|
|
18
|
+
const render_leaf_ts_1 = require("./render-leaf.js");
|
|
18
19
|
/** Assert every leaf in the predicate appears as a constraint string in
|
|
19
20
|
* `summary.constraints`. Returns the missing templates (without the leaf
|
|
20
21
|
* values filled in) when any leaf was dropped. */
|
|
@@ -38,18 +39,11 @@ function summaryCrossCheck(predicate, summary) {
|
|
|
38
39
|
function collect(node, out) {
|
|
39
40
|
switch (node.op) {
|
|
40
41
|
case 'and':
|
|
41
|
-
case 'or':
|
|
42
42
|
for (const child of node.children)
|
|
43
43
|
collect(child, out);
|
|
44
44
|
return;
|
|
45
|
-
case 'not':
|
|
46
|
-
collect(node.child, out);
|
|
47
|
-
return;
|
|
48
45
|
case 'eq':
|
|
49
|
-
case 'lt':
|
|
50
46
|
case 'lte':
|
|
51
|
-
case 'gt':
|
|
52
|
-
case 'gte':
|
|
53
47
|
pushComparison(node.left, node.right, node.op, out);
|
|
54
48
|
return;
|
|
55
49
|
case 'in':
|
|
@@ -67,7 +61,7 @@ function pushComparison(left, right, op, out) {
|
|
|
67
61
|
return;
|
|
68
62
|
}
|
|
69
63
|
if (left.kind === 'call_arg' && op === 'eq' && right.kind === 'literal_vec') {
|
|
70
|
-
out.push(`Path must be exactly [${right.elements.map(renderVecElement).join(', ')}]`);
|
|
64
|
+
out.push(`Path must be exactly [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`);
|
|
71
65
|
return;
|
|
72
66
|
}
|
|
73
67
|
if (left.kind === 'call_arg_len' && op === 'eq' && right.kind === 'literal_u32') {
|
|
@@ -78,7 +72,7 @@ function pushComparison(left, right, op, out) {
|
|
|
78
72
|
// the cross-check can assert a faithful summary.
|
|
79
73
|
if (left.kind === 'call_arg_field') {
|
|
80
74
|
const head = `arg[${left.index}] element[${left.element}].${left.field}`;
|
|
81
|
-
const sep = op === 'eq' ? '=' : comparisonOpText(op);
|
|
75
|
+
const sep = op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(op);
|
|
82
76
|
if (right.kind === 'literal_address') {
|
|
83
77
|
out.push(`${head} ${sep} ${right.value}`);
|
|
84
78
|
return;
|
|
@@ -87,14 +81,6 @@ function pushComparison(left, right, op, out) {
|
|
|
87
81
|
out.push(`${head} ${sep} ${right.value}`);
|
|
88
82
|
return;
|
|
89
83
|
}
|
|
90
|
-
if (right.kind === 'literal_bytes') {
|
|
91
|
-
out.push(`${head} ${sep} ${right.value}`);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (right.kind === 'literal_u64') {
|
|
95
|
-
out.push(`${head} ${sep} ${right.value}`);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
84
|
if (right.kind === 'literal_i128') {
|
|
99
85
|
out.push(`${head} ${sep} ${right.value}`);
|
|
100
86
|
return;
|
|
@@ -104,117 +90,28 @@ function pushComparison(left, right, op, out) {
|
|
|
104
90
|
return;
|
|
105
91
|
}
|
|
106
92
|
if (right.kind === 'literal_vec') {
|
|
107
|
-
out.push(`${head} ${sep} [${right.elements.map(renderVecElement).join(', ')}]`);
|
|
93
|
+
out.push(`${head} ${sep} [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`);
|
|
108
94
|
return;
|
|
109
95
|
}
|
|
110
96
|
}
|
|
111
|
-
// Mirrors the builder's wording byte-for-byte: how many calls the rule
|
|
112
|
-
// permits, since the bound counts the calls already made.
|
|
113
|
-
if (left.kind === 'invocation_count_in_window' && right.kind === 'literal_u32') {
|
|
114
|
-
const allowed = op === 'lt' ? right.value : right.value + 1;
|
|
115
|
-
out.push(`At most ${allowed} calls per ${left.windowSecs} seconds`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
97
|
// Mirrors the builder's per-call cap line. Both sides must render the same
|
|
119
98
|
// string: cross-check compares the summary against the predicate, so a
|
|
120
99
|
// shape only ONE of them renders would read as a dropped constraint.
|
|
121
100
|
if (left.kind === 'call_arg') {
|
|
122
101
|
const head = `arg[${left.index}]`;
|
|
123
|
-
const sep = op === 'eq' ? '=' : comparisonOpText(op);
|
|
102
|
+
const sep = op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(op);
|
|
124
103
|
if (right.kind === 'literal_i128' ||
|
|
125
|
-
right.kind === 'literal_u64' ||
|
|
126
104
|
right.kind === 'literal_u32' ||
|
|
127
105
|
right.kind === 'literal_address' ||
|
|
128
|
-
right.kind === 'literal_symbol'
|
|
129
|
-
right.kind === 'literal_bytes') {
|
|
106
|
+
right.kind === 'literal_symbol') {
|
|
130
107
|
out.push(`${head} ${sep} ${right.value}`);
|
|
131
108
|
return;
|
|
132
109
|
}
|
|
133
|
-
if (right.kind === 'call_arg_scaled') {
|
|
134
|
-
out.push(`${head} >= arg[${right.index}] * ${right.num}/${right.den}`);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (left.kind === 'amount' && right.kind === 'literal_i128') {
|
|
139
|
-
out.push(`Amount <= ${right.value}`);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if (left.kind === 'oracle_price' && right.kind === 'oracle_threshold') {
|
|
143
|
-
out.push(`Only when oracle_price(${left.asset}) ${comparisonOpText(op)} ${right.value} (${right.decimals} dp)`);
|
|
144
|
-
return;
|
|
145
110
|
}
|
|
146
111
|
}
|
|
147
112
|
function pushMembership(needle, haystack, out) {
|
|
148
113
|
if (needle.kind !== 'call_arg')
|
|
149
114
|
return;
|
|
150
|
-
const list = haystack.map(renderHaystackElement).join(', ');
|
|
115
|
+
const list = haystack.map(render_leaf_ts_1.renderHaystackElement).join(', ');
|
|
151
116
|
out.push(`Recipient/arg must be one of [${list}]`);
|
|
152
117
|
}
|
|
153
|
-
function renderVecElement(leaf) {
|
|
154
|
-
switch (leaf.kind) {
|
|
155
|
-
case 'literal_address':
|
|
156
|
-
return leaf.value;
|
|
157
|
-
case 'literal_i128':
|
|
158
|
-
return leaf.value;
|
|
159
|
-
case 'literal_symbol':
|
|
160
|
-
return leaf.value;
|
|
161
|
-
case 'literal_u32':
|
|
162
|
-
return String(leaf.value);
|
|
163
|
-
case 'literal_u64':
|
|
164
|
-
return leaf.value;
|
|
165
|
-
case 'literal_bytes':
|
|
166
|
-
return leaf.value;
|
|
167
|
-
case 'literal_vec':
|
|
168
|
-
return `[${leaf.elements.map(renderVecElement).join(', ')}]`;
|
|
169
|
-
case 'call_contract':
|
|
170
|
-
case 'call_fn':
|
|
171
|
-
case 'call_arg':
|
|
172
|
-
case 'call_arg_len':
|
|
173
|
-
case 'call_arg_field':
|
|
174
|
-
case 'call_arg_scaled':
|
|
175
|
-
case 'amount':
|
|
176
|
-
case 'window_spent':
|
|
177
|
-
case 'now':
|
|
178
|
-
case 'valid_until':
|
|
179
|
-
case 'invocation_count_in_window':
|
|
180
|
-
case 'oracle_price':
|
|
181
|
-
return `<${leaf.kind}>`;
|
|
182
|
-
// Show the declared basis, not just the digits. A threshold on the wrong
|
|
183
|
-
// basis is the one policy error the contract cannot detect, so the review
|
|
184
|
-
// card is where a human has to be able to see it.
|
|
185
|
-
case 'oracle_threshold':
|
|
186
|
-
return `${leaf.value} (${leaf.decimals} dp)`;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
function renderHaystackElement(leaf) {
|
|
190
|
-
if (leaf.kind === 'literal_address')
|
|
191
|
-
return leaf.value;
|
|
192
|
-
if (leaf.kind === 'literal_i128')
|
|
193
|
-
return leaf.value;
|
|
194
|
-
if (leaf.kind === 'literal_symbol')
|
|
195
|
-
return leaf.value;
|
|
196
|
-
if (leaf.kind === 'literal_u32')
|
|
197
|
-
return String(leaf.value);
|
|
198
|
-
if (leaf.kind === 'literal_u64')
|
|
199
|
-
return leaf.value;
|
|
200
|
-
if (leaf.kind === 'literal_bytes')
|
|
201
|
-
return leaf.value;
|
|
202
|
-
if (leaf.kind === 'literal_vec') {
|
|
203
|
-
return `[${leaf.elements.map(renderHaystackElement).join(', ')}]`;
|
|
204
|
-
}
|
|
205
|
-
return `<${leaf.kind}>`;
|
|
206
|
-
}
|
|
207
|
-
function comparisonOpText(op) {
|
|
208
|
-
switch (op) {
|
|
209
|
-
case 'lt':
|
|
210
|
-
return '<';
|
|
211
|
-
case 'lte':
|
|
212
|
-
return '<=';
|
|
213
|
-
case 'gt':
|
|
214
|
-
return '>';
|
|
215
|
-
case 'gte':
|
|
216
|
-
return '>=';
|
|
217
|
-
case 'eq':
|
|
218
|
-
return '==';
|
|
219
|
-
}
|
|
220
|
-
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Leaf and operator rendering shared by the review-card builder and the
|
|
3
|
+
// summary cross-check. Both turn the SAME predicate into human-readable text -
|
|
4
|
+
// the cross-check exists to confirm the builder's summary matches the predicate
|
|
5
|
+
// - so rendering a leaf differently in the two would make the check compare a
|
|
6
|
+
// string against itself under a different spelling and pass on a real mismatch.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.renderVecElement = renderVecElement;
|
|
9
|
+
exports.renderHaystackElement = renderHaystackElement;
|
|
10
|
+
exports.comparisonOpText = comparisonOpText;
|
|
11
|
+
function renderVecElement(leaf) {
|
|
12
|
+
switch (leaf.kind) {
|
|
13
|
+
case 'literal_address':
|
|
14
|
+
return leaf.value;
|
|
15
|
+
case 'literal_i128':
|
|
16
|
+
return leaf.value;
|
|
17
|
+
case 'literal_symbol':
|
|
18
|
+
return leaf.value;
|
|
19
|
+
case 'literal_u32':
|
|
20
|
+
return String(leaf.value);
|
|
21
|
+
case 'literal_vec':
|
|
22
|
+
return `[${leaf.elements.map(renderVecElement).join(', ')}]`;
|
|
23
|
+
case 'call_contract':
|
|
24
|
+
case 'call_fn':
|
|
25
|
+
case 'call_arg':
|
|
26
|
+
case 'call_arg_len':
|
|
27
|
+
case 'call_arg_field':
|
|
28
|
+
return `<${leaf.kind}>`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function renderHaystackElement(leaf) {
|
|
32
|
+
if (leaf.kind === 'literal_address')
|
|
33
|
+
return leaf.value;
|
|
34
|
+
if (leaf.kind === 'literal_i128')
|
|
35
|
+
return leaf.value;
|
|
36
|
+
if (leaf.kind === 'literal_symbol')
|
|
37
|
+
return leaf.value;
|
|
38
|
+
if (leaf.kind === 'literal_u32')
|
|
39
|
+
return String(leaf.value);
|
|
40
|
+
if (leaf.kind === 'literal_vec') {
|
|
41
|
+
return `[${leaf.elements.map(renderHaystackElement).join(', ')}]`;
|
|
42
|
+
}
|
|
43
|
+
return `<${leaf.kind}>`;
|
|
44
|
+
}
|
|
45
|
+
function comparisonOpText(op) {
|
|
46
|
+
switch (op) {
|
|
47
|
+
case 'lte':
|
|
48
|
+
return '<=';
|
|
49
|
+
case 'eq':
|
|
50
|
+
return '==';
|
|
51
|
+
}
|
|
52
|
+
}
|