@tangle-network/agent-provider-tangle 0.14.0 → 0.14.2
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 +61 -54
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/tangle-confidential-attestation.d.ts +31 -0
- package/dist/tangle-confidential-attestation.js +165 -0
- package/dist/tangle-interactive.d.ts +2 -2
- package/dist/tangle-interactive.js +95 -140
- package/dist/tangle-types.d.ts +36 -45
- package/dist/tangle-workspace-branching.js +294 -73
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# @tangle-network/agent-provider-tangle
|
|
2
2
|
|
|
3
3
|
Wraps `@tangle-network/sandbox` as an `AgentEnvironmentProvider`.
|
|
4
|
-
The peer range is `>=0.
|
|
5
|
-
The floor is 0.
|
|
4
|
+
The peer range is `>=0.34.0 <1.0.0`, and this package is developed and tested against 0.34.3.
|
|
5
|
+
The floor is 0.34.0 because workspace branching uses the keyed snapshot, fork,
|
|
6
6
|
lookup, and cleanup operations added to that SDK.
|
|
7
7
|
The provider fails closed when the configured backend or its catalog entry cannot be read.
|
|
8
8
|
Newer SDKs may also provide `getBackend()` as a lookup over the same catalog.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { Sandbox } from
|
|
12
|
-
import { createTangleProvider } from
|
|
11
|
+
import { Sandbox } from "@tangle-network/sandbox";
|
|
12
|
+
import { createTangleProvider } from "@tangle-network/agent-provider-tangle";
|
|
13
13
|
|
|
14
14
|
const provider = createTangleProvider({
|
|
15
15
|
client: new Sandbox({ apiKey: process.env.TANGLE_API_KEY }),
|
|
16
|
-
})
|
|
16
|
+
});
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Detached dispatch returns the immutable Sandbox execution receipt in `controlRef`.
|
|
@@ -43,16 +43,16 @@ One provider reaches deployments of different ages, which is why the environment
|
|
|
43
43
|
|
|
44
44
|
Each deployment flag this adapter reads gates the claims it backs, and no flag is read that gates nothing:
|
|
45
45
|
|
|
46
|
-
| Deployment flag
|
|
47
|
-
|
|
|
48
|
-
| `dispatch.runControlRef`
|
|
46
|
+
| Deployment flag | Claims it gates |
|
|
47
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
48
|
+
| `dispatch.runControlRef` | `streaming.detach`, `streaming.turnIdempotency`, `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
49
49
|
| `dispatch.executionIdOnAdmission` | `streaming.detach`, `streaming.turnIdempotency`, `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
50
|
-
| `cancel.canonicalRunCancellation` | `sessions.continue`, `retainedControl`, `session.cancelRun`
|
|
51
|
-
| `cancel.digestBound`
|
|
52
|
-
| `cancel.idempotent`
|
|
53
|
-
| `runs.eventReplay`
|
|
54
|
-
| `runs.executionScopedStatus`
|
|
55
|
-
| `interactions.responseDedupe`
|
|
50
|
+
| `cancel.canonicalRunCancellation` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
51
|
+
| `cancel.digestBound` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
52
|
+
| `cancel.idempotent` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
53
|
+
| `runs.eventReplay` | `streaming.replay`, `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
54
|
+
| `runs.executionScopedStatus` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
55
|
+
| `interactions.responseDedupe` | `interactions`, `environment.respondToInteraction`, `session.respondToInteraction` |
|
|
56
56
|
|
|
57
57
|
Detached dispatch carries the caller's exact reference and refuses a receipt that does not name the execution back, so it needs both `dispatch` flags and a session handle to reach the run through.
|
|
58
58
|
`sessions.continue`, `retainedControl`, and `session.cancelRun` need every flag in the table, because the capability schema refuses a partial retained-control block and each identity rests on its own flag.
|
|
@@ -70,19 +70,19 @@ The claim and the two methods stand or fall together.
|
|
|
70
70
|
It is offered on the environment and on a session handle; the environment routes the command to the session its binding names.
|
|
71
71
|
The command carries only the answer the caller supplied. No field is filled in on the caller's behalf, and an answer the outstanding ask's spec rejects is refused with the field named.
|
|
72
72
|
|
|
73
|
-
| Result
|
|
74
|
-
|
|
|
75
|
-
| The deployment recorded and delivered the response
|
|
76
|
-
| The deployment already holds this exact response
|
|
73
|
+
| Result | Acknowledgement status |
|
|
74
|
+
| ------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
75
|
+
| The deployment recorded and delivered the response | `accepted` |
|
|
76
|
+
| The deployment already holds this exact response | `already_resolved_same` |
|
|
77
77
|
| The deployment already holds a different response for the ask | `already_resolved_different`, message naming the recorded digest |
|
|
78
|
-
| The command names another provider, environment, or session
|
|
79
|
-
| The deployment refuses the binding
|
|
80
|
-
| The ask is unknown to the deployment
|
|
81
|
-
| The session is unknown to the deployment
|
|
82
|
-
| The ask left the outstanding set on a deadline
|
|
83
|
-
| The ask's spec rejects the answer
|
|
84
|
-
| The response is recorded and delivery is unconfirmed
|
|
85
|
-
| The request did not reach the route
|
|
78
|
+
| The command names another provider, environment, or session | `binding_mismatch` |
|
|
79
|
+
| The deployment refuses the binding | `binding_mismatch` |
|
|
80
|
+
| The ask is unknown to the deployment | `unknown_interaction` |
|
|
81
|
+
| The session is unknown to the deployment | `unknown_run` |
|
|
82
|
+
| The ask left the outstanding set on a deadline | `expired` |
|
|
83
|
+
| The ask's spec rejects the answer | `invalid_response`, message naming each field |
|
|
84
|
+
| The response is recorded and delivery is unconfirmed | `transport_failure`, `retryable: true` |
|
|
85
|
+
| The request did not reach the route | `transport_failure`, `retryable: true` |
|
|
86
86
|
|
|
87
87
|
A refusal is never reported as a success, and a recorded response whose delivery the deployment does not confirm is never reported as `accepted`.
|
|
88
88
|
|
|
@@ -150,24 +150,24 @@ Clean branch resources before deleting the source or use a platform reaper.
|
|
|
150
150
|
import {
|
|
151
151
|
workspaceCheckpointRequestDigest,
|
|
152
152
|
workspaceForkRequestDigest,
|
|
153
|
-
} from
|
|
153
|
+
} from "@tangle-network/agent-interface";
|
|
154
154
|
|
|
155
155
|
const checkpoint = await environment.workspaceBranching?.checkpoint({
|
|
156
156
|
source: exactRun,
|
|
157
|
-
idempotencyKey:
|
|
157
|
+
idempotencyKey: "checkpoint-before-analysis",
|
|
158
158
|
requestDigest: workspaceCheckpointRequestDigest({ source: exactRun }),
|
|
159
|
-
})
|
|
159
|
+
});
|
|
160
160
|
|
|
161
|
-
if (checkpoint?.status ===
|
|
161
|
+
if (checkpoint?.status === "created" || checkpoint?.status === "replayed") {
|
|
162
162
|
const fork = await environment.workspaceBranching?.fork({
|
|
163
163
|
checkpoint: checkpoint.checkpoint,
|
|
164
|
-
placement: { kind:
|
|
165
|
-
idempotencyKey:
|
|
164
|
+
placement: { kind: "sandbox", sandboxId: "analysis-worker" },
|
|
165
|
+
idempotencyKey: "analysis-worker",
|
|
166
166
|
requestDigest: workspaceForkRequestDigest({
|
|
167
167
|
checkpoint: checkpoint.checkpoint,
|
|
168
|
-
placement: { kind:
|
|
168
|
+
placement: { kind: "sandbox", sandboxId: "analysis-worker" },
|
|
169
169
|
}),
|
|
170
|
-
})
|
|
170
|
+
});
|
|
171
171
|
}
|
|
172
172
|
```
|
|
173
173
|
|
|
@@ -192,28 +192,35 @@ const provider = createTangleProvider({
|
|
|
192
192
|
client: new Sandbox({ apiKey: process.env.TANGLE_API_KEY }),
|
|
193
193
|
confidentialAttestationVerifier: async ({ report, attestation }) =>
|
|
194
194
|
(await verifyTangleQuote({ report, attestation })) ?? null,
|
|
195
|
-
})
|
|
195
|
+
});
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
The provider stores the raw report in `ConfidentialAttestation.quote` through
|
|
199
|
+
`encodeTangleConfidentialAttestationQuote`.
|
|
200
|
+
The quote is a versioned canonical JSON object with base64url byte fields.
|
|
201
|
+
Use `decodeTangleConfidentialAttestationQuote` to recover a report and reject
|
|
202
|
+
legacy numeric-array quotes, unknown fields, malformed bytes, and oversized
|
|
203
|
+
reports before verification.
|
|
204
|
+
|
|
198
205
|
## Environment observation
|
|
199
206
|
|
|
200
207
|
`environment.observe()` returns the normalized `AgentEnvironmentObservation`.
|
|
201
208
|
Every surface carries a freshness discriminator, so a value the Sandbox SDK does not report is visibly absent instead of arriving as a measured zero.
|
|
202
209
|
The `observation` capability flag for a surface is true only when a source can put a value on it for that environment; the observation itself always carries the surface, with `unavailable` and its reason when there is nothing to report.
|
|
203
210
|
|
|
204
|
-
| Surface
|
|
205
|
-
|
|
|
206
|
-
| `identity`
|
|
207
|
-
| `lifecycle.status`
|
|
208
|
-
| `lifecycle.cleanup`
|
|
209
|
-
| `endpoint`
|
|
210
|
-
| `placement.verified`
|
|
211
|
-
| `resources.requested`
|
|
212
|
-
| `resources.effective`
|
|
213
|
-
| `resourceUse.current` / `peak` | cgroup `memoryCurrentMb` / `memoryPeakMb`
|
|
214
|
-
| `modelUsage`
|
|
215
|
-
| `computeBilling`
|
|
216
|
-
| `accountUsage`
|
|
211
|
+
| Surface | Sandbox source | State when the source is missing |
|
|
212
|
+
| ------------------------------ | ---------------------------------------------------------- | --------------------------------------- |
|
|
213
|
+
| `identity` | provider name and `box.id` | always known |
|
|
214
|
+
| `lifecycle.status` | `box.status` after a refresh | `stale` with the refresh failure |
|
|
215
|
+
| `lifecycle.cleanup` | `box.expiresAt` as a scheduled retirement | omitted from the lifecycle value |
|
|
216
|
+
| `endpoint` | scheme, host, and explicit port of `connection.runtimeUrl` | `unavailable` |
|
|
217
|
+
| `placement.verified` | `client.describePlacement(box)` | `unavailable` |
|
|
218
|
+
| `resources.requested` | the `create()` resource request | omitted on an environment rebuilt by id |
|
|
219
|
+
| `resources.effective` | cgroup `memoryLimitMb` and the attached GPU lease | `unavailable` |
|
|
220
|
+
| `resourceUse.current` / `peak` | cgroup `memoryCurrentMb` / `memoryPeakMb` | `unavailable` |
|
|
221
|
+
| `modelUsage` | the newest execution this handle measured | `unavailable` |
|
|
222
|
+
| `computeBilling` | the GPU lease's billed or estimated customer cost | `unavailable` |
|
|
223
|
+
| `accountUsage` | `client.subscription()` and `client.usage()` | `unavailable` |
|
|
217
224
|
|
|
218
225
|
Four quantities have no Sandbox source at all, and the adapter reports them as absent rather than deriving them.
|
|
219
226
|
There is no effective CPU or disk figure anywhere in the SDK, so `resources.effective` carries memory and the accelerator only.
|
|
@@ -259,16 +266,16 @@ Set `teamId` inside `exactProcess` to scope create, lookup, and recovery to one
|
|
|
259
266
|
const provider = createTangleProvider({
|
|
260
267
|
client: new Sandbox({ apiKey: process.env.TANGLE_API_KEY }),
|
|
261
268
|
exactProcess: {},
|
|
262
|
-
})
|
|
269
|
+
});
|
|
263
270
|
|
|
264
271
|
const environment = await provider.exactProcess!.create({
|
|
265
|
-
image:
|
|
266
|
-
egress: { mode:
|
|
272
|
+
image: "ghcr.io/acme/agent@sha256:<64-hex-manifest-digest>",
|
|
273
|
+
egress: { mode: "blocked" },
|
|
267
274
|
maxLifetimeMs: 120_000,
|
|
268
275
|
resources: { cpu: 1, memoryMb: 1024, diskMb: 1024 },
|
|
269
|
-
metadata: { executionId:
|
|
270
|
-
idempotencyKey:
|
|
271
|
-
})
|
|
276
|
+
metadata: { executionId: "run-1" },
|
|
277
|
+
idempotencyKey: "run-1",
|
|
278
|
+
});
|
|
272
279
|
```
|
|
273
280
|
|
|
274
281
|
The adapter rejects ordinary sandboxes during create, recovery, and list operations.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export { createTangleProvider } from "./tangle-provider.js";
|
|
|
4
4
|
export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
|
|
5
5
|
export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
6
6
|
export type { TangleWorkspaceBranchingOptions } from "./tangle-workspace-branching.js";
|
|
7
|
+
export { decodeTangleConfidentialAttestationQuote, encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION, } from "./tangle-confidential-attestation.js";
|
|
8
|
+
export type { TangleConfidentialAttestationReport } from "./tangle-confidential-attestation.js";
|
|
7
9
|
export { safeEndpointFromConnection } from "./tangle-observation.js";
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export * from "./tangle-types.js";
|
|
|
2
2
|
export { createTangleProvider } from "./tangle-provider.js";
|
|
3
3
|
export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
|
|
4
4
|
export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
5
|
+
export { decodeTangleConfidentialAttestationQuote, encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION, } from "./tangle-confidential-attestation.js";
|
|
5
6
|
export { safeEndpointFromConnection } from "./tangle-observation.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SandboxTeeAttestationReportLike } from "./tangle-types.js";
|
|
2
|
+
/** Version of the canonical opaque quote carried by a confidential attestation. */
|
|
3
|
+
export declare const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION: 1;
|
|
4
|
+
/** Kind discriminator prevents this codec from accepting another quote format. */
|
|
5
|
+
export declare const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND: "tangle-sandbox-tee";
|
|
6
|
+
/** Raw evidence bound before it can enter a portable attestation. */
|
|
7
|
+
export declare const MAX_TEE_EVIDENCE_BYTES = 16384;
|
|
8
|
+
/** Raw measurement bound before it can enter a portable attestation. */
|
|
9
|
+
export declare const MAX_TEE_MEASUREMENT_BYTES = 256;
|
|
10
|
+
/** Read-only report shape accepted by the quote encoder. */
|
|
11
|
+
export interface TangleConfidentialAttestationReport {
|
|
12
|
+
readonly tee_type: string;
|
|
13
|
+
readonly evidence: readonly number[];
|
|
14
|
+
readonly measurement: readonly number[];
|
|
15
|
+
readonly timestamp: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Encode a Sandbox TEE report without decimal JSON byte arrays.
|
|
19
|
+
*
|
|
20
|
+
* The returned JSON uses a fixed key order and unpadded base64url byte fields.
|
|
21
|
+
* `undefined` means the report is not a valid bounded report or the quote does
|
|
22
|
+
* not fit the shared confidential-attestation field.
|
|
23
|
+
*/
|
|
24
|
+
export declare function encodeTangleConfidentialAttestationQuote(report: TangleConfidentialAttestationReport): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Decode and canonicalize a provider quote.
|
|
27
|
+
*
|
|
28
|
+
* The parser rejects old numeric-array quotes, unknown fields, non-canonical
|
|
29
|
+
* JSON, malformed base64url, and reports outside the provider bounds.
|
|
30
|
+
*/
|
|
31
|
+
export declare function decodeTangleConfidentialAttestationQuote(quote: unknown): SandboxTeeAttestationReportLike | undefined;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH, } from "@tangle-network/agent-interface";
|
|
2
|
+
/** Version of the canonical opaque quote carried by a confidential attestation. */
|
|
3
|
+
export const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION = 1;
|
|
4
|
+
/** Kind discriminator prevents this codec from accepting another quote format. */
|
|
5
|
+
export const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND = "tangle-sandbox-tee";
|
|
6
|
+
/** Raw evidence bound before it can enter a portable attestation. */
|
|
7
|
+
export const MAX_TEE_EVIDENCE_BYTES = 16_384;
|
|
8
|
+
/** Raw measurement bound before it can enter a portable attestation. */
|
|
9
|
+
export const MAX_TEE_MEASUREMENT_BYTES = 256;
|
|
10
|
+
const MAX_TEE_TYPE_LENGTH = 64;
|
|
11
|
+
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/u;
|
|
12
|
+
const QUOTE_KEYS = [
|
|
13
|
+
"version",
|
|
14
|
+
"kind",
|
|
15
|
+
"tee_type",
|
|
16
|
+
"evidence",
|
|
17
|
+
"measurement",
|
|
18
|
+
"timestamp",
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Encode a Sandbox TEE report without decimal JSON byte arrays.
|
|
22
|
+
*
|
|
23
|
+
* The returned JSON uses a fixed key order and unpadded base64url byte fields.
|
|
24
|
+
* `undefined` means the report is not a valid bounded report or the quote does
|
|
25
|
+
* not fit the shared confidential-attestation field.
|
|
26
|
+
*/
|
|
27
|
+
export function encodeTangleConfidentialAttestationQuote(report) {
|
|
28
|
+
if (!validReportShape(report))
|
|
29
|
+
return undefined;
|
|
30
|
+
const document = {
|
|
31
|
+
version: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION,
|
|
32
|
+
kind: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND,
|
|
33
|
+
tee_type: report.tee_type,
|
|
34
|
+
evidence: bytesToBase64url(report.evidence),
|
|
35
|
+
measurement: bytesToBase64url(report.measurement),
|
|
36
|
+
timestamp: report.timestamp,
|
|
37
|
+
};
|
|
38
|
+
const quote = JSON.stringify(document);
|
|
39
|
+
return quote.length <= CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH
|
|
40
|
+
? quote
|
|
41
|
+
: undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decode and canonicalize a provider quote.
|
|
45
|
+
*
|
|
46
|
+
* The parser rejects old numeric-array quotes, unknown fields, non-canonical
|
|
47
|
+
* JSON, malformed base64url, and reports outside the provider bounds.
|
|
48
|
+
*/
|
|
49
|
+
export function decodeTangleConfidentialAttestationQuote(quote) {
|
|
50
|
+
if (typeof quote !== "string" ||
|
|
51
|
+
quote.length === 0 ||
|
|
52
|
+
quote.length > CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(quote);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const document = quoteDocument(parsed);
|
|
63
|
+
if (document === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
const report = reportFromDocument(document);
|
|
66
|
+
if (report === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
return encodeTangleConfidentialAttestationQuote(report) === quote
|
|
69
|
+
? report
|
|
70
|
+
: undefined;
|
|
71
|
+
}
|
|
72
|
+
function validReportShape(report) {
|
|
73
|
+
if (!isPlainObject(report))
|
|
74
|
+
return false;
|
|
75
|
+
if (!hasExactKeys(report, ["tee_type", "evidence", "measurement", "timestamp"])) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return (validTeeType(report.tee_type) &&
|
|
79
|
+
validBytes(report.evidence, MAX_TEE_EVIDENCE_BYTES) &&
|
|
80
|
+
validBytes(report.measurement, MAX_TEE_MEASUREMENT_BYTES) &&
|
|
81
|
+
Number.isSafeInteger(report.timestamp) &&
|
|
82
|
+
report.timestamp > 0);
|
|
83
|
+
}
|
|
84
|
+
function quoteDocument(value) {
|
|
85
|
+
if (!isPlainObject(value) || !hasExactKeys(value, QUOTE_KEYS))
|
|
86
|
+
return undefined;
|
|
87
|
+
const candidate = value;
|
|
88
|
+
const teeType = candidate.tee_type;
|
|
89
|
+
const evidenceValue = candidate.evidence;
|
|
90
|
+
const measurementValue = candidate.measurement;
|
|
91
|
+
const timestamp = candidate.timestamp;
|
|
92
|
+
if (candidate.version !== TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION ||
|
|
93
|
+
candidate.kind !== TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND ||
|
|
94
|
+
!validTeeType(teeType) ||
|
|
95
|
+
typeof evidenceValue !== "string" ||
|
|
96
|
+
typeof measurementValue !== "string" ||
|
|
97
|
+
typeof timestamp !== "number" ||
|
|
98
|
+
!Number.isSafeInteger(timestamp) ||
|
|
99
|
+
timestamp <= 0) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const evidence = base64urlToBytes(evidenceValue, MAX_TEE_EVIDENCE_BYTES);
|
|
103
|
+
const measurement = base64urlToBytes(measurementValue, MAX_TEE_MEASUREMENT_BYTES);
|
|
104
|
+
if (evidence === undefined || measurement === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
version: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION,
|
|
108
|
+
kind: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND,
|
|
109
|
+
tee_type: teeType,
|
|
110
|
+
evidence: evidenceValue,
|
|
111
|
+
measurement: measurementValue,
|
|
112
|
+
timestamp,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function reportFromDocument(document) {
|
|
116
|
+
const evidence = base64urlToBytes(document.evidence, MAX_TEE_EVIDENCE_BYTES);
|
|
117
|
+
const measurement = base64urlToBytes(document.measurement, MAX_TEE_MEASUREMENT_BYTES);
|
|
118
|
+
if (evidence === undefined || measurement === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
const report = {
|
|
121
|
+
tee_type: document.tee_type,
|
|
122
|
+
evidence: Array.from(evidence),
|
|
123
|
+
measurement: Array.from(measurement),
|
|
124
|
+
timestamp: document.timestamp,
|
|
125
|
+
};
|
|
126
|
+
return validReportShape(report) ? report : undefined;
|
|
127
|
+
}
|
|
128
|
+
function validTeeType(value) {
|
|
129
|
+
return (typeof value === "string" &&
|
|
130
|
+
value.length > 0 &&
|
|
131
|
+
value.length <= MAX_TEE_TYPE_LENGTH &&
|
|
132
|
+
value.trim() === value &&
|
|
133
|
+
!/[\u0000-\u001f\u007f]/u.test(value));
|
|
134
|
+
}
|
|
135
|
+
function validBytes(value, maxLength) {
|
|
136
|
+
return (Array.isArray(value) &&
|
|
137
|
+
value.length > 0 &&
|
|
138
|
+
value.length <= maxLength &&
|
|
139
|
+
value.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255));
|
|
140
|
+
}
|
|
141
|
+
function bytesToBase64url(bytes) {
|
|
142
|
+
return Buffer.from(Uint8Array.from(bytes)).toString("base64url");
|
|
143
|
+
}
|
|
144
|
+
function base64urlToBytes(value, maxLength) {
|
|
145
|
+
if (value.length === 0 || !BASE64URL_PATTERN.test(value))
|
|
146
|
+
return undefined;
|
|
147
|
+
const bytes = Buffer.from(value, "base64url");
|
|
148
|
+
if (bytes.length === 0 ||
|
|
149
|
+
bytes.length > maxLength ||
|
|
150
|
+
bytes.toString("base64url") !== value) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return bytes;
|
|
154
|
+
}
|
|
155
|
+
function isPlainObject(value) {
|
|
156
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
const prototype = Object.getPrototypeOf(value);
|
|
160
|
+
return prototype === Object.prototype || prototype === null;
|
|
161
|
+
}
|
|
162
|
+
function hasExactKeys(value, expected) {
|
|
163
|
+
const keys = Object.keys(value);
|
|
164
|
+
return keys.length === expected.length && keys.every((key) => expected.includes(key));
|
|
165
|
+
}
|
|
@@ -10,8 +10,8 @@ export interface TangleInteractiveAgentRegistry {
|
|
|
10
10
|
/**
|
|
11
11
|
* Test the linked SDK surface without accepting any older interactive shape.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Sandbox owns the exact reference, control claim, terminal binding, and
|
|
14
|
+
* mutation acknowledgements. An older handle shape fails this test closed.
|
|
15
15
|
*/
|
|
16
16
|
export declare function sandboxBacksInteractiveAgent(box: SandboxInstanceLike): boolean;
|
|
17
17
|
/**
|