@abloatai/transaction 0.37.1 → 0.38.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/dist/ai-sdk/index.d.ts +3 -1
- package/dist/ai-sdk/index.d.ts.map +1 -1
- package/dist/ai-sdk/index.js +2 -1
- package/dist/ai-sdk/index.js.map +1 -1
- package/dist/ai-sdk/modelTools.d.ts +67 -0
- package/dist/ai-sdk/modelTools.d.ts.map +1 -0
- package/dist/ai-sdk/modelTools.js +89 -0
- package/dist/ai-sdk/modelTools.js.map +1 -0
- package/dist/ai-sdk/toolOptions.d.ts +8 -0
- package/dist/ai-sdk/toolOptions.d.ts.map +1 -0
- package/dist/ai-sdk/toolOptions.js +2 -0
- package/dist/ai-sdk/toolOptions.js.map +1 -0
- package/dist/ai-sdk/{coordinatedTool.d.ts → updateTool.d.ts} +21 -28
- package/dist/ai-sdk/updateTool.d.ts.map +1 -0
- package/dist/ai-sdk/{coordinatedTool.js → updateTool.js} +39 -50
- package/dist/ai-sdk/updateTool.js.map +1 -0
- package/dist/auth/capability.d.ts +6 -0
- package/dist/auth/capability.d.ts.map +1 -1
- package/dist/auth/capability.js +4 -0
- package/dist/auth/capability.js.map +1 -1
- package/dist/auth/capabilityLifecycle.d.ts +3 -0
- package/dist/auth/capabilityLifecycle.d.ts.map +1 -1
- package/dist/auth/identity.d.ts +6 -4
- package/dist/auth/identity.d.ts.map +1 -1
- package/dist/auth/identity.js +7 -11
- package/dist/auth/identity.js.map +1 -1
- package/dist/auth/schemas.d.ts +5 -5
- package/dist/auth/schemas.d.ts.map +1 -1
- package/dist/auth/schemas.js +5 -2
- package/dist/auth/schemas.js.map +1 -1
- package/dist/auth/sessionMint.d.ts.map +1 -1
- package/dist/auth/sessionMint.js +3 -0
- package/dist/auth/sessionMint.js.map +1 -1
- package/dist/branches.d.ts +251 -0
- package/dist/branches.d.ts.map +1 -0
- package/dist/branches.js +108 -0
- package/dist/branches.js.map +1 -0
- package/dist/coordination/awaitClaimGrant.d.ts +2 -0
- package/dist/coordination/awaitClaimGrant.d.ts.map +1 -1
- package/dist/coordination/awaitClaimGrant.js +15 -2
- package/dist/coordination/awaitClaimGrant.js.map +1 -1
- package/dist/coordination/schema.d.ts +2 -0
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +2 -0
- package/dist/coordination/schema.js.map +1 -1
- package/dist/docs/catalog.d.ts +5 -5
- package/dist/docs/catalog.d.ts.map +1 -1
- package/dist/docs/catalog.js +12 -12
- package/dist/docs/catalog.js.map +1 -1
- package/dist/footprint.d.ts +3 -5
- package/dist/footprint.d.ts.map +1 -1
- package/dist/footprint.js +0 -0
- package/dist/footprint.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keys/index.d.ts +15 -7
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +45 -14
- package/dist/keys/index.js.map +1 -1
- package/dist/schema/openapi.d.ts +1 -1
- package/dist/schema/openapi.d.ts.map +1 -1
- package/dist/schema/openapi.js +308 -29
- package/dist/schema/openapi.js.map +1 -1
- package/dist/server/commit.d.ts +2 -15
- package/dist/server/commit.d.ts.map +1 -1
- package/dist/source/adapter.d.ts +7 -2
- package/dist/source/adapter.d.ts.map +1 -1
- package/dist/source/adapter.js +4 -2
- package/dist/source/adapter.js.map +1 -1
- package/dist/source/adapterFactory.d.ts +10 -0
- package/dist/source/adapterFactory.d.ts.map +1 -0
- package/dist/source/adapterFactory.js +33 -0
- package/dist/source/adapterFactory.js.map +1 -0
- package/dist/source/adapterProfile.d.ts +55 -0
- package/dist/source/adapterProfile.d.ts.map +1 -0
- package/dist/source/adapterProfile.js +54 -0
- package/dist/source/adapterProfile.js.map +1 -0
- package/dist/source/adapters/drizzle.d.ts.map +1 -1
- package/dist/source/adapters/drizzle.js +5 -2
- package/dist/source/adapters/drizzle.js.map +1 -1
- package/dist/source/adapters/kysely.d.ts.map +1 -1
- package/dist/source/adapters/kysely.js +7 -4
- package/dist/source/adapters/kysely.js.map +1 -1
- package/dist/source/adapters/memory.d.ts.map +1 -1
- package/dist/source/adapters/memory.js +5 -2
- package/dist/source/adapters/memory.js.map +1 -1
- package/dist/source/adapters/prisma.d.ts.map +1 -1
- package/dist/source/adapters/prisma.js +5 -2
- package/dist/source/adapters/prisma.js.map +1 -1
- package/dist/source/conformance.d.ts.map +1 -1
- package/dist/source/conformance.js +18 -0
- package/dist/source/conformance.js.map +1 -1
- package/dist/source/index.d.ts +2 -0
- package/dist/source/index.d.ts.map +1 -1
- package/dist/source/index.js +2 -0
- package/dist/source/index.js.map +1 -1
- package/dist/source/types.d.ts +3 -14
- package/dist/source/types.d.ts.map +1 -1
- package/dist/source/types.js.map +1 -1
- package/dist/syncLog/contract.d.ts +1 -1
- package/dist/syncLog/contract.d.ts.map +1 -1
- package/dist/syncLog/contract.js +1 -2
- package/dist/syncLog/contract.js.map +1 -1
- package/dist/wire/accountResponses.d.ts +5 -9
- package/dist/wire/accountResponses.d.ts.map +1 -1
- package/dist/wire/accountResponses.js +5 -6
- package/dist/wire/accountResponses.js.map +1 -1
- package/dist/wire/delta.d.ts +11 -0
- package/dist/wire/delta.d.ts.map +1 -1
- package/dist/wire/delta.js +11 -0
- package/dist/wire/delta.js.map +1 -1
- package/dist/wire/inboundFrames.d.ts +2 -0
- package/dist/wire/inboundFrames.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/ai-sdk/index.ts +19 -6
- package/src/ai-sdk/modelTools.ts +165 -0
- package/src/ai-sdk/toolOptions.ts +18 -0
- package/src/ai-sdk/{coordinatedTool.ts → updateTool.ts} +59 -69
- package/src/auth/capability.ts +4 -0
- package/src/auth/identity.ts +16 -16
- package/src/auth/schemas.ts +5 -2
- package/src/auth/sessionMint.ts +3 -0
- package/src/branches.ts +134 -0
- package/src/coordination/awaitClaimGrant.ts +18 -2
- package/src/coordination/schema.ts +2 -0
- package/src/docs/catalog.ts +24 -12
- package/src/footprint.ts +0 -0
- package/src/index.ts +1 -0
- package/src/keys/index.ts +56 -17
- package/src/schema/openapi.ts +366 -38
- package/src/server/commit.ts +2 -15
- package/src/source/adapter.ts +7 -2
- package/src/source/adapterFactory.ts +60 -0
- package/src/source/adapterProfile.ts +75 -0
- package/src/source/adapters/drizzle.ts +5 -2
- package/src/source/adapters/kysely.ts +10 -4
- package/src/source/adapters/memory.ts +5 -2
- package/src/source/adapters/prisma.ts +5 -2
- package/src/source/conformance.ts +18 -0
- package/src/source/index.ts +14 -0
- package/src/source/types.ts +3 -14
- package/src/syncLog/contract.ts +1 -2
- package/src/wire/accountResponses.ts +5 -6
- package/src/wire/delta.ts +11 -0
- package/dist/ai-sdk/coordinatedTool.d.ts.map +0 -1
- package/dist/ai-sdk/coordinatedTool.js.map +0 -1
|
@@ -55,6 +55,8 @@ export interface ClaimGrantInfo {
|
|
|
55
55
|
* `undefined` when the server does not fence (no minter wired).
|
|
56
56
|
*/
|
|
57
57
|
readonly fenceToken?: number;
|
|
58
|
+
/** Authoritative branch watermark captured when the lease was granted. */
|
|
59
|
+
readonly readAt?: number;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/** Read the server-stamped fencing token off a grant frame, if present. */
|
|
@@ -62,6 +64,10 @@ function readFenceToken(p: Record<string, unknown>): number | undefined {
|
|
|
62
64
|
return typeof p.fenceToken === 'number' ? p.fenceToken : undefined;
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
function readWatermark(p: Record<string, unknown>): number | undefined {
|
|
68
|
+
return typeof p.readAt === 'number' ? p.readAt : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
export function awaitClaimGrant(
|
|
66
72
|
transport: GrantTransport,
|
|
67
73
|
claimId: string,
|
|
@@ -102,8 +108,13 @@ export function awaitClaimGrant(
|
|
|
102
108
|
if (p?.claimId === claimId) {
|
|
103
109
|
logger.debug(`claim: acquired ${claimId} (target was free)`);
|
|
104
110
|
const fenceToken = readFenceToken(p);
|
|
111
|
+
const readAt = readWatermark(p);
|
|
105
112
|
settle(() => {
|
|
106
|
-
resolve({
|
|
113
|
+
resolve({
|
|
114
|
+
waited: false,
|
|
115
|
+
...(fenceToken !== undefined ? { fenceToken } : {}),
|
|
116
|
+
...(readAt !== undefined ? { readAt } : {}),
|
|
117
|
+
});
|
|
107
118
|
});
|
|
108
119
|
}
|
|
109
120
|
}),
|
|
@@ -115,8 +126,13 @@ export function awaitClaimGrant(
|
|
|
115
126
|
// turn now" moment after waiting behind a holder.
|
|
116
127
|
logger.info(`claim: granted ${claimId} — your turn (waited in queue)`);
|
|
117
128
|
const fenceToken = readFenceToken(p);
|
|
129
|
+
const readAt = readWatermark(p);
|
|
118
130
|
settle(() => {
|
|
119
|
-
resolve({
|
|
131
|
+
resolve({
|
|
132
|
+
waited: true,
|
|
133
|
+
...(fenceToken !== undefined ? { fenceToken } : {}),
|
|
134
|
+
...(readAt !== undefined ? { readAt } : {}),
|
|
135
|
+
});
|
|
120
136
|
});
|
|
121
137
|
}
|
|
122
138
|
}),
|
|
@@ -626,6 +626,7 @@ export type ClaimLost = z.infer<typeof claimLostSchema>;
|
|
|
626
626
|
export const claimAcquiredSchema = z.object({
|
|
627
627
|
claimId: z.string(),
|
|
628
628
|
fenceToken: z.number().optional(),
|
|
629
|
+
readAt: z.number().int().nonnegative().optional(),
|
|
629
630
|
target: targetRefSchema,
|
|
630
631
|
});
|
|
631
632
|
export type ClaimAcquired = z.infer<typeof claimAcquiredSchema>;
|
|
@@ -640,6 +641,7 @@ export type ClaimAcquired = z.infer<typeof claimAcquiredSchema>;
|
|
|
640
641
|
export const claimGrantedSchema = z.object({
|
|
641
642
|
claimId: z.string(),
|
|
642
643
|
fenceToken: z.number().optional(),
|
|
644
|
+
readAt: z.number().int().nonnegative().optional(),
|
|
643
645
|
target: targetRefSchema,
|
|
644
646
|
});
|
|
645
647
|
export type ClaimGranted = z.infer<typeof claimGrantedSchema>;
|
package/src/docs/catalog.ts
CHANGED
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* same line — so a page states its own promise exactly once, in the source that
|
|
15
15
|
* ships in the npm tarball and still reads correctly as plain markdown.
|
|
16
16
|
*
|
|
17
|
-
* The published set
|
|
18
|
-
* `docs/examples/*.md`, plus the three root files
|
|
19
|
-
* — `docs/internal/**` above all — is
|
|
20
|
-
* exclusion is the only thing keeping internal notes off
|
|
21
|
-
* that they read from here.
|
|
17
|
+
* The published set is an explicit allowlist: `docs/*.md`,
|
|
18
|
+
* `docs/examples/*.md`, `docs/integrations/*.md`, plus the three root files.
|
|
19
|
+
* Anything else under `docs/` — `docs/internal/**` above all — is not
|
|
20
|
+
* catalogued, and that exclusion is the only thing keeping internal notes off
|
|
21
|
+
* the public routes now that they read from here.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { readdir, readFile } from 'node:fs/promises';
|
|
@@ -115,7 +115,16 @@ export async function readDocsCatalog(packageRoot: string): Promise<DocEntry[]>
|
|
|
115
115
|
const entries: DocEntry[] = [];
|
|
116
116
|
|
|
117
117
|
entries.push(...(await readDocsDirectory(docsDir, 'guide')));
|
|
118
|
-
entries.push(
|
|
118
|
+
entries.push(
|
|
119
|
+
...(await readDocsDirectory(join(docsDir, 'examples'), 'example', 'examples')),
|
|
120
|
+
);
|
|
121
|
+
entries.push(
|
|
122
|
+
...(await readDocsDirectory(
|
|
123
|
+
join(docsDir, 'integrations'),
|
|
124
|
+
'guide',
|
|
125
|
+
'integrations',
|
|
126
|
+
)),
|
|
127
|
+
);
|
|
119
128
|
|
|
120
129
|
for (const file of PACKAGE_FILES) {
|
|
121
130
|
const path = join(packageRoot, file.filename);
|
|
@@ -135,12 +144,15 @@ export async function readDocsCatalog(packageRoot: string): Promise<DocEntry[]>
|
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
/**
|
|
138
|
-
* Read one flat directory of markdown. Deliberately
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* public route.
|
|
147
|
+
* Read one explicitly allowed flat directory of markdown. Deliberately
|
|
148
|
+
* non-recursive: descending from `docs/` would catalogue `docs/internal/**`,
|
|
149
|
+
* notes that must not reach a public route.
|
|
142
150
|
*/
|
|
143
|
-
async function readDocsDirectory(
|
|
151
|
+
async function readDocsDirectory(
|
|
152
|
+
dir: string,
|
|
153
|
+
kind: DocKind,
|
|
154
|
+
slugPrefix?: string,
|
|
155
|
+
): Promise<DocEntry[]> {
|
|
144
156
|
let names: string[];
|
|
145
157
|
try {
|
|
146
158
|
const found = await readdir(dir, { withFileTypes: true });
|
|
@@ -157,7 +169,7 @@ async function readDocsDirectory(dir: string, kind: DocKind): Promise<DocEntry[]
|
|
|
157
169
|
const stem = basename(name, '.md');
|
|
158
170
|
const { title, description } = parseDocHeader(body);
|
|
159
171
|
entries.push({
|
|
160
|
-
slug:
|
|
172
|
+
slug: slugPrefix ? `${slugPrefix}/${stem}` : stem,
|
|
161
173
|
title: title ?? stem,
|
|
162
174
|
description,
|
|
163
175
|
kind,
|
package/src/footprint.ts
CHANGED
|
Binary file
|
package/src/index.ts
CHANGED
package/src/keys/index.ts
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* the `@abloatai/transaction/keys` subpath and kept off the main browser-facing entry
|
|
7
7
|
* so a browser bundle never pulls in `node:crypto`.
|
|
8
8
|
*
|
|
9
|
-
* A key looks like
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* A data-plane key looks like
|
|
10
|
+
* `<sk|rk|ek|pk>_<live|test>_<30 base62 chars><6-char base62 CRC32 checksum>`.
|
|
11
|
+
* A control-plane management key is `mk_<body><checksum>` and deliberately has
|
|
12
|
+
* no live/test segment: it cannot access application data. The recognizable prefix lets secret
|
|
12
13
|
* scanners spot a leaked key, and the trailing checksum lets the format reject a
|
|
13
14
|
* mistyped or forged key locally, without a database round-trip. Older keys
|
|
14
15
|
* (roughly a 43-character base64url body with no checksum) still validate by hash
|
|
@@ -28,7 +29,10 @@ import {
|
|
|
28
29
|
|
|
29
30
|
// ── Vocabulary ──────────────────────────────────────────────────────────
|
|
30
31
|
|
|
31
|
-
// The
|
|
32
|
+
// The five credential kinds:
|
|
33
|
+
// management (mk_) — project control-plane authority. It can manage projects
|
|
34
|
+
// and branches and mint leaf branch credentials, but has
|
|
35
|
+
// no data-plane read/write authority and no livemode.
|
|
32
36
|
// secret (sk_) — backend and server-to-server use, including agents. Full
|
|
33
37
|
// authority; never expose one in a browser.
|
|
34
38
|
// restricted (rk_) — a scoped server key, such as an agent session token or a
|
|
@@ -41,7 +45,13 @@ import {
|
|
|
41
45
|
// exchanged, never expires, nothing to refresh. It grants
|
|
42
46
|
// read access to the organization's data and cannot write or
|
|
43
47
|
// reach any control-plane operation.
|
|
44
|
-
export const API_KEY_KINDS = [
|
|
48
|
+
export const API_KEY_KINDS = [
|
|
49
|
+
'management',
|
|
50
|
+
'secret',
|
|
51
|
+
'restricted',
|
|
52
|
+
'ephemeral',
|
|
53
|
+
'publishable',
|
|
54
|
+
] as const;
|
|
45
55
|
export type ApiKeyKind = (typeof API_KEY_KINDS)[number];
|
|
46
56
|
|
|
47
57
|
// A key's environment is the CREDENTIAL axis, not the plane axis: the format
|
|
@@ -51,12 +61,14 @@ export const API_KEY_ENVS = KEY_ENVIRONMENTS;
|
|
|
51
61
|
export type ApiKeyEnv = KeyEnvironment;
|
|
52
62
|
|
|
53
63
|
const PREFIX_BY_KIND: Record<ApiKeyKind, string> = {
|
|
64
|
+
management: 'mk',
|
|
54
65
|
secret: 'sk',
|
|
55
66
|
restricted: 'rk',
|
|
56
67
|
ephemeral: 'ek',
|
|
57
68
|
publishable: 'pk',
|
|
58
69
|
};
|
|
59
70
|
const KIND_BY_PREFIX: Record<string, ApiKeyKind> = {
|
|
71
|
+
mk: 'management',
|
|
60
72
|
sk: 'secret',
|
|
61
73
|
rk: 'restricted',
|
|
62
74
|
ek: 'ephemeral',
|
|
@@ -72,7 +84,9 @@ const CHECKSUM_LEN = 6;
|
|
|
72
84
|
const CHECKSUMMED_BODY_LEN = KEY_BODY_LEN + CHECKSUM_LEN;
|
|
73
85
|
|
|
74
86
|
/** `<sk|rk|ek|pk>_<live|test>_<body>`; the body charset covers base62 as well as the legacy base64url form. */
|
|
75
|
-
const
|
|
87
|
+
const DATA_KEY_RE = /^(sk|rk|ek|pk)_(live|test)_([0-9A-Za-z\-_]+)$/;
|
|
88
|
+
/** `mk_<body>`; management credentials have no data livemode segment. */
|
|
89
|
+
const MANAGEMENT_KEY_RE = /^(mk)_([0-9A-Za-z\-_]+)$/;
|
|
76
90
|
const BASE62_RE = /^[0-9A-Za-z]+$/;
|
|
77
91
|
|
|
78
92
|
// ── Checksum (standard CRC-32, GitHub-compatible) ───────────────────────
|
|
@@ -128,8 +142,9 @@ export interface ParsedApiKey {
|
|
|
128
142
|
/** The original plaintext. */
|
|
129
143
|
raw: string;
|
|
130
144
|
kind: ApiKeyKind;
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
/** Null for `mk_`, which has no data-plane livemode. */
|
|
146
|
+
env: ApiKeyEnv | null;
|
|
147
|
+
/** The random body + checksum after the recognizable prefix. */
|
|
133
148
|
body: string;
|
|
134
149
|
/** True when this is the new checksummed format (36-char base62 body). */
|
|
135
150
|
checksummed: boolean;
|
|
@@ -146,17 +161,20 @@ function bodyIsChecksummed(body: string): boolean {
|
|
|
146
161
|
* `checksummed: false` and is left for the server to validate by hash.
|
|
147
162
|
*/
|
|
148
163
|
export const apiKeySchema = z.string().transform((raw, ctx): ParsedApiKey => {
|
|
149
|
-
const
|
|
150
|
-
|
|
164
|
+
const dataMatch = DATA_KEY_RE.exec(raw);
|
|
165
|
+
const managementMatch = MANAGEMENT_KEY_RE.exec(raw);
|
|
166
|
+
if (!dataMatch && !managementMatch) {
|
|
151
167
|
ctx.addIssue({ code: 'custom', message: 'not a valid Ablo API key format' });
|
|
152
168
|
return z.NEVER;
|
|
153
169
|
}
|
|
154
|
-
const
|
|
170
|
+
const prefix = dataMatch?.[1] ?? managementMatch?.[1];
|
|
171
|
+
const env = dataMatch?.[2];
|
|
172
|
+
const body = dataMatch?.[3] ?? managementMatch?.[2];
|
|
155
173
|
const kind = prefix === undefined ? undefined : KIND_BY_PREFIX[prefix];
|
|
156
174
|
// Unreachable on a KEY_RE match (all three groups are non-optional and the
|
|
157
175
|
// prefix alternation is exactly the KIND_BY_PREFIX key set) — narrows the
|
|
158
176
|
// regex-group lookups for the checks below.
|
|
159
|
-
if (kind === undefined ||
|
|
177
|
+
if (kind === undefined || body === undefined) {
|
|
160
178
|
ctx.addIssue({ code: 'custom', message: 'not a valid Ablo API key format' });
|
|
161
179
|
return z.NEVER;
|
|
162
180
|
}
|
|
@@ -168,7 +186,10 @@ export const apiKeySchema = z.string().transform((raw, ctx): ParsedApiKey => {
|
|
|
168
186
|
return {
|
|
169
187
|
raw,
|
|
170
188
|
kind,
|
|
171
|
-
env:
|
|
189
|
+
env:
|
|
190
|
+
kind === 'management'
|
|
191
|
+
? null
|
|
192
|
+
: environmentFromKeyPrefix(env as KeyPrefixEnvironment),
|
|
172
193
|
body,
|
|
173
194
|
checksummed,
|
|
174
195
|
};
|
|
@@ -199,13 +220,13 @@ export function environmentFromStoredKeyPrefix(prefix: string): KeyEnvironment |
|
|
|
199
220
|
|
|
200
221
|
/** True when the key uses the new checksummed format (regardless of validity). */
|
|
201
222
|
export function isChecksummedKey(raw: string): boolean {
|
|
202
|
-
const body =
|
|
223
|
+
const body = DATA_KEY_RE.exec(raw)?.[3] ?? MANAGEMENT_KEY_RE.exec(raw)?.[2];
|
|
203
224
|
return body !== undefined && bodyIsChecksummed(body);
|
|
204
225
|
}
|
|
205
226
|
|
|
206
227
|
/** Verify the embedded checksum. Meaningful only for checksummed-format keys. */
|
|
207
228
|
export function keyChecksumMatches(raw: string): boolean {
|
|
208
|
-
const body =
|
|
229
|
+
const body = DATA_KEY_RE.exec(raw)?.[3] ?? MANAGEMENT_KEY_RE.exec(raw)?.[2];
|
|
209
230
|
if (body === undefined || !bodyIsChecksummed(body)) return false;
|
|
210
231
|
return checksum6(raw.slice(0, -CHECKSUM_LEN)) === body.slice(KEY_BODY_LEN);
|
|
211
232
|
}
|
|
@@ -217,15 +238,33 @@ export function keyChecksumMatches(raw: string): boolean {
|
|
|
217
238
|
* once), its SHA-256 hash (persisted), and the 12-char display prefix.
|
|
218
239
|
*/
|
|
219
240
|
export function generateApiKey(
|
|
220
|
-
env: ApiKeyEnv = 'production',
|
|
241
|
+
env: ApiKeyEnv | null = 'production',
|
|
221
242
|
kind: ApiKeyKind = 'secret',
|
|
222
243
|
): { plaintext: string; hash: string; prefix: string } {
|
|
223
244
|
const body = randomBase62(KEY_BODY_LEN);
|
|
224
|
-
|
|
245
|
+
if (kind === 'management' && env !== null) {
|
|
246
|
+
throw new Error('management credentials do not have a live/test mode');
|
|
247
|
+
}
|
|
248
|
+
if (kind !== 'management' && env === null) {
|
|
249
|
+
throw new Error(`${kind} credentials require a live/test mode`);
|
|
250
|
+
}
|
|
251
|
+
const payload =
|
|
252
|
+
kind === 'management'
|
|
253
|
+
? `${PREFIX_BY_KIND[kind]}_${body}`
|
|
254
|
+
: `${PREFIX_BY_KIND[kind]}_${environmentToKeyPrefix(env as ApiKeyEnv)}_${body}`;
|
|
225
255
|
const plaintext = `${payload}${checksum6(payload)}`;
|
|
226
256
|
return { plaintext, hash: hashApiKey(plaintext), prefix: plaintext.slice(0, 12) };
|
|
227
257
|
}
|
|
228
258
|
|
|
259
|
+
/** Mint a project-scoped, control-plane-only credential. */
|
|
260
|
+
export function generateManagementKey(): {
|
|
261
|
+
plaintext: string;
|
|
262
|
+
hash: string;
|
|
263
|
+
prefix: string;
|
|
264
|
+
} {
|
|
265
|
+
return generateApiKey(null, 'management');
|
|
266
|
+
}
|
|
267
|
+
|
|
229
268
|
/**
|
|
230
269
|
* The stable SHA-256 hex digest of a plaintext key, computed both when a key is
|
|
231
270
|
* minted and when one is looked up. A fast hash is the right choice here rather
|