@stigmer/runner 3.12.8 → 3.13.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/.build-fingerprint +1 -1
- package/dist/activities/call-agent.d.ts +3 -2
- package/dist/activities/call-agent.js +13 -4
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/discover-mcp-server.js +1 -1
- package/dist/activities/execute-cursor/fetch-interceptor.js +29 -16
- package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +14 -0
- package/dist/activities/execute-cursor/index.js +55 -5
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +15 -9
- package/dist/activities/execute-cursor/message-translator.js +15 -9
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +2 -2
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +9 -0
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -1
- package/dist/activities/execute-deep-agent/setup.js +20 -3
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/client/stigmer-client.js +7 -2
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/payload-codecs.d.ts +1 -1
- package/dist/payload-codecs.js +6 -4
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.js +1 -1
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/runner.js.map +1 -1
- package/dist/shared/memory-retrieval.d.ts +126 -0
- package/dist/shared/memory-retrieval.js +293 -0
- package/dist/shared/memory-retrieval.js.map +1 -0
- package/dist/shared/runner-credential-keys.js +2 -1
- package/dist/shared/runner-credential-keys.js.map +1 -1
- package/dist/shared/zip-extract.js +1 -1
- package/dist/shared/zip-extract.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/history-encryption-e2e.test.ts +1 -1
- package/src/activities/call-agent.ts +14 -4
- package/src/activities/discover-mcp-server.ts +1 -1
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +40 -1
- package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +49 -15
- package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +1 -1
- package/src/activities/execute-cursor/fetch-interceptor.ts +37 -16
- package/src/activities/execute-cursor/index.ts +58 -5
- package/src/activities/execute-cursor/message-translator.ts +15 -9
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +3 -2
- package/src/activities/execute-deep-agent/attachment-injector.ts +2 -2
- package/src/activities/execute-deep-agent/index.ts +10 -0
- package/src/activities/execute-deep-agent/setup.ts +34 -6
- package/src/client/stigmer-client.ts +7 -2
- package/src/payload-codecs.ts +12 -9
- package/src/runner-manager.ts +1 -1
- package/src/runner.ts +1 -1
- package/src/shared/__tests__/memory-retrieval.test.ts +310 -0
- package/src/shared/__tests__/skill-mount.test.ts +1 -1
- package/src/shared/__tests__/skill-writer.test.ts +1 -1
- package/src/shared/__tests__/zip-extract.test.ts +1 -1
- package/src/shared/memory-retrieval.ts +383 -0
- package/src/shared/runner-credential-keys.ts +2 -1
- package/src/shared/zip-extract.ts +1 -1
- package/dist/claimcheck/compressor.d.ts +0 -2
- package/dist/claimcheck/compressor.js +0 -8
- package/dist/claimcheck/compressor.js.map +0 -1
- package/dist/claimcheck/config.d.ts +0 -7
- package/dist/claimcheck/config.js +0 -10
- package/dist/claimcheck/config.js.map +0 -1
- package/dist/claimcheck/index.d.ts +0 -3
- package/dist/claimcheck/index.js +0 -4
- package/dist/claimcheck/index.js.map +0 -1
- package/dist/claimcheck/payload-codec.d.ts +0 -23
- package/dist/claimcheck/payload-codec.js +0 -105
- package/dist/claimcheck/payload-codec.js.map +0 -1
- package/dist/encryption/config.d.ts +0 -64
- package/dist/encryption/config.js +0 -113
- package/dist/encryption/config.js.map +0 -1
- package/dist/encryption/index.d.ts +0 -3
- package/dist/encryption/index.js +0 -3
- package/dist/encryption/index.js.map +0 -1
- package/dist/encryption/payload-codec.d.ts +0 -41
- package/dist/encryption/payload-codec.js +0 -130
- package/dist/encryption/payload-codec.js.map +0 -1
- package/dist/shared/zip-structure.d.ts +0 -61
- package/dist/shared/zip-structure.js +0 -128
- package/dist/shared/zip-structure.js.map +0 -1
- package/src/__test-utils__/zip-fixtures.ts +0 -206
- package/src/__tests__/claimcheck-codec.test.ts +0 -256
- package/src/__tests__/encryption-codec.test.ts +0 -287
- package/src/__tests__/fixtures/encrypted-payload-fixture.json +0 -15
- package/src/claimcheck/compressor.ts +0 -9
- package/src/claimcheck/config.ts +0 -20
- package/src/claimcheck/index.ts +0 -3
- package/src/claimcheck/payload-codec.ts +0 -139
- package/src/encryption/config.ts +0 -161
- package/src/encryption/index.ts +0 -3
- package/src/encryption/payload-codec.ts +0 -152
- package/src/shared/zip-structure.ts +0 -181
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared ZIP fixture builder for runner unit tests.
|
|
3
|
-
*
|
|
4
|
-
* Emits complete, real-shaped archives — local file headers (optionally
|
|
5
|
-
* streaming-style, the Go stdlib writer's default), payloads, central
|
|
6
|
-
* directory, and EOCD — because that is the only shape that can reach the
|
|
7
|
-
* runner: both editions' skill push gates validate artifacts with
|
|
8
|
-
* central-directory-based readers (see zip-extract.ts's module doc and
|
|
9
|
-
* design record 017). Earlier fixtures emitted local headers only, a
|
|
10
|
-
* shape no real ZIP writer produces, and were coupled to the old parser's
|
|
11
|
-
* front-to-back walk.
|
|
12
|
-
*
|
|
13
|
-
* Mirrors the conformance suite's `zipFilesStreaming()`
|
|
14
|
-
* (test/conformance/src/support/skills.ts) so unit fixtures and
|
|
15
|
-
* cross-edition fixtures share one shape.
|
|
16
|
-
*
|
|
17
|
-
* Lives in src/__test-utils__/ so `tsc --noEmit` covers it while
|
|
18
|
-
* tsconfig.build.json keeps it out of dist/.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { deflateRawSync } from "node:zlib";
|
|
22
|
-
|
|
23
|
-
export interface ZipFixtureFile {
|
|
24
|
-
name: string;
|
|
25
|
-
/** Text content is UTF-8 encoded; pass bytes directly for binary payloads. */
|
|
26
|
-
content: string | Uint8Array;
|
|
27
|
-
/** Compression method for the entry. Defaults to stored. */
|
|
28
|
-
method?: "stored" | "deflated";
|
|
29
|
-
/**
|
|
30
|
-
* Emit the entry the way Go's archive/zip does by default: general-purpose
|
|
31
|
-
* flag bit 3 set, zeroed sizes in the local header, and a trailing data
|
|
32
|
-
* descriptor (with the conventional signature) carrying the real values.
|
|
33
|
-
*/
|
|
34
|
-
streaming?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Lie about the entry's uncompressed size everywhere the writer would
|
|
37
|
-
* declare it (local header, data descriptor, central directory). Models a
|
|
38
|
-
* crafted archive whose declarations disagree with its actual payload —
|
|
39
|
-
* the input class the attachment injector's declared-size enforcement
|
|
40
|
-
* exists to reject (issue #567).
|
|
41
|
-
*/
|
|
42
|
-
declaredUncompressedSize?: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ZipFixtureOptions {
|
|
46
|
-
/** Trailing archive comment appended after the EOCD record. */
|
|
47
|
-
comment?: string;
|
|
48
|
-
/**
|
|
49
|
-
* Drop the central directory and EOCD, leaving only local headers and
|
|
50
|
-
* payloads. No real ZIP writer produces this shape — it models a download
|
|
51
|
-
* truncated before the archive's index.
|
|
52
|
-
*/
|
|
53
|
-
omitCentralDirectory?: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Build a ZIP archive from path + content pairs. */
|
|
57
|
-
export function buildZip(files: ZipFixtureFile[], options?: ZipFixtureOptions): Uint8Array {
|
|
58
|
-
const out = new ByteWriter();
|
|
59
|
-
|
|
60
|
-
interface WrittenEntry {
|
|
61
|
-
nameBytes: Uint8Array;
|
|
62
|
-
payload: Uint8Array;
|
|
63
|
-
declaredUncompressed: number;
|
|
64
|
-
crc: number;
|
|
65
|
-
flags: number;
|
|
66
|
-
method: number;
|
|
67
|
-
localHeaderOffset: number;
|
|
68
|
-
}
|
|
69
|
-
const written: WrittenEntry[] = [];
|
|
70
|
-
|
|
71
|
-
for (const file of files) {
|
|
72
|
-
const contentBytes =
|
|
73
|
-
typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content;
|
|
74
|
-
const deflated = file.method === "deflated";
|
|
75
|
-
const payload = deflated ? new Uint8Array(deflateRawSync(contentBytes)) : contentBytes;
|
|
76
|
-
const entry: WrittenEntry = {
|
|
77
|
-
nameBytes: new TextEncoder().encode(file.name),
|
|
78
|
-
payload,
|
|
79
|
-
declaredUncompressed: file.declaredUncompressedSize ?? contentBytes.length,
|
|
80
|
-
crc: crc32(contentBytes),
|
|
81
|
-
flags: file.streaming ? 0x0008 : 0,
|
|
82
|
-
method: deflated ? 8 : 0,
|
|
83
|
-
localHeaderOffset: out.length,
|
|
84
|
-
};
|
|
85
|
-
written.push(entry);
|
|
86
|
-
|
|
87
|
-
out.u32(0x04034b50); // local file header signature
|
|
88
|
-
out.u16(20); // version needed to extract
|
|
89
|
-
out.u16(entry.flags);
|
|
90
|
-
out.u16(entry.method);
|
|
91
|
-
out.u16(0); // mod time
|
|
92
|
-
out.u16(0); // mod date
|
|
93
|
-
out.u32(file.streaming ? 0 : entry.crc);
|
|
94
|
-
out.u32(file.streaming ? 0 : payload.length);
|
|
95
|
-
out.u32(file.streaming ? 0 : entry.declaredUncompressed);
|
|
96
|
-
out.u16(entry.nameBytes.length);
|
|
97
|
-
out.u16(0); // extra field length
|
|
98
|
-
out.raw(entry.nameBytes);
|
|
99
|
-
out.raw(payload);
|
|
100
|
-
|
|
101
|
-
if (file.streaming) {
|
|
102
|
-
out.u32(0x08074b50); // data descriptor signature (Go writes it)
|
|
103
|
-
out.u32(entry.crc);
|
|
104
|
-
out.u32(payload.length);
|
|
105
|
-
out.u32(entry.declaredUncompressed);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (options?.omitCentralDirectory) {
|
|
110
|
-
return out.toUint8Array();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const centralDirectoryOffset = out.length;
|
|
114
|
-
for (const entry of written) {
|
|
115
|
-
out.u32(0x02014b50); // central directory record signature
|
|
116
|
-
out.u16(20); // version made by
|
|
117
|
-
out.u16(20); // version needed to extract
|
|
118
|
-
out.u16(entry.flags);
|
|
119
|
-
out.u16(entry.method);
|
|
120
|
-
out.u16(0); // mod time
|
|
121
|
-
out.u16(0); // mod date
|
|
122
|
-
out.u32(entry.crc);
|
|
123
|
-
out.u32(entry.payload.length);
|
|
124
|
-
out.u32(entry.declaredUncompressed);
|
|
125
|
-
out.u16(entry.nameBytes.length);
|
|
126
|
-
out.u16(0); // extra field length
|
|
127
|
-
out.u16(0); // comment length
|
|
128
|
-
out.u16(0); // disk number start
|
|
129
|
-
out.u16(0); // internal attributes
|
|
130
|
-
out.u32(0); // external attributes
|
|
131
|
-
out.u32(entry.localHeaderOffset);
|
|
132
|
-
out.raw(entry.nameBytes);
|
|
133
|
-
}
|
|
134
|
-
const centralDirectorySize = out.length - centralDirectoryOffset;
|
|
135
|
-
|
|
136
|
-
const commentBytes = new TextEncoder().encode(options?.comment ?? "");
|
|
137
|
-
out.u32(0x06054b50); // EOCD signature
|
|
138
|
-
out.u16(0); // disk number
|
|
139
|
-
out.u16(0); // disk with central directory
|
|
140
|
-
out.u16(written.length); // entries on this disk
|
|
141
|
-
out.u16(written.length); // total entries
|
|
142
|
-
out.u32(centralDirectorySize);
|
|
143
|
-
out.u32(centralDirectoryOffset);
|
|
144
|
-
out.u16(commentBytes.length);
|
|
145
|
-
out.raw(commentBytes);
|
|
146
|
-
|
|
147
|
-
return out.toUint8Array();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Chunked assembly instead of a number[]-per-byte accumulator: fixtures at
|
|
151
|
-
// the injector's 100 MB zip-bomb limit are built from payload-sized chunks,
|
|
152
|
-
// which a per-byte spread-push cannot survive (argument-count overflow).
|
|
153
|
-
class ByteWriter {
|
|
154
|
-
private readonly chunks: Uint8Array[] = [];
|
|
155
|
-
private size = 0;
|
|
156
|
-
|
|
157
|
-
get length(): number {
|
|
158
|
-
return this.size;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
u16(v: number): void {
|
|
162
|
-
this.raw(new Uint8Array([v & 0xff, (v >>> 8) & 0xff]));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
u32(v: number): void {
|
|
166
|
-
this.raw(new Uint8Array([v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff]));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
raw(b: Uint8Array): void {
|
|
170
|
-
this.chunks.push(b);
|
|
171
|
-
this.size += b.length;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
toUint8Array(): Uint8Array {
|
|
175
|
-
const result = new Uint8Array(this.size);
|
|
176
|
-
let offset = 0;
|
|
177
|
-
for (const chunk of this.chunks) {
|
|
178
|
-
result.set(chunk, offset);
|
|
179
|
-
offset += chunk.length;
|
|
180
|
-
}
|
|
181
|
-
return result;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Standard CRC-32 (IEEE 802.3, the ZIP checksum), table-driven so fixtures
|
|
186
|
-
// at the injector's 100 MB limit stay cheap to build. Semantically identical
|
|
187
|
-
// to the conformance suite's inline bit-loop helper.
|
|
188
|
-
const CRC_TABLE = (() => {
|
|
189
|
-
const table = new Uint32Array(256);
|
|
190
|
-
for (let n = 0; n < 256; n++) {
|
|
191
|
-
let c = n;
|
|
192
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
193
|
-
c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
|
|
194
|
-
}
|
|
195
|
-
table[n] = c;
|
|
196
|
-
}
|
|
197
|
-
return table;
|
|
198
|
-
})();
|
|
199
|
-
|
|
200
|
-
function crc32(data: Uint8Array): number {
|
|
201
|
-
let crc = 0xffffffff;
|
|
202
|
-
for (let i = 0; i < data.length; i++) {
|
|
203
|
-
crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ data[i]!) & 0xff]!;
|
|
204
|
-
}
|
|
205
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
206
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { ClaimcheckPayloadCodec } from "../claimcheck/payload-codec.js";
|
|
3
|
-
import { compress } from "../claimcheck/compressor.js";
|
|
4
|
-
import type { ClaimcheckConfig } from "../claimcheck/config.js";
|
|
5
|
-
import { makeInMemoryArtifactStorage } from "../__test-utils__/fake-artifact-storage.js";
|
|
6
|
-
import type { Payload } from "@temporalio/common";
|
|
7
|
-
|
|
8
|
-
function makeConfig(overrides: Partial<ClaimcheckConfig> = {}): ClaimcheckConfig {
|
|
9
|
-
return {
|
|
10
|
-
enabled: true,
|
|
11
|
-
thresholdBytes: 1024,
|
|
12
|
-
compressionEnabled: true,
|
|
13
|
-
keyPrefix: "claimcheck/",
|
|
14
|
-
...overrides,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeStorage() {
|
|
19
|
-
// The canonical in-memory double; `uploads` aliases its backing Map so the
|
|
20
|
-
// existing `storage.uploads.*` assertions keep working, and `download` reads
|
|
21
|
-
// straight from what `encode` uploaded (no fetch to stub).
|
|
22
|
-
const { storage, blobs } = makeInMemoryArtifactStorage();
|
|
23
|
-
return Object.assign(storage, { uploads: blobs });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function makePayload(data: string | Buffer): Payload {
|
|
27
|
-
const buf = typeof data === "string" ? Buffer.from(data) : data;
|
|
28
|
-
return {
|
|
29
|
-
metadata: { encoding: Buffer.from("binary/plain") },
|
|
30
|
-
data: buf,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function makeLargePayload(sizeBytes: number): Payload {
|
|
35
|
-
const buf = Buffer.alloc(sizeBytes, "x");
|
|
36
|
-
return {
|
|
37
|
-
metadata: { encoding: Buffer.from("binary/plain") },
|
|
38
|
-
data: buf,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
describe("ClaimcheckPayloadCodec", () => {
|
|
43
|
-
let storage: ReturnType<typeof makeStorage>;
|
|
44
|
-
let codec: ClaimcheckPayloadCodec;
|
|
45
|
-
|
|
46
|
-
beforeEach(() => {
|
|
47
|
-
storage = makeStorage();
|
|
48
|
-
codec = new ClaimcheckPayloadCodec(storage, makeConfig());
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe("encode", () => {
|
|
52
|
-
it("passes through payloads below threshold unchanged", async () => {
|
|
53
|
-
const payload = makePayload("small data");
|
|
54
|
-
const [result] = await codec.encode([payload]);
|
|
55
|
-
expect(result).toBe(payload);
|
|
56
|
-
expect(storage.uploads.size).toBe(0);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("passes through empty payloads", async () => {
|
|
60
|
-
const payload: Payload = { metadata: {}, data: undefined };
|
|
61
|
-
const [result] = await codec.encode([payload]);
|
|
62
|
-
expect(result).toBe(payload);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("offloads payloads at or above threshold", async () => {
|
|
66
|
-
const payload = makeLargePayload(1024);
|
|
67
|
-
const [result] = await codec.encode([payload]);
|
|
68
|
-
|
|
69
|
-
expect(result).not.toBe(payload);
|
|
70
|
-
expect(storage.uploads.size).toBe(1);
|
|
71
|
-
|
|
72
|
-
const markerMeta = result.metadata?.["encoding"];
|
|
73
|
-
expect(Buffer.from(markerMeta!).toString()).toBe("binary/claimcheck");
|
|
74
|
-
|
|
75
|
-
const marker = JSON.parse(Buffer.from(result.data!).toString());
|
|
76
|
-
expect(marker.key).toMatch(/^claimcheck\//);
|
|
77
|
-
expect(marker.size).toBe(1024);
|
|
78
|
-
expect(marker.compressed).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("skips compression when it does not reduce size", async () => {
|
|
82
|
-
const randomBuf = Buffer.from(
|
|
83
|
-
Array.from({ length: 1024 }, () => Math.floor(Math.random() * 256)),
|
|
84
|
-
);
|
|
85
|
-
const payload: Payload = {
|
|
86
|
-
metadata: { encoding: Buffer.from("binary/plain") },
|
|
87
|
-
data: randomBuf,
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const compressedSize = compress(randomBuf).length;
|
|
91
|
-
if (compressedSize >= randomBuf.length) {
|
|
92
|
-
const [result] = await codec.encode([payload]);
|
|
93
|
-
const marker = JSON.parse(Buffer.from(result.data!).toString());
|
|
94
|
-
expect(marker.compressed).toBe(false);
|
|
95
|
-
|
|
96
|
-
const uploaded = storage.uploads.values().next().value!;
|
|
97
|
-
expect(uploaded.length).toBe(randomBuf.length);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("does not compress when compression is disabled", async () => {
|
|
102
|
-
codec = new ClaimcheckPayloadCodec(
|
|
103
|
-
storage,
|
|
104
|
-
makeConfig({ compressionEnabled: false }),
|
|
105
|
-
);
|
|
106
|
-
const payload = makeLargePayload(1024);
|
|
107
|
-
const [result] = await codec.encode([payload]);
|
|
108
|
-
|
|
109
|
-
const marker = JSON.parse(Buffer.from(result.data!).toString());
|
|
110
|
-
expect(marker.compressed).toBe(false);
|
|
111
|
-
|
|
112
|
-
const uploaded = storage.uploads.values().next().value!;
|
|
113
|
-
expect(uploaded.length).toBe(1024);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("handles multiple payloads in a batch", async () => {
|
|
117
|
-
const small = makePayload("tiny");
|
|
118
|
-
const large = makeLargePayload(2048);
|
|
119
|
-
|
|
120
|
-
const results = await codec.encode([small, large]);
|
|
121
|
-
expect(results[0]).toBe(small);
|
|
122
|
-
expect(results[1]).not.toBe(large);
|
|
123
|
-
expect(storage.uploads.size).toBe(1);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("uses configured key prefix", async () => {
|
|
127
|
-
codec = new ClaimcheckPayloadCodec(
|
|
128
|
-
storage,
|
|
129
|
-
makeConfig({ keyPrefix: "custom-prefix/" }),
|
|
130
|
-
);
|
|
131
|
-
const payload = makeLargePayload(1024);
|
|
132
|
-
const [result] = await codec.encode([payload]);
|
|
133
|
-
|
|
134
|
-
const marker = JSON.parse(Buffer.from(result.data!).toString());
|
|
135
|
-
expect(marker.key).toMatch(/^custom-prefix\//);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe("decode", () => {
|
|
140
|
-
it("passes through non-claimcheck payloads unchanged", async () => {
|
|
141
|
-
const payload = makePayload("normal data");
|
|
142
|
-
const [result] = await codec.decode([payload]);
|
|
143
|
-
expect(result).toBe(payload);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("retrieves and decompresses offloaded payloads (round-trip)", async () => {
|
|
147
|
-
const original = makeLargePayload(2048);
|
|
148
|
-
const [encoded] = await codec.encode([original]);
|
|
149
|
-
|
|
150
|
-
// decode reads back through storage.download from what encode uploaded.
|
|
151
|
-
const [decoded] = await codec.decode([encoded]);
|
|
152
|
-
expect(Buffer.from(decoded.data!)).toEqual(original.data);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("restores the original payload metadata (round-trip)", async () => {
|
|
156
|
-
// The restored payload must carry its ORIGINAL encoding, not the
|
|
157
|
-
// marker's binary/claimcheck — otherwise no payload converter can
|
|
158
|
-
// interpret it and composition with the encryption codec breaks.
|
|
159
|
-
const original: Payload = {
|
|
160
|
-
metadata: {
|
|
161
|
-
encoding: Buffer.from("json/plain"),
|
|
162
|
-
"custom-key": Buffer.from("custom-value"),
|
|
163
|
-
},
|
|
164
|
-
data: Buffer.alloc(2048, "y"),
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const [encoded] = await codec.encode([original]);
|
|
168
|
-
const [decoded] = await codec.decode([encoded]);
|
|
169
|
-
|
|
170
|
-
expect(Buffer.from(decoded.metadata!["encoding"]!).toString()).toBe("json/plain");
|
|
171
|
-
expect(Buffer.from(decoded.metadata!["custom-key"]!).toString()).toBe("custom-value");
|
|
172
|
-
expect(Buffer.from(decoded.data!)).toEqual(original.data);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it("decodes legacy markers written before metadata preservation", async () => {
|
|
176
|
-
const original = makeLargePayload(2048);
|
|
177
|
-
const [encoded] = await codec.encode([original]);
|
|
178
|
-
|
|
179
|
-
// Simulate a pre-fix marker: strip the metadata field.
|
|
180
|
-
const marker = JSON.parse(Buffer.from(encoded.data!).toString());
|
|
181
|
-
delete marker.metadata;
|
|
182
|
-
const legacy: Payload = {
|
|
183
|
-
metadata: encoded.metadata,
|
|
184
|
-
data: Buffer.from(JSON.stringify(marker)),
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const [decoded] = await codec.decode([legacy]);
|
|
188
|
-
expect(Buffer.from(decoded.data!)).toEqual(original.data);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("retrieves uncompressed payloads correctly", async () => {
|
|
192
|
-
codec = new ClaimcheckPayloadCodec(
|
|
193
|
-
storage,
|
|
194
|
-
makeConfig({ compressionEnabled: false }),
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
const original = makeLargePayload(1500);
|
|
198
|
-
const [encoded] = await codec.encode([original]);
|
|
199
|
-
|
|
200
|
-
const [decoded] = await codec.decode([encoded]);
|
|
201
|
-
expect(Buffer.from(decoded.data!)).toEqual(original.data);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("wraps a download failure with the claimcheck error contract", async () => {
|
|
205
|
-
const encoded: Payload = {
|
|
206
|
-
metadata: { encoding: Buffer.from("binary/claimcheck") },
|
|
207
|
-
data: Buffer.from(JSON.stringify({
|
|
208
|
-
key: "claimcheck/missing",
|
|
209
|
-
size: 100,
|
|
210
|
-
compressed: false,
|
|
211
|
-
})),
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// Drive the failure through the port; the proxy download surfaces the HTTP
|
|
215
|
-
// status, which the codec must preserve in its wrapped, key-scoped message.
|
|
216
|
-
storage.download.mockRejectedValueOnce(
|
|
217
|
-
new Error("Artifact download failed (HTTP 404) for key 'claimcheck/missing'"),
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
await expect(codec.decode([encoded])).rejects.toThrow(
|
|
221
|
-
/Claimcheck retrieve failed for key claimcheck\/missing.*HTTP 404/,
|
|
222
|
-
);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe("threshold boundary", () => {
|
|
227
|
-
it("does NOT offload at threshold - 1", async () => {
|
|
228
|
-
const payload = makeLargePayload(1023);
|
|
229
|
-
const [result] = await codec.encode([payload]);
|
|
230
|
-
expect(result).toBe(payload);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("offloads at exactly threshold", async () => {
|
|
234
|
-
const payload = makeLargePayload(1024);
|
|
235
|
-
const [result] = await codec.encode([payload]);
|
|
236
|
-
expect(result).not.toBe(payload);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("offloads above threshold", async () => {
|
|
240
|
-
const payload = makeLargePayload(1025);
|
|
241
|
-
const [result] = await codec.encode([payload]);
|
|
242
|
-
expect(result).not.toBe(payload);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
describe("compressor", () => {
|
|
247
|
-
it("compresses and decompresses correctly", async () => {
|
|
248
|
-
const { compress: c, decompress: d } = await import("../claimcheck/compressor.js");
|
|
249
|
-
const input = Buffer.from("hello world ".repeat(100));
|
|
250
|
-
const compressed = c(input);
|
|
251
|
-
expect(compressed.length).toBeLessThan(input.length);
|
|
252
|
-
const decompressed = d(compressed);
|
|
253
|
-
expect(decompressed).toEqual(input);
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
});
|