@kaana.ai/contracts 0.0.0-stage → 4.4.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 +673 -0
- package/NOTICE +21 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +29 -0
- package/dist/internal/attribution.d.ts +116 -0
- package/dist/internal/attribution.js +82 -0
- package/dist/internal/canonical.d.ts +31 -0
- package/dist/internal/canonical.js +57 -0
- package/dist/internal/catalogue.d.ts +1059 -0
- package/dist/internal/catalogue.js +389 -0
- package/dist/internal/credentialControl.d.ts +329 -0
- package/dist/internal/credentialControl.js +256 -0
- package/dist/internal/decisions.d.ts +245 -0
- package/dist/internal/decisions.js +69 -0
- package/dist/internal/economics.d.ts +27 -0
- package/dist/internal/economics.js +26 -0
- package/dist/internal/embeddings.d.ts +164 -0
- package/dist/internal/embeddings.js +59 -0
- package/dist/internal/errors.d.ts +185 -0
- package/dist/internal/errors.js +211 -0
- package/dist/internal/grant.d.ts +699 -0
- package/dist/internal/grant.js +445 -0
- package/dist/internal/identifiers.d.ts +63 -0
- package/dist/internal/identifiers.js +65 -0
- package/dist/internal/privateAutoExecution.d.ts +158 -0
- package/dist/internal/privateAutoExecution.js +188 -0
- package/dist/internal/realtime.d.ts +1005 -0
- package/dist/internal/realtime.js +187 -0
- package/dist/internal/request.d.ts +1079 -0
- package/dist/internal/request.js +404 -0
- package/dist/internal/routing.d.ts +90 -0
- package/dist/internal/routing.js +139 -0
- package/dist/internal/scopedExecution.d.ts +74 -0
- package/dist/internal/scopedExecution.js +61 -0
- package/dist/internal/settlement.d.ts +205 -0
- package/dist/internal/settlement.js +71 -0
- package/dist/internal/streamEvents.d.ts +422 -0
- package/dist/internal/streamEvents.js +171 -0
- package/dist/internal/usage.d.ts +127 -0
- package/dist/internal/usage.js +109 -0
- package/dist/internal.d.ts +30 -0
- package/dist/internal.js +30 -0
- package/dist/public/audio.d.ts +347 -0
- package/dist/public/audio.js +236 -0
- package/dist/public/audioFormats.d.ts +64 -0
- package/dist/public/audioFormats.js +60 -0
- package/dist/public/catalogue.d.ts +914 -0
- package/dist/public/catalogue.js +516 -0
- package/dist/public/chatCompletions.d.ts +381 -0
- package/dist/public/chatCompletions.js +321 -0
- package/dist/public/decisions.d.ts +182 -0
- package/dist/public/decisions.js +257 -0
- package/dist/public/embeddings.d.ts +34 -0
- package/dist/public/embeddings.js +66 -0
- package/dist/public/errors.d.ts +195 -0
- package/dist/public/errors.js +274 -0
- package/dist/public/generations.d.ts +330 -0
- package/dist/public/generations.js +121 -0
- package/dist/public/identifiers.d.ts +139 -0
- package/dist/public/identifiers.js +160 -0
- package/dist/public/images.d.ts +36 -0
- package/dist/public/images.js +36 -0
- package/dist/public/messages.d.ts +297 -0
- package/dist/public/messages.js +251 -0
- package/dist/public/money.d.ts +308 -0
- package/dist/public/money.js +237 -0
- package/dist/public/policy.d.ts +19 -0
- package/dist/public/policy.js +20 -0
- package/dist/public/realtime.d.ts +2006 -0
- package/dist/public/realtime.js +797 -0
- package/dist/public/responses.d.ts +253 -0
- package/dist/public/responses.js +95 -0
- package/dist/public/streamEvents.d.ts +372 -0
- package/dist/public/streamEvents.js +157 -0
- package/dist/public/version.d.ts +184 -0
- package/dist/public/version.js +184 -0
- package/openapi.json +6967 -0
- package/package.json +44 -4
- package/README.md +0 -3
package/NOTICE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
============================================================================
|
|
2
|
+
NOTICE
|
|
3
|
+
@kaana.ai/contracts
|
|
4
|
+
Copyright (c) 2026-present The Oxy Collective, Inc.
|
|
5
|
+
============================================================================
|
|
6
|
+
|
|
7
|
+
This product is licensed under the Breathe License 1.0, license identifier
|
|
8
|
+
LicenseRef-Breathe-1.0. See the LICENSE file in this directory. Commercial
|
|
9
|
+
terms are available at
|
|
10
|
+
https://github.com/OxyHQ/.github/blob/main/LICENSE-COMMERCIAL.md
|
|
11
|
+
|
|
12
|
+
Attribution under Section 3.1 of the Breathe License is required of
|
|
13
|
+
everyone, including paying commercial licensees, and cannot be waived.
|
|
14
|
+
|
|
15
|
+
----------------------------------------------------------------------------
|
|
16
|
+
THIRD PARTY COMPONENTS
|
|
17
|
+
----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
This package contains no third party source. Its dependencies are resolved
|
|
20
|
+
and installed by the package manager and remain governed by their own
|
|
21
|
+
licenses.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kaana.ai/contracts` — the public contract of `https://kaana.ai/v1`.
|
|
3
|
+
*
|
|
4
|
+
* Every request and response of the public API, its stream events, the public
|
|
5
|
+
* error vocabulary, the catalogue with prices, power levels, audio
|
|
6
|
+
* capabilities and generation records, as Zod schemas with their inferred
|
|
7
|
+
* types. The shapes Oxy and Kaana exchange privately live under
|
|
8
|
+
* `@kaana.ai/contracts/internal` and are not part of the public API.
|
|
9
|
+
*
|
|
10
|
+
* Accounts, applications, `oxy_sk_…` keys, limits and billing are managed in
|
|
11
|
+
* Oxy Console (`https://console.oxy.so`), never here.
|
|
12
|
+
*/
|
|
13
|
+
export * from './public/audio.js';
|
|
14
|
+
export * from './public/audioFormats.js';
|
|
15
|
+
export * from './public/catalogue.js';
|
|
16
|
+
export * from './public/chatCompletions.js';
|
|
17
|
+
export * from './public/decisions.js';
|
|
18
|
+
export * from './public/embeddings.js';
|
|
19
|
+
export * from './public/errors.js';
|
|
20
|
+
export * from './public/generations.js';
|
|
21
|
+
export * from './public/identifiers.js';
|
|
22
|
+
export * from './public/images.js';
|
|
23
|
+
export * from './public/messages.js';
|
|
24
|
+
export * from './public/money.js';
|
|
25
|
+
export * from './public/policy.js';
|
|
26
|
+
export * from './public/realtime.js';
|
|
27
|
+
export * from './public/responses.js';
|
|
28
|
+
export * from './public/streamEvents.js';
|
|
29
|
+
export * from './public/version.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kaana.ai/contracts` — the public contract of `https://kaana.ai/v1`.
|
|
3
|
+
*
|
|
4
|
+
* Every request and response of the public API, its stream events, the public
|
|
5
|
+
* error vocabulary, the catalogue with prices, power levels, audio
|
|
6
|
+
* capabilities and generation records, as Zod schemas with their inferred
|
|
7
|
+
* types. The shapes Oxy and Kaana exchange privately live under
|
|
8
|
+
* `@kaana.ai/contracts/internal` and are not part of the public API.
|
|
9
|
+
*
|
|
10
|
+
* Accounts, applications, `oxy_sk_…` keys, limits and billing are managed in
|
|
11
|
+
* Oxy Console (`https://console.oxy.so`), never here.
|
|
12
|
+
*/
|
|
13
|
+
export * from './public/audio.js';
|
|
14
|
+
export * from './public/audioFormats.js';
|
|
15
|
+
export * from './public/catalogue.js';
|
|
16
|
+
export * from './public/chatCompletions.js';
|
|
17
|
+
export * from './public/decisions.js';
|
|
18
|
+
export * from './public/embeddings.js';
|
|
19
|
+
export * from './public/errors.js';
|
|
20
|
+
export * from './public/generations.js';
|
|
21
|
+
export * from './public/identifiers.js';
|
|
22
|
+
export * from './public/images.js';
|
|
23
|
+
export * from './public/messages.js';
|
|
24
|
+
export * from './public/money.js';
|
|
25
|
+
export * from './public/policy.js';
|
|
26
|
+
export * from './public/realtime.js';
|
|
27
|
+
export * from './public/responses.js';
|
|
28
|
+
export * from './public/streamEvents.js';
|
|
29
|
+
export * from './public/version.js';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical attribution carried by the signed envelope.
|
|
3
|
+
*
|
|
4
|
+
* Moved from `@oxy.so/contracts` 4.12.0 (`src/inference/attribution.ts`) by Kaana ADR 0002.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
/**
|
|
8
|
+
* The inference capability scopes the data plane needs to know about.
|
|
9
|
+
*
|
|
10
|
+
* A credential may carry many other Oxy scopes; only these cross the boundary,
|
|
11
|
+
* because the data plane's authorization questions are exactly "may this caller
|
|
12
|
+
* invoke", "may it read the catalogue", "may it read usage", "may it read or
|
|
13
|
+
* write routing", "may it read or write provider connections". Everything else
|
|
14
|
+
* is the control plane's business and is not the data plane's to hold.
|
|
15
|
+
*/
|
|
16
|
+
export declare const INFERENCE_SCOPES: readonly ["inference:invoke", "inference:models:read", "inference:usage:read", "inference:routing:read", "inference:routing:write", "inference:providers:read", "inference:providers:write"];
|
|
17
|
+
export declare const inferenceScopeSchema: z.ZodEnum<{
|
|
18
|
+
"inference:invoke": "inference:invoke";
|
|
19
|
+
"inference:models:read": "inference:models:read";
|
|
20
|
+
"inference:usage:read": "inference:usage:read";
|
|
21
|
+
"inference:routing:read": "inference:routing:read";
|
|
22
|
+
"inference:routing:write": "inference:routing:write";
|
|
23
|
+
"inference:providers:read": "inference:providers:read";
|
|
24
|
+
"inference:providers:write": "inference:providers:write";
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* The financially responsible principal, and the ONLY identity a charge may be
|
|
28
|
+
* booked against.
|
|
29
|
+
*
|
|
30
|
+
* It is its own type — not a field on a larger principal object — precisely so
|
|
31
|
+
* that a function taking "who pays" cannot be handed a user, a session, a
|
|
32
|
+
* device or an application. It cannot be constructed from a delegated user id:
|
|
33
|
+
* the brands differ, and this object accepts no other key.
|
|
34
|
+
*/
|
|
35
|
+
export declare const billingPrincipalSchema: z.ZodObject<{
|
|
36
|
+
accountId: z.core.$ZodBranded<z.ZodString, "OxyAccountId", "out">;
|
|
37
|
+
}, z.core.$strict>;
|
|
38
|
+
/**
|
|
39
|
+
* Who authenticated, as resolved by the Oxy edge before a request is forwarded.
|
|
40
|
+
*
|
|
41
|
+
* Mirrors what a verified Oxy service token carries (`appId`, `credentialId`,
|
|
42
|
+
* `ownerAccountId`, `environment`, effective scopes) so that the two
|
|
43
|
+
* authentication paths — a machine API key and a first-party service token —
|
|
44
|
+
* produce one shape downstream. The data plane authorizes against this
|
|
45
|
+
* envelope; it does not re-derive access from its own database, because it has
|
|
46
|
+
* no account graph to re-derive it from.
|
|
47
|
+
*/
|
|
48
|
+
export declare const authenticatedPrincipalSchema: z.ZodObject<{
|
|
49
|
+
billing: z.ZodObject<{
|
|
50
|
+
accountId: z.core.$ZodBranded<z.ZodString, "OxyAccountId", "out">;
|
|
51
|
+
}, z.core.$strict>;
|
|
52
|
+
applicationId: z.ZodString;
|
|
53
|
+
credentialId: z.ZodString;
|
|
54
|
+
environment: z.ZodEnum<{
|
|
55
|
+
development: "development";
|
|
56
|
+
staging: "staging";
|
|
57
|
+
production: "production";
|
|
58
|
+
}>;
|
|
59
|
+
inferenceScopes: z.ZodArray<z.ZodEnum<{
|
|
60
|
+
"inference:invoke": "inference:invoke";
|
|
61
|
+
"inference:models:read": "inference:models:read";
|
|
62
|
+
"inference:usage:read": "inference:usage:read";
|
|
63
|
+
"inference:routing:read": "inference:routing:read";
|
|
64
|
+
"inference:routing:write": "inference:routing:write";
|
|
65
|
+
"inference:providers:read": "inference:providers:read";
|
|
66
|
+
"inference:providers:write": "inference:providers:write";
|
|
67
|
+
}>>;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
/**
|
|
70
|
+
* The attribution block carried by every request, receipt and ledger record.
|
|
71
|
+
*
|
|
72
|
+
* `userId` is the OPTIONAL delegated end user — Alia's `X-Oxy-User-Id`. It is
|
|
73
|
+
* attribution only: it never changes which account is charged, never grants
|
|
74
|
+
* access, and lives outside {@link billingPrincipalSchema} so that no code path
|
|
75
|
+
* can read it as the payer.
|
|
76
|
+
*
|
|
77
|
+
* `requestId` is generated by the OXY EDGE, on admission and before
|
|
78
|
+
* authentication completes, and is always present (ADR 0007). Not by the data
|
|
79
|
+
* plane: a data plane that minted the correlation key could not correlate a
|
|
80
|
+
* request it never received, and the requests rejected at the edge are exactly
|
|
81
|
+
* the ones an operator most needs to find.
|
|
82
|
+
*
|
|
83
|
+
* `generationId` is present once a generation exists, which is why it is
|
|
84
|
+
* optional on a request and expected on a receipt. That one IS the data plane's,
|
|
85
|
+
* because only the data plane knows how many outputs a request produced.
|
|
86
|
+
*/
|
|
87
|
+
export declare const inferenceAttributionSchema: z.ZodObject<{
|
|
88
|
+
principal: z.ZodObject<{
|
|
89
|
+
billing: z.ZodObject<{
|
|
90
|
+
accountId: z.core.$ZodBranded<z.ZodString, "OxyAccountId", "out">;
|
|
91
|
+
}, z.core.$strict>;
|
|
92
|
+
applicationId: z.ZodString;
|
|
93
|
+
credentialId: z.ZodString;
|
|
94
|
+
environment: z.ZodEnum<{
|
|
95
|
+
development: "development";
|
|
96
|
+
staging: "staging";
|
|
97
|
+
production: "production";
|
|
98
|
+
}>;
|
|
99
|
+
inferenceScopes: z.ZodArray<z.ZodEnum<{
|
|
100
|
+
"inference:invoke": "inference:invoke";
|
|
101
|
+
"inference:models:read": "inference:models:read";
|
|
102
|
+
"inference:usage:read": "inference:usage:read";
|
|
103
|
+
"inference:routing:read": "inference:routing:read";
|
|
104
|
+
"inference:routing:write": "inference:routing:write";
|
|
105
|
+
"inference:providers:read": "inference:providers:read";
|
|
106
|
+
"inference:providers:write": "inference:providers:write";
|
|
107
|
+
}>>;
|
|
108
|
+
}, z.core.$strip>;
|
|
109
|
+
userId: z.ZodOptional<z.core.$ZodBranded<z.ZodString, "DelegatedUserId", "out">>;
|
|
110
|
+
requestId: z.ZodString;
|
|
111
|
+
generationId: z.ZodOptional<z.ZodString>;
|
|
112
|
+
}, z.core.$strip>;
|
|
113
|
+
export type InferenceScope = z.infer<typeof inferenceScopeSchema>;
|
|
114
|
+
export type BillingPrincipal = z.infer<typeof billingPrincipalSchema>;
|
|
115
|
+
export type AuthenticatedPrincipal = z.infer<typeof authenticatedPrincipalSchema>;
|
|
116
|
+
export type InferenceAttribution = z.infer<typeof inferenceAttributionSchema>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical attribution carried by the signed envelope.
|
|
3
|
+
*
|
|
4
|
+
* Moved from `@oxy.so/contracts` 4.12.0 (`src/inference/attribution.ts`) by Kaana ADR 0002.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { delegatedUserIdSchema, generationIdSchema, inferenceEnvironmentSchema, oxyApplicationIdSchema, oxyCredentialIdSchema, requestIdSchema, } from '../public/identifiers.js';
|
|
8
|
+
import { oxyAccountIdSchema } from './identifiers.js';
|
|
9
|
+
/**
|
|
10
|
+
* The inference capability scopes the data plane needs to know about.
|
|
11
|
+
*
|
|
12
|
+
* A credential may carry many other Oxy scopes; only these cross the boundary,
|
|
13
|
+
* because the data plane's authorization questions are exactly "may this caller
|
|
14
|
+
* invoke", "may it read the catalogue", "may it read usage", "may it read or
|
|
15
|
+
* write routing", "may it read or write provider connections". Everything else
|
|
16
|
+
* is the control plane's business and is not the data plane's to hold.
|
|
17
|
+
*/
|
|
18
|
+
export const INFERENCE_SCOPES = [
|
|
19
|
+
'inference:invoke',
|
|
20
|
+
'inference:models:read',
|
|
21
|
+
'inference:usage:read',
|
|
22
|
+
'inference:routing:read',
|
|
23
|
+
'inference:routing:write',
|
|
24
|
+
'inference:providers:read',
|
|
25
|
+
'inference:providers:write',
|
|
26
|
+
];
|
|
27
|
+
export const inferenceScopeSchema = z.enum(INFERENCE_SCOPES);
|
|
28
|
+
/**
|
|
29
|
+
* The financially responsible principal, and the ONLY identity a charge may be
|
|
30
|
+
* booked against.
|
|
31
|
+
*
|
|
32
|
+
* It is its own type — not a field on a larger principal object — precisely so
|
|
33
|
+
* that a function taking "who pays" cannot be handed a user, a session, a
|
|
34
|
+
* device or an application. It cannot be constructed from a delegated user id:
|
|
35
|
+
* the brands differ, and this object accepts no other key.
|
|
36
|
+
*/
|
|
37
|
+
export const billingPrincipalSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
accountId: oxyAccountIdSchema,
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
/**
|
|
43
|
+
* Who authenticated, as resolved by the Oxy edge before a request is forwarded.
|
|
44
|
+
*
|
|
45
|
+
* Mirrors what a verified Oxy service token carries (`appId`, `credentialId`,
|
|
46
|
+
* `ownerAccountId`, `environment`, effective scopes) so that the two
|
|
47
|
+
* authentication paths — a machine API key and a first-party service token —
|
|
48
|
+
* produce one shape downstream. The data plane authorizes against this
|
|
49
|
+
* envelope; it does not re-derive access from its own database, because it has
|
|
50
|
+
* no account graph to re-derive it from.
|
|
51
|
+
*/
|
|
52
|
+
export const authenticatedPrincipalSchema = z.object({
|
|
53
|
+
billing: billingPrincipalSchema,
|
|
54
|
+
applicationId: oxyApplicationIdSchema,
|
|
55
|
+
credentialId: oxyCredentialIdSchema,
|
|
56
|
+
environment: inferenceEnvironmentSchema,
|
|
57
|
+
inferenceScopes: z.array(inferenceScopeSchema),
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* The attribution block carried by every request, receipt and ledger record.
|
|
61
|
+
*
|
|
62
|
+
* `userId` is the OPTIONAL delegated end user — Alia's `X-Oxy-User-Id`. It is
|
|
63
|
+
* attribution only: it never changes which account is charged, never grants
|
|
64
|
+
* access, and lives outside {@link billingPrincipalSchema} so that no code path
|
|
65
|
+
* can read it as the payer.
|
|
66
|
+
*
|
|
67
|
+
* `requestId` is generated by the OXY EDGE, on admission and before
|
|
68
|
+
* authentication completes, and is always present (ADR 0007). Not by the data
|
|
69
|
+
* plane: a data plane that minted the correlation key could not correlate a
|
|
70
|
+
* request it never received, and the requests rejected at the edge are exactly
|
|
71
|
+
* the ones an operator most needs to find.
|
|
72
|
+
*
|
|
73
|
+
* `generationId` is present once a generation exists, which is why it is
|
|
74
|
+
* optional on a request and expected on a receipt. That one IS the data plane's,
|
|
75
|
+
* because only the data plane knows how many outputs a request produced.
|
|
76
|
+
*/
|
|
77
|
+
export const inferenceAttributionSchema = z.object({
|
|
78
|
+
principal: authenticatedPrincipalSchema,
|
|
79
|
+
userId: delegatedUserIdSchema.optional(),
|
|
80
|
+
requestId: requestIdSchema,
|
|
81
|
+
generationId: generationIdSchema.optional(),
|
|
82
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON and the canonical input hash.
|
|
3
|
+
*
|
|
4
|
+
* Operations with a source approval (scoped execution, Private Auto, the Jev
|
|
5
|
+
* classifier) bind the exact input they were approved for. Kaana computes the
|
|
6
|
+
* hash of the input it is about to execute and Oxy compares it with the one
|
|
7
|
+
* the approval fixed (Kaana ADR 0002, decision 4), so both sides must hash the
|
|
8
|
+
* same bytes. This is that one definition.
|
|
9
|
+
*
|
|
10
|
+
* `canonicalJson` is moved from `@oxy.so/contracts` 4.12.0
|
|
11
|
+
* (`canonicalScopedExecutionJson`, `src/inference/scopedExecution.ts`), and Go
|
|
12
|
+
* restates it in `internal/contract/scoped_execution.go`.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Canonical JSON wire bytes: recursive UTF-16 key order, JSON string/number
|
|
16
|
+
* encoding, arrays in order, no whitespace. Hash the whole signed input, not a
|
|
17
|
+
* reparsed/defaulted decision. Reject non-JSON values rather than dropping them.
|
|
18
|
+
*/
|
|
19
|
+
export declare function canonicalJson(value: unknown): string;
|
|
20
|
+
/** The name scoped execution has always used for {@link canonicalJson}. */
|
|
21
|
+
export declare const canonicalScopedExecutionJson: typeof canonicalJson;
|
|
22
|
+
/**
|
|
23
|
+
* The canonical input hash: lowercase hex SHA-256 of {@link canonicalJson} of
|
|
24
|
+
* the request input, UTF-8 encoded.
|
|
25
|
+
*
|
|
26
|
+
* Asynchronous because it uses Web Crypto, which every runtime this package
|
|
27
|
+
* supports (Node 20+, Bun, browsers, Hermes with a crypto polyfill) provides
|
|
28
|
+
* without importing a platform module. Never logged or persisted: it binds
|
|
29
|
+
* transient signed bytes.
|
|
30
|
+
*/
|
|
31
|
+
export declare function canonicalInputSha256(input: unknown): Promise<string>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON and the canonical input hash.
|
|
3
|
+
*
|
|
4
|
+
* Operations with a source approval (scoped execution, Private Auto, the Jev
|
|
5
|
+
* classifier) bind the exact input they were approved for. Kaana computes the
|
|
6
|
+
* hash of the input it is about to execute and Oxy compares it with the one
|
|
7
|
+
* the approval fixed (Kaana ADR 0002, decision 4), so both sides must hash the
|
|
8
|
+
* same bytes. This is that one definition.
|
|
9
|
+
*
|
|
10
|
+
* `canonicalJson` is moved from `@oxy.so/contracts` 4.12.0
|
|
11
|
+
* (`canonicalScopedExecutionJson`, `src/inference/scopedExecution.ts`), and Go
|
|
12
|
+
* restates it in `internal/contract/scoped_execution.go`.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Canonical JSON wire bytes: recursive UTF-16 key order, JSON string/number
|
|
16
|
+
* encoding, arrays in order, no whitespace. Hash the whole signed input, not a
|
|
17
|
+
* reparsed/defaulted decision. Reject non-JSON values rather than dropping them.
|
|
18
|
+
*/
|
|
19
|
+
export function canonicalJson(value) {
|
|
20
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
const entries = Array.from(value, (entry, index) => {
|
|
26
|
+
if (!(index in value))
|
|
27
|
+
throw new Error('Canonical JSON requires dense JSON arrays.');
|
|
28
|
+
return canonicalJson(entry);
|
|
29
|
+
});
|
|
30
|
+
return `[${entries.join(',')}]`;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === 'object' &&
|
|
33
|
+
value !== null &&
|
|
34
|
+
Object.getPrototypeOf(value) === Object.prototype) {
|
|
35
|
+
const entries = Object.keys(value)
|
|
36
|
+
.sort()
|
|
37
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`);
|
|
38
|
+
return `{${entries.join(',')}}`;
|
|
39
|
+
}
|
|
40
|
+
throw new Error('Canonical JSON requires JSON wire values.');
|
|
41
|
+
}
|
|
42
|
+
/** The name scoped execution has always used for {@link canonicalJson}. */
|
|
43
|
+
export const canonicalScopedExecutionJson = canonicalJson;
|
|
44
|
+
/**
|
|
45
|
+
* The canonical input hash: lowercase hex SHA-256 of {@link canonicalJson} of
|
|
46
|
+
* the request input, UTF-8 encoded.
|
|
47
|
+
*
|
|
48
|
+
* Asynchronous because it uses Web Crypto, which every runtime this package
|
|
49
|
+
* supports (Node 20+, Bun, browsers, Hermes with a crypto polyfill) provides
|
|
50
|
+
* without importing a platform module. Never logged or persisted: it binds
|
|
51
|
+
* transient signed bytes.
|
|
52
|
+
*/
|
|
53
|
+
export async function canonicalInputSha256(input) {
|
|
54
|
+
const bytes = new TextEncoder().encode(canonicalJson(input));
|
|
55
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
|
|
56
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
57
|
+
}
|