otto-intel-mcp 0.1.2 → 0.1.3
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 -2
- package/dist/adapter/cdp-signer.d.ts +44 -5
- package/dist/adapter/cdp-signer.js +168 -18
- package/dist/adapter/evm-call-failure.d.ts +29 -0
- package/dist/adapter/evm-call-failure.js +109 -0
- package/dist/adapter/refusal.d.ts +1 -1
- package/dist/adapter/refusal.js +13 -0
- package/dist/adapter/route-simulator.d.ts +91 -0
- package/dist/adapter/route-simulator.js +143 -0
- package/dist/executable-equities.d.ts +55 -0
- package/dist/executable-equities.js +116 -0
- package/dist/executable-yield-markets.d.ts +261 -0
- package/dist/executable-yield-markets.js +154 -0
- package/dist/execution-delegated-definition.d.ts +1 -0
- package/dist/execution-delegated-definition.js +13 -2
- package/dist/execution-delegation-admin-definition.d.ts +126 -5
- package/dist/execution-delegation-admin-definition.js +154 -5
- package/dist/execution-delegation-admin.d.ts +48 -2
- package/dist/execution-delegation-admin.js +314 -12
- package/dist/execution-delegation-cap-store.d.ts +369 -0
- package/dist/execution-delegation-cap-store.js +1083 -0
- package/dist/execution-delegation-policy.d.ts +153 -6
- package/dist/execution-delegation-policy.js +260 -16
- package/dist/execution-delegation.d.ts +35 -0
- package/dist/execution-delegation.js +224 -9
- package/dist/execution-errors.d.ts +1 -1
- package/dist/execution-errors.js +98 -0
- package/dist/execution-index.d.ts +3 -2
- package/dist/execution-index.js +3 -2
- package/dist/execution-registration.d.ts +51 -2
- package/dist/execution-registration.js +45 -2
- package/dist/execution-tools.d.ts +44 -1
- package/dist/execution-tools.js +157 -13
- package/dist/lifi-execution-client.d.ts +6 -0
- package/dist/lifi-execution-client.js +3 -0
- package/dist/stock-buy-definition.d.ts +338 -0
- package/dist/stock-buy-definition.js +127 -0
- package/dist/stock-buy-oracle.d.ts +8 -0
- package/dist/stock-buy-oracle.js +22 -0
- package/dist/stock-buy.d.ts +17 -0
- package/dist/stock-buy.js +164 -0
- package/dist/tool-definitions.js +1 -1
- package/dist/yield-deposit-definition.d.ts +323 -0
- package/dist/yield-deposit-definition.js +121 -0
- package/dist/yield-deposit.d.ts +16 -0
- package/dist/yield-deposit.js +141 -0
- package/dist/yield-withdraw-definition.d.ts +306 -0
- package/dist/yield-withdraw-definition.js +88 -0
- package/dist/yield-withdraw.d.ts +21 -0
- package/dist/yield-withdraw.js +120 -0
- package/package.json +28 -4
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-owned yield execution registry.
|
|
3
|
+
*
|
|
4
|
+
* The dApp's server route and TEA import the exact same records. Discovery can
|
|
5
|
+
* attach APY data to a record, but it cannot create or mutate execution
|
|
6
|
+
* eligibility. The canonical commitment is server-only (both consumers run in
|
|
7
|
+
* Node) and covers the complete registry record, including nested fields.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
function deepFreeze(value) {
|
|
11
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
12
|
+
for (const nested of Object.values(value))
|
|
13
|
+
deepFreeze(nested);
|
|
14
|
+
Object.freeze(value);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
export const BASE_USDC_AAVE_V3_MARKET = deepFreeze({
|
|
19
|
+
id: 'base-usdc-aave-v3',
|
|
20
|
+
venue: 'AAVE V3',
|
|
21
|
+
protocol: 'AAVE',
|
|
22
|
+
chainId: 8453,
|
|
23
|
+
chainSlug: 'base',
|
|
24
|
+
asset: {
|
|
25
|
+
symbol: 'USDC',
|
|
26
|
+
group: 'USDC',
|
|
27
|
+
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
28
|
+
decimals: 6,
|
|
29
|
+
},
|
|
30
|
+
protocolTarget: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5',
|
|
31
|
+
vaultAddress: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5',
|
|
32
|
+
nativeValue: '0',
|
|
33
|
+
builder: 'aave-native',
|
|
34
|
+
selectors: {
|
|
35
|
+
approve: '0x095ea7b3',
|
|
36
|
+
supply: '0x617ba037',
|
|
37
|
+
withdraw: '0x69328dec',
|
|
38
|
+
},
|
|
39
|
+
// An existing exact allowance lets the SDK omit approve. No other leg is valid.
|
|
40
|
+
allowedCallSequences: {
|
|
41
|
+
deposit: [['approve', 'supply'], ['supply']],
|
|
42
|
+
withdraw: [['withdraw']],
|
|
43
|
+
},
|
|
44
|
+
safeBinding: {
|
|
45
|
+
executionOwner: 'SAFE',
|
|
46
|
+
depositReceiver: 'SAFE',
|
|
47
|
+
withdrawReceiver: 'SAFE',
|
|
48
|
+
},
|
|
49
|
+
discoveryMarketId: 'aave:8453:0xa238dd80c259a72e81d7e4664a9801593f98d1c5:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
50
|
+
});
|
|
51
|
+
export const BASE_USDC_MORPHO_GAUNTLET_PRIME_MARKET = deepFreeze({
|
|
52
|
+
id: 'base-usdc-morpho-gauntlet-prime',
|
|
53
|
+
venue: 'MORPHO · GAUNTLET USDC PRIME',
|
|
54
|
+
protocol: 'MORPHO',
|
|
55
|
+
chainId: 8453,
|
|
56
|
+
chainSlug: 'base',
|
|
57
|
+
asset: {
|
|
58
|
+
symbol: 'USDC',
|
|
59
|
+
group: 'USDC',
|
|
60
|
+
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
61
|
+
decimals: 6,
|
|
62
|
+
},
|
|
63
|
+
protocolTarget: '0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61',
|
|
64
|
+
vaultAddress: '0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61',
|
|
65
|
+
vaultShareDecimals: 18,
|
|
66
|
+
nativeValue: '0',
|
|
67
|
+
builder: 'morpho-native',
|
|
68
|
+
selectors: {
|
|
69
|
+
approve: '0x095ea7b3',
|
|
70
|
+
deposit: '0x6e553f65',
|
|
71
|
+
withdraw: '0xb460af94',
|
|
72
|
+
redeem: '0xba087652',
|
|
73
|
+
},
|
|
74
|
+
allowedCallSequences: {
|
|
75
|
+
deposit: [['approve', 'deposit'], ['deposit']],
|
|
76
|
+
withdraw: [['withdraw'], ['redeem']],
|
|
77
|
+
},
|
|
78
|
+
safeBinding: {
|
|
79
|
+
executionOwner: 'SAFE',
|
|
80
|
+
depositReceiver: 'SAFE',
|
|
81
|
+
withdrawReceiver: 'SAFE',
|
|
82
|
+
},
|
|
83
|
+
discoveryMarketId: 'morpho:8453:0xee8f4ec5672f09119b96ab6fb59c27e1b7e44b61:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
84
|
+
});
|
|
85
|
+
export const BASE_USDC_MORPHO_STEAKHOUSE_PRIME_MARKET = deepFreeze({
|
|
86
|
+
id: 'base-usdc-morpho-steakhouse-prime',
|
|
87
|
+
venue: 'MORPHO · STEAKHOUSE PRIME USDC',
|
|
88
|
+
protocol: 'MORPHO',
|
|
89
|
+
chainId: 8453,
|
|
90
|
+
chainSlug: 'base',
|
|
91
|
+
asset: {
|
|
92
|
+
symbol: 'USDC',
|
|
93
|
+
group: 'USDC',
|
|
94
|
+
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
95
|
+
decimals: 6,
|
|
96
|
+
},
|
|
97
|
+
protocolTarget: '0xBEEFE94c8aD530842bfE7d8B397938fFc1cb83b2',
|
|
98
|
+
vaultAddress: '0xBEEFE94c8aD530842bfE7d8B397938fFc1cb83b2',
|
|
99
|
+
vaultShareDecimals: 18,
|
|
100
|
+
nativeValue: '0',
|
|
101
|
+
builder: 'morpho-native',
|
|
102
|
+
selectors: {
|
|
103
|
+
approve: '0x095ea7b3',
|
|
104
|
+
deposit: '0x6e553f65',
|
|
105
|
+
withdraw: '0xb460af94',
|
|
106
|
+
redeem: '0xba087652',
|
|
107
|
+
},
|
|
108
|
+
allowedCallSequences: {
|
|
109
|
+
deposit: [['approve', 'deposit'], ['deposit']],
|
|
110
|
+
withdraw: [['withdraw'], ['redeem']],
|
|
111
|
+
},
|
|
112
|
+
safeBinding: {
|
|
113
|
+
executionOwner: 'SAFE',
|
|
114
|
+
depositReceiver: 'SAFE',
|
|
115
|
+
withdrawReceiver: 'SAFE',
|
|
116
|
+
},
|
|
117
|
+
discoveryMarketId: 'morpho:8453:0xbeefe94c8ad530842bfe7d8b397938ffc1cb83b2:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
118
|
+
});
|
|
119
|
+
export const EXECUTABLE_YIELD_MARKETS = deepFreeze([
|
|
120
|
+
BASE_USDC_AAVE_V3_MARKET,
|
|
121
|
+
BASE_USDC_MORPHO_GAUNTLET_PRIME_MARKET,
|
|
122
|
+
BASE_USDC_MORPHO_STEAKHOUSE_PRIME_MARKET,
|
|
123
|
+
]);
|
|
124
|
+
function canonicalJson(value) {
|
|
125
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
126
|
+
return JSON.stringify(value);
|
|
127
|
+
}
|
|
128
|
+
if (typeof value === 'number') {
|
|
129
|
+
if (!Number.isFinite(value))
|
|
130
|
+
throw new Error('executable yield registry contains a non-finite number');
|
|
131
|
+
return JSON.stringify(value);
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(value))
|
|
134
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
135
|
+
if (typeof value === 'object') {
|
|
136
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
137
|
+
return `{${entries
|
|
138
|
+
.map(([key, nested]) => `${JSON.stringify(key)}:${canonicalJson(nested)}`)
|
|
139
|
+
.join(',')}}`;
|
|
140
|
+
}
|
|
141
|
+
throw new Error('executable yield registry contains a non-canonical value');
|
|
142
|
+
}
|
|
143
|
+
/** SHA-256 commitment over every enumerable field in the complete, canonical registry record. */
|
|
144
|
+
export function getExecutableYieldMarketCommitment(market) {
|
|
145
|
+
const canonical = canonicalJson(market);
|
|
146
|
+
const digest = createHash('sha256')
|
|
147
|
+
.update(`otto:executable-yield-market:v1:${canonical}`, 'utf8')
|
|
148
|
+
.digest('hex');
|
|
149
|
+
return `sha256:${digest}`;
|
|
150
|
+
}
|
|
151
|
+
export function getExecutableYieldMarket(marketId) {
|
|
152
|
+
return EXECUTABLE_YIELD_MARKETS.find((market) => market.id === marketId) ?? null;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=executable-yield-markets.js.map
|
|
@@ -129,6 +129,7 @@ export declare const DELEGATED_SEND_RESULT_SCHEMA: z.ZodObject<{
|
|
|
129
129
|
policy_name: z.ZodString;
|
|
130
130
|
rules_digest: z.ZodString;
|
|
131
131
|
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
132
|
+
user_cap_usd: z.ZodNumber;
|
|
132
133
|
}, z.core.$strict>;
|
|
133
134
|
delegation: z.ZodObject<{
|
|
134
135
|
expires_at: z.ZodString;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { EXECUTION_ENVELOPE_SCHEMA, PREPARE_SWAP_INPUT_SCHEMA, } from './execution-tool-definitions.js';
|
|
9
|
-
import { MODEL_B_PER_SWAP_CAP_USD } from './execution-delegation-policy.js';
|
|
9
|
+
import { MODEL_B_PER_SWAP_CAP_MIN_USD, MODEL_B_PER_SWAP_CAP_USD, } from './execution-delegation-policy.js';
|
|
10
10
|
export const DELEGATED_EXECUTION_TOOL_NAME = 'otto_submit_under_delegation';
|
|
11
11
|
/** Per-request caller authentication rides THIS header — never a tool argument (arguments are logged). */
|
|
12
12
|
export const DELEGATION_AUTH_HEADER = 'x-otto-delegation-auth';
|
|
@@ -78,7 +78,18 @@ export const DELEGATED_SEND_RESULT_SCHEMA = z
|
|
|
78
78
|
policy_id: z.string(),
|
|
79
79
|
policy_name: z.string(),
|
|
80
80
|
rules_digest: z.string().regex(/^[0-9a-f]{64}$/),
|
|
81
|
+
/** The ENGINE's ceiling — the constant in the one shared project policy. Still a literal. */
|
|
81
82
|
per_swap_cap_usd: z.literal(MODEL_B_PER_SWAP_CAP_USD),
|
|
83
|
+
/**
|
|
84
|
+
* The cap THIS person chose when they minted, enforced by Otto's server on top of the ceiling.
|
|
85
|
+
* A bounded integer rather than a literal, because it is the whole point that it can be lower —
|
|
86
|
+
* but it can never exceed the ceiling, and that bound is here rather than left to the caller.
|
|
87
|
+
*/
|
|
88
|
+
user_cap_usd: z
|
|
89
|
+
.number()
|
|
90
|
+
.int()
|
|
91
|
+
.min(MODEL_B_PER_SWAP_CAP_MIN_USD)
|
|
92
|
+
.max(MODEL_B_PER_SWAP_CAP_USD),
|
|
82
93
|
})
|
|
83
94
|
.strict(),
|
|
84
95
|
delegation: z.object({ expires_at: z.string() }).strict(),
|
|
@@ -108,7 +119,7 @@ export const DELEGATED_TOOL_ANNOTATIONS = Object.freeze({
|
|
|
108
119
|
export const DELEGATED_EXECUTION_TOOL_DEFINITION = Object.freeze({
|
|
109
120
|
name: DELEGATED_EXECUTION_TOOL_NAME,
|
|
110
121
|
title: 'Submit an Otto-prepared swap under a CDP end-user delegation',
|
|
111
|
-
description: `Submit a verified otto_prepare_swap envelope on Base under the end user's CDP delegation. Arguments: { userId, envelope (the otto_prepare_swap result), prepareInput (the exact otto_prepare_swap request, with slippage.minAmountOut) } — validated strictly AFTER the caller is authenticated, so the listed input schema is intentionally permissive. Server-to-server only: the caller presents Otto's delegation secret in the \`${DELEGATION_AUTH_HEADER}\` request header (never in arguments); a call without it is refused before anything is read. Before any send the server confirms, in this order: the Model-B project policy is installed and reads back by name and rules digest; the user's grant is active past the envelope's valid_until; the account is one of that end user's own CDP accounts as CDP reports them; the envelope passes the 13-check verifier for exactly the request that produced it; the plan is inside the fence (allow-listed input token, reviewed spender, gross at or below the ${MODEL_B_PER_SWAP_CAP_USD} USD per-swap cap). Then the three steps (allowance reset, approval, swap) are submitted in order under the delegation, each waited to a sealed canonical block; a halt clears the allowance and reports the read-back. One delegated plan at a time per user. Coinbase's engine bounds the delegated key to Model-B swap shapes through LI.FI up to the per
|
|
122
|
+
description: `Submit a verified otto_prepare_swap envelope on Base under the end user's CDP delegation. Arguments: { userId, envelope (the otto_prepare_swap result), prepareInput (the exact otto_prepare_swap request, with slippage.minAmountOut) } — validated strictly AFTER the caller is authenticated, so the listed input schema is intentionally permissive. Server-to-server only: the caller presents Otto's delegation secret in the \`${DELEGATION_AUTH_HEADER}\` request header (never in arguments); a call without it is refused before anything is read. Before any send the server confirms, in this order: the Model-B project policy is installed and reads back by name and rules digest; the user's grant is active past the envelope's valid_until; the account is one of that end user's own CDP accounts as CDP reports them; the envelope passes the 13-check verifier for exactly the request that produced it; the plan is inside the fence (allow-listed input token, reviewed spender, gross at or below the ${MODEL_B_PER_SWAP_CAP_USD} USD engine ceiling); and the plan is at or below the per-swap cap THIS user chose when they minted, which is recorded against their grant on this server. A delegation with no recorded cap is REFUSED (DELEGATION_CAP_UNRECORDED) rather than defaulted to the ceiling \u2014 mint the permission again to set one. Then the three steps (allowance reset, approval, swap) are submitted in order under the delegation, each waited to a sealed canonical block; a halt clears the allowance and reports the read-back. One delegated plan at a time per user. Coinbase's engine bounds the delegated key to Model-B swap shapes through LI.FI up to the ${MODEL_B_PER_SWAP_CAP_USD} USD ceiling, which is shared by every delegated user and cannot be narrowed per user by the engine; the user's own lower cap and the receiver binding are Otto's server-side checks, and the user can revoke at any time.`,
|
|
112
123
|
inputSchema: DELEGATED_TOOL_TRANSPORT_SCHEMA,
|
|
113
124
|
strictInputSchema: SUBMIT_UNDER_DELEGATION_INPUT_SCHEMA,
|
|
114
125
|
outputSchema: DELEGATED_SEND_RESULT_SCHEMA,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* execution-delegation-admin-definition.ts — the
|
|
3
|
-
* D10 = (a), 2026-08-29): `otto_delegation_fence`, `otto_delegation_status`, `otto_delegation_revoke
|
|
2
|
+
* execution-delegation-admin-definition.ts — the four header-authed delegation ADMIN tools (PR-1b, seat ruling
|
|
3
|
+
* D10 = (a), 2026-08-29): `otto_delegation_fence`, `otto_delegation_status`, `otto_delegation_revoke`, and
|
|
4
|
+
* `otto_delegation_cap` (Founder word `delegate cap choice`, 2026-09-02 — the per-swap cap a person chose,
|
|
5
|
+
* recorded against the grant they just minted).
|
|
4
6
|
*
|
|
5
7
|
* Same seam and admission as `otto_submit_under_delegation`: they register only with a rig, the caller presents
|
|
6
8
|
* Otto's server secret in the `x-otto-delegation-auth` request header (never in arguments), the registered
|
|
@@ -18,7 +20,9 @@ import { DELEGATED_TOOL_TRANSPORT_SCHEMA } from './execution-delegated-definitio
|
|
|
18
20
|
export declare const DELEGATION_FENCE_TOOL_NAME: "otto_delegation_fence";
|
|
19
21
|
export declare const DELEGATION_STATUS_TOOL_NAME: "otto_delegation_status";
|
|
20
22
|
export declare const DELEGATION_REVOKE_TOOL_NAME: "otto_delegation_revoke";
|
|
21
|
-
|
|
23
|
+
/** Records the per-swap cap the person chose, against the grant they just minted. */
|
|
24
|
+
export declare const DELEGATION_CAP_TOOL_NAME: "otto_delegation_cap";
|
|
25
|
+
export declare const DELEGATION_ADMIN_TOOL_NAMES: readonly ["otto_delegation_fence", "otto_delegation_status", "otto_delegation_revoke", "otto_delegation_cap"];
|
|
22
26
|
/** The one PUBLIC delegation tool: the fence's public promise (present / name / digest / cap) — never user or grant data. */
|
|
23
27
|
export declare const DELEGATION_FENCE_STATUS_TOOL_NAME: "otto_delegation_fence_status";
|
|
24
28
|
export declare const DELEGATION_FENCE_INPUT_SCHEMA: z.ZodObject<{}, z.core.$strict>;
|
|
@@ -49,9 +53,74 @@ export declare const DELEGATION_STATUS_RESULT_SCHEMA: z.ZodObject<{
|
|
|
49
53
|
delegation: z.ZodObject<{
|
|
50
54
|
active: z.ZodBoolean;
|
|
51
55
|
expires_at: z.ZodOptional<z.ZodString>;
|
|
56
|
+
per_swap_cap_usd: z.ZodOptional<z.ZodNumber>;
|
|
52
57
|
}, z.core.$strict>;
|
|
53
58
|
revocation_requested_here: z.ZodBoolean;
|
|
59
|
+
ruleset_version: z.ZodNumber;
|
|
60
|
+
mint_enabled: z.ZodBoolean;
|
|
54
61
|
}, z.core.$strict>;
|
|
62
|
+
/**
|
|
63
|
+
* 🔴 TWO PHASES, BECAUSE THE RECORD NOW COMES BEFORE THE GRANT.
|
|
64
|
+
*
|
|
65
|
+
* Rounds 1 and 2 recorded a cap against a grant that already existed, and the codex gate broke both the
|
|
66
|
+
* same way: an expiry is the only thing a send can learn about a live grant, so two grants that share one
|
|
67
|
+
* are indistinguishable at enforcement however the row is keyed. Round 3 removes the collision instead of
|
|
68
|
+
* describing it.
|
|
69
|
+
*
|
|
70
|
+
* `reserve` — the SERVER picks a whole-second instant at or BEFORE the person's chosen duration that no
|
|
71
|
+
* durable row for this user occupies, writes the cap against it, and answers with the instant. The
|
|
72
|
+
* caller then mints with `mint_expires_at`, which is that instant plus a one-millisecond filler CDP
|
|
73
|
+
* truncates away (a bare `.000Z` is refused upstream — see `mintExpiryFor`).
|
|
74
|
+
* `confirm` — after the mint, the server re-reads the grant from CDP and refuses unless the instant CDP
|
|
75
|
+
* reports IS the reserved one. On a mismatch the fresh grant is REVOKED: a permission whose expiry
|
|
76
|
+
* does not match the row it was reserved against is one nothing may act under.
|
|
77
|
+
*
|
|
78
|
+
* The user is still never named by the caller — both phases resolve the identity from the access token.
|
|
79
|
+
*/
|
|
80
|
+
export declare const DELEGATION_CAP_RESERVE_INPUT_SCHEMA: z.ZodObject<{
|
|
81
|
+
phase: z.ZodLiteral<"reserve">;
|
|
82
|
+
accessToken: z.ZodString;
|
|
83
|
+
perSwapCapUsd: z.ZodNumber;
|
|
84
|
+
requestedDays: z.ZodNumber;
|
|
85
|
+
mintKey: z.ZodString;
|
|
86
|
+
}, z.core.$strict>;
|
|
87
|
+
export declare const DELEGATION_CAP_CONFIRM_INPUT_SCHEMA: z.ZodObject<{
|
|
88
|
+
phase: z.ZodLiteral<"confirm">;
|
|
89
|
+
accessToken: z.ZodString;
|
|
90
|
+
mintKey: z.ZodString;
|
|
91
|
+
}, z.core.$strict>;
|
|
92
|
+
export declare const DELEGATION_CAP_INPUT_SCHEMA: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
93
|
+
phase: z.ZodLiteral<"reserve">;
|
|
94
|
+
accessToken: z.ZodString;
|
|
95
|
+
perSwapCapUsd: z.ZodNumber;
|
|
96
|
+
requestedDays: z.ZodNumber;
|
|
97
|
+
mintKey: z.ZodString;
|
|
98
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
99
|
+
phase: z.ZodLiteral<"confirm">;
|
|
100
|
+
accessToken: z.ZodString;
|
|
101
|
+
mintKey: z.ZodString;
|
|
102
|
+
}, z.core.$strict>], "phase">;
|
|
103
|
+
export declare const DELEGATION_CAP_RESULT_SCHEMA: z.ZodObject<{
|
|
104
|
+
phase: z.ZodEnum<{
|
|
105
|
+
reserve: "reserve";
|
|
106
|
+
confirm: "confirm";
|
|
107
|
+
}>;
|
|
108
|
+
user_id: z.ZodString;
|
|
109
|
+
expires_at: z.ZodString;
|
|
110
|
+
mint_expires_at: z.ZodOptional<z.ZodString>;
|
|
111
|
+
per_swap_cap_usd: z.ZodNumber;
|
|
112
|
+
state: z.ZodEnum<{
|
|
113
|
+
legacy: "legacy";
|
|
114
|
+
reserved: "reserved";
|
|
115
|
+
confirmed_active: "confirmed_active";
|
|
116
|
+
superseded: "superseded";
|
|
117
|
+
revoked: "revoked";
|
|
118
|
+
expired: "expired";
|
|
119
|
+
}>;
|
|
120
|
+
ruleset_version: z.ZodNumber;
|
|
121
|
+
recorded: z.ZodBoolean;
|
|
122
|
+
}, z.core.$strict>;
|
|
123
|
+
export type DelegationCapResult = z.infer<typeof DELEGATION_CAP_RESULT_SCHEMA>;
|
|
55
124
|
export declare const DELEGATION_REVOKE_INPUT_SCHEMA: z.ZodUnion<readonly [z.ZodObject<{
|
|
56
125
|
accessToken: z.ZodString;
|
|
57
126
|
}, z.core.$strict>, z.ZodObject<{
|
|
@@ -73,6 +142,9 @@ export declare const DELEGATION_FENCE_STATUS_RESULT_SCHEMA: z.ZodObject<{
|
|
|
73
142
|
present: z.ZodBoolean;
|
|
74
143
|
policy_name: z.ZodString;
|
|
75
144
|
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
145
|
+
per_swap_cap_min_usd: z.ZodLiteral<1>;
|
|
146
|
+
ruleset_version: z.ZodNumber;
|
|
147
|
+
mint_enabled: z.ZodBoolean;
|
|
76
148
|
project_id: z.ZodOptional<z.ZodString>;
|
|
77
149
|
rules_digest: z.ZodOptional<z.ZodString>;
|
|
78
150
|
reason: z.ZodOptional<z.ZodEnum<{
|
|
@@ -86,12 +158,15 @@ export type DelegationFenceStatusResult = z.infer<typeof DELEGATION_FENCE_STATUS
|
|
|
86
158
|
export declare const DELEGATION_FENCE_STATUS_TOOL_DEFINITION: Readonly<{
|
|
87
159
|
name: "otto_delegation_fence_status";
|
|
88
160
|
title: "Read the Model-B delegation fence (public promise: present, name, rules digest, per-swap cap)";
|
|
89
|
-
description: "PUBLIC and read-only, no header: whether the Model-B project policy that fences every delegated send is installed and v-current on this server, with its versioned name, rules digest, the per-swap cap in USD, and Otto's PUBLIC CDP project id (a UUID — the authority an agent binds its mint to). Exposes ONLY the fence's public promise — never an end user, a grant, an address or a policy's contents. An agent about to mint a delegation (otto-execute delegate) reads this first, mints to the published project_id and no other, and refuses to mint when the fence is not present; the server enforces fence-before-authority on every delegated send regardless. Arguments: {}.";
|
|
161
|
+
description: "PUBLIC and read-only, no header: whether the Model-B project policy that fences every delegated send is installed and v-current on this server, with its versioned name, rules digest, the per-swap cap ceiling in USD (shared by every delegated user — a person may choose any whole-dollar cap from per_swap_cap_min_usd up to it, enforced server-side and recorded against their own grant), and Otto's PUBLIC CDP project id (a UUID — the authority an agent binds its mint to). Exposes ONLY the fence's public promise — never an end user, a grant, an address or a policy's contents. An agent about to mint a delegation (otto-execute delegate) reads this first, mints to the published project_id and no other, and refuses to mint when the fence is not present; the server enforces fence-before-authority on every delegated send regardless. Arguments: {}.";
|
|
90
162
|
inputSchema: z.ZodObject<{}, z.core.$strict>;
|
|
91
163
|
outputSchema: z.ZodObject<{
|
|
92
164
|
present: z.ZodBoolean;
|
|
93
165
|
policy_name: z.ZodString;
|
|
94
166
|
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
167
|
+
per_swap_cap_min_usd: z.ZodLiteral<1>;
|
|
168
|
+
ruleset_version: z.ZodNumber;
|
|
169
|
+
mint_enabled: z.ZodBoolean;
|
|
95
170
|
project_id: z.ZodOptional<z.ZodString>;
|
|
96
171
|
rules_digest: z.ZodOptional<z.ZodString>;
|
|
97
172
|
reason: z.ZodOptional<z.ZodEnum<{
|
|
@@ -152,7 +227,7 @@ export declare const DELEGATION_FENCE_TOOL_DEFINITION: Readonly<{
|
|
|
152
227
|
export declare const DELEGATION_STATUS_TOOL_DEFINITION: Readonly<{
|
|
153
228
|
name: "otto_delegation_status";
|
|
154
229
|
title: "Read one end user's delegation status (token-bound)";
|
|
155
|
-
description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry). Arguments: { accessToken } \u2014 the server resolves the user from the token; a client-sent user id is refused. Output is bound to that user; nothing about any other user is returned. ${string}`;
|
|
230
|
+
description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry and the per-swap cap recorded for it). Arguments: { accessToken } \u2014 the server resolves the user from the token; a client-sent user id is refused. delegation.per_swap_cap_usd is the number THIS user chose at mint time; it is ABSENT when no cap is recorded for the grant, which means the delegated send will refuse it \u2014 say so rather than showing the shared ceiling. fence.per_swap_cap_usd is that shared ceiling and is never a per-user number. Output is bound to that user; nothing about any other user is returned. ${string}`;
|
|
156
231
|
inputSchema: z.ZodObject<{}, z.core.$loose>;
|
|
157
232
|
strictInputSchema: z.ZodObject<{
|
|
158
233
|
accessToken: z.ZodString;
|
|
@@ -169,8 +244,11 @@ export declare const DELEGATION_STATUS_TOOL_DEFINITION: Readonly<{
|
|
|
169
244
|
delegation: z.ZodObject<{
|
|
170
245
|
active: z.ZodBoolean;
|
|
171
246
|
expires_at: z.ZodOptional<z.ZodString>;
|
|
247
|
+
per_swap_cap_usd: z.ZodOptional<z.ZodNumber>;
|
|
172
248
|
}, z.core.$strict>;
|
|
173
249
|
revocation_requested_here: z.ZodBoolean;
|
|
250
|
+
ruleset_version: z.ZodNumber;
|
|
251
|
+
mint_enabled: z.ZodBoolean;
|
|
174
252
|
}, z.core.$strict>;
|
|
175
253
|
annotations: Readonly<{
|
|
176
254
|
readOnlyHint: true;
|
|
@@ -206,3 +284,46 @@ export declare const DELEGATION_REVOKE_TOOL_DEFINITION: Readonly<{
|
|
|
206
284
|
openWorldHint: true;
|
|
207
285
|
}>;
|
|
208
286
|
}>;
|
|
287
|
+
export declare const DELEGATION_CAP_TOOL_DEFINITION: Readonly<{
|
|
288
|
+
name: "otto_delegation_cap";
|
|
289
|
+
title: "Reserve the expiry a delegation is minted with, record the chosen per-swap cap against it, then confirm the mint";
|
|
290
|
+
description: `TWO PHASES, both header authed, both resolving the end user from the access token. { phase: "reserve", accessToken, perSwapCapUsd, requestedDays, mintKey } picks a whole-second expiry instant at or BEFORE the requested duration that no durable row for this user already occupies, writes the chosen cap (whole USD, 1\u20265000) against it, and answers { expires_at, mint_expires_at, per_swap_cap_usd, recorded }. Call createDelegation with mint_expires_at EXACTLY as given \u2014 it is the reserved instant plus a one-millisecond filler, because CDP refuses an expiry ending .000 and truncates the filler away \u2014 and with mintKey as the idempotencyKey. Then { phase: "confirm", accessToken, mintKey } re-reads the grant from CDP and refuses (DELEGATION_CAP_GRANT_CHANGED) unless the instant CDP reports IS the reserved one, revoking the fresh grant when it is not. Why this shape: Coinbase issues no grant id and a send can learn nothing about a live grant except its expiry, so the expiry is made an identity BY CONSTRUCTION \u2014 reserved server-side, recorded before the mint, asserted after it. Until a reservation exists, otto_submit_under_delegation refuses that delegation (DELEGATION_CAP_UNRECORDED) rather than falling back to the 5000 USD ceiling. A reservation is IMMUTABLE per mint key \u2014 pressing again returns the same instant and the cap already in force, so raising a cap means minting a new permission (which the user is told replaces the old one). Coinbase's engine cannot hold this number: end-user accounts are fenced by ONE project-scope policy shared by every delegated user, so a lower per-user cap is enforced by Otto's server alone. ${string}`;
|
|
291
|
+
inputSchema: z.ZodObject<{}, z.core.$loose>;
|
|
292
|
+
strictInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
293
|
+
phase: z.ZodLiteral<"reserve">;
|
|
294
|
+
accessToken: z.ZodString;
|
|
295
|
+
perSwapCapUsd: z.ZodNumber;
|
|
296
|
+
requestedDays: z.ZodNumber;
|
|
297
|
+
mintKey: z.ZodString;
|
|
298
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
299
|
+
phase: z.ZodLiteral<"confirm">;
|
|
300
|
+
accessToken: z.ZodString;
|
|
301
|
+
mintKey: z.ZodString;
|
|
302
|
+
}, z.core.$strict>], "phase">;
|
|
303
|
+
outputSchema: z.ZodObject<{
|
|
304
|
+
phase: z.ZodEnum<{
|
|
305
|
+
reserve: "reserve";
|
|
306
|
+
confirm: "confirm";
|
|
307
|
+
}>;
|
|
308
|
+
user_id: z.ZodString;
|
|
309
|
+
expires_at: z.ZodString;
|
|
310
|
+
mint_expires_at: z.ZodOptional<z.ZodString>;
|
|
311
|
+
per_swap_cap_usd: z.ZodNumber;
|
|
312
|
+
state: z.ZodEnum<{
|
|
313
|
+
legacy: "legacy";
|
|
314
|
+
reserved: "reserved";
|
|
315
|
+
confirmed_active: "confirmed_active";
|
|
316
|
+
superseded: "superseded";
|
|
317
|
+
revoked: "revoked";
|
|
318
|
+
expired: "expired";
|
|
319
|
+
}>;
|
|
320
|
+
ruleset_version: z.ZodNumber;
|
|
321
|
+
recorded: z.ZodBoolean;
|
|
322
|
+
}, z.core.$strict>;
|
|
323
|
+
annotations: Readonly<{
|
|
324
|
+
readOnlyHint: false;
|
|
325
|
+
destructiveHint: false;
|
|
326
|
+
idempotentHint: true;
|
|
327
|
+
openWorldHint: true;
|
|
328
|
+
}>;
|
|
329
|
+
}>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* execution-delegation-admin-definition.ts — the
|
|
3
|
-
* D10 = (a), 2026-08-29): `otto_delegation_fence`, `otto_delegation_status`, `otto_delegation_revoke
|
|
2
|
+
* execution-delegation-admin-definition.ts — the four header-authed delegation ADMIN tools (PR-1b, seat ruling
|
|
3
|
+
* D10 = (a), 2026-08-29): `otto_delegation_fence`, `otto_delegation_status`, `otto_delegation_revoke`, and
|
|
4
|
+
* `otto_delegation_cap` (Founder word `delegate cap choice`, 2026-09-02 — the per-swap cap a person chose,
|
|
5
|
+
* recorded against the grant they just minted).
|
|
4
6
|
*
|
|
5
7
|
* Same seam and admission as `otto_submit_under_delegation`: they register only with a rig, the caller presents
|
|
6
8
|
* Otto's server secret in the `x-otto-delegation-auth` request header (never in arguments), the registered
|
|
@@ -14,16 +16,26 @@
|
|
|
14
16
|
* proxy exposes only the token variant (asserted in PR-2).
|
|
15
17
|
*/
|
|
16
18
|
import { z } from 'zod';
|
|
17
|
-
import { MODEL_B_PER_SWAP_CAP_USD } from './execution-delegation-policy.js';
|
|
19
|
+
import { MODEL_B_DELEGATION_EXPIRY_MAX_DAYS, MODEL_B_DELEGATION_EXPIRY_MIN_DAYS, MODEL_B_PER_SWAP_CAP_MIN_USD, MODEL_B_PER_SWAP_CAP_USD, } from './execution-delegation-policy.js';
|
|
18
20
|
import { DELEGATION_AUTH_HEADER, DELEGATED_TOOL_TRANSPORT_SCHEMA, } from './execution-delegated-definition.js';
|
|
21
|
+
import { CAP_RECORD_STATES, MINT_KEY_PATTERN } from './execution-delegation-cap-store.js';
|
|
19
22
|
export const DELEGATION_FENCE_TOOL_NAME = 'otto_delegation_fence';
|
|
20
23
|
export const DELEGATION_STATUS_TOOL_NAME = 'otto_delegation_status';
|
|
21
24
|
export const DELEGATION_REVOKE_TOOL_NAME = 'otto_delegation_revoke';
|
|
25
|
+
/** Records the per-swap cap the person chose, against the grant they just minted. */
|
|
26
|
+
export const DELEGATION_CAP_TOOL_NAME = 'otto_delegation_cap';
|
|
22
27
|
export const DELEGATION_ADMIN_TOOL_NAMES = Object.freeze([
|
|
23
28
|
DELEGATION_FENCE_TOOL_NAME,
|
|
24
29
|
DELEGATION_STATUS_TOOL_NAME,
|
|
25
30
|
DELEGATION_REVOKE_TOOL_NAME,
|
|
31
|
+
DELEGATION_CAP_TOOL_NAME,
|
|
26
32
|
]);
|
|
33
|
+
/** A chosen per-swap cap, as every schema on this surface bounds it: whole USD, 1 … the engine ceiling. */
|
|
34
|
+
const PER_SWAP_CAP_SCHEMA = z
|
|
35
|
+
.number()
|
|
36
|
+
.int()
|
|
37
|
+
.min(MODEL_B_PER_SWAP_CAP_MIN_USD)
|
|
38
|
+
.max(MODEL_B_PER_SWAP_CAP_USD);
|
|
27
39
|
/** The one PUBLIC delegation tool: the fence's public promise (present / name / digest / cap) — never user or grant data. */
|
|
28
40
|
export const DELEGATION_FENCE_STATUS_TOOL_NAME = 'otto_delegation_fence_status';
|
|
29
41
|
const END_USER_ID_PATTERN = /^[a-zA-Z0-9-]{1,100}$/;
|
|
@@ -67,9 +79,117 @@ export const DELEGATION_STATUS_RESULT_SCHEMA = z
|
|
|
67
79
|
/** Active = reads back from CDP, not expired, and no revocation was requested on this server. */
|
|
68
80
|
active: z.boolean(),
|
|
69
81
|
expires_at: z.string().optional(),
|
|
82
|
+
/**
|
|
83
|
+
* The per-swap cap recorded for THIS grant — the number the person chose when they minted.
|
|
84
|
+
* ABSENT when none is recorded, and that absence is load-bearing: a delegated send for a grant
|
|
85
|
+
* with no recorded cap refuses, so the surface reading this must say the permission is not usable
|
|
86
|
+
* rather than quietly showing the ceiling.
|
|
87
|
+
*/
|
|
88
|
+
per_swap_cap_usd: PER_SWAP_CAP_SCHEMA.optional(),
|
|
70
89
|
})
|
|
71
90
|
.strict(),
|
|
72
91
|
revocation_requested_here: z.boolean(),
|
|
92
|
+
/**
|
|
93
|
+
* The cap-ruleset version THIS worker implements. The page and the agent CLI refuse to reserve or
|
|
94
|
+
* mint against a server reporting less than the version they were built for — a rolling deploy runs
|
|
95
|
+
* two binaries at once and the Coinbase fence is identical on both, so the fence cannot tell them
|
|
96
|
+
* apart and this is what does.
|
|
97
|
+
*/
|
|
98
|
+
ruleset_version: z.number().int().min(1),
|
|
99
|
+
/**
|
|
100
|
+
* Whether a NEW permission can be created on this worker at all (the staged-deploy gate). It never
|
|
101
|
+
* gates this read and never gates revoke: pausing the creation of authority must not pause its
|
|
102
|
+
* withdrawal, and a permission that already stands keeps working under its own cap.
|
|
103
|
+
*/
|
|
104
|
+
mint_enabled: z.boolean(),
|
|
105
|
+
})
|
|
106
|
+
.strict();
|
|
107
|
+
/* ── cap (round 3: RESERVE an expiry and record the cap against it, then CONFIRM the mint) ── */
|
|
108
|
+
/**
|
|
109
|
+
* 🔴 TWO PHASES, BECAUSE THE RECORD NOW COMES BEFORE THE GRANT.
|
|
110
|
+
*
|
|
111
|
+
* Rounds 1 and 2 recorded a cap against a grant that already existed, and the codex gate broke both the
|
|
112
|
+
* same way: an expiry is the only thing a send can learn about a live grant, so two grants that share one
|
|
113
|
+
* are indistinguishable at enforcement however the row is keyed. Round 3 removes the collision instead of
|
|
114
|
+
* describing it.
|
|
115
|
+
*
|
|
116
|
+
* `reserve` — the SERVER picks a whole-second instant at or BEFORE the person's chosen duration that no
|
|
117
|
+
* durable row for this user occupies, writes the cap against it, and answers with the instant. The
|
|
118
|
+
* caller then mints with `mint_expires_at`, which is that instant plus a one-millisecond filler CDP
|
|
119
|
+
* truncates away (a bare `.000Z` is refused upstream — see `mintExpiryFor`).
|
|
120
|
+
* `confirm` — after the mint, the server re-reads the grant from CDP and refuses unless the instant CDP
|
|
121
|
+
* reports IS the reserved one. On a mismatch the fresh grant is REVOKED: a permission whose expiry
|
|
122
|
+
* does not match the row it was reserved against is one nothing may act under.
|
|
123
|
+
*
|
|
124
|
+
* The user is still never named by the caller — both phases resolve the identity from the access token.
|
|
125
|
+
*/
|
|
126
|
+
export const DELEGATION_CAP_RESERVE_INPUT_SCHEMA = z
|
|
127
|
+
.object({
|
|
128
|
+
phase: z.literal('reserve'),
|
|
129
|
+
accessToken: ACCESS_TOKEN_SCHEMA,
|
|
130
|
+
perSwapCapUsd: PER_SWAP_CAP_SCHEMA,
|
|
131
|
+
/**
|
|
132
|
+
* How long the person asked for. The server turns it into an instant itself — a caller-supplied expiry
|
|
133
|
+
* would put the choice of the enforcement key back in the browser, and it is the server choosing it
|
|
134
|
+
* that makes the key unique.
|
|
135
|
+
*/
|
|
136
|
+
requestedDays: z
|
|
137
|
+
.number()
|
|
138
|
+
.int()
|
|
139
|
+
.min(MODEL_B_DELEGATION_EXPIRY_MIN_DAYS)
|
|
140
|
+
.max(MODEL_B_DELEGATION_EXPIRY_MAX_DAYS),
|
|
141
|
+
/**
|
|
142
|
+
* The `idempotencyKey` the caller WILL pass to `createDelegation`. It makes the reservation itself
|
|
143
|
+
* retry-safe: pressing again after a lost response returns the same instant rather than consuming a
|
|
144
|
+
* second one. It is NOT the enforcement key — `(userId, expiry)` is.
|
|
145
|
+
*/
|
|
146
|
+
mintKey: z.string().regex(MINT_KEY_PATTERN, 'must be the mint idempotency key'),
|
|
147
|
+
})
|
|
148
|
+
.strict();
|
|
149
|
+
export const DELEGATION_CAP_CONFIRM_INPUT_SCHEMA = z
|
|
150
|
+
.object({
|
|
151
|
+
phase: z.literal('confirm'),
|
|
152
|
+
accessToken: ACCESS_TOKEN_SCHEMA,
|
|
153
|
+
/** Which reservation this mint was for. The reserved instant is read from the store, never from the caller. */
|
|
154
|
+
mintKey: z.string().regex(MINT_KEY_PATTERN, 'must be the mint idempotency key'),
|
|
155
|
+
})
|
|
156
|
+
.strict();
|
|
157
|
+
export const DELEGATION_CAP_INPUT_SCHEMA = z.discriminatedUnion('phase', [
|
|
158
|
+
DELEGATION_CAP_RESERVE_INPUT_SCHEMA,
|
|
159
|
+
DELEGATION_CAP_CONFIRM_INPUT_SCHEMA,
|
|
160
|
+
]);
|
|
161
|
+
export const DELEGATION_CAP_RESULT_SCHEMA = z
|
|
162
|
+
.object({
|
|
163
|
+
phase: z.enum(['reserve', 'confirm']),
|
|
164
|
+
user_id: z.string().regex(END_USER_ID_PATTERN),
|
|
165
|
+
/**
|
|
166
|
+
* The RESERVED whole-second instant — the enforcement key. On `confirm` it is also what CDP reports for
|
|
167
|
+
* the live grant, because the call refuses otherwise.
|
|
168
|
+
*/
|
|
169
|
+
expires_at: z.string(),
|
|
170
|
+
/**
|
|
171
|
+
* What to hand `createDelegation`: the reserved instant plus the one-millisecond filler. Present on
|
|
172
|
+
* `reserve` only — by `confirm` the mint has already happened.
|
|
173
|
+
*/
|
|
174
|
+
mint_expires_at: z.string().optional(),
|
|
175
|
+
/**
|
|
176
|
+
* The cap IN FORCE at that instant, read back from the binding index — not necessarily the number the
|
|
177
|
+
* caller asked for.
|
|
178
|
+
*/
|
|
179
|
+
per_swap_cap_usd: PER_SWAP_CAP_SCHEMA,
|
|
180
|
+
/**
|
|
181
|
+
* Where this permission is in its lifecycle. `reserved` authorises NOTHING — only `confirmed_active`
|
|
182
|
+
* does, and only while it is the one the send gate reads back.
|
|
183
|
+
*/
|
|
184
|
+
state: z.enum(CAP_RECORD_STATES),
|
|
185
|
+
/** The cap ruleset this record requires; a worker that implements an older one refuses it. */
|
|
186
|
+
ruleset_version: z.number().int().min(1),
|
|
187
|
+
/**
|
|
188
|
+
* True when THIS call wrote the transition. False when it had already happened for this mint key:
|
|
189
|
+
* a reservation is immutable and a confirmation is one-shot, so the fields above are what already
|
|
190
|
+
* stands, and a caller wanting a different number mints a new permission.
|
|
191
|
+
*/
|
|
192
|
+
recorded: z.boolean(),
|
|
73
193
|
})
|
|
74
194
|
.strict();
|
|
75
195
|
/* ── revoke ── */
|
|
@@ -97,7 +217,21 @@ export const DELEGATION_FENCE_STATUS_RESULT_SCHEMA = z
|
|
|
97
217
|
.object({
|
|
98
218
|
present: z.boolean(),
|
|
99
219
|
policy_name: z.string(),
|
|
220
|
+
/**
|
|
221
|
+
* The ENGINE's ceiling, shared by every delegated user — never one person's cap. A public tool has no
|
|
222
|
+
* user, so it can only ever publish this number and the floor below it.
|
|
223
|
+
*/
|
|
100
224
|
per_swap_cap_usd: z.literal(MODEL_B_PER_SWAP_CAP_USD),
|
|
225
|
+
/** The floor of the range a person may choose at mint time; their choice is enforced server-side. */
|
|
226
|
+
per_swap_cap_min_usd: z.literal(MODEL_B_PER_SWAP_CAP_MIN_USD),
|
|
227
|
+
/** The cap-ruleset version this worker implements; a client built for a newer one refuses to mint. */
|
|
228
|
+
ruleset_version: z.number().int().min(1),
|
|
229
|
+
/**
|
|
230
|
+
* Whether this worker will create a NEW permission at all. Published so the page and the CLI can say
|
|
231
|
+
* so before a person signs in, rather than discovering it at the reserve. Permissions that already
|
|
232
|
+
* stand keep working, and revoke always works.
|
|
233
|
+
*/
|
|
234
|
+
mint_enabled: z.boolean(),
|
|
101
235
|
/** Otto's PUBLIC CDP project id (a UUID): the authority the agent CLI binds its mint to. */
|
|
102
236
|
project_id: z
|
|
103
237
|
.string()
|
|
@@ -114,7 +248,7 @@ export const DELEGATION_FENCE_STATUS_RESULT_SCHEMA = z
|
|
|
114
248
|
export const DELEGATION_FENCE_STATUS_TOOL_DEFINITION = Object.freeze({
|
|
115
249
|
name: DELEGATION_FENCE_STATUS_TOOL_NAME,
|
|
116
250
|
title: 'Read the Model-B delegation fence (public promise: present, name, rules digest, per-swap cap)',
|
|
117
|
-
description: `PUBLIC and read-only, no header: whether the Model-B project policy that fences every delegated send is installed and v-current on this server, with its versioned name, rules digest, the per-swap cap in USD, and Otto's PUBLIC CDP project id (a UUID — the authority an agent binds its mint to). Exposes ONLY the fence's public promise — never an end user, a grant, an address or a policy's contents. An agent about to mint a delegation (otto-execute delegate) reads this first, mints to the published project_id and no other, and refuses to mint when the fence is not present; the server enforces fence-before-authority on every delegated send regardless. Arguments: {}.`,
|
|
251
|
+
description: `PUBLIC and read-only, no header: whether the Model-B project policy that fences every delegated send is installed and v-current on this server, with its versioned name, rules digest, the per-swap cap ceiling in USD (shared by every delegated user \u2014 a person may choose any whole-dollar cap from per_swap_cap_min_usd up to it, enforced server-side and recorded against their own grant), and Otto's PUBLIC CDP project id (a UUID — the authority an agent binds its mint to). Exposes ONLY the fence's public promise — never an end user, a grant, an address or a policy's contents. An agent about to mint a delegation (otto-execute delegate) reads this first, mints to the published project_id and no other, and refuses to mint when the fence is not present; the server enforces fence-before-authority on every delegated send regardless. Arguments: {}.`,
|
|
118
252
|
inputSchema: DELEGATION_FENCE_STATUS_INPUT_SCHEMA,
|
|
119
253
|
outputSchema: DELEGATION_FENCE_STATUS_RESULT_SCHEMA,
|
|
120
254
|
annotations: Object.freeze({
|
|
@@ -142,7 +276,7 @@ export const DELEGATION_FENCE_TOOL_DEFINITION = Object.freeze({
|
|
|
142
276
|
export const DELEGATION_STATUS_TOOL_DEFINITION = Object.freeze({
|
|
143
277
|
name: DELEGATION_STATUS_TOOL_NAME,
|
|
144
278
|
title: "Read one end user's delegation status (token-bound)",
|
|
145
|
-
description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry). Arguments: { accessToken } — the server resolves the user from the token; a client-sent user id is refused. Output is bound to that user; nothing about any other user is returned. ${AUTH_SENTENCE}`,
|
|
279
|
+
description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry and the per-swap cap recorded for it). Arguments: { accessToken } — the server resolves the user from the token; a client-sent user id is refused. delegation.per_swap_cap_usd is the number THIS user chose at mint time; it is ABSENT when no cap is recorded for the grant, which means the delegated send will refuse it — say so rather than showing the shared ceiling. fence.per_swap_cap_usd is that shared ceiling and is never a per-user number. Output is bound to that user; nothing about any other user is returned. ${AUTH_SENTENCE}`,
|
|
146
280
|
inputSchema: DELEGATED_TOOL_TRANSPORT_SCHEMA,
|
|
147
281
|
strictInputSchema: DELEGATION_STATUS_INPUT_SCHEMA,
|
|
148
282
|
outputSchema: DELEGATION_STATUS_RESULT_SCHEMA,
|
|
@@ -167,4 +301,19 @@ export const DELEGATION_REVOKE_TOOL_DEFINITION = Object.freeze({
|
|
|
167
301
|
openWorldHint: true,
|
|
168
302
|
}),
|
|
169
303
|
});
|
|
304
|
+
export const DELEGATION_CAP_TOOL_DEFINITION = Object.freeze({
|
|
305
|
+
name: DELEGATION_CAP_TOOL_NAME,
|
|
306
|
+
title: "Reserve the expiry a delegation is minted with, record the chosen per-swap cap against it, then confirm the mint",
|
|
307
|
+
description: `TWO PHASES, both header authed, both resolving the end user from the access token. { phase: "reserve", accessToken, perSwapCapUsd, requestedDays, mintKey } picks a whole-second expiry instant at or BEFORE the requested duration that no durable row for this user already occupies, writes the chosen cap (whole USD, ${MODEL_B_PER_SWAP_CAP_MIN_USD}…${MODEL_B_PER_SWAP_CAP_USD}) against it, and answers { expires_at, mint_expires_at, per_swap_cap_usd, recorded }. Call createDelegation with mint_expires_at EXACTLY as given — it is the reserved instant plus a one-millisecond filler, because CDP refuses an expiry ending .000 and truncates the filler away — and with mintKey as the idempotencyKey. Then { phase: "confirm", accessToken, mintKey } re-reads the grant from CDP and refuses (DELEGATION_CAP_GRANT_CHANGED) unless the instant CDP reports IS the reserved one, revoking the fresh grant when it is not. Why this shape: Coinbase issues no grant id and a send can learn nothing about a live grant except its expiry, so the expiry is made an identity BY CONSTRUCTION — reserved server-side, recorded before the mint, asserted after it. Until a reservation exists, otto_submit_under_delegation refuses that delegation (DELEGATION_CAP_UNRECORDED) rather than falling back to the ${MODEL_B_PER_SWAP_CAP_USD} USD ceiling. A reservation is IMMUTABLE per mint key — pressing again returns the same instant and the cap already in force, so raising a cap means minting a new permission (which the user is told replaces the old one). Coinbase's engine cannot hold this number: end-user accounts are fenced by ONE project-scope policy shared by every delegated user, so a lower per-user cap is enforced by Otto's server alone. ${AUTH_SENTENCE}`,
|
|
308
|
+
inputSchema: DELEGATED_TOOL_TRANSPORT_SCHEMA,
|
|
309
|
+
strictInputSchema: DELEGATION_CAP_INPUT_SCHEMA,
|
|
310
|
+
outputSchema: DELEGATION_CAP_RESULT_SCHEMA,
|
|
311
|
+
annotations: Object.freeze({
|
|
312
|
+
readOnlyHint: false,
|
|
313
|
+
destructiveHint: false,
|
|
314
|
+
// Idempotent by construction: the record is immutable, so a repeat returns what is already there.
|
|
315
|
+
idempotentHint: true,
|
|
316
|
+
openWorldHint: true,
|
|
317
|
+
}),
|
|
318
|
+
});
|
|
170
319
|
//# sourceMappingURL=execution-delegation-admin-definition.js.map
|