@cef-ai/cli 2.7.6 → 2.8.1
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 +15 -0
- package/dist/cli-program.d.ts +2 -0
- package/dist/cli-program.d.ts.map +1 -1
- package/dist/cli-program.js +17 -0
- package/dist/cli-program.js.map +1 -1
- package/dist/commands/cubby.d.ts +7 -0
- package/dist/commands/cubby.d.ts.map +1 -1
- package/dist/commands/cubby.js +8 -4
- package/dist/commands/cubby.js.map +1 -1
- package/dist/commands/eval.d.ts +217 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +860 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/eval.test.d.ts +2 -0
- package/dist/commands/eval.test.d.ts.map +1 -0
- package/dist/commands/eval.test.js +474 -0
- package/dist/commands/eval.test.js.map +1 -0
- package/dist/commands/push.d.ts +23 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +70 -1
- package/dist/commands/push.js.map +1 -1
- package/dist/lib/access-token.d.ts +33 -0
- package/dist/lib/access-token.d.ts.map +1 -1
- package/dist/lib/access-token.js +89 -20
- package/dist/lib/access-token.js.map +1 -1
- package/dist/lib/bucket-owner.d.ts +45 -0
- package/dist/lib/bucket-owner.d.ts.map +1 -0
- package/dist/lib/bucket-owner.js +107 -0
- package/dist/lib/bucket-owner.js.map +1 -0
- package/dist/lib/ddc.d.ts +22 -0
- package/dist/lib/ddc.d.ts.map +1 -1
- package/dist/lib/ddc.js +72 -20
- package/dist/lib/ddc.js.map +1 -1
- package/dist/lib/eval-auth.d.ts +139 -0
- package/dist/lib/eval-auth.d.ts.map +1 -0
- package/dist/lib/eval-auth.js +385 -0
- package/dist/lib/eval-auth.js.map +1 -0
- package/dist/lib/eval-auth.test.d.ts +2 -0
- package/dist/lib/eval-auth.test.d.ts.map +1 -0
- package/dist/lib/eval-auth.test.js +192 -0
- package/dist/lib/eval-auth.test.js.map +1 -0
- package/dist/lib/eval-store.d.ts +62 -0
- package/dist/lib/eval-store.d.ts.map +1 -0
- package/dist/lib/eval-store.js +334 -0
- package/dist/lib/eval-store.js.map +1 -0
- package/dist/lib/eval-store.test.d.ts +2 -0
- package/dist/lib/eval-store.test.d.ts.map +1 -0
- package/dist/lib/eval-store.test.js +155 -0
- package/dist/lib/eval-store.test.js.map +1 -0
- package/dist/lib/eval-target.d.ts +122 -0
- package/dist/lib/eval-target.d.ts.map +1 -0
- package/dist/lib/eval-target.js +320 -0
- package/dist/lib/eval-target.js.map +1 -0
- package/dist/lib/eval-target.test.d.ts +2 -0
- package/dist/lib/eval-target.test.d.ts.map +1 -0
- package/dist/lib/eval-target.test.js +209 -0
- package/dist/lib/eval-target.test.js.map +1 -0
- package/dist/lib/eval-workflow.d.ts +24 -0
- package/dist/lib/eval-workflow.d.ts.map +1 -0
- package/dist/lib/eval-workflow.js +91 -0
- package/dist/lib/eval-workflow.js.map +1 -0
- package/package.json +8 -4
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3-gateway-backed `EvalStore`.
|
|
3
|
+
*
|
|
4
|
+
* Datasets live on the S3 gateway, NOT on the DDC content gateway that
|
|
5
|
+
* `cef push` writes to. ROC's own `agentServiceAccess/wellKnown.ts` states why
|
|
6
|
+
* these cannot be the same path: "the content gateway cannot resolve S3 keys:
|
|
7
|
+
* the S3 gateway addresses objects through its own Postgres key->CID index,
|
|
8
|
+
* which the CDN knows nothing about." They are two systems with two key spaces,
|
|
9
|
+
* which is also why `cef eval push` is its own command rather than a flag on
|
|
10
|
+
* `cef push`.
|
|
11
|
+
*/
|
|
12
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
13
|
+
import type { EvalStore } from "@cef-ai/eval";
|
|
14
|
+
import type { CredentialSource } from "./eval-auth.js";
|
|
15
|
+
export interface S3StoreOptions {
|
|
16
|
+
bucket: string;
|
|
17
|
+
client: S3Client;
|
|
18
|
+
/** Key prefix within the bucket. Defaults to none. */
|
|
19
|
+
root?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The gateway refused `failed`'s credential (403): a client with a fresh
|
|
22
|
+
* one, or undefined when there is none. Each request is retried once.
|
|
23
|
+
*/
|
|
24
|
+
renew?: (failed: S3Client) => Promise<S3Client | undefined>;
|
|
25
|
+
}
|
|
26
|
+
/** A refused credential: expired, revoked, or never valid. */
|
|
27
|
+
export declare function isAuthFailure(err: unknown): boolean;
|
|
28
|
+
export declare function s3Store(opts: S3StoreOptions): EvalStore;
|
|
29
|
+
/**
|
|
30
|
+
* Build an S3 client for a gateway endpoint. With a `CredentialSource` the
|
|
31
|
+
* client asks it for a credential, carrying its expiry, so the SDK asks again
|
|
32
|
+
* inside its refresh window (5 min before expiry) and the source mints anew.
|
|
33
|
+
*/
|
|
34
|
+
export declare function gatewayClient(cfg: {
|
|
35
|
+
endpoint: string;
|
|
36
|
+
region?: string;
|
|
37
|
+
} & ({
|
|
38
|
+
accessKeyId: string;
|
|
39
|
+
secretAccessKey: string;
|
|
40
|
+
sessionToken?: string;
|
|
41
|
+
credentials?: never;
|
|
42
|
+
} | {
|
|
43
|
+
credentials: CredentialSource;
|
|
44
|
+
})): S3Client;
|
|
45
|
+
/**
|
|
46
|
+
* The store `cef eval` uses: a gateway client over a credential source, and
|
|
47
|
+
* on a 403 one fresh client per refused credential — concurrent requests that
|
|
48
|
+
* fail together share it instead of minting one each.
|
|
49
|
+
*/
|
|
50
|
+
export declare function gatewayStore(opts: {
|
|
51
|
+
endpoint: string;
|
|
52
|
+
bucket: string;
|
|
53
|
+
region?: string;
|
|
54
|
+
credentials: CredentialSource;
|
|
55
|
+
root?: string;
|
|
56
|
+
makeClient?: (cfg: {
|
|
57
|
+
endpoint: string;
|
|
58
|
+
region?: string;
|
|
59
|
+
credentials: CredentialSource;
|
|
60
|
+
}) => S3Client;
|
|
61
|
+
}): EvalStore;
|
|
62
|
+
//# sourceMappingURL=eval-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-store.d.ts","sourceRoot":"","sources":["../../src/lib/eval-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAML,QAAQ,EAET,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,CAAC;IACjB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;CAC7D;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAInD;AAuBD,wBAAgB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CA8NvD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,CACA;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,GAC5F;IAAE,WAAW,EAAE,gBAAgB,CAAA;CAAE,CACpC,GAAG,QAAQ,CA+CX;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,gBAAgB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,gBAAgB,CAAA;KAAE,KAAK,QAAQ,CAAC;CACtG,GAAG,SAAS,CAwBZ"}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3-gateway-backed `EvalStore`.
|
|
3
|
+
*
|
|
4
|
+
* Datasets live on the S3 gateway, NOT on the DDC content gateway that
|
|
5
|
+
* `cef push` writes to. ROC's own `agentServiceAccess/wellKnown.ts` states why
|
|
6
|
+
* these cannot be the same path: "the content gateway cannot resolve S3 keys:
|
|
7
|
+
* the S3 gateway addresses objects through its own Postgres key->CID index,
|
|
8
|
+
* which the CDN knows nothing about." They are two systems with two key spaces,
|
|
9
|
+
* which is also why `cef eval push` is its own command rather than a flag on
|
|
10
|
+
* `cef push`.
|
|
11
|
+
*/
|
|
12
|
+
import { GetObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
|
13
|
+
/** A refused credential: expired, revoked, or never valid. */
|
|
14
|
+
export function isAuthFailure(err) {
|
|
15
|
+
const e = err;
|
|
16
|
+
return e?.$metadata?.httpStatusCode === 403
|
|
17
|
+
|| e?.name === "InvalidAccessKeyId" || e?.name === "ExpiredToken" || e?.name === "AccessDenied";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `listObjects` on the gateway caps a page at 1000 keys and does NOT signal
|
|
21
|
+
* truncation to a naive caller — a known ceiling recorded in the design. So
|
|
22
|
+
* this paginates explicitly with the continuation token rather than trusting a
|
|
23
|
+
* single response, and every caller gets the whole prefix or an error.
|
|
24
|
+
*
|
|
25
|
+
* Silent truncation is the dangerous shape: a dataset that quietly
|
|
26
|
+
* stops at 1000 cases cuts a version missing the rest, and the experiment
|
|
27
|
+
* built from it looks complete.
|
|
28
|
+
*/
|
|
29
|
+
/** The endpoint host, for a warning that has to say WHICH gateway misbehaved. */
|
|
30
|
+
async function endpointLabel(client) {
|
|
31
|
+
try {
|
|
32
|
+
const endpoint = client.config
|
|
33
|
+
.endpoint;
|
|
34
|
+
return (await endpoint?.())?.hostname ?? "the S3 endpoint";
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return "the S3 endpoint";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function s3Store(opts) {
|
|
41
|
+
const { bucket } = opts;
|
|
42
|
+
let client = opts.client;
|
|
43
|
+
/*
|
|
44
|
+
* ONE RETRY ON A REFUSED CREDENTIAL. The client re-asks its credential
|
|
45
|
+
* provider before expiry on its own; this covers the rest (revoked, a clock
|
|
46
|
+
* that disagrees with the gateway's) without turning a real permission
|
|
47
|
+
* error into a loop.
|
|
48
|
+
*/
|
|
49
|
+
const send = async (command) => {
|
|
50
|
+
const used = client;
|
|
51
|
+
try {
|
|
52
|
+
return (await used.send(command));
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (!opts.renew || !isAuthFailure(err))
|
|
56
|
+
throw err;
|
|
57
|
+
const fresh = await opts.renew(used);
|
|
58
|
+
if (!fresh)
|
|
59
|
+
throw err;
|
|
60
|
+
client = fresh;
|
|
61
|
+
return (await fresh.send(command));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const root = opts.root ? opts.root.replace(/\/*$/, "/") : "";
|
|
65
|
+
const full = (key) => `${root}${key}`;
|
|
66
|
+
const get = async (key) => {
|
|
67
|
+
try {
|
|
68
|
+
const res = await send(new GetObjectCommand({ Bucket: bucket, Key: full(key) }));
|
|
69
|
+
return (await res.Body?.transformToString()) ?? null;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const name = err.name;
|
|
73
|
+
// A genuinely absent object is `null`; anything else (auth, network,
|
|
74
|
+
// a bad bucket) must propagate — swallowing it would make `putCase`'s
|
|
75
|
+
// written-once check silently pass and overwrite a real case.
|
|
76
|
+
if (name === "NoSuchKey" || name === "NotFound")
|
|
77
|
+
return null;
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
async list(prefix) {
|
|
83
|
+
const keys = [];
|
|
84
|
+
let token;
|
|
85
|
+
do {
|
|
86
|
+
const page = await send(new ListObjectsV2Command({
|
|
87
|
+
Bucket: bucket,
|
|
88
|
+
Prefix: full(prefix),
|
|
89
|
+
ContinuationToken: token,
|
|
90
|
+
}));
|
|
91
|
+
for (const obj of page.Contents ?? []) {
|
|
92
|
+
if (obj.Key)
|
|
93
|
+
keys.push(root ? obj.Key.slice(root.length) : obj.Key);
|
|
94
|
+
}
|
|
95
|
+
// Truncated with nowhere to continue from is the exact silent
|
|
96
|
+
// truncation this function exists to prevent: returning the short list
|
|
97
|
+
// reads as a complete one, and the version cut from it is missing cases
|
|
98
|
+
// nobody will notice are gone.
|
|
99
|
+
if (page.IsTruncated && !page.NextContinuationToken) {
|
|
100
|
+
throw new Error(`[eval] listing "${full(prefix)}" in bucket "${bucket}" came back ` +
|
|
101
|
+
`TRUNCATED after ${keys.length} key(s) with no ` +
|
|
102
|
+
`NextContinuationToken, so the rest of the prefix cannot be ` +
|
|
103
|
+
`fetched. Refusing to return a short list — a set that quietly ` +
|
|
104
|
+
`stops cuts a version missing cases, and the experiment built ` +
|
|
105
|
+
`from it looks complete.`);
|
|
106
|
+
}
|
|
107
|
+
token = page.IsTruncated ? page.NextContinuationToken : undefined;
|
|
108
|
+
} while (token);
|
|
109
|
+
return keys.sort();
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* Child prefixes through `Delimiter: "/"`, paged the same way and with the
|
|
113
|
+
* same refusal of a truncated page that has nowhere to continue — what
|
|
114
|
+
* lets `listDatasets`/`findExperiment` skip every run record in the bucket.
|
|
115
|
+
*/
|
|
116
|
+
async listPrefixes(prefix) {
|
|
117
|
+
const out = new Set();
|
|
118
|
+
let token;
|
|
119
|
+
do {
|
|
120
|
+
const page = await send(new ListObjectsV2Command({
|
|
121
|
+
Bucket: bucket,
|
|
122
|
+
Prefix: full(prefix),
|
|
123
|
+
Delimiter: "/",
|
|
124
|
+
ContinuationToken: token,
|
|
125
|
+
}));
|
|
126
|
+
for (const p of page.CommonPrefixes ?? []) {
|
|
127
|
+
if (p.Prefix)
|
|
128
|
+
out.add(root ? p.Prefix.slice(root.length) : p.Prefix);
|
|
129
|
+
}
|
|
130
|
+
if (page.IsTruncated && !page.NextContinuationToken) {
|
|
131
|
+
throw new Error(`[eval] listing the prefixes of "${full(prefix)}" in bucket "${bucket}" came back TRUNCATED ` +
|
|
132
|
+
`with no NextContinuationToken — refusing to return a short list.`);
|
|
133
|
+
}
|
|
134
|
+
token = page.IsTruncated ? page.NextContinuationToken : undefined;
|
|
135
|
+
} while (token);
|
|
136
|
+
return [...out].sort();
|
|
137
|
+
},
|
|
138
|
+
get,
|
|
139
|
+
async put(key, body) {
|
|
140
|
+
await send(new PutObjectCommand({
|
|
141
|
+
Bucket: bucket,
|
|
142
|
+
Key: full(key),
|
|
143
|
+
Body: body,
|
|
144
|
+
ContentType: "application/json",
|
|
145
|
+
}));
|
|
146
|
+
},
|
|
147
|
+
/**
|
|
148
|
+
* Write-if-absent. A real compare-and-set on every store this runs against,
|
|
149
|
+
* including Cere's DDC S3 gateway — verified, not assumed.
|
|
150
|
+
*
|
|
151
|
+
* Two layers, in order:
|
|
152
|
+
*
|
|
153
|
+
* 1. READ FIRST, and return `false` without attempting any write. Catches
|
|
154
|
+
* every collision older than one round trip, and — because it never
|
|
155
|
+
* writes on that path — it is also what keeps a store that ignored the
|
|
156
|
+
* header from corrupting rather than merely losing a cut.
|
|
157
|
+
*
|
|
158
|
+
* 2. `If-None-Match: "*"` on the PUT, with `412` read as "taken". This is
|
|
159
|
+
* the part that actually closes the race, step 1 or no step 1.
|
|
160
|
+
*
|
|
161
|
+
* Step 2 was believed inert on the DDC gateway: it is a hand-written Go
|
|
162
|
+
* service whose documented PutObject path is "stream to DDC, then UPSERT
|
|
163
|
+
* the index row" with no precondition step, its published error model lists
|
|
164
|
+
* no `412`, and nothing in this repo or ROC had ever sent the header. Two
|
|
165
|
+
* company ADR paragraphs contradicted each other on it.
|
|
166
|
+
*
|
|
167
|
+
* Probed against dev rather than left as a caveat: the first conditional
|
|
168
|
+
* PUT succeeded, the SECOND returned `PreconditionFailed` / 412, and the
|
|
169
|
+
* stored bytes were still the first writer's. The gateway honours it, and
|
|
170
|
+
* the ADR paragraph claiming conditional writes exist is the correct one.
|
|
171
|
+
*
|
|
172
|
+
* The unconditional fallback below stays for a store that rejects the
|
|
173
|
+
* header outright with 501 — one unknown header must not be able to take
|
|
174
|
+
* `eval push --cut` down entirely — and step 1 still guards that path. NOT
|
|
175
|
+
* 400: that covers a malformed key, a bad body, a signing problem and much
|
|
176
|
+
* else, and downgrading on it would let two concurrent cutters both
|
|
177
|
+
* believe they won while step 2 was never really tried.
|
|
178
|
+
*/
|
|
179
|
+
async putIfAbsent(key, body) {
|
|
180
|
+
if ((await get(key)) !== null)
|
|
181
|
+
return false;
|
|
182
|
+
const command = (conditional) => new PutObjectCommand({
|
|
183
|
+
Bucket: bucket,
|
|
184
|
+
Key: full(key),
|
|
185
|
+
Body: body,
|
|
186
|
+
ContentType: "application/json",
|
|
187
|
+
...(conditional ? { IfNoneMatch: "*" } : {}),
|
|
188
|
+
});
|
|
189
|
+
try {
|
|
190
|
+
await send(command(true));
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
const code = err.$metadata
|
|
195
|
+
?.httpStatusCode;
|
|
196
|
+
// 412: the key was taken. 409 (`ConditionalRequestConflict`) means a
|
|
197
|
+
// concurrent write collided and AWS says to retry — reporting it as
|
|
198
|
+
// "taken" sends the caller back to re-list and try the next number,
|
|
199
|
+
// which is the same recovery and is self-correcting if the key turns
|
|
200
|
+
// out to be free after all.
|
|
201
|
+
if (err.name === "PreconditionFailed")
|
|
202
|
+
return false;
|
|
203
|
+
if (code === 412 || code === 409)
|
|
204
|
+
return false;
|
|
205
|
+
// A server that does not IMPLEMENT the header must not take the whole
|
|
206
|
+
// feature down with it: `cef eval push --cut` has to keep working
|
|
207
|
+
// against a gateway that answers 501. 501 ONLY — a 400 is "your request
|
|
208
|
+
// was wrong", which covers a malformed key, a bad body, a signing
|
|
209
|
+
// problem and much else, and downgrading on it means two cutters both
|
|
210
|
+
// believe they won while step 2 was never really tried. And the
|
|
211
|
+
// downgrade is announced: a gateway that regresses out of conditional
|
|
212
|
+
// writes must be visible, not a comment nobody reads.
|
|
213
|
+
if (code === 501) {
|
|
214
|
+
console.warn(`[eval] ${await endpointLabel(client)} answered 501 to a conditional ` +
|
|
215
|
+
`PUT (If-None-Match: *) on "${full(key)}" — falling back to an ` +
|
|
216
|
+
`UNCONDITIONAL write. The read check above still stands, but two ` +
|
|
217
|
+
`concurrent writers can now both believe they won a version or a ` +
|
|
218
|
+
`revision. This gateway no longer supports write-once.`);
|
|
219
|
+
await send(command(false));
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
/**
|
|
226
|
+
* The claim `storeConformance` refuses to run without, and the one the
|
|
227
|
+
* doc comment above spends forty lines earning: the SERVER adjudicates,
|
|
228
|
+
* through `If-None-Match: "*"` with 412/409 read as "taken".
|
|
229
|
+
*
|
|
230
|
+
* Pinned, not asserted-by-comment: `eval-store.test.ts` drives this
|
|
231
|
+
* `putIfAbsent` and reads the header off the `PutObjectCommand` the store
|
|
232
|
+
* actually sent. Change the mechanism and that test goes red next to this
|
|
233
|
+
* word.
|
|
234
|
+
*
|
|
235
|
+
* One honest caveat, and it is wider than "one write": the 501 branch
|
|
236
|
+
* above is in a per-call `catch`, so against a gateway that answers 501 it
|
|
237
|
+
* fires on EVERY `putIfAbsent` for as long as that lasts. The whole
|
|
238
|
+
* process is writing unconditionally, with only step 1's read between two
|
|
239
|
+
* cutters and a silently replaced manifest. The per-call `console.warn` is
|
|
240
|
+
* the right mitigation precisely because of that — it names the endpoint
|
|
241
|
+
* every time rather than once — but nobody should read this as a single
|
|
242
|
+
* degraded write.
|
|
243
|
+
*
|
|
244
|
+
* The declaration stays `conditional-write` regardless, because it
|
|
245
|
+
* describes what this store SENDS. A store whose declared mechanism
|
|
246
|
+
* flipped mid-run would be a claim nobody could check against the code,
|
|
247
|
+
* which is the one thing the word has to be.
|
|
248
|
+
*/
|
|
249
|
+
atomicity: "conditional-write",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Build an S3 client for a gateway endpoint. With a `CredentialSource` the
|
|
254
|
+
* client asks it for a credential, carrying its expiry, so the SDK asks again
|
|
255
|
+
* inside its refresh window (5 min before expiry) and the source mints anew.
|
|
256
|
+
*/
|
|
257
|
+
export function gatewayClient(cfg) {
|
|
258
|
+
const credentials = "credentials" in cfg && cfg.credentials
|
|
259
|
+
? async () => {
|
|
260
|
+
const c = await cfg.credentials.get();
|
|
261
|
+
return {
|
|
262
|
+
accessKeyId: c.accessKeyId,
|
|
263
|
+
secretAccessKey: c.secretAccessKey,
|
|
264
|
+
...(c.expiresAt !== undefined ? { expiration: new Date(c.expiresAt) } : {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
: {
|
|
268
|
+
accessKeyId: cfg.accessKeyId,
|
|
269
|
+
secretAccessKey: cfg.secretAccessKey,
|
|
270
|
+
...(cfg.sessionToken ? { sessionToken: cfg.sessionToken } : {}),
|
|
271
|
+
};
|
|
272
|
+
const config = {
|
|
273
|
+
endpoint: cfg.endpoint,
|
|
274
|
+
region: cfg.region ?? "us-east-1",
|
|
275
|
+
// The gateway addresses buckets by path, not by virtual host.
|
|
276
|
+
forcePathStyle: true,
|
|
277
|
+
credentials,
|
|
278
|
+
};
|
|
279
|
+
const client = new S3Client(config);
|
|
280
|
+
// The gateway routes on `/<bucket>/<key>` and reads the empty last segment of
|
|
281
|
+
// a bucket-level path (`/<bucket>/`, which is what the AWS SDK sends under
|
|
282
|
+
// `forcePathStyle`) as a key, answering `400 InvalidArgument: key is
|
|
283
|
+
// required`. Keyed commands are left alone: an object key may legitimately
|
|
284
|
+
// end in `/`. Keying off the command input rather than the path shape also
|
|
285
|
+
// survives a `--endpoint` that carries a path prefix, where `/s3/<bucket>/`
|
|
286
|
+
// is indistinguishable from a bucket-and-key path by pattern alone.
|
|
287
|
+
//
|
|
288
|
+
// ROC needs this too and has `removeTrailingBucketSlashForDdcGateway` in
|
|
289
|
+
// `browserBucketStorage.ts`; that one matches on the path shape, so it is the
|
|
290
|
+
// same behaviour for the bare-host endpoints both use.
|
|
291
|
+
client.middlewareStack.add((next) => (args) => {
|
|
292
|
+
const request = args.request;
|
|
293
|
+
const keyed = typeof args.input === "object" && args.input !== null && "Key" in args.input;
|
|
294
|
+
if (request?.path && request.path.length > 1 && request.path.endsWith("/") && !keyed) {
|
|
295
|
+
request.path = request.path.replace(/\/+$/, "");
|
|
296
|
+
}
|
|
297
|
+
return next(args);
|
|
298
|
+
}, { name: "removeTrailingBucketSlashForDdcGateway", step: "build" });
|
|
299
|
+
return client;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* The store `cef eval` uses: a gateway client over a credential source, and
|
|
303
|
+
* on a 403 one fresh client per refused credential — concurrent requests that
|
|
304
|
+
* fail together share it instead of minting one each.
|
|
305
|
+
*/
|
|
306
|
+
export function gatewayStore(opts) {
|
|
307
|
+
const make = () => (opts.makeClient ?? gatewayClient)({
|
|
308
|
+
endpoint: opts.endpoint, ...(opts.region ? { region: opts.region } : {}), credentials: opts.credentials,
|
|
309
|
+
});
|
|
310
|
+
let current = make();
|
|
311
|
+
let renewing;
|
|
312
|
+
return s3Store({
|
|
313
|
+
bucket: opts.bucket,
|
|
314
|
+
client: current,
|
|
315
|
+
...(opts.root ? { root: opts.root } : {}),
|
|
316
|
+
renew: async (failed) => {
|
|
317
|
+
if (failed !== current)
|
|
318
|
+
return current;
|
|
319
|
+
renewing ??= (async () => {
|
|
320
|
+
try {
|
|
321
|
+
if (!(await opts.credentials.invalidate()))
|
|
322
|
+
return undefined;
|
|
323
|
+
current = make();
|
|
324
|
+
return current;
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
renewing = undefined;
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
return renewing;
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=eval-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-store.js","sourceRoot":"","sources":["../../src/lib/eval-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-store.test.d.ts","sourceRoot":"","sources":["../../src/lib/eval-store.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `s3Store` against an in-process S3 that behaves like the gateway where it
|
|
3
|
+
* matters: a list page holds at most PAGE keys, and `If-None-Match: *` on an
|
|
4
|
+
* existing key answers 412. A plain memory map would pass a store that reads
|
|
5
|
+
* one page or ignores the condition.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, it } from "vitest";
|
|
8
|
+
import { GetObjectCommand, ListObjectsV2Command, PutObjectCommand, } from "@aws-sdk/client-s3";
|
|
9
|
+
import { addCase, createDataset, cutVersion, loadVersion, storeConformance } from "@cef-ai/eval";
|
|
10
|
+
import { mintedCredentials, staticCredentials } from "./eval-auth.js";
|
|
11
|
+
import { gatewayStore, s3Store } from "./eval-store.js";
|
|
12
|
+
const PAGE = 3;
|
|
13
|
+
function fakeS3(opts = {}) {
|
|
14
|
+
const objects = new Map();
|
|
15
|
+
const sent = [];
|
|
16
|
+
const send = async (cmd) => {
|
|
17
|
+
const input = cmd.input;
|
|
18
|
+
sent.push({ name: cmd.constructor.name, input });
|
|
19
|
+
if (cmd instanceof GetObjectCommand) {
|
|
20
|
+
const body = objects.get(String(input.Key));
|
|
21
|
+
if (body === undefined)
|
|
22
|
+
throw Object.assign(new Error("NoSuchKey"), { name: "NoSuchKey" });
|
|
23
|
+
return { Body: { transformToString: async () => body } };
|
|
24
|
+
}
|
|
25
|
+
if (cmd instanceof PutObjectCommand) {
|
|
26
|
+
const key = String(input.Key);
|
|
27
|
+
if (input.IfNoneMatch === "*") {
|
|
28
|
+
if (opts.conditional === "501")
|
|
29
|
+
throw Object.assign(new Error("NotImplemented"), { $metadata: { httpStatusCode: 501 } });
|
|
30
|
+
if (objects.has(key))
|
|
31
|
+
throw Object.assign(new Error("PreconditionFailed"), { name: "PreconditionFailed", $metadata: { httpStatusCode: 412 } });
|
|
32
|
+
}
|
|
33
|
+
objects.set(key, String(input.Body));
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
if (cmd instanceof ListObjectsV2Command) {
|
|
37
|
+
const prefix = String(input.Prefix ?? "");
|
|
38
|
+
const keys = [...objects.keys()].filter((k) => k.startsWith(prefix)).sort();
|
|
39
|
+
// With a delimiter, S3 rolls every key below a "/" into one CommonPrefix and pages over the mix.
|
|
40
|
+
const entries = input.Delimiter === "/"
|
|
41
|
+
? [...new Set(keys.map((k) => {
|
|
42
|
+
const slash = k.indexOf("/", prefix.length);
|
|
43
|
+
return slash < 0 ? k : `${k.slice(0, slash + 1)}`;
|
|
44
|
+
}))].sort()
|
|
45
|
+
: keys;
|
|
46
|
+
const start = input.ContinuationToken ? Number(input.ContinuationToken) : 0;
|
|
47
|
+
const page = entries.slice(start, start + PAGE);
|
|
48
|
+
const more = start + PAGE < entries.length;
|
|
49
|
+
return {
|
|
50
|
+
Contents: page.filter((k) => !k.endsWith("/") || input.Delimiter !== "/").map((Key) => ({ Key })),
|
|
51
|
+
CommonPrefixes: input.Delimiter === "/" ? page.filter((k) => k.endsWith("/")).map((Prefix) => ({ Prefix })) : [],
|
|
52
|
+
IsTruncated: more,
|
|
53
|
+
...(more ? { NextContinuationToken: String(start + PAGE) } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`unexpected command ${cmd.constructor.name}`);
|
|
57
|
+
};
|
|
58
|
+
return { client: { send }, objects, sent };
|
|
59
|
+
}
|
|
60
|
+
describe("s3Store", () => {
|
|
61
|
+
for (const check of storeConformance(async () => s3Store({ bucket: "b", client: fakeS3().client }))) {
|
|
62
|
+
// The 1050-key paging check is slow against nothing in particular; paging is covered below.
|
|
63
|
+
if (check.name.includes("pages past"))
|
|
64
|
+
continue;
|
|
65
|
+
it(check.name, check.run);
|
|
66
|
+
}
|
|
67
|
+
it("lists every page, under the root, with the root stripped", async () => {
|
|
68
|
+
const s3 = fakeS3();
|
|
69
|
+
const store = s3Store({ bucket: "b", client: s3.client, root: "team" });
|
|
70
|
+
for (let i = 0; i < 7; i++)
|
|
71
|
+
await store.put(`evals/k${i}`, "x");
|
|
72
|
+
await store.put("other/z", "x");
|
|
73
|
+
expect(await store.list("evals/")).toEqual(["evals/k0", "evals/k1", "evals/k2", "evals/k3", "evals/k4", "evals/k5", "evals/k6"]);
|
|
74
|
+
expect([...s3.objects.keys()].every((k) => k.startsWith("team/"))).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
it("reads an absent key as null and refuses a taken one with a conditional put", async () => {
|
|
77
|
+
const s3 = fakeS3();
|
|
78
|
+
const store = s3Store({ bucket: "b", client: s3.client });
|
|
79
|
+
expect(await store.get("nope")).toBeNull();
|
|
80
|
+
expect(await store.putIfAbsent("k", "1")).toBe(true);
|
|
81
|
+
expect(await store.putIfAbsent("k", "2")).toBe(false);
|
|
82
|
+
expect(await store.get("k")).toBe("1");
|
|
83
|
+
expect(s3.sent.filter((c) => c.name === "PutObjectCommand").every((c) => c.input.IfNoneMatch === "*")).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
it("lists child prefixes across pages, under the root, with the root stripped", async () => {
|
|
86
|
+
const s3 = fakeS3();
|
|
87
|
+
const store = s3Store({ bucket: "b", client: s3.client, root: "team" });
|
|
88
|
+
for (const d of ["a", "b", "c", "d", "e"]) {
|
|
89
|
+
await store.put(`evals/datasets/${d}/header.json`, "{}");
|
|
90
|
+
await store.put(`evals/datasets/${d}/cases/x/r1.json`, "{}");
|
|
91
|
+
}
|
|
92
|
+
await store.put("evals/datasets/loose.json", "{}");
|
|
93
|
+
expect(await store.listPrefixes("evals/datasets/")).toEqual(["a", "b", "c", "d", "e"].map((d) => `evals/datasets/${d}/`));
|
|
94
|
+
expect(s3.sent.filter((c) => c.name === "ListObjectsV2Command").every((c) => c.input.Delimiter === "/")).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
it("falls back to an unconditional write only on 501", async () => {
|
|
97
|
+
const store = s3Store({ bucket: "b", client: fakeS3({ conditional: "501" }).client });
|
|
98
|
+
expect(await store.putIfAbsent("k", "1")).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it("holds a dataset end to end", async () => {
|
|
101
|
+
const store = s3Store({ bucket: "b", client: fakeS3().client });
|
|
102
|
+
await createDataset(store, { dataset: "triage", workflowId: "wf" });
|
|
103
|
+
for (const id of ["a", "b", "c", "d"])
|
|
104
|
+
await addCase(store, "wf", "triage", { id, input: { id } });
|
|
105
|
+
await cutVersion(store, "wf", "triage");
|
|
106
|
+
expect((await loadVersion(store, "wf", "triage", 1)).cases.map((c) => c.case.id)).toEqual(["a", "b", "c", "d"]);
|
|
107
|
+
});
|
|
108
|
+
it("replaces a refused credential once, shared by every request it failed, and retries each", async () => {
|
|
109
|
+
const backing = fakeS3();
|
|
110
|
+
let n = 0;
|
|
111
|
+
const credentials = mintedCredentials({
|
|
112
|
+
mint: async () => ({ accessKeyId: `k${++n}`, secretAccessKey: "s", expiresAt: Date.now() + 3600_000 }),
|
|
113
|
+
});
|
|
114
|
+
await credentials.get();
|
|
115
|
+
const clients = [];
|
|
116
|
+
const store = gatewayStore({
|
|
117
|
+
endpoint: "https://gw", bucket: "b", credentials,
|
|
118
|
+
makeClient: (cfg) => {
|
|
119
|
+
const send = async (cmd) => {
|
|
120
|
+
const key = (await cfg.credentials.get()).accessKeyId;
|
|
121
|
+
// The first credential was revoked behind our back.
|
|
122
|
+
if (key === "k1") {
|
|
123
|
+
// One request was already in flight on the old credential, and is refused after the renewal.
|
|
124
|
+
if (cmd.input.Key === "slow")
|
|
125
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
126
|
+
throw Object.assign(new Error("InvalidAccessKeyId"), { name: "InvalidAccessKeyId", $metadata: { httpStatusCode: 403 } });
|
|
127
|
+
}
|
|
128
|
+
return backing.client.send(cmd);
|
|
129
|
+
};
|
|
130
|
+
clients.push(`client${clients.length + 1}`);
|
|
131
|
+
return { send };
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
await Promise.all([store.get("slow"), store.put("a", "1"), store.put("b", "2"), store.get("a")]);
|
|
135
|
+
expect(await store.get("a")).toBe("1");
|
|
136
|
+
expect(await store.get("b")).toBe("2");
|
|
137
|
+
expect(clients).toEqual(["client1", "client2"]);
|
|
138
|
+
expect(n).toBe(2);
|
|
139
|
+
});
|
|
140
|
+
it("does not retry a refusal it cannot fix", async () => {
|
|
141
|
+
let calls = 0;
|
|
142
|
+
const store = gatewayStore({
|
|
143
|
+
endpoint: "https://gw", bucket: "b", credentials: staticCredentials({ accessKeyId: "AK", secretAccessKey: "SK" }),
|
|
144
|
+
makeClient: () => ({
|
|
145
|
+
send: async () => {
|
|
146
|
+
calls++;
|
|
147
|
+
throw Object.assign(new Error("AccessDenied"), { name: "AccessDenied", $metadata: { httpStatusCode: 403 } });
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
await expect(store.put("a", "1")).rejects.toThrow(/AccessDenied/);
|
|
152
|
+
expect(calls).toBe(1);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=eval-store.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-store.test.js","sourceRoot":"","sources":["../../src/lib/eval-store.test.ts"],"names":[],"mappings":"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"}
|