@mida-context/sdk 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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7115 -0
- package/dist/types/api/app.d.ts +99 -0
- package/dist/types/api/auth-pure.d.ts +51 -0
- package/dist/types/api/auth.d.ts +17 -0
- package/dist/types/api/authorize.d.ts +24 -0
- package/dist/types/api/batch-deny.d.ts +22 -0
- package/dist/types/api/batch-routes.d.ts +69 -0
- package/dist/types/api/batch-store.d.ts +96 -0
- package/dist/types/api/batcher.d.ts +220 -0
- package/dist/types/api/browser.d.ts +14 -0
- package/dist/types/api/chain-budget.d.ts +61 -0
- package/dist/types/api/chain-views.d.ts +87 -0
- package/dist/types/api/client.d.ts +295 -0
- package/dist/types/api/deny-overlay.d.ts +81 -0
- package/dist/types/api/errors.d.ts +30 -0
- package/dist/types/api/file-stores.d.ts +7 -0
- package/dist/types/api/index.d.ts +17 -0
- package/dist/types/api/secure-fs.d.ts +12 -0
- package/dist/types/api/store.d.ts +51 -0
- package/dist/types/api/stores.d.ts +171 -0
- package/dist/types/api/verify-assertion.d.ts +22 -0
- package/dist/types/api/wire.d.ts +27 -0
- package/dist/types/chain/abis.d.ts +2125 -0
- package/dist/types/chain/browser.d.ts +20 -0
- package/dist/types/chain/deployment-fs.d.ts +15 -0
- package/dist/types/chain/deployment.d.ts +33 -0
- package/dist/types/chain/deployments.generated.d.ts +1 -0
- package/dist/types/chain/gas.d.ts +47 -0
- package/dist/types/chain/history.d.ts +52 -0
- package/dist/types/chain/index.d.ts +13 -0
- package/dist/types/chain/local.d.ts +34 -0
- package/dist/types/chain/logs.d.ts +62 -0
- package/dist/types/chain/placements.d.ts +92 -0
- package/dist/types/chain/read-scope.d.ts +66 -0
- package/dist/types/chain/registry.d.ts +16 -0
- package/dist/types/chain/sponsored.d.ts +93 -0
- package/dist/types/chain/transport.d.ts +53 -0
- package/dist/types/chain/writes.d.ts +165 -0
- package/dist/types/crypto/aead.d.ts +5 -0
- package/dist/types/crypto/bytes.d.ts +4 -0
- package/dist/types/crypto/derive.d.ts +20 -0
- package/dist/types/crypto/index.d.ts +6 -0
- package/dist/types/crypto/object.d.ts +42 -0
- package/dist/types/crypto/payload.d.ts +21 -0
- package/dist/types/crypto/wraps.d.ts +36 -0
- package/dist/types/grant-advisor/advise.d.ts +23 -0
- package/dist/types/grant-advisor/authority.d.ts +44 -0
- package/dist/types/grant-advisor/index.d.ts +5 -0
- package/dist/types/grant-advisor/manifest.d.ts +66 -0
- package/dist/types/grant-advisor/policy.d.ts +91 -0
- package/dist/types/grant-advisor/signatures.d.ts +10 -0
- package/dist/types/mida-context-sdk/daemon.d.ts +27 -0
- package/dist/types/mida-context-sdk/errors.d.ts +27 -0
- package/dist/types/mida-context-sdk/index.d.ts +5 -0
- package/dist/types/mida-context-sdk/local.d.ts +40 -0
- package/dist/types/mida-context-sdk/mida.d.ts +65 -0
- package/dist/types/mida-context-sdk/transport.d.ts +137 -0
- package/dist/types/protocol/batch.d.ts +161 -0
- package/dist/types/protocol/constants.d.ts +56 -0
- package/dist/types/protocol/errors.d.ts +15 -0
- package/dist/types/protocol/ids.d.ts +72 -0
- package/dist/types/protocol/index.d.ts +11 -0
- package/dist/types/protocol/namespaces.d.ts +15 -0
- package/dist/types/protocol/owner-link.d.ts +146 -0
- package/dist/types/protocol/typed-data.d.ts +365 -0
- package/dist/types/protocol/types.d.ts +215 -0
- package/dist/types/protocol/webauthn-assertion.d.ts +29 -0
- package/dist/types/protocol/wire.d.ts +8 -0
- package/dist/types/sdk/agent.d.ts +279 -0
- package/dist/types/sdk/batched.d.ts +68 -0
- package/dist/types/sdk/connect.d.ts +88 -0
- package/dist/types/sdk/index.d.ts +7 -0
- package/dist/types/sdk/request-store.d.ts +26 -0
- package/dist/types/storage/index.d.ts +23 -0
- package/package.json +50 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Address, AgentRecord, Hex } from "../protocol/index.js";
|
|
2
|
+
import { RegistryReader } from "./chain-views.js";
|
|
3
|
+
import type { CapabilityView, ContextRecordView } from "./chain-views.js";
|
|
4
|
+
/**
|
|
5
|
+
* One chain read costs one platform subrequest. Cloudflare's Workers FREE plan allows 50 per
|
|
6
|
+
* invocation (check the current Workers limits page), so a request's Monad budget is 30 — the rest
|
|
7
|
+
* of the allowance covers D1, the rate-limit binding and logging inside the same invocation. The
|
|
8
|
+
* same cap is enforced in the self-hosted app: a bounded request is a request that can be reasoned
|
|
9
|
+
* about, whatever serves it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_CHAIN_READS_PER_REQUEST = 30;
|
|
12
|
+
/** Thrown the moment a request tries to spend one chain read more than its per-request budget. */
|
|
13
|
+
export declare class ChainReadBudgetExceeded extends Error {
|
|
14
|
+
readonly code: "CHAIN_READ_BUDGET_EXHAUSTED";
|
|
15
|
+
constructor(message?: string);
|
|
16
|
+
}
|
|
17
|
+
export declare function isChainReadBudgetExceeded(value: unknown): value is ChainReadBudgetExceeded;
|
|
18
|
+
/**
|
|
19
|
+
* The counting wrapper built per request around the chain reader. Every method delegates to the
|
|
20
|
+
* wrapped reader but spends one unit of budget first; the call that would exceed it throws instead
|
|
21
|
+
* of running, so the platform's own subrequest ceiling is never reached. It extends RegistryReader
|
|
22
|
+
* on purpose: the class has an ECMAScript private field, which makes it nominal in TypeScript — a
|
|
23
|
+
* structural stand-in could never be passed where a RegistryReader is expected. Route code reads
|
|
24
|
+
* `remaining` to size its own scan caps (pending re-checks, object lists) so this wrapper's throw
|
|
25
|
+
* stays the last line of defence, not the control flow.
|
|
26
|
+
*/
|
|
27
|
+
export declare class BudgetedReader extends RegistryReader {
|
|
28
|
+
#private;
|
|
29
|
+
/**
|
|
30
|
+
* `memo` is the operation-scope the request belongs to (in-9 R-5): when the caller carries a
|
|
31
|
+
* read-scope token, the app hands every request of that operation the same map, so an identical
|
|
32
|
+
* NON-AUTHORIZATION question a sibling request already asked is answered once per operation.
|
|
33
|
+
* in-12 N-10 draws the line: an authorization answer — identity resolution, the agent record,
|
|
34
|
+
* capability and epoch state, the authority verdict — is never shared across HTTP requests, so a
|
|
35
|
+
* token held past a revocation can only ever return content verifications, never a stale
|
|
36
|
+
* "allowed". A memo hit spends no budget: it is not a chain read at all. A rejected call is
|
|
37
|
+
* evicted so one transient failure never poisons the operation.
|
|
38
|
+
*/
|
|
39
|
+
constructor(inner: RegistryReader, limit?: number, memo?: Map<string, Promise<unknown>>);
|
|
40
|
+
/** Chain reads spent so far in this request. */
|
|
41
|
+
get spent(): number;
|
|
42
|
+
/** Reads still available; route logic sizes its own caps from this before asking the chain. */
|
|
43
|
+
get remaining(): number;
|
|
44
|
+
now(): Promise<bigint>;
|
|
45
|
+
agentIdOfSigner(signer: Address): Promise<Hex | null>;
|
|
46
|
+
getAgent(agentId: Hex): Promise<AgentRecord | null>;
|
|
47
|
+
getCapability(capabilityId: Hex): Promise<CapabilityView | null>;
|
|
48
|
+
agentEpoch(owner: Address, agentId: Hex): Promise<bigint>;
|
|
49
|
+
activeCapabilityIds(owner: Address, agentId: Hex): Promise<readonly Hex[]>;
|
|
50
|
+
hasAuthority(owner: Address, agentId: Hex, namespaceId: Hex, permissions: number, provenanceBits: number): Promise<boolean>;
|
|
51
|
+
requiredReadEpoch(owner: Address, namespaceId: Hex): Promise<bigint>;
|
|
52
|
+
epochPublicKey(owner: Address, namespaceId: Hex, epoch: bigint): Promise<Hex | null>;
|
|
53
|
+
isWriteEpochValid(owner: Address, namespaceId: Hex, epoch: bigint): Promise<boolean>;
|
|
54
|
+
ownerP256Key(owner: Address): Promise<{
|
|
55
|
+
qx: bigint;
|
|
56
|
+
qy: bigint;
|
|
57
|
+
} | null>;
|
|
58
|
+
getRecord(contextId: Hex): Promise<ContextRecordView | null>;
|
|
59
|
+
recordBatchSize(): Promise<number>;
|
|
60
|
+
getRecords(contextIds: readonly Hex[]): Promise<(ContextRecordView | null)[]>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Address, AgentRecord, Hex } from "../protocol/index.js";
|
|
2
|
+
import type { ChainContext } from "../chain/index.js";
|
|
3
|
+
/** Spec §10.3 Capability, as read from CapabilityRegistry. */
|
|
4
|
+
export interface CapabilityView {
|
|
5
|
+
owner: Address;
|
|
6
|
+
agentId: Hex;
|
|
7
|
+
namespaceId: Hex;
|
|
8
|
+
permissions: number;
|
|
9
|
+
provenancePolicy: number;
|
|
10
|
+
issuedAt: bigint;
|
|
11
|
+
expiresAt: bigint;
|
|
12
|
+
agentEpoch: bigint;
|
|
13
|
+
grantedAtReadEpoch: bigint;
|
|
14
|
+
revoked: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Spec §11.3 ContextRecord, as read from ContextRegistry. */
|
|
17
|
+
export interface ContextRecordView {
|
|
18
|
+
contextId: Hex;
|
|
19
|
+
owner: Address;
|
|
20
|
+
author: Hex;
|
|
21
|
+
namespaceId: Hex;
|
|
22
|
+
lineageId: Hex;
|
|
23
|
+
parentId: Hex;
|
|
24
|
+
manifestHash: Hex;
|
|
25
|
+
ciphertextCommitment: Hex;
|
|
26
|
+
evidenceCommitment: Hex;
|
|
27
|
+
readEpoch: bigint;
|
|
28
|
+
createdAt: bigint;
|
|
29
|
+
expiresAt: bigint;
|
|
30
|
+
version: number;
|
|
31
|
+
recordType: number;
|
|
32
|
+
lineagePolicy: number;
|
|
33
|
+
kind: number;
|
|
34
|
+
provenanceSource: number;
|
|
35
|
+
}
|
|
36
|
+
/** The most contextIds one batched `getRecords` asks about — one Multicall3 `eth_call` per batch. */
|
|
37
|
+
export declare const RECORDS_PER_MULTICALL = 200;
|
|
38
|
+
/**
|
|
39
|
+
* Every Monad read the Context API and SDK make. Nothing here is cached: each call reads current chain state, so no
|
|
40
|
+
* local value can make Monad authorization true (§12, `currentlyAllowedByMonad`).
|
|
41
|
+
*/
|
|
42
|
+
export declare class RegistryReader {
|
|
43
|
+
#private;
|
|
44
|
+
readonly context: ChainContext;
|
|
45
|
+
constructor(context: ChainContext);
|
|
46
|
+
/**
|
|
47
|
+
* The probed batch size once known, undefined while unprobed. Lets a BudgetedReader return a
|
|
48
|
+
* cached answer without charging the request for a chain read that does not happen.
|
|
49
|
+
*/
|
|
50
|
+
get knownRecordBatchSize(): number | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* The most contextIds one `getRecords` call answers in a single chain read: RECORDS_PER_MULTICALL
|
|
53
|
+
* when the chain carries Multicall3, 1 where it does not. Probed once per reader with `getCode`
|
|
54
|
+
* and cached; a chain that cannot answer the probe cannot run a multicall either, so a failed
|
|
55
|
+
* probe also resolves to 1 — the per-row path — rather than breaking reads on an RPC that does
|
|
56
|
+
* not serve `eth_getCode`.
|
|
57
|
+
*/
|
|
58
|
+
recordBatchSize(): Promise<number>;
|
|
59
|
+
now(): Promise<bigint>;
|
|
60
|
+
agentIdOfSigner(signer: Address): Promise<Hex | null>;
|
|
61
|
+
getAgent(agentId: Hex): Promise<AgentRecord | null>;
|
|
62
|
+
getCapability(capabilityId: Hex): Promise<CapabilityView | null>;
|
|
63
|
+
agentEpoch(owner: Address, agentId: Hex): Promise<bigint>;
|
|
64
|
+
/** Capability IDs ever granted by this owner to this agent; revoked/expired entries linger until a grant compacts the list. */
|
|
65
|
+
activeCapabilityIds(owner: Address, agentId: Hex): Promise<readonly Hex[]>;
|
|
66
|
+
hasAuthority(owner: Address, agentId: Hex, namespaceId: Hex, permissions: number, provenanceBits: number): Promise<boolean>;
|
|
67
|
+
requiredReadEpoch(owner: Address, namespaceId: Hex): Promise<bigint>;
|
|
68
|
+
epochPublicKey(owner: Address, namespaceId: Hex, epoch: bigint): Promise<Hex | null>;
|
|
69
|
+
isWriteEpochValid(owner: Address, namespaceId: Hex, epoch: bigint): Promise<boolean>;
|
|
70
|
+
ownerP256Key(owner: Address): Promise<{
|
|
71
|
+
qx: bigint;
|
|
72
|
+
qy: bigint;
|
|
73
|
+
} | null>;
|
|
74
|
+
getRecord(contextId: Hex): Promise<ContextRecordView | null>;
|
|
75
|
+
/**
|
|
76
|
+
* `getRecord` for a batch of contextIds. With Multicall3 the batch is ONE `eth_call` — hundreds of
|
|
77
|
+
* never-anchored uploads can no longer spend a request's read budget a row at a time. The
|
|
78
|
+
* `ContextNotFound` revert of an orphaned upload comes back as a null entry — "not anchored",
|
|
79
|
+
* never an error — and it is the ONLY failure that does: an out-of-gas, an undecodable result or
|
|
80
|
+
* any other revert throws exactly as `getRecord` throws, so a caller can never read a list that
|
|
81
|
+
* silently dropped a real record. Chains without Multicall3 fall back to one `getRecord` per id,
|
|
82
|
+
* unchanged.
|
|
83
|
+
* Callers pass at most `recordBatchSize()` ids per call, so each call costs exactly one unit of
|
|
84
|
+
* read budget — or `contextIds.length` units on the per-row path, identical to `getRecord`.
|
|
85
|
+
*/
|
|
86
|
+
getRecords(contextIds: readonly Hex[]): Promise<(ContextRecordView | null)[]>;
|
|
87
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import type { Address, BatchSaveMessage, Hex, ObjectManifest, ReaderEpochWrap, SignedAgentCapabilityManifest } from "../protocol/index.js";
|
|
2
|
+
import type { LocalAccount } from "viem";
|
|
3
|
+
import type { WebAuthnAssertionInput } from "./verify-assertion.js";
|
|
4
|
+
import type { AnchoredObject, ObjectUploadBody } from "./wire.js";
|
|
5
|
+
import type { DenyState, RevocationTarget } from "./deny-overlay.js";
|
|
6
|
+
export interface ContextApiClientOptions {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
account: LocalAccount;
|
|
9
|
+
chainId: bigint;
|
|
10
|
+
capabilityRegistry: Address;
|
|
11
|
+
fetch?: (input: string, init: RequestInit) => Promise<Response>;
|
|
12
|
+
clock?: () => bigint;
|
|
13
|
+
/**
|
|
14
|
+
* The one logical read operation this client's requests belong to (in-9 R-5, hardened in-12
|
|
15
|
+
* N-10): signer address → the read-scope token the server issued it. The token is minted by the
|
|
16
|
+
* server — HMAC-signed, signer-bound, eight seconds — so this map is only ever filled from
|
|
17
|
+
* response headers: a request stamps the token it currently holds, then adopts the fresh one the
|
|
18
|
+
* response carries once the held one has lapsed. Never set on a client that outlives one
|
|
19
|
+
* operation.
|
|
20
|
+
*/
|
|
21
|
+
readScope?: Map<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Where the store's compat warnings land (in-12 N-7): default stderr — right for a bare
|
|
24
|
+
* script — while the daemon passes its own log so the line reaches a log someone reads.
|
|
25
|
+
* Once per process either way.
|
|
26
|
+
*/
|
|
27
|
+
warn?: (message: string) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Signs every request with MidaHttpRequestV1 (§12.1); the signature covers the exact body bytes sent. The typed route
|
|
31
|
+
* methods implement the FakeVault's VaultContextApi port structurally, so the same client serves owners and agents.
|
|
32
|
+
*/
|
|
33
|
+
export declare class ContextApiClient implements ContextApiRoutes {
|
|
34
|
+
#private;
|
|
35
|
+
readonly account: LocalAccount;
|
|
36
|
+
constructor(options: ContextApiClientOptions);
|
|
37
|
+
request<T>(method: string, path: string, options?: {
|
|
38
|
+
query?: Record<string, string>;
|
|
39
|
+
body?: unknown;
|
|
40
|
+
signed?: boolean;
|
|
41
|
+
}): Promise<T>;
|
|
42
|
+
putObject(upload: ObjectUploadBody): Promise<{
|
|
43
|
+
contextId: Hex;
|
|
44
|
+
manifestHash: Hex;
|
|
45
|
+
state: "pending";
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* The pre-register question (in-3 I6): may this signer register for `owner`/`namespaceId` with
|
|
49
|
+
* this capability right now? Answers 200 or throws the store's code — WRITE_DENIED while the
|
|
50
|
+
* owner's revoke is only staged (pending on Monad), CAPABILITY_REVOKED once it has landed.
|
|
51
|
+
*/
|
|
52
|
+
writeAuthority(input: {
|
|
53
|
+
owner: Address;
|
|
54
|
+
namespaceId: Hex;
|
|
55
|
+
capabilityId: Hex;
|
|
56
|
+
expectedParentId?: Hex;
|
|
57
|
+
}): Promise<{
|
|
58
|
+
ok: true;
|
|
59
|
+
} | {
|
|
60
|
+
ok: true;
|
|
61
|
+
}>;
|
|
62
|
+
listObjects(input: {
|
|
63
|
+
owner: Address;
|
|
64
|
+
namespaceId: Hex;
|
|
65
|
+
capabilityId?: Hex;
|
|
66
|
+
}): Promise<ListObjectsResult>;
|
|
67
|
+
getManifest(contextId: Hex, capabilityId?: Hex): Promise<{
|
|
68
|
+
manifest: ObjectManifest;
|
|
69
|
+
manifestHash: Hex;
|
|
70
|
+
}>;
|
|
71
|
+
putAgentManifest(envelope: SignedAgentCapabilityManifest): Promise<{
|
|
72
|
+
bodyHash: Hex;
|
|
73
|
+
envelopeHash: Hex;
|
|
74
|
+
}>;
|
|
75
|
+
getAgentManifest(bodyHash: Hex): Promise<SignedAgentCapabilityManifest>;
|
|
76
|
+
publishEpochWrap(wrap: ReaderEpochWrap): Promise<{
|
|
77
|
+
stored: true;
|
|
78
|
+
}>;
|
|
79
|
+
getEpochWrap(input: {
|
|
80
|
+
owner: Address;
|
|
81
|
+
namespaceId: Hex;
|
|
82
|
+
readEpoch: bigint;
|
|
83
|
+
agentId: Hex;
|
|
84
|
+
agentKeyVersion: number;
|
|
85
|
+
capabilityId: Hex;
|
|
86
|
+
}): Promise<ReaderEpochWrap>;
|
|
87
|
+
requestRevocationDeny(target: {
|
|
88
|
+
capabilityId: Hex;
|
|
89
|
+
} | {
|
|
90
|
+
owner: Address;
|
|
91
|
+
agentId: Hex;
|
|
92
|
+
}): Promise<{
|
|
93
|
+
intentId: Hex;
|
|
94
|
+
state: string;
|
|
95
|
+
cancellationNonce: string;
|
|
96
|
+
}>;
|
|
97
|
+
cancelRevocation(intentId: Hex, input: {
|
|
98
|
+
expiresAt: bigint;
|
|
99
|
+
assertion: WebAuthnAssertionInput;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
intentId: Hex;
|
|
102
|
+
state: string;
|
|
103
|
+
}>;
|
|
104
|
+
listRevocations(state?: DenyState): Promise<RevocationIntentView[]>;
|
|
105
|
+
reissueRevocationNonce(intentId: Hex): Promise<{
|
|
106
|
+
intentId: Hex;
|
|
107
|
+
state: string;
|
|
108
|
+
cancellationNonce: string;
|
|
109
|
+
}>;
|
|
110
|
+
batchStatus(): Promise<{
|
|
111
|
+
enabled: boolean;
|
|
112
|
+
batchAnchor?: Address;
|
|
113
|
+
reason?: string;
|
|
114
|
+
message?: string;
|
|
115
|
+
}>;
|
|
116
|
+
postBatchSave(body: BatchedSaveWire): Promise<{
|
|
117
|
+
state: "QUEUED";
|
|
118
|
+
receipt: BatchReceipt;
|
|
119
|
+
}>;
|
|
120
|
+
getBatchSave(contextId: Hex, capabilityId?: Hex): Promise<{
|
|
121
|
+
state: BatchedItemState | "REJECTED";
|
|
122
|
+
reason: string | null;
|
|
123
|
+
item?: BatchedReadItem;
|
|
124
|
+
}>;
|
|
125
|
+
listBatchSaves(input: {
|
|
126
|
+
owner: Address;
|
|
127
|
+
namespaceId: Hex;
|
|
128
|
+
capabilityId?: Hex;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
items: BatchedReadItem[];
|
|
131
|
+
partial: boolean;
|
|
132
|
+
}>;
|
|
133
|
+
flushBatch(): Promise<{
|
|
134
|
+
flushed: boolean;
|
|
135
|
+
reason?: "empty" | "rate-limited";
|
|
136
|
+
}>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The store answered HTTP `status` with bytes that are not a Mida body — an old deploy's
|
|
140
|
+
* plain-text 404 on a route it does not have, a proxy or Cloudflare error page (in-11 R-3).
|
|
141
|
+
* Carries the status so a caller can route on it; the drain reads `code` for a stable name.
|
|
142
|
+
* This replaced the bare `SyntaxError` a JSON.parse used to leave, which the drain could only
|
|
143
|
+
* file as an anonymous "chain-error" and retry eight times before dropping the save.
|
|
144
|
+
*/
|
|
145
|
+
export declare class StoreHttpError extends Error {
|
|
146
|
+
readonly status: number;
|
|
147
|
+
readonly code = "STORE_UNREACHABLE";
|
|
148
|
+
constructor(status: number, body: string);
|
|
149
|
+
}
|
|
150
|
+
/** Retries `listObjects` performs after the first response still carries `x-mida-partial`. */
|
|
151
|
+
export declare const LIST_PARTIAL_MAX_RETRIES = 3;
|
|
152
|
+
/**
|
|
153
|
+
* What `listObjects` returns: the objects it verified plus `partial` — true when the server left
|
|
154
|
+
* rows unexamined after the initial request and all retries. An honest field, never a hidden
|
|
155
|
+
* property: a caller that ignores it cannot mistake a truncated list for a complete one.
|
|
156
|
+
*/
|
|
157
|
+
export interface ListObjectsResult {
|
|
158
|
+
objects: AnchoredObject[];
|
|
159
|
+
partial: boolean;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* What `GET /revocations` returns per intent: everything an owner needs to find and cancel a stale
|
|
163
|
+
* deny — except the cancellation nonce, which the store only hands out when the deny is created or
|
|
164
|
+
* its nonce reissued.
|
|
165
|
+
*/
|
|
166
|
+
export interface RevocationIntentView {
|
|
167
|
+
intentId: Hex;
|
|
168
|
+
state: DenyState;
|
|
169
|
+
target: RevocationTarget;
|
|
170
|
+
agentEpochAtIntent: string | null;
|
|
171
|
+
}
|
|
172
|
+
/** Wire form of `BatchSaveMessage`: the two uint64 fields travel as base-10 strings, everything else verbatim. */
|
|
173
|
+
export type BatchedSaveMessageWire = Omit<BatchSaveMessage, "readEpoch" | "expiresAt"> & {
|
|
174
|
+
readEpoch: string;
|
|
175
|
+
expiresAt: string;
|
|
176
|
+
};
|
|
177
|
+
export interface BatchedSaveWire {
|
|
178
|
+
message: BatchedSaveMessageWire;
|
|
179
|
+
signature: Hex;
|
|
180
|
+
manifest: ObjectManifest;
|
|
181
|
+
ciphertext: Hex;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* "HELD" is the in-3 store-side hold: the row's author is on the active deny list, so the batcher
|
|
185
|
+
* keeps it off the send path while the revoke is pending on Monad — it re-joins QUEUED when the
|
|
186
|
+
* deny clears with the grant still valid, or becomes REJECTED once the revoke lands.
|
|
187
|
+
*/
|
|
188
|
+
export type BatchedItemState = "QUEUED" | "SUBMITTED" | "ANCHORED" | "HELD";
|
|
189
|
+
export interface BatchedReadItem {
|
|
190
|
+
state: BatchedItemState;
|
|
191
|
+
save: BatchedSaveWire;
|
|
192
|
+
contextId: Hex;
|
|
193
|
+
receivedAt: number;
|
|
194
|
+
batchId?: Hex;
|
|
195
|
+
position?: number;
|
|
196
|
+
lineageId?: Hex;
|
|
197
|
+
version?: number;
|
|
198
|
+
proof?: Hex[];
|
|
199
|
+
}
|
|
200
|
+
export interface BatchReceipt {
|
|
201
|
+
contextId: Hex;
|
|
202
|
+
receivedAt: number;
|
|
203
|
+
sequence: string;
|
|
204
|
+
signature: Hex;
|
|
205
|
+
}
|
|
206
|
+
export interface ContextApiRoutes {
|
|
207
|
+
putObject(upload: ObjectUploadBody): Promise<{
|
|
208
|
+
contextId: Hex;
|
|
209
|
+
manifestHash: Hex;
|
|
210
|
+
state: "pending";
|
|
211
|
+
}>;
|
|
212
|
+
writeAuthority(input: {
|
|
213
|
+
owner: Address;
|
|
214
|
+
namespaceId: Hex;
|
|
215
|
+
capabilityId: Hex;
|
|
216
|
+
expectedParentId?: Hex;
|
|
217
|
+
}): Promise<{
|
|
218
|
+
ok: true;
|
|
219
|
+
}>;
|
|
220
|
+
listObjects(input: {
|
|
221
|
+
owner: Address;
|
|
222
|
+
namespaceId: Hex;
|
|
223
|
+
capabilityId?: Hex;
|
|
224
|
+
}): Promise<ListObjectsResult>;
|
|
225
|
+
getManifest(contextId: Hex, capabilityId?: Hex): Promise<{
|
|
226
|
+
manifest: ObjectManifest;
|
|
227
|
+
manifestHash: Hex;
|
|
228
|
+
}>;
|
|
229
|
+
putAgentManifest(envelope: SignedAgentCapabilityManifest): Promise<{
|
|
230
|
+
bodyHash: Hex;
|
|
231
|
+
envelopeHash: Hex;
|
|
232
|
+
}>;
|
|
233
|
+
getAgentManifest(bodyHash: Hex): Promise<SignedAgentCapabilityManifest>;
|
|
234
|
+
publishEpochWrap(wrap: ReaderEpochWrap): Promise<{
|
|
235
|
+
stored: true;
|
|
236
|
+
}>;
|
|
237
|
+
getEpochWrap(input: {
|
|
238
|
+
owner: Address;
|
|
239
|
+
namespaceId: Hex;
|
|
240
|
+
readEpoch: bigint;
|
|
241
|
+
agentId: Hex;
|
|
242
|
+
agentKeyVersion: number;
|
|
243
|
+
capabilityId: Hex;
|
|
244
|
+
}): Promise<ReaderEpochWrap>;
|
|
245
|
+
requestRevocationDeny(target: {
|
|
246
|
+
capabilityId: Hex;
|
|
247
|
+
} | {
|
|
248
|
+
owner: Address;
|
|
249
|
+
agentId: Hex;
|
|
250
|
+
}): Promise<{
|
|
251
|
+
intentId: Hex;
|
|
252
|
+
state: string;
|
|
253
|
+
cancellationNonce: string;
|
|
254
|
+
}>;
|
|
255
|
+
cancelRevocation(intentId: Hex, input: {
|
|
256
|
+
expiresAt: bigint;
|
|
257
|
+
assertion: WebAuthnAssertionInput;
|
|
258
|
+
}): Promise<{
|
|
259
|
+
intentId: Hex;
|
|
260
|
+
state: string;
|
|
261
|
+
}>;
|
|
262
|
+
listRevocations(state?: DenyState): Promise<RevocationIntentView[]>;
|
|
263
|
+
reissueRevocationNonce(intentId: Hex): Promise<{
|
|
264
|
+
intentId: Hex;
|
|
265
|
+
state: string;
|
|
266
|
+
cancellationNonce: string;
|
|
267
|
+
}>;
|
|
268
|
+
batchStatus(): Promise<{
|
|
269
|
+
enabled: boolean;
|
|
270
|
+
batchAnchor?: Address;
|
|
271
|
+
reason?: string;
|
|
272
|
+
message?: string;
|
|
273
|
+
}>;
|
|
274
|
+
postBatchSave(body: BatchedSaveWire): Promise<{
|
|
275
|
+
state: "QUEUED";
|
|
276
|
+
receipt: BatchReceipt;
|
|
277
|
+
}>;
|
|
278
|
+
getBatchSave(contextId: Hex, capabilityId?: Hex): Promise<{
|
|
279
|
+
state: BatchedItemState | "REJECTED";
|
|
280
|
+
reason: string | null;
|
|
281
|
+
item?: BatchedReadItem;
|
|
282
|
+
}>;
|
|
283
|
+
listBatchSaves(input: {
|
|
284
|
+
owner: Address;
|
|
285
|
+
namespaceId: Hex;
|
|
286
|
+
capabilityId?: Hex;
|
|
287
|
+
}): Promise<{
|
|
288
|
+
items: BatchedReadItem[];
|
|
289
|
+
partial: boolean;
|
|
290
|
+
}>;
|
|
291
|
+
flushBatch(): Promise<{
|
|
292
|
+
flushed: boolean;
|
|
293
|
+
reason?: "empty" | "rate-limited";
|
|
294
|
+
}>;
|
|
295
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Address, Hex } from "../protocol/index.js";
|
|
2
|
+
import type { RegistryReader } from "./chain-views.js";
|
|
3
|
+
import type { DenyStore } from "./stores.js";
|
|
4
|
+
export type RevocationTarget = {
|
|
5
|
+
kind: "capability";
|
|
6
|
+
capabilityId: Hex;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "agent";
|
|
9
|
+
agentId: Hex;
|
|
10
|
+
};
|
|
11
|
+
export type DenyState = "active" | "anchored" | "cancelled";
|
|
12
|
+
export interface RevocationIntent {
|
|
13
|
+
id: Hex;
|
|
14
|
+
owner: Address;
|
|
15
|
+
target: RevocationTarget;
|
|
16
|
+
state: DenyState;
|
|
17
|
+
/** Owner-agent epoch when the intent was recorded; an agent revocation is anchored once the chain epoch exceeds it. */
|
|
18
|
+
agentEpochAtIntent: string | null;
|
|
19
|
+
/** Base-10 uint256 consumed by a successful cancellation; null once consumed or no longer cancellable. */
|
|
20
|
+
cancellationNonce: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** The persisted shape `denies`, `reconcile` and `cancel` dereference; a malformed entry fails construction. */
|
|
23
|
+
export declare function isRevocationIntent(value: unknown): value is RevocationIntent;
|
|
24
|
+
/**
|
|
25
|
+
* The file-backed DenyStore: the revocation intents as one JSON file, kept in memory once loaded and rewritten
|
|
26
|
+
* atomically on every mutation. An unreadable or malformed file fails closed at construction: starting empty would
|
|
27
|
+
* silently restore authority a pending deny removed, which §12.5 forbids. Only a missing file means a fresh start.
|
|
28
|
+
*/
|
|
29
|
+
export declare class FileDenyStore implements DenyStore {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(file: string);
|
|
32
|
+
list(): Promise<RevocationIntent[]>;
|
|
33
|
+
get(id: Hex): Promise<RevocationIntent | undefined>;
|
|
34
|
+
insert(intent: RevocationIntent): Promise<void>;
|
|
35
|
+
update(intent: RevocationIntent): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* §12.5 fast revocation overlay over a DenyStore. Exactly three transitions exist:
|
|
39
|
+
* active → anchored matching Monad revocation observed (reconcile)
|
|
40
|
+
* active → active transaction failed, missing or reorged out (no timeout ever clears a deny)
|
|
41
|
+
* active → cancelled fresh owner P256-approved cancellation (cancel)
|
|
42
|
+
* It can only reduce authority: `effectiveAllowed = currentlyAllowedByMonad AND NOT localDeny`.
|
|
43
|
+
*/
|
|
44
|
+
export declare class DenyOverlay {
|
|
45
|
+
#private;
|
|
46
|
+
constructor(store: DenyStore | string);
|
|
47
|
+
list(): Promise<RevocationIntent[]>;
|
|
48
|
+
get(id: Hex): Promise<RevocationIntent | undefined>;
|
|
49
|
+
create(owner: Address, target: RevocationTarget, agentEpochAtIntent: bigint | null): Promise<RevocationIntent>;
|
|
50
|
+
/** True when an active deny matches this owner and either this agent relationship or this exact capability. */
|
|
51
|
+
denies(input: {
|
|
52
|
+
owner: Address;
|
|
53
|
+
agentId: Hex;
|
|
54
|
+
capabilityId: Hex;
|
|
55
|
+
}): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* For wrap publication, which names no capability: an active deny on this agent, or on any of its capabilities in
|
|
58
|
+
* this namespace, blocks publication.
|
|
59
|
+
*/
|
|
60
|
+
deniesRelationship(reader: RegistryReader, input: {
|
|
61
|
+
owner: Address;
|
|
62
|
+
agentId: Hex;
|
|
63
|
+
namespaceId: Hex;
|
|
64
|
+
}): Promise<boolean>;
|
|
65
|
+
/** active → anchored only when Monad shows the matching revocation. Failed or missing transactions leave it active. */
|
|
66
|
+
reconcile(reader: RegistryReader): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* The same pass narrowed to one owner (M3-D6): a request authenticated as an owner must not
|
|
69
|
+
* spend chain reads on every other owner's intents — reconcile only what the caller may see.
|
|
70
|
+
*/
|
|
71
|
+
reconcileOwner(reader: RegistryReader, owner: Address): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Re-arms an active intent with a fresh cancellation nonce and retires the old ticket (M3-D4).
|
|
74
|
+
* The creation response is the only other place a nonce leaves the store; an owner clearing a
|
|
75
|
+
* stale deny it did not just stage — a revoke that failed on an earlier run — needs a new one.
|
|
76
|
+
* Anything but active refuses: an anchored intent must never be re-armed.
|
|
77
|
+
*/
|
|
78
|
+
reissueNonce(id: Hex, owner: Address): Promise<RevocationIntent>;
|
|
79
|
+
/** active → cancelled. The caller must already have verified a fresh P256 assertion over this nonce. */
|
|
80
|
+
cancel(id: Hex, owner: Address, nonce: bigint): Promise<RevocationIntent>;
|
|
81
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MidaErrorCode } from "../protocol/index.js";
|
|
2
|
+
export interface ApiErrorBody {
|
|
3
|
+
error: {
|
|
4
|
+
code: MidaErrorCode;
|
|
5
|
+
message: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare function statusFor(code: MidaErrorCode): number;
|
|
9
|
+
/**
|
|
10
|
+
* Unknown failures never leak internals and never read as authorization (in-6 R4). The chain
|
|
11
|
+
* error's kind picks the answer (in-11 R-8): a genuinely unreachable RPC — rate-limited, 5xx,
|
|
12
|
+
* dropped, timed out, or this request's own Monad read budget — is 503 CHAIN_UNAVAILABLE and
|
|
13
|
+
* retryable; an RPC that answered "no contract here" is 502 CHAIN_MISCONFIGURED and a provider
|
|
14
|
+
* that refused the key is 502 RPC_AUTH_REJECTED, neither retryable. A viem failure that is none
|
|
15
|
+
* of those — the chain answered something unexpected — is 500 INTERNAL_ERROR rather than a 503
|
|
16
|
+
* that would lie "unreachable". All still return no data (fail closed): the Sep 25 incident was
|
|
17
|
+
* a busy RPC wearing CAPABILITY_DENIED.
|
|
18
|
+
*
|
|
19
|
+
* `options.rpcHint` names THIS store's RPC-endpoint setting in the operator hint (in-14 F-4):
|
|
20
|
+
* the hosted Worker's is the RPC_URL variable; the local persistent store's is the rpcUrl key in
|
|
21
|
+
* network.json. A caller that passes neither gets a setting-neutral hint rather than a name that
|
|
22
|
+
* store does not have.
|
|
23
|
+
*/
|
|
24
|
+
export declare function toErrorBody(error: unknown, options?: {
|
|
25
|
+
rpcHint?: string;
|
|
26
|
+
}): {
|
|
27
|
+
status: number;
|
|
28
|
+
body: ApiErrorBody;
|
|
29
|
+
};
|
|
30
|
+
export declare function errorFromBody(status: number, body: unknown): Error;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ContextStores } from "./stores.js";
|
|
2
|
+
/**
|
|
3
|
+
* The default stores: one directory on the local filesystem holding the object metadata, content-addressed blobs,
|
|
4
|
+
* replay log and deny overlay. `createContextApi` builds these when it is given `dataDir` and no `stores`, so the
|
|
5
|
+
* local `midad` server and every existing test run exactly the app they ran before.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fileStores(dataDir: string): ContextStores;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from "./auth.js";
|
|
2
|
+
export * from "./authorize.js";
|
|
3
|
+
export * from "./batch-deny.js";
|
|
4
|
+
export * from "./batch-routes.js";
|
|
5
|
+
export * from "./batch-store.js";
|
|
6
|
+
export * from "./batcher.js";
|
|
7
|
+
export * from "./chain-budget.js";
|
|
8
|
+
export * from "./chain-views.js";
|
|
9
|
+
export * from "./client.js";
|
|
10
|
+
export * from "./deny-overlay.js";
|
|
11
|
+
export * from "./errors.js";
|
|
12
|
+
export * from "./file-stores.js";
|
|
13
|
+
export * from "./stores.js";
|
|
14
|
+
export * from "./verify-assertion.js";
|
|
15
|
+
export * from "./app.js";
|
|
16
|
+
export * from "./store.js";
|
|
17
|
+
export * from "./wire.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data tree holds ciphertext, reader wraps and auth state — it is user-only: every directory
|
|
3
|
+
* is mode 0700 and every file 0600, the same rules the midad home follows. `secureDir` makes a
|
|
4
|
+
* folder (and every ancestor down to `root`) exist at 0700, `writeJsonAtomic` is the home's
|
|
5
|
+
* writeSecretJson shape (temp file opened "wx" 0600, fsynced, renamed over the target, parent
|
|
6
|
+
* fsynced), and `repairModes` re-pins an existing tree at startup so a folder made before these
|
|
7
|
+
* rules — or chmodded by hand — is corrected rather than trusted.
|
|
8
|
+
*/
|
|
9
|
+
export declare function secureDir(root: string, dir: string): void;
|
|
10
|
+
export declare function writeJsonAtomic(root: string, path: string, value: unknown): void;
|
|
11
|
+
/** Creates `root` at 0700 if absent, then walks the whole tree pinning dirs to 0700 and files to 0600. */
|
|
12
|
+
export declare function repairModes(root: string): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Address, Hex, ObjectManifest, ReaderEpochWrap } from "../protocol/index.js";
|
|
2
|
+
import { FsStorage } from "../storage/index.js";
|
|
3
|
+
import type { ManifestIndexEntry, ObjectStore, WrapKey } from "./stores.js";
|
|
4
|
+
/** A ciphertext upload's immutable metadata. Served as context only after Monad holds matching commitments (§12.2). */
|
|
5
|
+
export interface StoredObject {
|
|
6
|
+
contextId: Hex;
|
|
7
|
+
owner: Address;
|
|
8
|
+
/** The authenticated signer that uploaded it; feeds the per-signer quotas. Absent in pre-M3 files → read as owner. */
|
|
9
|
+
uploader: Address;
|
|
10
|
+
namespaceId: Hex;
|
|
11
|
+
authorId: Hex;
|
|
12
|
+
objectNonce: Hex;
|
|
13
|
+
expectedParentId: Hex;
|
|
14
|
+
manifest: ObjectManifest;
|
|
15
|
+
manifestHash: Hex;
|
|
16
|
+
uploadedAt: string;
|
|
17
|
+
/**
|
|
18
|
+
* ISO-8601 UTC of the first verified isAnchored match, or null while pending. Set once, never cleared:
|
|
19
|
+
* a Monad record cannot be un-registered, so a row that matched once matches forever. Absent in
|
|
20
|
+
* pre-M3-A2 files → read as null (pending), which is always safe — it only costs a re-check.
|
|
21
|
+
*/
|
|
22
|
+
anchoredAt: string | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The file-backed ObjectStore, the default when `createContextApi` is given a data directory: content-addressed
|
|
26
|
+
* blobs in FsStorage (ciphertext and signed manifest envelopes), plus JSON files for object metadata, reader wraps,
|
|
27
|
+
* the manifest body-hash index and the per-signer PUT counts. Every path segment is validated hex. Being a
|
|
28
|
+
* single-writer file store, its check-and-record operations are trivially atomic.
|
|
29
|
+
*/
|
|
30
|
+
export declare class ApiStore implements ObjectStore {
|
|
31
|
+
#private;
|
|
32
|
+
readonly blobs: FsStorage;
|
|
33
|
+
constructor(dataDir: string);
|
|
34
|
+
putObject(object: StoredObject): Promise<void>;
|
|
35
|
+
getObject(contextId: Hex): Promise<StoredObject | undefined>;
|
|
36
|
+
listObjects(owner: Address, namespaceId: Hex): Promise<StoredObject[]>;
|
|
37
|
+
putObjectWithinPending(object: StoredObject, maxPendingBytes: number, blob: Uint8Array, pendingSince?: Date): Promise<"stored" | "repeat" | "over-cap">;
|
|
38
|
+
pendingByUploader(uploader: Address): Promise<StoredObject[]>;
|
|
39
|
+
markAnchored(contextId: Hex, anchoredAt: string): Promise<void>;
|
|
40
|
+
putWrap(wrap: ReaderEpochWrap): Promise<void>;
|
|
41
|
+
getWrap(key: WrapKey): Promise<ReaderEpochWrap | undefined>;
|
|
42
|
+
setManifestIndex(bodyHash: Hex, envelopeHash: Hex, opts?: {
|
|
43
|
+
storedAt?: string;
|
|
44
|
+
verifiedAt?: string | null;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
getManifestIndex(bodyHash: Hex): Promise<ManifestIndexEntry | undefined>;
|
|
47
|
+
recordPut(signer: Address, day: string): Promise<number>;
|
|
48
|
+
recordManifestPut(signer: Address, day: string): Promise<number>;
|
|
49
|
+
sweepPending(olderThan: Date, stillPending: (object: StoredObject) => Promise<boolean>, blobGraceCutoff?: Date): Promise<number>;
|
|
50
|
+
sweepManifests(olderThan: Date, blobGraceCutoff?: Date): Promise<number>;
|
|
51
|
+
}
|