@codyswann/lisa 4.72.6 → 4.73.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/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +19 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +4 -2
- package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +398 -6
- package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +175 -7
- package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +7 -1
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +11 -66
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +3 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +10 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +162 -121
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +8 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-scope.mjs +177 -0
- package/all/copy-overwrite/scripts/lib/npm-update-policy.mjs +73 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +1 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-daemon.mjs +227 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-resource.mjs +176 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-storage.mjs +201 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql.mjs +220 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-runtime-contract.mjs +75 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-downloads.mjs +187 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-identity.mjs +288 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tools.mjs +292 -0
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +3 -0
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +1 -1
- package/all/copy-overwrite/scripts/lisa-scratch-run.sh +7 -4
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +148 -20
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +126 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +2 -1
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +79 -17
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +1 -1
- package/scripts/two-channel-couplings.json +1 -1
|
@@ -36,6 +36,25 @@ export function optionalLockFields(value) {
|
|
|
36
36
|
return ["bunLockSha256"];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Optional runtime authority is a digest, never an unsigned executable profile. */
|
|
40
|
+
export function optionalRuntimeFields(value) {
|
|
41
|
+
if (!Object.hasOwn(value, "runtimeSha256")) return [];
|
|
42
|
+
requireProof(
|
|
43
|
+
typeof value.runtimeSha256 === "string" && HEX.test(value.runtimeSha256),
|
|
44
|
+
"invalid original Rails runtime digest"
|
|
45
|
+
);
|
|
46
|
+
return ["runtimeSha256"];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** One exact optional projection preserves historical keys at producer and verifier boundaries. */
|
|
50
|
+
export function signedProposalFields(value) {
|
|
51
|
+
return Object.fromEntries(
|
|
52
|
+
[...optionalLockFields(value), ...optionalRuntimeFields(value)].map(
|
|
53
|
+
name => [name, value[name]]
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
/** Signed original presence selects one closed two- or three-file cohort. */
|
|
40
59
|
export function proposalFileNames(value) {
|
|
41
60
|
const files = optionalLockFields(value).length
|
|
@@ -14,11 +14,12 @@ import {
|
|
|
14
14
|
canonicalJson,
|
|
15
15
|
exactKeys,
|
|
16
16
|
proposalFileNames,
|
|
17
|
-
|
|
17
|
+
signedProposalFields,
|
|
18
18
|
DESCRIPTOR_KEYS,
|
|
19
19
|
HEX,
|
|
20
20
|
OBJECT_ID,
|
|
21
21
|
} from "./automation-provenance-contract.mjs";
|
|
22
|
+
import { assertCommittedRuntime } from "./npm-update-rails-runtime-contract.mjs";
|
|
22
23
|
|
|
23
24
|
/** Git reads always use the actual checkout and bounded shared runner. */
|
|
24
25
|
export function git(args) {
|
|
@@ -98,7 +99,7 @@ export function localDescriptor(descriptor, messageBytes, reference, policy) {
|
|
|
98
99
|
}).decode(messageBytes);
|
|
99
100
|
exactKeys(
|
|
100
101
|
descriptor,
|
|
101
|
-
[...DESCRIPTOR_KEYS, ...
|
|
102
|
+
[...DESCRIPTOR_KEYS, ...Object.keys(signedProposalFields(descriptor))],
|
|
102
103
|
"descriptor"
|
|
103
104
|
);
|
|
104
105
|
requireProof(
|
|
@@ -142,6 +143,7 @@ export function localDescriptor(descriptor, messageBytes, reference, policy) {
|
|
|
142
143
|
descriptor.parent === git(["rev-parse", "HEAD"]),
|
|
143
144
|
"proposal parent differs"
|
|
144
145
|
);
|
|
146
|
+
assertCommittedRuntime(descriptor, git);
|
|
145
147
|
requireProof(
|
|
146
148
|
OBJECT_ID.test(descriptor.tree) && descriptor.tree === git(["write-tree"]),
|
|
147
149
|
"final staged tree differs"
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
2
|
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
3
|
/** Bounded lossless evidence syntax; no digest is authenticated by syntax alone. */
|
|
4
|
+
const SOURCE_MAP_FORMAT = "json-source-map";
|
|
5
|
+
const PROOF_MAP_FORMAT = "json-proof-map";
|
|
6
|
+
const TAXONOMY = "Disk/missing/corrupt";
|
|
4
7
|
const credentialField =
|
|
5
8
|
/^(?:api[_-]?key|access[_-]?token|password|passwd|secret)$/iu;
|
|
6
9
|
const credentialToken =
|
|
@@ -34,15 +37,18 @@ const isHash = value =>
|
|
|
34
37
|
|
|
35
38
|
/** Native validation and duplicate-aware lexical spans share the exact same text. */
|
|
36
39
|
const lexical = text => {
|
|
40
|
+
const tokens = [];
|
|
41
|
+
for (const token of text.matchAll(
|
|
42
|
+
/"(?:\\[\s\S]|[^"\\])*"|[{}[\],:]|[^{}[\],:\s]+/gu
|
|
43
|
+
)) {
|
|
44
|
+
if (tokens.length >= 8192) throw new Error("Bounded JSON tokens");
|
|
45
|
+
tokens.push(token);
|
|
46
|
+
}
|
|
37
47
|
const value = JSON.parse(text);
|
|
38
|
-
const tokens = [
|
|
39
|
-
...text.matchAll(/"(?:\\[\s\S]|[^"\\])*"|[{}[\],:]|[^{}[\],:\s]+/gu),
|
|
40
|
-
];
|
|
41
|
-
if (tokens.length > 8192) throw new Error("Bounded JSON tokens");
|
|
42
48
|
const state = { cursor: 0, spans: [] };
|
|
43
49
|
visit(tokens, state, [], 0, text);
|
|
44
50
|
if (state.cursor !== tokens.length) throw new Error("Trailing JSON tokens");
|
|
45
|
-
return { value, spans: state.spans };
|
|
51
|
+
return { value, spans: state.spans, tokens };
|
|
46
52
|
};
|
|
47
53
|
|
|
48
54
|
/** Duplicate decoded keys and escaped attribution spellings always fail closed. */
|
|
@@ -161,6 +167,392 @@ export const evidenceMaps = bytes => {
|
|
|
161
167
|
return result;
|
|
162
168
|
};
|
|
163
169
|
|
|
170
|
+
const coordinateFormats = new Map([
|
|
171
|
+
[
|
|
172
|
+
SOURCE_MAP_FORMAT,
|
|
173
|
+
new Set([
|
|
174
|
+
"/selected_artifact_hashes",
|
|
175
|
+
"/source_hashes",
|
|
176
|
+
"/source_hashes_after",
|
|
177
|
+
]),
|
|
178
|
+
],
|
|
179
|
+
["json-root-map", new Set([""])],
|
|
180
|
+
["markdown-json-files", new Set(["/files"])],
|
|
181
|
+
[PROOF_MAP_FORMAT, new Set(["/proof_sha256"])],
|
|
182
|
+
]);
|
|
183
|
+
const record = value =>
|
|
184
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
185
|
+
const fieldsExactly = (value, fields) =>
|
|
186
|
+
record(value) &&
|
|
187
|
+
Object.keys(value).length === fields.length &&
|
|
188
|
+
fields.every(field => Object.hasOwn(value, field));
|
|
189
|
+
const objectId = (value, width) =>
|
|
190
|
+
typeof value === "string" &&
|
|
191
|
+
[40, 64].includes(width) &&
|
|
192
|
+
new RegExp(`^[a-f0-9]{${width}}$`, "u").test(value);
|
|
193
|
+
const documentText = bytes => {
|
|
194
|
+
if (bytes.length > 1024 * 1024) return null;
|
|
195
|
+
const text = bytes.toString("utf8");
|
|
196
|
+
return Buffer.from(text).equals(bytes) ? text : null;
|
|
197
|
+
};
|
|
198
|
+
const coordinateOrigin = (origin, width) =>
|
|
199
|
+
fieldsExactly(origin, [
|
|
200
|
+
"commit",
|
|
201
|
+
"blob",
|
|
202
|
+
"path",
|
|
203
|
+
"format",
|
|
204
|
+
"map_pointer",
|
|
205
|
+
"value_span",
|
|
206
|
+
]) &&
|
|
207
|
+
objectId(origin.commit, width) &&
|
|
208
|
+
objectId(origin.blob, width) &&
|
|
209
|
+
safeSourcePath(origin.path) &&
|
|
210
|
+
coordinateFormats.get(origin.format)?.has(origin.map_pointer) === true &&
|
|
211
|
+
Array.isArray(origin.value_span) &&
|
|
212
|
+
origin.value_span.length === 2 &&
|
|
213
|
+
origin.value_span.every(
|
|
214
|
+
value => Number.isSafeInteger(value) && value >= 0 && value <= 1024 * 1024
|
|
215
|
+
) &&
|
|
216
|
+
origin.value_span[1] - origin.value_span[0] === 64;
|
|
217
|
+
const coordinatePreimage = (preimage, width) => {
|
|
218
|
+
if (preimage?.kind === "source")
|
|
219
|
+
return Object.hasOwn(preimage, "commit")
|
|
220
|
+
? fieldsExactly(preimage, ["kind", "commit"]) &&
|
|
221
|
+
objectId(preimage.commit, width)
|
|
222
|
+
: fieldsExactly(preimage, ["kind"]);
|
|
223
|
+
return (
|
|
224
|
+
fieldsExactly(preimage, ["kind", "commit", "path"]) &&
|
|
225
|
+
preimage.kind === "archive" &&
|
|
226
|
+
objectId(preimage.commit, width) &&
|
|
227
|
+
preimage.path === "evidence/archive/neutral-artifact.json"
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/** Closed version1 coordinates carry no digest, opaque original key or caller role. */
|
|
232
|
+
export const coordinateManifest = (bytes, width) => {
|
|
233
|
+
const text = documentText(bytes);
|
|
234
|
+
if (text === null) return null;
|
|
235
|
+
try {
|
|
236
|
+
const parsed = lexical(text);
|
|
237
|
+
if (
|
|
238
|
+
parsed.tokens.some(
|
|
239
|
+
token =>
|
|
240
|
+
token[0].startsWith('"') &&
|
|
241
|
+
token[0] !== JSON.stringify(JSON.parse(token[0]))
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
return null;
|
|
245
|
+
const value = parsed.value;
|
|
246
|
+
if (
|
|
247
|
+
!fieldsExactly(value, ["version", "entries"]) ||
|
|
248
|
+
value.version !== 1 ||
|
|
249
|
+
!Array.isArray(value.entries) ||
|
|
250
|
+
value.entries.length > 256
|
|
251
|
+
)
|
|
252
|
+
return null;
|
|
253
|
+
if (
|
|
254
|
+
value.entries.some(
|
|
255
|
+
entry =>
|
|
256
|
+
!fieldsExactly(entry, ["origin", "preimage"]) ||
|
|
257
|
+
!coordinateOrigin(entry.origin, width) ||
|
|
258
|
+
!coordinatePreimage(entry.preimage, width)
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
return null;
|
|
262
|
+
return value.entries;
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/** Scan maximal opening markers without backtracking across a long fence line. */
|
|
269
|
+
const openingFence = line => {
|
|
270
|
+
const state = { start: 0, end: 0 };
|
|
271
|
+
while (state.start < 3 && line[state.start] === " ") state.start++;
|
|
272
|
+
state.end = state.start;
|
|
273
|
+
const marker = line[state.start];
|
|
274
|
+
if (marker !== "`" && marker !== "~") return null;
|
|
275
|
+
while (line[state.end] === marker) state.end++;
|
|
276
|
+
const info = line.slice(state.end);
|
|
277
|
+
return state.end - state.start >= 3 && !/[\r\n]/u.test(info)
|
|
278
|
+
? [line.slice(state.start, state.end), info]
|
|
279
|
+
: null;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/** Strip only trailing CR/LF bytes, with a single bounded backwards pass. */
|
|
283
|
+
const withoutLineEnding = line => {
|
|
284
|
+
const state = { end: line.length };
|
|
285
|
+
while (state.end > 0 && /[\r\n]/u.test(line[state.end - 1])) state.end--;
|
|
286
|
+
return line.slice(0, state.end);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** Track actual Markdown fences, including nested-looking text inside another fence. */
|
|
290
|
+
const markdownFences = text => {
|
|
291
|
+
const result = [];
|
|
292
|
+
let active = null;
|
|
293
|
+
let offset = 0;
|
|
294
|
+
for (const line of text.split(/(?<=\n)/u)) {
|
|
295
|
+
const clean = withoutLineEnding(line);
|
|
296
|
+
if (active) {
|
|
297
|
+
const close = clean.match(/^ {0,3}(`{3,}|~{3,})[ \t]*$/u);
|
|
298
|
+
if (
|
|
299
|
+
close &&
|
|
300
|
+
close[1][0] === active.marker[0] &&
|
|
301
|
+
close[1].length >= active.marker.length
|
|
302
|
+
) {
|
|
303
|
+
result.push({
|
|
304
|
+
...active,
|
|
305
|
+
bodyEnd: offset,
|
|
306
|
+
end: offset + line.length,
|
|
307
|
+
closed: true,
|
|
308
|
+
});
|
|
309
|
+
active = null;
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
const open = openingFence(clean);
|
|
313
|
+
if (open && !(open[0][0] === "`" && open[1].includes("`")))
|
|
314
|
+
active = {
|
|
315
|
+
start: offset,
|
|
316
|
+
marker: open[0],
|
|
317
|
+
info: open[1].trim(),
|
|
318
|
+
bodyStart: offset + line.length,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
offset += line.length;
|
|
322
|
+
}
|
|
323
|
+
if (active)
|
|
324
|
+
result.push({
|
|
325
|
+
...active,
|
|
326
|
+
bodyEnd: text.length,
|
|
327
|
+
end: text.length,
|
|
328
|
+
closed: false,
|
|
329
|
+
});
|
|
330
|
+
return result;
|
|
331
|
+
};
|
|
332
|
+
const markdownTokensWithinBudget = text => {
|
|
333
|
+
let count = 0;
|
|
334
|
+
for (const _token of text.matchAll(
|
|
335
|
+
/"(?:\\[\s\S]|[^"\\])*"|[`~]+|[\p{L}\p{N}]+|[^\s\p{L}\p{N}]/gu
|
|
336
|
+
))
|
|
337
|
+
if (++count > 8192) return false;
|
|
338
|
+
return true;
|
|
339
|
+
};
|
|
340
|
+
const completeMap = (value, isPath) =>
|
|
341
|
+
record(value) &&
|
|
342
|
+
Object.keys(value).length > 0 &&
|
|
343
|
+
Object.keys(value).length <= 256 &&
|
|
344
|
+
Object.entries(value).every(
|
|
345
|
+
([key, literal]) => isPath(key) && isHash(literal)
|
|
346
|
+
);
|
|
347
|
+
const opaqueKey = key => typeof key === "string" && key.length > 0;
|
|
348
|
+
const coordinateMap = (parsed, role, format, offset = 0) => {
|
|
349
|
+
const value = role === null ? parsed.value : parsed.value[role];
|
|
350
|
+
if (
|
|
351
|
+
!completeMap(
|
|
352
|
+
value,
|
|
353
|
+
format === PROOF_MAP_FORMAT ? opaqueKey : safeSourcePath
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
return null;
|
|
357
|
+
const spans = parsed.spans
|
|
358
|
+
.filter(span =>
|
|
359
|
+
role === null
|
|
360
|
+
? span.path.length === 1
|
|
361
|
+
: span.path.length === 2 && span.path[0] === role
|
|
362
|
+
)
|
|
363
|
+
.map(span => ({
|
|
364
|
+
file: span.path.at(-1),
|
|
365
|
+
hash: span.hash,
|
|
366
|
+
start: span.start + offset,
|
|
367
|
+
end: span.end + offset,
|
|
368
|
+
}));
|
|
369
|
+
if (spans.length !== Object.keys(value).length) return null;
|
|
370
|
+
return {
|
|
371
|
+
kind: format === PROOF_MAP_FORMAT ? "proof" : "source",
|
|
372
|
+
format,
|
|
373
|
+
pointer: role === null ? "" : `/${role}`,
|
|
374
|
+
value: parsed.value,
|
|
375
|
+
spans,
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
const coordinateJsonMaps = parsed => {
|
|
379
|
+
if (!record(parsed.value)) return [];
|
|
380
|
+
if (completeMap(parsed.value, safeSourcePath))
|
|
381
|
+
return [coordinateMap(parsed, null, "json-root-map")];
|
|
382
|
+
const roles = [
|
|
383
|
+
"selected_artifact_hashes",
|
|
384
|
+
"source_hashes",
|
|
385
|
+
"source_hashes_after",
|
|
386
|
+
"proof_sha256",
|
|
387
|
+
].filter(role => Object.hasOwn(parsed.value, role));
|
|
388
|
+
const result = roles.map(role =>
|
|
389
|
+
coordinateMap(
|
|
390
|
+
parsed,
|
|
391
|
+
role,
|
|
392
|
+
role === "proof_sha256" ? PROOF_MAP_FORMAT : SOURCE_MAP_FORMAT
|
|
393
|
+
)
|
|
394
|
+
);
|
|
395
|
+
return result.some(map => map === null) ? [] : result;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
/** Complete documents derive exact fixed map roles independently of manifest assertions. */
|
|
399
|
+
export const coordinateMaps = bytes => {
|
|
400
|
+
const text = documentText(bytes);
|
|
401
|
+
if (text === null) return [];
|
|
402
|
+
try {
|
|
403
|
+
return coordinateJsonMaps(lexical(text));
|
|
404
|
+
} catch {
|
|
405
|
+
/* A complete Markdown document may supply one independently parsed JSON fence. */
|
|
406
|
+
}
|
|
407
|
+
if (!markdownTokensWithinBudget(text)) return [];
|
|
408
|
+
const blocks = markdownFences(text).filter(block => block.info === "json");
|
|
409
|
+
if (blocks.length !== 1 || !blocks[0].closed) return [];
|
|
410
|
+
try {
|
|
411
|
+
const block = blocks[0];
|
|
412
|
+
const parsed = lexical(text.slice(block.bodyStart, block.bodyEnd));
|
|
413
|
+
if (
|
|
414
|
+
!record(parsed.value) ||
|
|
415
|
+
!completeMap(parsed.value.files, safeSourcePath) ||
|
|
416
|
+
!completeMap(parsed.value.logs, opaqueKey)
|
|
417
|
+
)
|
|
418
|
+
return [];
|
|
419
|
+
const map = coordinateMap(
|
|
420
|
+
parsed,
|
|
421
|
+
"files",
|
|
422
|
+
"markdown-json-files",
|
|
423
|
+
Buffer.byteLength(text.slice(0, block.bodyStart))
|
|
424
|
+
);
|
|
425
|
+
return map ? [map] : [];
|
|
426
|
+
} catch {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const paragraphBreak = line =>
|
|
432
|
+
/^\s*$/u.test(line) ||
|
|
433
|
+
/^ {0,3}(?:>|#{1,6}[ \t]|`{3,}|~{3,}|(?:[-*+] |\d+[.)] ))/u.test(line) ||
|
|
434
|
+
/^(?: {4}|\t)/u.test(line);
|
|
435
|
+
const paragraphAt = (text, position) => {
|
|
436
|
+
const lines = [];
|
|
437
|
+
let start = 0;
|
|
438
|
+
for (const textLine of text.split(/(?<=\n)/u)) {
|
|
439
|
+
lines.push({ text: textLine, start, end: start + textLine.length });
|
|
440
|
+
start += textLine.length;
|
|
441
|
+
}
|
|
442
|
+
const index = lines.findIndex(
|
|
443
|
+
line => position >= line.start && position < line.end
|
|
444
|
+
);
|
|
445
|
+
if (index < 0) return null;
|
|
446
|
+
let first = index;
|
|
447
|
+
let last = index;
|
|
448
|
+
while (first > 0 && !paragraphBreak(lines[first].text)) {
|
|
449
|
+
if (/^\s*$/u.test(lines[first - 1].text)) break;
|
|
450
|
+
if (paragraphBreak(lines[first - 1].text)) {
|
|
451
|
+
if (/^ {0,3}>/u.test(lines[first - 1].text)) return null;
|
|
452
|
+
if (/^ {0,3}[-*+] /u.test(lines[first - 1].text)) first--;
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
first--;
|
|
456
|
+
}
|
|
457
|
+
while (last + 1 < lines.length && !paragraphBreak(lines[last + 1].text))
|
|
458
|
+
last++;
|
|
459
|
+
return {
|
|
460
|
+
start: lines[first].start,
|
|
461
|
+
text: text.slice(lines[first].start, lines[last].end),
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
const inlineCode = text => {
|
|
465
|
+
const ranges = [];
|
|
466
|
+
const runs = [...text.matchAll(/`+/gu)];
|
|
467
|
+
let index = 0;
|
|
468
|
+
while (index < runs.length) {
|
|
469
|
+
const close = runs.findIndex(
|
|
470
|
+
(run, candidate) =>
|
|
471
|
+
candidate > index && run[0].length === runs[index][0].length
|
|
472
|
+
);
|
|
473
|
+
if (close < 0) return null;
|
|
474
|
+
ranges.push([runs[index].index, runs[close].index + runs[close][0].length]);
|
|
475
|
+
index = close + 1;
|
|
476
|
+
}
|
|
477
|
+
return ranges;
|
|
478
|
+
};
|
|
479
|
+
const quotedRanges = text => {
|
|
480
|
+
const ranges = [];
|
|
481
|
+
let quote = null;
|
|
482
|
+
let start = 0;
|
|
483
|
+
for (let index = 0; index < text.length; index++) {
|
|
484
|
+
const char = text[index];
|
|
485
|
+
if (char !== "'" && char !== '"') continue;
|
|
486
|
+
if (quote === char) {
|
|
487
|
+
ranges.push([start, index + 1]);
|
|
488
|
+
quote = null;
|
|
489
|
+
} else if (quote === null) {
|
|
490
|
+
if (char === "'" && /[\p{L}\p{N}]/u.test(text[index - 1] ?? "")) continue;
|
|
491
|
+
quote = char;
|
|
492
|
+
start = index;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return quote === null ? ranges : null;
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
/** One exact taxonomy occurrence may live inside a complete larger Markdown paragraph. */
|
|
499
|
+
export const paragraphNarrativeSpan = (bytes, lineStart, lineEnd) => {
|
|
500
|
+
const text = documentText(bytes);
|
|
501
|
+
if (text === null || !markdownTokensWithinBudget(text)) return null;
|
|
502
|
+
const position = bytes.subarray(0, lineStart).toString("utf8").length;
|
|
503
|
+
if (
|
|
504
|
+
markdownFences(text).some(
|
|
505
|
+
block => position >= block.start && position < block.end
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
return null;
|
|
509
|
+
const paragraph = paragraphAt(text, position);
|
|
510
|
+
if (!paragraph) return null;
|
|
511
|
+
const phrase = `AccessDenied boundaries, ${TAXONOMY} falsifications`;
|
|
512
|
+
const index = paragraph.text.indexOf(phrase);
|
|
513
|
+
if (
|
|
514
|
+
index < 0 ||
|
|
515
|
+
paragraph.text.indexOf(phrase, index + 1) >= 0 ||
|
|
516
|
+
!/[,.]/u.test(paragraph.text[index + phrase.length] ?? "")
|
|
517
|
+
)
|
|
518
|
+
return null;
|
|
519
|
+
const ranges = inlineCode(paragraph.text);
|
|
520
|
+
if (
|
|
521
|
+
ranges === null ||
|
|
522
|
+
ranges.some(([start, end]) => index < end && index + phrase.length > start)
|
|
523
|
+
)
|
|
524
|
+
return null;
|
|
525
|
+
let prose = paragraph.text;
|
|
526
|
+
for (const [start, end] of [...ranges].reverse())
|
|
527
|
+
prose = `${prose.slice(0, start)}identifier${prose.slice(end)}`;
|
|
528
|
+
const quotes = quotedRanges(prose);
|
|
529
|
+
const proseIndex = prose.indexOf(phrase);
|
|
530
|
+
if (
|
|
531
|
+
quotes === null ||
|
|
532
|
+
quotes.some(
|
|
533
|
+
([start, end]) => proseIndex < end && proseIndex + phrase.length > start
|
|
534
|
+
)
|
|
535
|
+
)
|
|
536
|
+
return null;
|
|
537
|
+
if (
|
|
538
|
+
!/^(?: {0,3}[-*+] )?\p{L}[\p{L}\p{N}\s,./():;!?"'-]*[.!?]\s*$/u.test(prose)
|
|
539
|
+
)
|
|
540
|
+
return null;
|
|
541
|
+
if (
|
|
542
|
+
[...prose.matchAll(/(?:^|\s)([a-z_-]+)\s*:/giu)].some(
|
|
543
|
+
match => credentialField.test(match[1]) || credentialToken.test(match[1])
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
return null;
|
|
547
|
+
const start = Buffer.byteLength(
|
|
548
|
+
text.slice(0, paragraph.start + index + phrase.indexOf(TAXONOMY))
|
|
549
|
+
);
|
|
550
|
+
const end = start + TAXONOMY.length;
|
|
551
|
+
return start >= lineStart && end <= lineEnd
|
|
552
|
+
? { hash: TAXONOMY, start, end }
|
|
553
|
+
: null;
|
|
554
|
+
};
|
|
555
|
+
|
|
164
556
|
/** Only the fixed taxonomy in ordinary prose, outside Markdown code, is a candidate. */
|
|
165
557
|
export const narrativeSpan = (bytes, lineStart, lineEnd) => {
|
|
166
558
|
const prior = bytes.subarray(0, lineStart).toString("utf8");
|
|
@@ -172,7 +564,7 @@ export const narrativeSpan = (bytes, lineStart, lineEnd) => {
|
|
|
172
564
|
const line = bytes.subarray(lineStart, lineEnd).toString("utf8");
|
|
173
565
|
const prose = line.replace(/`[^`\r\n]{1,256}`/gu, "identifier");
|
|
174
566
|
if (!/^(?:[-*] )?[A-Za-z][A-Za-z0-9 ,./():-]*\.$/u.test(prose)) return null;
|
|
175
|
-
const taxonomy =
|
|
567
|
+
const taxonomy = TAXONOMY;
|
|
176
568
|
const phrase = `AccessDenied boundaries, ${taxonomy} falsifications`;
|
|
177
569
|
const index = line.indexOf(phrase);
|
|
178
570
|
if (index < 0 || line.indexOf(phrase, index + 1) >= 0) return null;
|