@pipelex/sdk 0.11.0 → 0.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.
@@ -0,0 +1,518 @@
1
+ /**
2
+ * The offline codegen drift check — pure hashing, no engine, no network, no API key.
3
+ *
4
+ * `client.codegen()` returns stamped artifacts plus a `codegen.lock`; a consumer that
5
+ * commits that tree needs a CI gate over it. This module is that gate — it proves the tree
6
+ * and its lock still agree with each other. Whether the tree still matches what the METHOD
7
+ * resolves to is a second, separate question: answering it needs the engine, so the check
8
+ * cannot, and a caller closes it by comparing {@link CodegenCheckReport.crateFingerprint}
9
+ * against a live `codegen()` response. It never regenerates: regeneration is a **dev action**
10
+ * (it needs the engine), the check is the **CI action** (it needs only hashes), so a
11
+ * template improvement upstream never reddens a consumer's CI.
12
+ *
13
+ * The check is a mirror of pipelex's `run_codegen_check`, and deliberately so: the
14
+ * spec pins the algorithm as "pure hashing, so any client (the CLI, an SDK, a short
15
+ * CI script) implements it identically". A verdict computed here must equal the one
16
+ * `pipelex codegen check` computes over the same bytes — including the drift detail
17
+ * strings, which are kept verbatim so a consumer switching between the CLI and this
18
+ * SDK reads the same report.
19
+ *
20
+ * **Pure by design.** No filesystem, no fetch, no Node builtins: the caller walks its
21
+ * own tree and hands in the text; this module owns the verdict. That is the same
22
+ * split pipelex draws between `build_stamped_projection` (pure) and
23
+ * `write_stamped_projection` (local materialization). Hashing goes through WebCrypto rather
24
+ * than `node:crypto` so this module adds no Node builtin to the barrel's import graph — which
25
+ * is not by itself the same as the barrel being browser-bundleable, since `upload.ts` still
26
+ * names `node:fs/promises` and a browser-targeting bundler must mark `node:*` external.
27
+ *
28
+ * @see {@link runCodegenCheck} for the caller's obligations, which are load-bearing:
29
+ * an incomplete file list or re-encoded text produces a *wrong verdict*, not an error.
30
+ */
31
+ import { parse as parseToml } from "smol-toml";
32
+ // ── Stamp grammar (mirror of pipelex's `codegen/stamp.py`) ───────────────
33
+ const STAMP_BEGIN_MARKER = ">>> pipelex-codegen-stamp >>>";
34
+ const STAMP_END_MARKER = "<<< pipelex-codegen-stamp <<<";
35
+ /** The line-comment syntax each stampable file type is stamped in. */
36
+ const COMMENT_PREFIX_BY_SUFFIX = { ".py": "#", ".ts": "//" };
37
+ /**
38
+ * The file suffixes codegen stamps — the mirror of pipelex's `STAMPABLE_SUFFIXES`.
39
+ *
40
+ * Exported so a caller's tree walk filters exactly like the check does: a file whose
41
+ * suffix is not here can never be an artifact and can never be an orphan, so it is
42
+ * skipped rather than rejected (this is what lets a consumer park a sidecar such as
43
+ * `sources.json` beside the lock).
44
+ */
45
+ export const STAMPABLE_ARTIFACT_SUFFIXES = Object.freeze(Object.keys(COMMENT_PREFIX_BY_SUFFIX).sort());
46
+ /** Whether `path` names a file type codegen stamps (and therefore one the check considers). */
47
+ export function isStampableArtifactPath(path) {
48
+ return Object.hasOwn(COMMENT_PREFIX_BY_SUFFIX, suffixOf(path));
49
+ }
50
+ /** The last dot-suffix of a path's final component (`""` for a dotfile or a suffixless name). */
51
+ function suffixOf(path) {
52
+ const name = path.slice(path.lastIndexOf("/") + 1);
53
+ const dotIndex = name.lastIndexOf(".");
54
+ return dotIndex <= 0 ? "" : name.slice(dotIndex);
55
+ }
56
+ /** The comment prefix for a path's file type, or `undefined` when the type is not stampable. */
57
+ function commentPrefixFor(path) {
58
+ return COMMENT_PREFIX_BY_SUFFIX[suffixOf(path)];
59
+ }
60
+ /**
61
+ * Split a stamped file into its recorded content hash and the body below the stamp,
62
+ * or `null` when no parseable stamp opens the text.
63
+ *
64
+ * Faithful to `parse_stamped` with one deliberate relaxation: the projection axes
65
+ * (`kind` / `target`) must be *present and well-formed*, but their values are not
66
+ * checked against this SDK's vocabulary. pipelex validates them against its own
67
+ * enums, which cannot lag its own emitter; an SDK copy can, and rejecting an
68
+ * unknown-but-valid future `kind` would report every artifact as `hand-edited`.
69
+ * For today's vocabulary the two are identical.
70
+ */
71
+ function parseStamped(text, commentPrefix) {
72
+ const beginLine = `${commentPrefix} ${STAMP_BEGIN_MARKER}`;
73
+ const endLine = `${commentPrefix} ${STAMP_END_MARKER}`;
74
+ if (!text.startsWith(beginLine)) {
75
+ return null;
76
+ }
77
+ const endIndex = text.indexOf(`\n${endLine}\n`);
78
+ if (endIndex === -1) {
79
+ return null;
80
+ }
81
+ const headerRegion = text.slice(beginLine.length + 1, endIndex);
82
+ const body = text.slice(endIndex + endLine.length + 2);
83
+ // Every line the emitter writes inside the fence carries the comment prefix, so anything else in
84
+ // there was injected by hand — and it would otherwise verify as pristine, since the hash covers
85
+ // only the body BELOW the fence. An executable line hiding inside a "DO NOT EDIT" block is not a
86
+ // valid stamp.
87
+ //
88
+ // The wide split is load-bearing here rather than incidental. U+2028 and U+2029 terminate a `//`
89
+ // comment in ECMAScript, so under a `\n`-only split a `.ts` header carrying a raw U+2028 followed
90
+ // by a statement is ONE prefixed line to this gate and TWO lines to the JavaScript engine: the
91
+ // check would report the file current while the engine runs the injected statement. The reference
92
+ // splits on the same set for the same reason (`str.splitlines()` in `parse_stamped`).
93
+ //
94
+ // Nothing legitimate is rejected — the emitter writes `<prefix> <key>: <value>` lines and nothing
95
+ // else, and no caller supplies the two fields that could carry arbitrary text. If either ever
96
+ // does, the line terminators get escaped AT THE EMITTER; this gate stays correct either way,
97
+ // because the emitter never writes a raw one.
98
+ if (pythonSplitLines(headerRegion).some((line) => !line.startsWith(commentPrefix))) {
99
+ return null;
100
+ }
101
+ const fields = parseStampFields(headerRegion, commentPrefix);
102
+ const projection = fields.get("projection");
103
+ if (projection === undefined || !isWellFormedProjection(projection)) {
104
+ return null;
105
+ }
106
+ if (!isJsonObject(fields.get("options") ?? "{}")) {
107
+ return null;
108
+ }
109
+ return { contentHash: fields.get("content_hash") ?? "", body };
110
+ }
111
+ /**
112
+ * The line boundaries Python's `str.splitlines()` breaks on — deliberately not just `\n`.
113
+ *
114
+ * `normalizeNewlines` has already folded `\r\n` and `\r` away, but `splitlines` also breaks on
115
+ * VT, FF, FS/GS/RS, NEL and the Unicode line/paragraph separators, and those survive it.
116
+ * Splitting on the narrower set diverged in both directions: a header carrying U+2028 inside a
117
+ * field value parsed here as one long value (current) where the reference truncates it
118
+ * (hand-edited), and a header whose lines were JOINED by U+2028 parsed there and not here.
119
+ */
120
+ // FS/GS/RS are line boundaries to Python's `splitlines()`, so matching them is the point.
121
+ // eslint-disable-next-line no-control-regex
122
+ const PYTHON_LINE_BOUNDARY = /\r\n|[\n\r\v\f\u001c\u001d\u001e\u0085\u2028\u2029]/u;
123
+ /**
124
+ * Split like Python's `str.splitlines()` — including its trailing-empty rule.
125
+ *
126
+ * `String.prototype.split` keeps a trailing empty segment when the text ends with a boundary, and
127
+ * yields `[""]` for the empty string, where `splitlines` drops the segment and yields `[]`. That
128
+ * difference is invisible to a field loop that skips lines without a `:`, but the prefix gate in
129
+ * `parseStamped` reads EVERY segment — so a header whose last line ends with a line boundary would
130
+ * be rejected here and accepted by the CLI, which is the SDK-is-stricter direction this module
131
+ * exists to avoid.
132
+ */
133
+ function pythonSplitLines(text) {
134
+ if (text === "") {
135
+ return [];
136
+ }
137
+ const lines = text.split(PYTHON_LINE_BOUNDARY);
138
+ if (lines[lines.length - 1] === "") {
139
+ lines.pop();
140
+ }
141
+ return lines;
142
+ }
143
+ /** The characters `str.isspace()` is true for — what Python's `str.strip()` removes. */
144
+ const PYTHON_SPACE_CLASS = "\\t\\n\\v\\f\\r\\u001c-\\u001f\\u0020\\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000";
145
+ const PYTHON_STRIP = new RegExp("^[" + PYTHON_SPACE_CLASS + "]+|[" + PYTHON_SPACE_CLASS + "]+" + "$", "gu");
146
+ /**
147
+ * Strip the whitespace Python's `str.strip()` strips — deliberately not JavaScript's `trim()`.
148
+ *
149
+ * The two sets disagree in both directions and each disagreement can flip a verdict. JS strips
150
+ * U+FEFF where Python does not, so a BOM planted before a recorded `content_hash` vanished here
151
+ * and the stamp still verified while the CLI reported `hand-edited`; Python strips U+001C-U+001F
152
+ * and U+0085 where JS does not. Mirroring `str.isspace()` is what keeps a parsed field value
153
+ * byte-identical across the two readers.
154
+ */
155
+ function pythonStrip(text) {
156
+ return text.replace(PYTHON_STRIP, "");
157
+ }
158
+ /** Whether `text` opens with a stamp block in the given comment syntax (the orphan predicate). */
159
+ function hasStamp(text, commentPrefix) {
160
+ return text.startsWith(`${commentPrefix} ${STAMP_BEGIN_MARKER}`);
161
+ }
162
+ function parseStampFields(headerRegion, commentPrefix) {
163
+ const fields = new Map();
164
+ // The same split rule as the gate in `parseStamped`, and it has to stay the same one: a narrower
165
+ // split here would rejoin a line the gate had already split, so a field value would swallow the
166
+ // injected text the gate exists to catch.
167
+ for (const rawLine of pythonSplitLines(headerRegion)) {
168
+ // `parseStamped` has already rejected every line without the prefix, so stripping it is
169
+ // unconditional. The tolerant fallback this replaces is what let an uncommented line through.
170
+ const stripped = pythonStrip(rawLine.slice(commentPrefix.length));
171
+ const separatorIndex = stripped.indexOf(":");
172
+ if (separatorIndex === -1) {
173
+ continue;
174
+ }
175
+ fields.set(pythonStrip(stripped.slice(0, separatorIndex)), pythonStrip(stripped.slice(separatorIndex + 1)));
176
+ }
177
+ return fields;
178
+ }
179
+ /** `<kind> / <target>` with an optional trailing ` / <pipe_ref>` for a per-pipe projection. */
180
+ function isWellFormedProjection(projection) {
181
+ const parts = projection.split("/").map((segment) => pythonStrip(segment));
182
+ return parts.length >= 2 && parts[0] !== "" && parts[1] !== "";
183
+ }
184
+ function isJsonObject(raw) {
185
+ let loaded;
186
+ try {
187
+ loaded = JSON.parse(raw);
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ return isRecord(loaded);
193
+ }
194
+ // ── Content hash (mirror of `compute_content_hash`) ──────────────────────
195
+ const TEXT_ENCODER = new TextEncoder();
196
+ /**
197
+ * The canonical content hash of a generated body: lowercase SHA-256 hex over its UTF-8 bytes.
198
+ *
199
+ * WebCrypto rather than `node:crypto`, so the SDK barrel stays importable from a browser
200
+ * bundle. (`crypto.subtle` is secure-context-only in a browser — https or localhost — which
201
+ * never binds a CI helper but is worth knowing before calling this from a page.)
202
+ */
203
+ async function computeContentHash(body) {
204
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", TEXT_ENCODER.encode(body));
205
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
206
+ }
207
+ // ── Errors ──────────────────────────────────────────────────────────────
208
+ /**
209
+ * A no-verdict condition: the lock is malformed, or a path (in the lock or in the
210
+ * supplied tree) is not a safe canonical artifact path.
211
+ *
212
+ * Deliberately NOT a drift and NOT a `PipelineRequestError`: nothing was requested
213
+ * over the wire, and the check could not produce a verdict at all. A CLI consumer
214
+ * maps a drift report to its exit-1 path and this error to exit 2, per the codegen
215
+ * spec's exit-code policy.
216
+ */
217
+ export class CodegenLockError extends Error {
218
+ constructor(message, options) {
219
+ super(message, options);
220
+ this.name = "CodegenLockError";
221
+ }
222
+ }
223
+ const MISSING_DETAIL = "Locked artifact is absent on disk.";
224
+ const NO_STAMP_DETAIL = "Stamp header is missing or unparseable.";
225
+ const STAMP_MISMATCH_DETAIL = "Body was edited below the stamp (stamp hash no longer matches).";
226
+ const MODIFIED_DETAIL = "Body no longer matches the locked hash — regenerate.";
227
+ const ORPHAN_DETAIL = "Stamped generated file not tracked by the lock — stale; remove or regenerate.";
228
+ /**
229
+ * Run the offline drift check over one generated tree.
230
+ *
231
+ * The algorithm, per the codegen spec's "Offline check algorithm":
232
+ *
233
+ * 1. For each artifact in the lock, locate it in `files` and recompute the hash of the
234
+ * body below its stamp; absent is a `missing` drift, a mismatch is a drift.
235
+ * 2. A locked file whose stamp is gone, unparseable, or self-inconsistent is `hand-edited`.
236
+ * 3. A stamped file the lock does not track is an `orphan` (the stale-artifact class a
237
+ * per-file stamp cannot catch on its own).
238
+ *
239
+ * At most **one** drift is reported per locked path, and `hand-edited` outranks
240
+ * `modified`: a hand edit trips both conditions and is reported once, as the hand edit.
241
+ *
242
+ * **The caller's obligations.** Each one, unmet, yields a *wrong verdict* rather than an
243
+ * error, so none of them can be left implicit:
244
+ *
245
+ * - **Pass the file's text as read, without reformatting it.** The hash is over exact UTF-8
246
+ * bytes and the stamp parser requires the text to *start with* the begin-marker line, so
247
+ * re-encoding, inserting a BOM, or running a formatter over an artifact reports
248
+ * `hand-edited`. Line endings are the one exception: `\r\n` and lone `\r` are normalized
249
+ * to `\n` first, mirroring the universal-newline translation the reference reader applies
250
+ * (see `normalizeNewlines`) — so a Windows checkout under `core.autocrlf=true` is NOT a
251
+ * false hand-edit here, just as it is not one for the CLI. Committing generated artifacts
252
+ * with a `.gitattributes` entry (e.g. `src/generated/** -text`) is still worth doing for
253
+ * diff hygiene; it is simply no longer load-bearing for the verdict.
254
+ * - **Walk the whole tree, recursively, from the lock's directory**, and pass paths
255
+ * relative to it. An incomplete list yields a wrong verdict in either direction: an
256
+ * omitted *locked* file is reported `missing` though it sits on disk, and an omitted
257
+ * *orphan* is never seen at all — `isCurrent: true`, a false negative on precisely the
258
+ * drift class orphan detection exists for. Filter with {@link isStampableArtifactPath}
259
+ * to walk exactly what the check considers. Pruning vendor/VCS directories and skipping
260
+ * symlinks is walk policy and stays with the caller (moot for a per-method generated
261
+ * directory, which holds nothing else).
262
+ * - **Decode the bytes yourself, and decode strictly.** `content` is already a `string`, so
263
+ * pipelex's "not valid UTF-8 → hand-edited" branch is unreachable here — and note the obvious
264
+ * reader does not hand it back to you: `readFile(path, "utf8")` substitutes U+FFFD for invalid
265
+ * bytes and never throws, so a corrupted artifact whose body legitimately contains U+FFFD can
266
+ * still hash to the locked value and report current. Decode with
267
+ * `new TextDecoder("utf-8", { fatal: true })` if that matters to you, and report a file you
268
+ * could not decode yourself — or omit it and accept a `missing` drift for it.
269
+ *
270
+ * What the check deliberately does **not** do: it never compares a stamp's
271
+ * `crate_fingerprint` against the lock's, and it never re-resolves the crate. Both need
272
+ * the engine, which is the whole point of the offline split.
273
+ *
274
+ * @throws {CodegenLockError} for a no-verdict condition — a malformed lock, a lock whose
275
+ * `lock_version` this build cannot read, an unsafe or duplicate artifact path in the lock, or a
276
+ * non-canonical or duplicate path in `files`.
277
+ */
278
+ export async function runCodegenCheck(input) {
279
+ const lock = parseLock(normalizeNewlines(input.lockContent));
280
+ const contentByPath = indexTreeFiles(input.files);
281
+ const drifts = [];
282
+ for (const [path, lockedHash] of sortedEntries(lock.hashByPath)) {
283
+ const content = contentByPath.get(path);
284
+ if (content === undefined) {
285
+ drifts.push({ path, category: "missing", detail: MISSING_DETAIL });
286
+ continue;
287
+ }
288
+ const drift = await checkPresentArtifact(path, content, lockedHash);
289
+ if (drift !== null) {
290
+ drifts.push(drift);
291
+ }
292
+ }
293
+ for (const [path, content] of sortedEntries(contentByPath)) {
294
+ if (lock.hashByPath.has(path)) {
295
+ continue;
296
+ }
297
+ const commentPrefix = commentPrefixFor(path);
298
+ if (commentPrefix !== undefined && hasStamp(content, commentPrefix)) {
299
+ drifts.push({ path, category: "orphan", detail: ORPHAN_DETAIL });
300
+ }
301
+ }
302
+ return {
303
+ drifts,
304
+ isCurrent: drifts.length === 0,
305
+ crateFingerprint: lock.crateFingerprint,
306
+ engineVersion: lock.engineVersion,
307
+ };
308
+ }
309
+ /**
310
+ * The drift for one locked artifact that is present, or `null` when it is current.
311
+ *
312
+ * The precedence is the point: an early return makes `hand-edited` outrank `modified`,
313
+ * so a hand edit (which trips both the stamp check and the lock check) is reported once.
314
+ */
315
+ async function checkPresentArtifact(path, content, lockedHash) {
316
+ // Never `undefined`: a lock path is suffix-validated when the lock is parsed.
317
+ const commentPrefix = commentPrefixFor(path);
318
+ const parsed = commentPrefix === undefined ? null : parseStamped(content, commentPrefix);
319
+ if (parsed === null) {
320
+ return { path, category: "hand-edited", detail: NO_STAMP_DETAIL };
321
+ }
322
+ const bodyHash = await computeContentHash(parsed.body);
323
+ if (parsed.contentHash !== bodyHash) {
324
+ return { path, category: "hand-edited", detail: STAMP_MISMATCH_DETAIL };
325
+ }
326
+ if (bodyHash !== lockedHash) {
327
+ return { path, category: "modified", detail: MODIFIED_DETAIL };
328
+ }
329
+ return null;
330
+ }
331
+ /** Index the supplied tree by path, rejecting unsafe and duplicate paths. */
332
+ function indexTreeFiles(files) {
333
+ const contentByPath = new Map();
334
+ for (const file of files) {
335
+ // The suffix rule is NOT applied here: a non-stampable file (a `sources.json`
336
+ // sidecar, say) is legitimately present in the tree and is simply skipped.
337
+ validateCanonicalPath(file.path);
338
+ if (contentByPath.has(file.path)) {
339
+ raisePathError(file.path, "Duplicate artifact path");
340
+ }
341
+ contentByPath.set(file.path, normalizeNewlines(file.content));
342
+ }
343
+ return contentByPath;
344
+ }
345
+ /**
346
+ * The lock format version this build reads — mirrors the reference's `CODEGEN_LOCK_VERSION`.
347
+ *
348
+ * Any change to the lock's key set, or to the meaning of an existing key, bumps it on both sides.
349
+ */
350
+ const CODEGEN_LOCK_VERSION = 1;
351
+ const LOCK_KEYS = new Set(["lock_version", "crate_fingerprint", "engine_version", "artifacts"]);
352
+ const LOCK_ARTIFACT_KEYS = new Set(["path", "content_hash"]);
353
+ /**
354
+ * Parse and validate a `codegen.lock`.
355
+ *
356
+ * Strict on shape, mirroring the reference model's `extra="forbid"`: an unknown key is
357
+ * a malformed lock, not a field to ignore. The lock is a Pipelex-owned artifact written
358
+ * by a versioned engine, so an unrecognized shape means the two disagree about what the
359
+ * hashes cover — better a loud no-verdict than a confident wrong one.
360
+ */
361
+ function parseLock(lockContent) {
362
+ let data;
363
+ try {
364
+ data = parseToml(lockContent);
365
+ }
366
+ catch (err) {
367
+ throw new CodegenLockError(`Malformed codegen lock: ${describeError(err)}`, { cause: err });
368
+ }
369
+ if (!isRecord(data)) {
370
+ throw new CodegenLockError("Malformed codegen lock: the top level is not a TOML table.");
371
+ }
372
+ rejectUnknownLockVersion(data);
373
+ rejectUnknownKeys(data, LOCK_KEYS, "codegen lock");
374
+ const crateFingerprint = readString(data, "crate_fingerprint", "codegen lock");
375
+ const engineVersion = readString(data, "engine_version", "codegen lock");
376
+ const rawArtifacts = data["artifacts"] ?? [];
377
+ if (!Array.isArray(rawArtifacts)) {
378
+ throw new CodegenLockError("Malformed codegen lock: 'artifacts' must be an array of tables.");
379
+ }
380
+ const hashByPath = new Map();
381
+ for (const rawArtifact of rawArtifacts) {
382
+ if (!isRecord(rawArtifact)) {
383
+ throw new CodegenLockError("Malformed codegen lock: each artifact must be a TOML table.");
384
+ }
385
+ rejectUnknownKeys(rawArtifact, LOCK_ARTIFACT_KEYS, "codegen lock artifact");
386
+ const path = readString(rawArtifact, "path", "codegen lock artifact");
387
+ const contentHash = readString(rawArtifact, "content_hash", "codegen lock artifact");
388
+ validateArtifactPath(path);
389
+ if (hashByPath.has(path)) {
390
+ raisePathError(path, "Duplicate artifact path");
391
+ }
392
+ hashByPath.set(path, contentHash);
393
+ }
394
+ return { crateFingerprint, engineVersion, hashByPath };
395
+ }
396
+ /**
397
+ * Refuse a lock whose format version this build cannot read, BEFORE its key set is validated.
398
+ *
399
+ * The ordering is the whole point of the field, and the easiest thing to get wrong. Run the other
400
+ * way round, `rejectUnknownKeys` rejects a future lock as a shape error over a key the writer was
401
+ * entitled to add — so the reader reports an opaque complaint about that key instead of naming the
402
+ * version and saying which side to upgrade, which is precisely the unactionable no-verdict the
403
+ * field was introduced to remove.
404
+ */
405
+ function rejectUnknownLockVersion(data) {
406
+ // No key at all means the lock was written before the field existed, which is version 1 by
407
+ // definition — nothing on disk needs migrating. It still faces the comparison below rather than
408
+ // returning early, or the day the constant moves every legacy lock would skip the gate and be
409
+ // validated against a schema it was never written for.
410
+ const rawVersion = data["lock_version"] ?? 1;
411
+ // `typeof` excludes a TOML boolean, which is the JavaScript shape of the reference's
412
+ // `bool`-is-an-`int` guard (there, `True == 1` would have read as version 1).
413
+ //
414
+ // A TOML float is NOT excluded, and cannot be: smol-toml decodes `1.0` to the same `1` as the
415
+ // integer, so this reader accepts a spelling the reference calls malformed. No emitter writes it,
416
+ // and the divergence is a no-verdict there against version 1 here.
417
+ const isVersionNumber = typeof rawVersion === "number" && Number.isInteger(rawVersion);
418
+ if (isVersionNumber && rawVersion === CODEGEN_LOCK_VERSION) {
419
+ return;
420
+ }
421
+ const reason = isVersionNumber && rawVersion > CODEGEN_LOCK_VERSION
422
+ ? `it declares lock_version ${rawVersion} — upgrade @pipelex/sdk to a build that reads it`
423
+ : `lock_version ${describeLockVersion(rawVersion)} is not a known codegen lock format version`;
424
+ throw new CodegenLockError(`Unsupported codegen lock version: this build reads lock_version ${CODEGEN_LOCK_VERSION}, but ${reason}.`);
425
+ }
426
+ /** Python's `!r` for the values TOML can put here, so both readers name a bad version the same way. */
427
+ function describeLockVersion(rawVersion) {
428
+ return typeof rawVersion === "string" ? `'${rawVersion}'` : String(rawVersion);
429
+ }
430
+ function rejectUnknownKeys(record, allowed, what) {
431
+ for (const key of Object.keys(record)) {
432
+ if (!allowed.has(key)) {
433
+ throw new CodegenLockError(`Malformed ${what}: unexpected key '${key}'.`);
434
+ }
435
+ }
436
+ }
437
+ function readString(record, key, what) {
438
+ const value = record[key];
439
+ if (typeof value !== "string") {
440
+ throw new CodegenLockError(`Malformed ${what}: '${key}' must be a string.`);
441
+ }
442
+ return value;
443
+ }
444
+ // ── Path validation (mirror of `validate_artifact_path` / `validate_artifact_paths`) ──
445
+ const CONTROL_CHARACTER = /\p{C}/u;
446
+ /**
447
+ * A Windows drive prefix as `PureWindowsPath` sees one: ANY single leading character followed
448
+ * by `:`, not only an ASCII letter. `1:models.py` and `_:models.py` are drives to the reference
449
+ * and were accepted here, making this validator the permissive side of the pair.
450
+ *
451
+ * The `s` flag is load-bearing, not decoration: without it `.` skips the line terminators, and
452
+ * U+2028 / U+2029 are separators rather than `\p{C}` controls, so they sail past the control
453
+ * check above and would be the last two drive prefixes this side still accepted. (`\n` and `\r`
454
+ * are the other characters `.` excludes, and those the control check already rejects.)
455
+ */
456
+ const WINDOWS_DRIVE = /^.:/su;
457
+ /**
458
+ * Validate one canonical relative path: the safety rules, without the suffix rule.
459
+ *
460
+ * These are the rules that keep a path from meaning something other than what it spells —
461
+ * an absolute path, a drive prefix, a `..` escape, a backslash a Windows caller would
462
+ * resolve differently, a control character. They apply to every path the check sees.
463
+ */
464
+ function validateCanonicalPath(path) {
465
+ if (path === "") {
466
+ raisePathError(path, "path is empty");
467
+ }
468
+ if (path.includes("\\")) {
469
+ raisePathError(path, "backslashes are not allowed; use forward slashes");
470
+ }
471
+ if (CONTROL_CHARACTER.test(path)) {
472
+ raisePathError(path, "control characters are not allowed");
473
+ }
474
+ if (path.startsWith("/") || WINDOWS_DRIVE.test(path)) {
475
+ raisePathError(path, "absolute paths and drive prefixes are not allowed");
476
+ }
477
+ if (path.split("/").some((part) => part === "" || part === "." || part === "..")) {
478
+ raisePathError(path, "empty, '.', and '..' path components are not allowed");
479
+ }
480
+ }
481
+ /** Validate one path the lock tracks: canonical, and of a suffix codegen actually stamps. */
482
+ function validateArtifactPath(path) {
483
+ validateCanonicalPath(path);
484
+ if (!isStampableArtifactPath(path)) {
485
+ const expected = STAMPABLE_ARTIFACT_SUFFIXES.join(", ");
486
+ raisePathError(path, `unsupported artifact suffix (expected one of: ${expected})`);
487
+ }
488
+ }
489
+ function raisePathError(path, reason) {
490
+ throw new CodegenLockError(`Unsafe codegen artifact path '${path}': ${reason}.`);
491
+ }
492
+ // ── Small helpers ───────────────────────────────────────────────────────
493
+ /**
494
+ * Translate `\r\n` and lone `\r` to `\n`, the way the reference implementation's reader does.
495
+ *
496
+ * NOT a convenience: it is what keeps this check and `pipelex codegen check` in agreement.
497
+ * pipelex reads artifacts with `Path.read_text()`, whose default `newline=None` applies
498
+ * Python universal-newline translation — so the reference check never sees a `\r`, and its
499
+ * verdict is over line-ending-normalized text rather than raw bytes. The same default on the
500
+ * write side means pipelex running on Windows *emits* CRLF artifacts whose locked hashes were
501
+ * computed over LF. Hashing raw bytes here would therefore report every artifact of a
502
+ * Windows-generated (or `core.autocrlf=true`-checked-out) tree as `hand-edited`, while the CLI
503
+ * called it current — a false positive on the exact tree a consumer's CI is gating.
504
+ */
505
+ function normalizeNewlines(text) {
506
+ return text.replace(/\r\n?/g, "\n");
507
+ }
508
+ function isRecord(value) {
509
+ return typeof value === "object" && value !== null && !Array.isArray(value);
510
+ }
511
+ /** Entries in ascending path order — the deterministic order drifts are reported in. */
512
+ function sortedEntries(map) {
513
+ return [...map.entries()].sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
514
+ }
515
+ function describeError(err) {
516
+ return err instanceof Error ? err.message : String(err);
517
+ }
518
+ //# sourceMappingURL=codegen-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-check.js","sourceRoot":"","sources":["../src/codegen-check.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * errors. Consumers should import everything they need from `@pipelex/sdk`.
8
8
  */
9
9
  /** Package version. Kept in sync with `package.json` — enforced by `tests/index.test.ts`. */
10
- export declare const SDK_VERSION = "0.11.0";
10
+ export declare const SDK_VERSION = "0.13.0";
11
11
  export * from "mthds/protocol";
12
12
  export { PipelexApiClient, DEFAULT_API_BASE_URL } from "./client.js";
13
13
  export type { MthdsFile, BuildInputsByMethodId, ValidateFilesOptions, PipelexApiClientOptions, } from "./client.js";
@@ -15,7 +15,9 @@ export { methodSourceToContents } from "./method-source.js";
15
15
  export { PipelexExecuteResult } from "./execute-result.js";
16
16
  export type { UploadableAsset, UploadFileOptions, UploadRecord } from "./upload.js";
17
17
  export type { PrepareInputsRequest, PreparedInputs } from "./prepare-inputs.js";
18
- export type { DiagnosticKind, DiagnosticRange, Diagnostic, LintResponse, FormatResponse, DictStuff, DictWorkingMemory, DictPipeOutput, DictRunResultExecute, DryRunStatus, ValidatedPipeEntry, PipelexValidationReport, PipelexInvalidReport, PipelexValidationResult, ValidationErrorCategory, ValidationErrorItem, ConceptRepresentationFormat, InputsTemplateFormat, MthdsFileItem, BuildRequestBase, BuildInputsRequest, BuildOutputRequest, BuildRunnerRequest, ConceptRequest, PipeSpecRequest, CrateInvalidReport, BuildInputsValidReport, BuildOutputValidReport, BuildRunnerValidReport, GeneratedArtifact, RunnerStructures, BuildInputsResponse, BuildOutputResponse, BuildRunnerResponse, ConceptResponse, PipeSpecResponse, } from "./models.js";
18
+ export type { DiagnosticKind, DiagnosticRange, Diagnostic, LintResponse, FormatResponse, DictStuff, DictWorkingMemory, DictPipeOutput, DictRunResultExecute, DryRunStatus, ValidatedPipeEntry, PipelexValidationReport, PipelexInvalidReport, PipelexValidationResult, ValidationErrorCategory, ValidationErrorItem, ConceptRepresentationFormat, InputsTemplateFormat, MthdsFileItem, CrateRequestBase, BuildRequestBase, BuildInputsRequest, BuildOutputRequest, BuildRunnerRequest, ConceptRequest, PipeSpecRequest, CrateInvalidReport, BuildInputsValidReport, BuildOutputValidReport, BuildRunnerValidReport, GeneratedArtifact, RunnerStructures, BuildInputsResponse, BuildOutputResponse, BuildRunnerResponse, ConceptResponse, PipeSpecResponse, ResolveRequest, ResolveValidReport, ResolveResponse, CodegenKind, CodegenTarget, CodegenRequest, CodegenValidReport, CodegenResponse, } from "./models.js";
19
+ export { runCodegenCheck, isStampableArtifactPath, STAMPABLE_ARTIFACT_SUFFIXES, CodegenLockError, } from "./codegen-check.js";
20
+ export type { CodegenCheckInput, CodegenCheckReport, CodegenDrift, CodegenDriftCategory, CodegenTreeFile, } from "./codegen-check.js";
19
21
  export type { UserProfile, ListMethodsQuery, MethodData, MethodDeletionState, MethodPage, MethodSummary, MethodWriteInput, Membership, MembershipsResponse, SubscriptionResponse, PlanView, InvoiceView, CheckoutResponse, ChangePlanResponse, BillingPortalResponse, PipelexApiKey, PipelexApiKeyCreated, PipelexApiKeyList, GatewayApiKey, GatewayApiKeyStatus, OnboardingRole, OnboardingCurrentTool, OnboardingInputType, OnboardingHeardFrom, OnboardingSubmission, ResolvedStorageUrl, UploadInput, UploadedFile, PipeStatus, ListRunsQuery, PipelineRun, RunDetail, RunErrorReport, RunPage, UpdateRunInput, } from "./product-models.js";
20
22
  export { isTerminalRunStatus, isSuccessRunStatus } from "./runs.js";
21
23
  export type { RunStatus, RunPublic, RunRead, RunResults, RunResultState, TokensUsageRecord, WaitForResultOptions, } from "./runs.js";
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * errors. Consumers should import everything they need from `@pipelex/sdk`.
8
8
  */
9
9
  /** Package version. Kept in sync with `package.json` — enforced by `tests/index.test.ts`. */
10
- export const SDK_VERSION = "0.11.0";
10
+ export const SDK_VERSION = "0.13.0";
11
11
  // ── Pure MTHDS Protocol surface (re-exported from the `mthds/protocol` subpath) ──
12
12
  // The standard's interface, wire models, request/options surface, abstract domain
13
13
  // shapes, and the protocol-base `PipelineRequestError`. Re-exported so apps have a
@@ -19,6 +19,11 @@ export { PipelexApiClient, DEFAULT_API_BASE_URL } from "./client.js";
19
19
  export { methodSourceToContents } from "./method-source.js";
20
20
  // The blocking `execute()` result — a `DictRunResultExecute` with a resolved `.main_stuff`.
21
21
  export { PipelexExecuteResult } from "./execute-result.js";
22
+ // ── Offline codegen drift check (pure — no filesystem, no network, no key) ──
23
+ // The CI half of the codegen trust chain: hand it a committed tree plus its
24
+ // `codegen.lock` and it returns the structured drift verdict. Regeneration needs
25
+ // the engine (a dev action); the check needs only hashes (the CI action).
26
+ export { runCodegenCheck, isStampableArtifactPath, STAMPABLE_ARTIFACT_SUFFIXES, CodegenLockError, } from "./codegen-check.js";
22
27
  // ── Run lifecycle (hosted extension — NOT part of the protocol) ──────
23
28
  export { isTerminalRunStatus, isSuccessRunStatus } from "./runs.js";
24
29
  // ── Typed errors (PipelineRequestError rides the protocol re-export above) ──
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6FAA6F;AAC7F,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEpC,oFAAoF;AACpF,kFAAkF;AAClF,mFAAmF;AACnF,wBAAwB;AACxB,cAAc,gBAAgB,CAAC;AAE/B,wEAAwE;AACxE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAQrE,+FAA+F;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,4FAA4F;AAC5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAsF3D,wEAAwE;AACxE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAWpE,+EAA+E;AAC/E,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,4BAA4B,EAC5B,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,gCAAgC,EAChC,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6FAA6F;AAC7F,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEpC,oFAAoF;AACpF,kFAAkF;AAClF,mFAAmF;AACnF,wBAAwB;AACxB,cAAc,gBAAgB,CAAC;AAE/B,wEAAwE;AACxE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAQrE,+FAA+F;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,4FAA4F;AAC5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAwD3D,+EAA+E;AAC/E,4EAA4E;AAC5E,iFAAiF;AACjF,0EAA0E;AAC1E,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAgD5B,wEAAwE;AACxE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAWpE,+EAA+E;AAC/E,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,4BAA4B,EAC5B,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,gCAAgC,EAChC,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,aAAa,CAAC"}