@gobing-ai/ts-llm-jsonl-importer 0.5.11 → 0.5.12

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 CHANGED
@@ -219,6 +219,50 @@ The custom `source` name flows through checkpoints, the ledger, and `ImportResul
219
219
  like a built-in key. The importer creates the target ETL table on demand if it does not exist
220
220
  (idempotent `CREATE TABLE IF NOT EXISTS`), so built-in tables are unaffected.
221
221
 
222
+ ## Skill-Call Capability Facts (E93)
223
+
224
+ `history_skill_call` classifies every observed skill invocation with four nullable facts plus a
225
+ stable identity:
226
+
227
+ - `invocation_id` — content-hash identity over `(source, session, kind, id, ordinal, file, line)`;
228
+ correlated representations of one invocation (e.g. a codex `$skill` marker plus its companion
229
+ `<skill>` block) share one id.
230
+ - `capability_kind` — `skill`, `command`, or `subagent` — where the invocation's target artifact
231
+ was resolved to (native classification first, then a unique supplied origin; `NULL` when unknown).
232
+ - `origin_identity` — the origin index identity for the resolved artifact.
233
+ - `evidence_kind` — `load` (skill observed executing/loaded), `request` (command spelled by the
234
+ user), or `delegation` (agent-to-agent subagent dispatch).
235
+ - `status` — `unknown` until a paired tool result rides the row (`ok`/`error`) or the harness
236
+ wrapper itself is the verified injection.
237
+
238
+ Capability classification never infers from name spelling alone: a `$sp-dev-run` marker without a
239
+ matching command record or skill block classifies as unknown. Pass a verified origin index to make
240
+ resolved origins part of the import contract (invalid entries reject the run before any write):
241
+
242
+ ```ts
243
+ const result = await runJsonlImport('codex', {
244
+ db,
245
+ files,
246
+ mode: 'incremental',
247
+ capabilityOrigins: [
248
+ {
249
+ source: 'codex',
250
+ skillName: 'sp:dev-run',
251
+ skillPath: '/skills/sp-dev-run/SKILL.md',
252
+ artifactDigest: sha256Text(body.trim()),
253
+ capabilityKind: 'skill',
254
+ originIdentity: 'sp/src/skills/dev-run',
255
+ },
256
+ ],
257
+ });
258
+ ```
259
+
260
+ Digest mismatches and conflicting origin entries never cause a silent guess: the row classifies as
261
+ unknown and the run reports a bounded, deduplicated validation finding. Shell reads of `SKILL.md`
262
+ files (`cat`, `sed -n '<range>p'`) inside tool payloads — including nested
263
+ `tools.exec_command({cmd: ...})` literals — surface as implicit loads. Results may arrive in a later
264
+ incremental run; pairing falls back to the database and upgrades the existing row in place.
265
+
222
266
  ## Boundary Notes
223
267
 
224
268
  - This package imports JSONL files and writes importer-owned tables; it does not model conversations, turns, tool calls, or analytics semantics.
@@ -0,0 +1,52 @@
1
+ import type { CapabilityOrigin } from './types';
2
+ /** Result of matching a record's observable identity against the supplied origin index. */
3
+ export interface CapabilityOriginMatch {
4
+ /** The unique agreeing origin, or null when nothing agreed. */
5
+ readonly origin: CapabilityOrigin | undefined;
6
+ /** Human-readable conflict reason when candidate origins disagreed (never a guess). */
7
+ readonly conflict: string | null;
8
+ }
9
+ /** Validate operator-supplied capability origins before the importer opens any write path. */
10
+ export declare function validateCapabilityOrigins(input: readonly CapabilityOrigin[] | undefined): readonly CapabilityOrigin[];
11
+ /**
12
+ * Match one invoking record against the supplied origin index for its source.
13
+ *
14
+ * Agreement is required on every observable dimension: canonical name always; exact path
15
+ * when the record observed one; complete-artifact digest when the record carried the full
16
+ * injected body. Name+path narrowing first, then digest filtering; zero matches resolve to
17
+ * unknown (no origin), more than one to a conflict. Pure — no persistence.
18
+ */
19
+ export declare function matchCapabilityOrigin(origins: readonly CapabilityOrigin[], source: string, canonicalName: string, observable: {
20
+ skillPath?: string | null;
21
+ artifactDigest?: string | null;
22
+ }): CapabilityOriginMatch;
23
+ /** Stable version-1 invocation-identity tuple → `invocation_id` (E93 §8.2). */
24
+ export declare function capabilityInvocationId(parts: {
25
+ source: string;
26
+ sessionId: string;
27
+ kind: 'invocation' | 'call' | 'record' | 'line';
28
+ id?: string;
29
+ ordinal?: number;
30
+ file?: string;
31
+ line?: number;
32
+ }): string;
33
+ /**
34
+ * Extract SKILL.md read targets from one literal shell command (E93 §8.2).
35
+ *
36
+ * Accepts only literal `cat [-n...] [--] <path...>` and `sed -n <range>... <path>` forms
37
+ * whose final segment resolves to a `SKILL.md` file. Expansions (`$`, backticks, escapes),
38
+ * redirections, multiple commands per argument and anything non-literal yield no targets.
39
+ */
40
+ export declare function parseLiteralReadTargets(command: string): readonly string[];
41
+ /**
42
+ * Extract literal command strings from nested `tools.exec_command({cmd: <literal>, ...})`
43
+ * payloads (E93 §8.2 — codex `functions.exec` bodies).
44
+ *
45
+ * Strict shape: `cmd` as the first key with a plain single/double-quoted string literal;
46
+ * any further keys must be plain literals; no template literals, concatenation or
47
+ * expansions. Matches must start a statement (after whitespace, `{`, `;` or `}`) so the
48
+ * same text quoted inside a larger string is not code; whole-line and block comments are
49
+ * skipped.
50
+ */
51
+ export declare function extractNestedExecCommandLiterals(payloadText: string): readonly string[];
52
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAkB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhE,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC9C,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAKD,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,GAAG,SAAS,gBAAgB,EAAE,CAqCrH;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACjC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1E,qBAAqB,CA4BvB;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IAChD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CA2BT;AA6HD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CA+C1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CA2CvF"}
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Capability-origin classification helpers (E93 task 1028).
3
+ *
4
+ * Pure helpers shared by the per-source skill-call detectors in `./mappers` and validated
5
+ * at the importer boundary. Two responsibilities:
6
+ *
7
+ * 1. **Origin matching** — a `history_skill_call` row's `capability_kind`/`origin_identity`
8
+ * is established by (a) native source identity (a command expansion envelope, a native
9
+ * command record, a delegation record) or (b) a unique supplied {@link CapabilityOrigin}
10
+ * whose source, canonical name, optional exact path and observed complete-artifact
11
+ * digest agree with the invoking record. Never from a name prefix, invocation
12
+ * restriction or marker spelling alone; conflicts classify as unknown and surface a
13
+ * bounded finding rather than guessing.
14
+ * 2. **Bounded literal grammars** — shell `cat [--] <path...>` / `sed -n '<range>p' <path>`
15
+ * read commands and nested `tools.exec_command({cmd: <literal>})` payloads are parsed as
16
+ * strict literal shapes only: quote-honoring, comment/quoted-code aware, rejecting
17
+ * expansions, substitutions and redirections. Anything outside the grammar yields no
18
+ * capability fact (an ordinary tool call), never a guess.
19
+ */
20
+ import { HistoryImportError } from './errors.js';
21
+ import { sha256Text } from './hash.js';
22
+ const CAPABILITY_KINDS = ['command', 'subagent', 'skill'];
23
+ const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
24
+ /** Validate operator-supplied capability origins before the importer opens any write path. */
25
+ export function validateCapabilityOrigins(input) {
26
+ if (input === undefined)
27
+ return [];
28
+ if (!Array.isArray(input)) {
29
+ throw new HistoryImportError('capabilityOrigins must be an array of CapabilityOrigin objects.', {
30
+ option: 'capabilityOrigins',
31
+ });
32
+ }
33
+ return input.map((origin, index) => {
34
+ const fail = (reason) => {
35
+ throw new HistoryImportError(`capabilityOrigins[${index}]: ${reason}`, {
36
+ option: 'capabilityOrigins',
37
+ index,
38
+ });
39
+ };
40
+ if (typeof origin !== 'object' || origin === null)
41
+ fail('entry must be an object.');
42
+ if (typeof origin.source !== 'string' || origin.source.trim() === '')
43
+ fail('source must be a non-empty string.');
44
+ if (typeof origin.skillName !== 'string' || origin.skillName.trim() === '') {
45
+ fail('skillName must be a non-empty string.');
46
+ }
47
+ if (typeof origin.artifactDigest !== 'string' || !DIGEST_PATTERN.test(origin.artifactDigest)) {
48
+ fail('artifactDigest must be a 64-character lowercase hex SHA-256 string.');
49
+ }
50
+ if (!CAPABILITY_KINDS.includes(origin.capabilityKind)) {
51
+ fail(`capabilityKind must be one of ${CAPABILITY_KINDS.map((k) => `'${k}'`).join(', ')}.`);
52
+ }
53
+ if (typeof origin.originIdentity !== 'string' || origin.originIdentity.trim() === '') {
54
+ fail('originIdentity must be a non-empty string.');
55
+ }
56
+ if (origin.skillPath !== undefined &&
57
+ (typeof origin.skillPath !== 'string' || origin.skillPath.trim() === '')) {
58
+ fail('skillPath must be a non-empty string when present.');
59
+ }
60
+ return origin;
61
+ });
62
+ }
63
+ /**
64
+ * Match one invoking record against the supplied origin index for its source.
65
+ *
66
+ * Agreement is required on every observable dimension: canonical name always; exact path
67
+ * when the record observed one; complete-artifact digest when the record carried the full
68
+ * injected body. Name+path narrowing first, then digest filtering; zero matches resolve to
69
+ * unknown (no origin), more than one to a conflict. Pure — no persistence.
70
+ */
71
+ export function matchCapabilityOrigin(origins, source, canonicalName, observable) {
72
+ let candidates = origins.filter((origin) => origin.source === source && origin.skillName === canonicalName);
73
+ if (candidates.length === 0)
74
+ return { origin: undefined, conflict: null };
75
+ if (observable.skillPath != null && observable.skillPath !== '') {
76
+ const byPath = candidates.filter((origin) => origin.skillPath !== undefined && origin.skillPath === observable.skillPath);
77
+ if (byPath.length > 0)
78
+ candidates = byPath;
79
+ }
80
+ if (observable.artifactDigest != null && observable.artifactDigest !== '') {
81
+ const byDigest = candidates.filter((origin) => origin.artifactDigest === observable.artifactDigest);
82
+ if (byDigest.length === 1)
83
+ return { origin: byDigest[0], conflict: null };
84
+ if (byDigest.length > 1) {
85
+ return {
86
+ origin: undefined,
87
+ conflict: `capabilityOrigins: ${byDigest.length} origins agree on digest for '${canonicalName}'.`,
88
+ };
89
+ }
90
+ return {
91
+ origin: undefined,
92
+ conflict: `capabilityOrigins: observed artifact digest does not match the supplied origin for '${canonicalName}'.`,
93
+ };
94
+ }
95
+ if (candidates.length === 1)
96
+ return { origin: candidates[0], conflict: null };
97
+ return {
98
+ origin: undefined,
99
+ conflict: `capabilityOrigins: ${candidates.length} origins match '${canonicalName}' without distinguishing evidence.`,
100
+ };
101
+ }
102
+ /** Stable version-1 invocation-identity tuple → `invocation_id` (E93 §8.2). */
103
+ export function capabilityInvocationId(parts) {
104
+ return sha256Text(JSON.stringify({
105
+ domain: 'capability-invocation',
106
+ v: 1,
107
+ source: parts.source,
108
+ sessionId: parts.sessionId,
109
+ kind: parts.kind,
110
+ id: parts.id ?? null,
111
+ ordinal: parts.ordinal ?? null,
112
+ file: parts.file ?? null,
113
+ line: parts.line ?? null,
114
+ }, Object.keys({
115
+ domain: 0,
116
+ v: 0,
117
+ source: 0,
118
+ sessionId: 0,
119
+ kind: 0,
120
+ id: 0,
121
+ ordinal: 0,
122
+ file: 0,
123
+ line: 0,
124
+ }).sort()));
125
+ }
126
+ /**
127
+ * Tokenize one command segment honoring single/double quotes. Inside double quotes,
128
+ * `$`/backtick mark the whole segment dynamic (expansion). Backslash escapes outside
129
+ * quotes mark it dynamic too — the bounded grammar only trusts plain literals.
130
+ */
131
+ function scanShellSegment(segment) {
132
+ const tokens = [];
133
+ let current = '';
134
+ let quote = null;
135
+ let dynamic = false;
136
+ for (let i = 0; i < segment.length; i += 1) {
137
+ const ch = segment[i];
138
+ if (quote === "'") {
139
+ if (ch === "'")
140
+ quote = null;
141
+ else
142
+ current += ch;
143
+ continue;
144
+ }
145
+ if (quote === '"') {
146
+ if (ch === '"')
147
+ quote = null;
148
+ else if (ch === '$' || ch === '`')
149
+ dynamic = true;
150
+ else
151
+ current += ch;
152
+ continue;
153
+ }
154
+ if (ch === "'" || ch === '"') {
155
+ quote = ch;
156
+ continue;
157
+ }
158
+ if (ch === '\\' || ch === '$' || ch === '`') {
159
+ dynamic = true;
160
+ continue;
161
+ }
162
+ if (ch === ' ' || ch === '\t' || ch === '\n') {
163
+ if (current !== '')
164
+ tokens.push(current);
165
+ current = '';
166
+ continue;
167
+ }
168
+ current += ch;
169
+ }
170
+ if (quote !== null)
171
+ dynamic = true; // unterminated quote — torn or foreign line
172
+ if (current !== '')
173
+ tokens.push(current);
174
+ return { tokens, dynamic };
175
+ }
176
+ /** Split a command string into segments at top-level `&&`, `||`, `;` and `|` separators. */
177
+ function splitShellSegments(command) {
178
+ const segments = [];
179
+ let current = '';
180
+ let quote = null;
181
+ for (let i = 0; i < command.length; i += 1) {
182
+ const ch = command[i];
183
+ if (quote !== null) {
184
+ current += ch;
185
+ if (ch === quote)
186
+ quote = null;
187
+ continue;
188
+ }
189
+ if (ch === "'" || ch === '"') {
190
+ quote = ch;
191
+ current += ch;
192
+ continue;
193
+ }
194
+ if (ch === ';') {
195
+ segments.push(current);
196
+ current = '';
197
+ continue;
198
+ }
199
+ if (ch === '|' || ch === '&') {
200
+ if (command[i + 1] === ch) {
201
+ segments.push(current);
202
+ current = '';
203
+ i += 1;
204
+ continue;
205
+ }
206
+ segments.push(current);
207
+ current = '';
208
+ continue;
209
+ }
210
+ current += ch;
211
+ }
212
+ segments.push(current);
213
+ return segments;
214
+ }
215
+ /** Final path segment of a candidate read target, or null. */
216
+ function lastPathSegment(path) {
217
+ const trimmed = path.replace(/\/+$/, '');
218
+ const idx = trimmed.lastIndexOf('/');
219
+ return idx === -1 ? trimmed : trimmed.slice(idx + 1);
220
+ }
221
+ /** A `sed -n` arg is a print range only when it is a pure line-address print form. */
222
+ function isSedPrintRange(token) {
223
+ return /^\$?[0-9]+(\s*,\s*(\$|[0-9]+))?p$/.test(token) || /^\$p$/.test(token);
224
+ }
225
+ /** Does a segment contain a redirection operator outside quotes? */
226
+ function hasRedirection(segment) {
227
+ let quote = null;
228
+ for (let i = 0; i < segment.length; i += 1) {
229
+ const ch = segment[i];
230
+ if (quote !== null) {
231
+ if (ch === quote)
232
+ quote = null;
233
+ continue;
234
+ }
235
+ if (ch === "'" || ch === '"') {
236
+ quote = ch;
237
+ continue;
238
+ }
239
+ if (ch === '>' || ch === '<')
240
+ return true;
241
+ }
242
+ return false;
243
+ }
244
+ /**
245
+ * Extract SKILL.md read targets from one literal shell command (E93 §8.2).
246
+ *
247
+ * Accepts only literal `cat [-n...] [--] <path...>` and `sed -n <range>... <path>` forms
248
+ * whose final segment resolves to a `SKILL.md` file. Expansions (`$`, backticks, escapes),
249
+ * redirections, multiple commands per argument and anything non-literal yield no targets.
250
+ */
251
+ export function parseLiteralReadTargets(command) {
252
+ if (typeof command !== 'string' || command.trim() === '')
253
+ return [];
254
+ const targets = [];
255
+ for (const segment of splitShellSegments(command)) {
256
+ if (segment.trim() === '' || hasRedirection(segment))
257
+ continue;
258
+ const trimmed = segment.trim();
259
+ if (trimmed.startsWith('#'))
260
+ continue; // comment line
261
+ const scan = scanShellSegment(trimmed);
262
+ if (scan.dynamic || scan.tokens.length === 0)
263
+ continue;
264
+ const [cmd, ...args] = scan.tokens;
265
+ if (cmd === 'cat') {
266
+ for (const arg of args) {
267
+ if (arg === '--')
268
+ continue; // end-of-options marker is literal-safe
269
+ if (arg.startsWith('-'))
270
+ continue; // options like -n
271
+ if (lastPathSegment(arg) === 'SKILL.md')
272
+ targets.push(arg);
273
+ }
274
+ continue;
275
+ }
276
+ if (cmd === 'sed') {
277
+ let sawNoPrint = false;
278
+ const rangeArgs = [];
279
+ for (const arg of args) {
280
+ if (arg === '--')
281
+ continue;
282
+ if (arg === '-n' || arg === '--quiet' || arg === '--silent') {
283
+ sawNoPrint = true;
284
+ continue;
285
+ }
286
+ if (arg.startsWith('-')) {
287
+ sawNoPrint = false; // any other flag leaves the bounded print form
288
+ break;
289
+ }
290
+ if (isSedPrintRange(arg)) {
291
+ rangeArgs.push(arg);
292
+ continue;
293
+ }
294
+ // First non-range non-flag arg: the path. Exactly one, after ranges.
295
+ if (rangeArgs.length === 0) {
296
+ sawNoPrint = false;
297
+ break;
298
+ }
299
+ if (lastPathSegment(arg) === 'SKILL.md')
300
+ targets.push(arg);
301
+ break; // only one path per sed invocation
302
+ }
303
+ if (!sawNoPrint)
304
+ continue;
305
+ }
306
+ }
307
+ return targets;
308
+ }
309
+ /**
310
+ * Extract literal command strings from nested `tools.exec_command({cmd: <literal>, ...})`
311
+ * payloads (E93 §8.2 — codex `functions.exec` bodies).
312
+ *
313
+ * Strict shape: `cmd` as the first key with a plain single/double-quoted string literal;
314
+ * any further keys must be plain literals; no template literals, concatenation or
315
+ * expansions. Matches must start a statement (after whitespace, `{`, `;` or `}`) so the
316
+ * same text quoted inside a larger string is not code; whole-line and block comments are
317
+ * skipped.
318
+ */
319
+ export function extractNestedExecCommandLiterals(payloadText) {
320
+ if (typeof payloadText !== 'string' || payloadText.trim() === '')
321
+ return [];
322
+ const literals = [];
323
+ const shape = /tools\.exec_command\(\s*\{\s*cmd:\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')((?:\s*,\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s*(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[-0-9.eE+]+|true|false|null))*)\s*\}\s*\)/g;
324
+ let inBlockComment = false;
325
+ for (const rawLine of payloadText.split(/\r?\n/)) {
326
+ let line = rawLine;
327
+ if (inBlockComment) {
328
+ const end = line.indexOf('*/');
329
+ if (end === -1)
330
+ continue;
331
+ line = line.slice(end + 2);
332
+ inBlockComment = false;
333
+ }
334
+ // Strip whole-line comments; a line that opens a block comment contributes only its prefix.
335
+ const trimmedStart = line.trimStart();
336
+ if (trimmedStart.startsWith('//'))
337
+ continue;
338
+ const blockStart = line.indexOf('/*');
339
+ if (blockStart !== -1) {
340
+ const end = line.indexOf('*/', blockStart + 2);
341
+ line = end === -1 ? line.slice(0, blockStart) : line.slice(0, blockStart) + line.slice(end + 2);
342
+ if (end === -1)
343
+ inBlockComment = true;
344
+ }
345
+ const statementStart = /(?:^|[{};]\s*)$/; // match must follow whitespace or a statement boundary
346
+ shape.lastIndex = 0;
347
+ let match = shape.exec(line);
348
+ while (match !== null) {
349
+ const before = line.slice(0, match.index);
350
+ if (statementStart.test(before)) {
351
+ const literal = match[1] ?? '';
352
+ const quote = literal[0] ?? '"';
353
+ const body = literal
354
+ .slice(1, -1)
355
+ .replaceAll(`\\${quote}`, quote)
356
+ .replaceAll('\\\\', '\\')
357
+ .replaceAll('\\n', '\n')
358
+ .replaceAll('\\t', '\t');
359
+ literals.push(body);
360
+ }
361
+ match = shape.exec(line);
362
+ }
363
+ }
364
+ return literals;
365
+ }
package/dist/hash.d.ts CHANGED
@@ -2,4 +2,13 @@
2
2
  export declare function stableJson(value: unknown): string;
3
3
  /** Compute a SHA-256 hash for an already-normalized record. */
4
4
  export declare function sha256(value: unknown): string;
5
+ /**
6
+ * SHA-256 over the raw UTF-8 bytes of a string (E93 task 1028).
7
+ *
8
+ * Distinct from {@link sha256}, which hashes the *stable-JSON encoding* of a value:
9
+ * artifact digests must be computed over the exact observed artifact bytes so a
10
+ * supplied {@link CapabilityOrigin} digest recorded from the real file matches the
11
+ * digest observed in a transcript wrapper.
12
+ */
13
+ export declare function sha256Text(text: string): string;
5
14
  //# sourceMappingURL=hash.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAajD;AAED,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE7C"}
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAajD;AAED,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE7C;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C"}
package/dist/hash.js CHANGED
@@ -18,3 +18,14 @@ export function stableJson(value) {
18
18
  export function sha256(value) {
19
19
  return createHash('sha256').update(stableJson(value)).digest('hex');
20
20
  }
21
+ /**
22
+ * SHA-256 over the raw UTF-8 bytes of a string (E93 task 1028).
23
+ *
24
+ * Distinct from {@link sha256}, which hashes the *stable-JSON encoding* of a value:
25
+ * artifact digests must be computed over the exact observed artifact bytes so a
26
+ * supplied {@link CapabilityOrigin} digest recorded from the real file matches the
27
+ * digest observed in a transcript wrapper.
28
+ */
29
+ export function sha256Text(text) {
30
+ return createHash('sha256').update(text, 'utf8').digest('hex');
31
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAER,aAAa,EACb,YAAY,EAGZ,gBAAgB,EAEnB,MAAM,SAAS,CAAC;AAqFjB;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA8ZrH"}
1
+ {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAER,aAAa,EACb,YAAY,EAGZ,gBAAgB,EAEnB,MAAM,SAAS,CAAC;AA4JjB;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAoerH"}