@codyswann/lisa 4.70.3 → 4.71.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 +174 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
- package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
- package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +188 -0
- package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +271 -0
- package/all/copy-overwrite/scripts/lib/history-secret-scanner.mjs +6 -296
- package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
- package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
- package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
- package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +272 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
- package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
- package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
- package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
- package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
- package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
- package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
- package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
- package/all/copy-overwrite/scripts/lisa-history-secrets.mjs +8 -1
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
- package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
- package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
- package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
- package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
- package/dist/codex/scripts/block-no-verify.sh +89 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +620 -0
- package/dist/core/lisa-owned-hash-ledger.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 +165 -13
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
- 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/block-no-verify.agy.sh +89 -5
- package/plugins/lisa/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
- 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/block-no-verify.sh +89 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
- 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/block-no-verify.agy.sh +89 -5
- package/plugins/src/base/hooks/block-no-verify.sh +89 -5
- package/scripts/check-shipped-surface-removals.mjs +96 -24
- package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
- package/scripts/two-channel-couplings.json +31 -7
- package/typescript/copy-contents/.husky/commit-msg +18 -0
- package/typescript/copy-overwrite/.prettierignore +2 -0
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
gitRead,
|
|
28
28
|
HistorySecretError,
|
|
29
29
|
} from "./history-secret-git.mjs";
|
|
30
|
+
import { classifyEvidence } from "./history-secret-evidence.mjs";
|
|
30
31
|
|
|
31
32
|
export const VERSION = "8.30.1";
|
|
32
33
|
/** Archive and extracted executable pins both come from the same official release. */
|
|
@@ -222,303 +223,12 @@ export const qualifyScanner = requested => {
|
|
|
222
223
|
return binary;
|
|
223
224
|
};
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const NARRATIVE = [
|
|
229
|
-
"AccessDenied boundaries,",
|
|
230
|
-
`${TAXONOMY} falsifications.`,
|
|
231
|
-
].join(" ");
|
|
232
|
-
const safePath = value =>
|
|
233
|
-
typeof value === "string" &&
|
|
234
|
-
value.length > 0 &&
|
|
235
|
-
!/[\\\u0000-\u001f\u007f]/u.test(value) &&
|
|
236
|
-
!/^(?:\/|[A-Za-z]:)/u.test(value) &&
|
|
237
|
-
value.split("/").every(part => part && part !== "." && part !== "..");
|
|
238
|
-
|
|
239
|
-
/** Read authentic regular Git blobs, never trimmed text or worktree evidence. */
|
|
240
|
-
const readBlob = (file, commit, cwd, budget) => {
|
|
241
|
-
if (!safePath(file)) return null;
|
|
242
|
-
const entries = gitRead(
|
|
243
|
-
["--literal-pathspecs", "ls-tree", "-z", commit, "--", file],
|
|
244
|
-
cwd
|
|
245
|
-
)
|
|
246
|
-
.split("\0")
|
|
247
|
-
.filter(Boolean);
|
|
248
|
-
if (entries.length !== 1) return null;
|
|
249
|
-
const entry = entries[0].match(
|
|
250
|
-
/^(100644|100755) blob ([a-f0-9]{40}|[a-f0-9]{64})\t([\s\S]*)$/u
|
|
251
|
-
);
|
|
252
|
-
if (!entry || entry[3] !== file || entry[2].length !== commit.length)
|
|
253
|
-
return null;
|
|
254
|
-
const sizeText = gitRead(["cat-file", "-s", entry[2]], cwd);
|
|
255
|
-
const size = Number(sizeText);
|
|
256
|
-
if (!/^\d+$/u.test(sizeText) || !Number.isSafeInteger(size))
|
|
257
|
-
throw new HistorySecretError(
|
|
258
|
-
"Git blob size is invalid. Repair complete Git objects and retry; safety is unproved."
|
|
259
|
-
);
|
|
260
|
-
if (size > budget.remaining) return null;
|
|
261
|
-
const result = spawnSync(
|
|
262
|
-
"git",
|
|
263
|
-
[NO_REPLACEMENTS, "cat-file", "blob", entry[2]],
|
|
264
|
-
{
|
|
265
|
-
cwd,
|
|
266
|
-
env: gitEnvironment(),
|
|
267
|
-
timeout: 120000,
|
|
268
|
-
maxBuffer: EVIDENCE_BUDGET,
|
|
269
|
-
}
|
|
270
|
-
);
|
|
271
|
-
if (
|
|
272
|
-
result.error ||
|
|
273
|
-
result.signal ||
|
|
274
|
-
result.status !== 0 ||
|
|
275
|
-
result.stdout.length !== size ||
|
|
276
|
-
createHash(commit.length === 40 ? "sha1" : "sha256")
|
|
277
|
-
.update(`blob ${size}\0`)
|
|
278
|
-
.update(result.stdout)
|
|
279
|
-
.digest("hex") !== entry[2]
|
|
280
|
-
)
|
|
226
|
+
/** Only structured safe attribution is serialized, never stderr or metadata strings. */
|
|
227
|
+
export const scanCommits = (commits, cwd, requested, heads = []) => {
|
|
228
|
+
if (!Array.isArray(heads) || heads.some(head => !commits.includes(head)))
|
|
281
229
|
throw new HistorySecretError(
|
|
282
|
-
"
|
|
230
|
+
"Evidence heads are not actual introduced commits. Supply original Git input and retry."
|
|
283
231
|
);
|
|
284
|
-
budget.remaining -= size;
|
|
285
|
-
return result.stdout;
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
/** Native JSON validation plus bounded duplicate-aware lexical role attribution. */
|
|
289
|
-
const evidenceMap = bytes => {
|
|
290
|
-
if (bytes.length > 1024 * 1024) return null;
|
|
291
|
-
const text = bytes.toString("utf8");
|
|
292
|
-
if (!Buffer.from(text).equals(bytes)) return null;
|
|
293
|
-
try {
|
|
294
|
-
const value = JSON.parse(text);
|
|
295
|
-
if (
|
|
296
|
-
!value ||
|
|
297
|
-
typeof value !== "object" ||
|
|
298
|
-
Array.isArray(value) ||
|
|
299
|
-
!value.source_sha256 ||
|
|
300
|
-
typeof value.source_sha256 !== "object" ||
|
|
301
|
-
Array.isArray(value.source_sha256)
|
|
302
|
-
)
|
|
303
|
-
return null;
|
|
304
|
-
const entries = Object.entries(value.source_sha256);
|
|
305
|
-
if (
|
|
306
|
-
!entries.length ||
|
|
307
|
-
entries.length > 256 ||
|
|
308
|
-
entries.some(
|
|
309
|
-
([file, hash]) =>
|
|
310
|
-
!safePath(file) ||
|
|
311
|
-
typeof hash !== "string" ||
|
|
312
|
-
!/^[a-f0-9]{64}$/u.test(hash)
|
|
313
|
-
)
|
|
314
|
-
)
|
|
315
|
-
return null;
|
|
316
|
-
const tokens = [
|
|
317
|
-
...text.matchAll(/"(?:\\[\s\S]|[^"\\])*"|[{}[\],:]|[^{}[\],:\s]+/gu),
|
|
318
|
-
];
|
|
319
|
-
if (tokens.length > 8192) return null;
|
|
320
|
-
let cursor = 0;
|
|
321
|
-
const spans = [];
|
|
322
|
-
const visit = (path, depth) => {
|
|
323
|
-
if (depth > 32) throw new Error("Bounded JSON depth");
|
|
324
|
-
const token = tokens[cursor++];
|
|
325
|
-
if (token[0] === "{") {
|
|
326
|
-
const keys = new Set();
|
|
327
|
-
while (tokens[cursor][0] !== "}") {
|
|
328
|
-
const key = JSON.parse(tokens[cursor++][0]);
|
|
329
|
-
if (keys.has(key)) throw new Error("Duplicate decoded JSON key");
|
|
330
|
-
keys.add(key);
|
|
331
|
-
cursor++; // Native JSON validation already proved the colon.
|
|
332
|
-
visit([...path, key], depth + 1);
|
|
333
|
-
if (tokens[cursor][0] === ",") cursor++;
|
|
334
|
-
}
|
|
335
|
-
cursor++;
|
|
336
|
-
} else if (token[0] === "[") {
|
|
337
|
-
let index = 0;
|
|
338
|
-
while (tokens[cursor][0] !== "]") {
|
|
339
|
-
visit([...path, index++], depth + 1);
|
|
340
|
-
if (tokens[cursor][0] === ",") cursor++;
|
|
341
|
-
}
|
|
342
|
-
cursor++;
|
|
343
|
-
} else if (
|
|
344
|
-
path.length === 2 &&
|
|
345
|
-
path[0] === "source_sha256" &&
|
|
346
|
-
token[0][0] === '"'
|
|
347
|
-
) {
|
|
348
|
-
const hash = JSON.parse(token[0]);
|
|
349
|
-
// Escaped values do not supply an unambiguous literal detector span.
|
|
350
|
-
if (token[0] !== JSON.stringify(hash))
|
|
351
|
-
throw new Error("Ambiguous digest spelling");
|
|
352
|
-
spans.push({
|
|
353
|
-
file: path[1],
|
|
354
|
-
hash,
|
|
355
|
-
start: Buffer.byteLength(text.slice(0, token.index + 1)),
|
|
356
|
-
end: Buffer.byteLength(
|
|
357
|
-
text.slice(0, token.index + token[0].length - 1)
|
|
358
|
-
),
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
visit([], 0);
|
|
363
|
-
return cursor === tokens.length && spans.length === entries.length
|
|
364
|
-
? { value, spans }
|
|
365
|
-
: null;
|
|
366
|
-
} catch {
|
|
367
|
-
// probe-direction: fail-closed — malformed, duplicate or over-budget JSON
|
|
368
|
-
// cannot establish evidence role; its detector findings remain blocking.
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
/** Clear only a uniquely attributed, independently established benign finding. */
|
|
374
|
-
const classifyEvidence = cwd => {
|
|
375
|
-
const budget = { remaining: EVIDENCE_BUDGET };
|
|
376
|
-
const blobs = new Map();
|
|
377
|
-
const maps = new Map();
|
|
378
|
-
const blob = (file, commit) => {
|
|
379
|
-
const key = `${commit}\0${file}`;
|
|
380
|
-
if (!blobs.has(key)) {
|
|
381
|
-
if (blobs.size >= 256) return null;
|
|
382
|
-
blobs.set(key, readBlob(file, commit, cwd, budget));
|
|
383
|
-
}
|
|
384
|
-
return blobs.get(key);
|
|
385
|
-
};
|
|
386
|
-
return row => {
|
|
387
|
-
if (
|
|
388
|
-
row.RuleID !== "generic-api-key" ||
|
|
389
|
-
!safePath(row.File) ||
|
|
390
|
-
row.SymlinkFile !== "" ||
|
|
391
|
-
row.Secret !== "REDACTED" ||
|
|
392
|
-
typeof row.Match !== "string" ||
|
|
393
|
-
row.EndLine !== row.StartLine ||
|
|
394
|
-
!Number.isSafeInteger(row.StartColumn) ||
|
|
395
|
-
!Number.isSafeInteger(row.EndColumn) ||
|
|
396
|
-
row.StartColumn < 1 ||
|
|
397
|
-
row.EndColumn < row.StartColumn
|
|
398
|
-
)
|
|
399
|
-
return false;
|
|
400
|
-
const bytes = blob(row.File, row.Commit);
|
|
401
|
-
if (!bytes) return false;
|
|
402
|
-
let lineStart = 0;
|
|
403
|
-
for (let line = 1; line < row.StartLine; line++) {
|
|
404
|
-
const next = bytes.indexOf(10, lineStart);
|
|
405
|
-
if (next < 0) return false;
|
|
406
|
-
lineStart = next + 1;
|
|
407
|
-
}
|
|
408
|
-
const newline = bytes.indexOf(10, lineStart);
|
|
409
|
-
const lineEnd = newline < 0 ? bytes.length : newline;
|
|
410
|
-
const replacements = row.Match.split("REDACTED");
|
|
411
|
-
if (replacements.length !== 2) return false;
|
|
412
|
-
const line = bytes.subarray(lineStart, lineEnd);
|
|
413
|
-
const attributed = (candidate, left, right) => {
|
|
414
|
-
const template = replacements.join(candidate);
|
|
415
|
-
if (template.split(candidate).length !== 2) return false;
|
|
416
|
-
const original = Buffer.from(template);
|
|
417
|
-
const offset = line.indexOf(original);
|
|
418
|
-
if (offset < 0 || line.indexOf(original, offset + 1) >= 0) return false;
|
|
419
|
-
const start = lineStart + offset;
|
|
420
|
-
const end = start + original.length;
|
|
421
|
-
if (
|
|
422
|
-
left !== start + Buffer.byteLength(replacements[0]) ||
|
|
423
|
-
right !== left + Buffer.byteLength(candidate)
|
|
424
|
-
)
|
|
425
|
-
return false;
|
|
426
|
-
// Pinned 8.30.1 detect/location.go counts a preceding fragment newline
|
|
427
|
-
// in columns after its first line. Prove exactly one of those two byte
|
|
428
|
-
// frames against the actual unique Git span; never infer fragment state.
|
|
429
|
-
return (
|
|
430
|
-
[0, 1].filter(
|
|
431
|
-
newlineByte =>
|
|
432
|
-
start === lineStart + row.StartColumn - 1 - newlineByte &&
|
|
433
|
-
end === lineStart + row.EndColumn - newlineByte
|
|
434
|
-
).length === 1
|
|
435
|
-
);
|
|
436
|
-
};
|
|
437
|
-
// A complete standalone validation paragraph excludes surrounding JSON,
|
|
438
|
-
// assignments and interpolated/code contexts, not merely their filenames.
|
|
439
|
-
if (
|
|
440
|
-
(bytes.equals(Buffer.from(NARRATIVE)) ||
|
|
441
|
-
bytes.equals(Buffer.from(`${NARRATIVE}\n`))) &&
|
|
442
|
-
attributed(
|
|
443
|
-
TAXONOMY,
|
|
444
|
-
NARRATIVE.indexOf(TAXONOMY),
|
|
445
|
-
NARRATIVE.indexOf(TAXONOMY) + TAXONOMY.length
|
|
446
|
-
)
|
|
447
|
-
)
|
|
448
|
-
return true;
|
|
449
|
-
const key = `${row.Commit}\0${row.File}`;
|
|
450
|
-
if (!maps.has(key)) {
|
|
451
|
-
const parsed = evidenceMap(bytes);
|
|
452
|
-
let valid = parsed !== null;
|
|
453
|
-
const revision =
|
|
454
|
-
parsed && Object.hasOwn(parsed.value, "source_revision")
|
|
455
|
-
? parsed.value.source_revision
|
|
456
|
-
: row.Commit;
|
|
457
|
-
if (parsed && Object.hasOwn(parsed.value, "source_revision")) {
|
|
458
|
-
valid =
|
|
459
|
-
typeof revision === "string" &&
|
|
460
|
-
new RegExp(`^[a-f0-9]{${row.Commit.length}}$`, "u").test(revision);
|
|
461
|
-
if (valid) {
|
|
462
|
-
const type = spawnSync(
|
|
463
|
-
"git",
|
|
464
|
-
[NO_REPLACEMENTS, "cat-file", "-t", revision],
|
|
465
|
-
{
|
|
466
|
-
cwd,
|
|
467
|
-
env: gitEnvironment(),
|
|
468
|
-
timeout: 120000,
|
|
469
|
-
maxBuffer: 1024 * 1024,
|
|
470
|
-
}
|
|
471
|
-
);
|
|
472
|
-
const ancestry = spawnSync(
|
|
473
|
-
"git",
|
|
474
|
-
[
|
|
475
|
-
NO_REPLACEMENTS,
|
|
476
|
-
"merge-base",
|
|
477
|
-
"--is-ancestor",
|
|
478
|
-
revision,
|
|
479
|
-
row.Commit,
|
|
480
|
-
],
|
|
481
|
-
{
|
|
482
|
-
cwd,
|
|
483
|
-
env: gitEnvironment(),
|
|
484
|
-
timeout: 120000,
|
|
485
|
-
maxBuffer: 1024 * 1024,
|
|
486
|
-
}
|
|
487
|
-
);
|
|
488
|
-
// probe-direction: fail-closed — an absent/unreachable revision or
|
|
489
|
-
// failed ancestry probe cannot establish a historical preimage.
|
|
490
|
-
valid =
|
|
491
|
-
!type.error &&
|
|
492
|
-
!type.signal &&
|
|
493
|
-
type.status === 0 &&
|
|
494
|
-
type.stdout.toString() === "commit\n" &&
|
|
495
|
-
!type.stderr.length &&
|
|
496
|
-
!ancestry.error &&
|
|
497
|
-
!ancestry.signal &&
|
|
498
|
-
ancestry.status === 0 &&
|
|
499
|
-
!ancestry.stdout.length &&
|
|
500
|
-
!ancestry.stderr.length;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
if (valid)
|
|
504
|
-
for (const span of parsed.spans) {
|
|
505
|
-
const sourceBytes = blob(span.file, revision);
|
|
506
|
-
if (!sourceBytes || digest(sourceBytes) !== span.hash) {
|
|
507
|
-
valid = false;
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
maps.set(key, valid ? parsed.spans : []);
|
|
512
|
-
}
|
|
513
|
-
const candidates = maps
|
|
514
|
-
.get(key)
|
|
515
|
-
.filter(span => attributed(span.hash, span.start, span.end));
|
|
516
|
-
return candidates.length === 1;
|
|
517
|
-
};
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
/** Only structured safe attribution is serialized, never stderr or metadata strings. */
|
|
521
|
-
export const scanCommits = (commits, cwd, requested) => {
|
|
522
232
|
const binary = qualifyScanner(requested);
|
|
523
233
|
const scratch = mkdtempSync(join(tmpdir(), "lisa-history-scan-"));
|
|
524
234
|
chmodSync(scratch, 0o700);
|
|
@@ -558,7 +268,7 @@ export const scanCommits = (commits, cwd, requested) => {
|
|
|
558
268
|
});
|
|
559
269
|
const findings = [];
|
|
560
270
|
const audit = auditedGit(scratch);
|
|
561
|
-
const benign = classifyEvidence(cwd);
|
|
271
|
+
const benign = classifyEvidence(cwd, commits, [...new Set(heads)]);
|
|
562
272
|
for (let offset = 0; offset < commits.length; offset += 100) {
|
|
563
273
|
const group = commits.slice(offset, offset + 100);
|
|
564
274
|
audit.begin();
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
|
+
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
|
+
|
|
4
|
+
/** Fixed-schema writer evidence and idempotent current claim. @module npm-updater */
|
|
5
|
+
import { required, CLAIM, validateProposal } from "./npm-update-contract.mjs";
|
|
6
|
+
import { baseline } from "./npm-update-prepare.mjs";
|
|
7
|
+
import { locateCheckpoint } from "./npm-update-recovery.mjs";
|
|
8
|
+
import { runProcess } from "./npm-update-process.mjs";
|
|
9
|
+
import { sha256 } from "./github-attestation-verifier.mjs";
|
|
10
|
+
import { registryVersion } from "./npm-update-npm.mjs";
|
|
11
|
+
import {
|
|
12
|
+
buildDraft,
|
|
13
|
+
qualityGates,
|
|
14
|
+
inspectLeaf,
|
|
15
|
+
leafRoles,
|
|
16
|
+
historicalFiling,
|
|
17
|
+
claimLeaf,
|
|
18
|
+
} from "./npm-update-leaf.mjs";
|
|
19
|
+
/** Probe the named surfaces, all-state relationships and committed history before filing. */
|
|
20
|
+
export async function filingEvidence(api, proposal, config, cwd) {
|
|
21
|
+
await baseline(
|
|
22
|
+
cwd,
|
|
23
|
+
{ PATH: process.env.PATH, HOME: "/nonexistent", GIT_TERMINAL_PROMPT: "0" },
|
|
24
|
+
proposal
|
|
25
|
+
);
|
|
26
|
+
await api.main(proposal.parent);
|
|
27
|
+
const query = `repo:${proposal.repository} npm is:issue in:title,body`;
|
|
28
|
+
const search = await api.request(
|
|
29
|
+
`search/issues?q=${encodeURIComponent(query)}&per_page=100`
|
|
30
|
+
);
|
|
31
|
+
required(
|
|
32
|
+
!search.incomplete_results && search.total_count <= 100,
|
|
33
|
+
"relationship search is incomplete or oversized"
|
|
34
|
+
);
|
|
35
|
+
for (const item of search.items) await api.issue(item.number);
|
|
36
|
+
const history = await runProcess(
|
|
37
|
+
"git",
|
|
38
|
+
[
|
|
39
|
+
"log",
|
|
40
|
+
proposal.parent,
|
|
41
|
+
"--max-count=20",
|
|
42
|
+
"--format=%H %s",
|
|
43
|
+
"--",
|
|
44
|
+
"package.json",
|
|
45
|
+
"package-lock.json",
|
|
46
|
+
],
|
|
47
|
+
{
|
|
48
|
+
cwd,
|
|
49
|
+
env: {
|
|
50
|
+
PATH: process.env.PATH,
|
|
51
|
+
HOME: "/nonexistent",
|
|
52
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
const registry = [];
|
|
57
|
+
for (const update of proposal.updates)
|
|
58
|
+
registry.push(await registryVersion(update, proposal));
|
|
59
|
+
const labels = (await api.list(`repos/${proposal.repository}/labels`)).map(
|
|
60
|
+
label => label.name
|
|
61
|
+
);
|
|
62
|
+
return {
|
|
63
|
+
history: {
|
|
64
|
+
command: `git log ${proposal.parent} --max-count=20 -- package.json package-lock.json`,
|
|
65
|
+
result: history.stdout.toString() || "No matching history.",
|
|
66
|
+
},
|
|
67
|
+
search: { query, total: search.total_count },
|
|
68
|
+
registry,
|
|
69
|
+
labels,
|
|
70
|
+
main: proposal.parent,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Same selected versions are discovered before deriving a new parent-bound key. */
|
|
75
|
+
export async function discoverPrior(api, proposal) {
|
|
76
|
+
const query = `repo:${proposal.repository} "${proposal.selectionKey}" is:issue in:body`;
|
|
77
|
+
const found = await api.request(
|
|
78
|
+
`search/issues?q=${encodeURIComponent(query)}&per_page=100`
|
|
79
|
+
);
|
|
80
|
+
required(
|
|
81
|
+
found.incomplete_results === false &&
|
|
82
|
+
Number.isSafeInteger(found.total_count) &&
|
|
83
|
+
found.total_count <= 1 &&
|
|
84
|
+
Array.isArray(found.items) &&
|
|
85
|
+
found.items.length === found.total_count,
|
|
86
|
+
"prior selection search is incomplete or ambiguous"
|
|
87
|
+
);
|
|
88
|
+
if (!found.total_count) return { status: "new" };
|
|
89
|
+
const issue = await api.issue(found.items[0].number);
|
|
90
|
+
required(
|
|
91
|
+
issue.state === "open" &&
|
|
92
|
+
!issue.pull_request &&
|
|
93
|
+
typeof issue.body === "string",
|
|
94
|
+
"closed or foreign prior proposal is preserved"
|
|
95
|
+
);
|
|
96
|
+
const selected = [
|
|
97
|
+
...issue.body.matchAll(/^Selection key: ([a-f0-9]{64})$/gm),
|
|
98
|
+
];
|
|
99
|
+
const parents = [...issue.body.matchAll(/^Base parent: ([a-f0-9]{40})$/gm)];
|
|
100
|
+
required(
|
|
101
|
+
selected.length === 1 &&
|
|
102
|
+
selected[0][1] === proposal.selectionKey &&
|
|
103
|
+
parents.length === 1,
|
|
104
|
+
"prior proposal identity differs"
|
|
105
|
+
);
|
|
106
|
+
return {
|
|
107
|
+
status:
|
|
108
|
+
parents[0][1] === proposal.parent ? "existing" : "stale-outstanding",
|
|
109
|
+
issue,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Restoration preserves original filing bytes while checking the live claim and holds. */
|
|
114
|
+
async function restoredAllocation({
|
|
115
|
+
api,
|
|
116
|
+
proposal,
|
|
117
|
+
policy,
|
|
118
|
+
config,
|
|
119
|
+
prior,
|
|
120
|
+
checkpoint,
|
|
121
|
+
evidence,
|
|
122
|
+
currentEvidence,
|
|
123
|
+
}) {
|
|
124
|
+
if (prior.issue) {
|
|
125
|
+
const state = await inspectLeaf(
|
|
126
|
+
prior.issue,
|
|
127
|
+
buildDraft(proposal, policy, config, evidence),
|
|
128
|
+
policy,
|
|
129
|
+
config
|
|
130
|
+
);
|
|
131
|
+
if (checkpoint?.signed) {
|
|
132
|
+
const allocation = checkpoint.payload.allocation;
|
|
133
|
+
required(
|
|
134
|
+
state.claimed &&
|
|
135
|
+
!state.unassigned &&
|
|
136
|
+
state.claims.length === 1 &&
|
|
137
|
+
String(state.claims[0].id) === allocation.claimCommentId &&
|
|
138
|
+
sha256(state.claims[0].body) === allocation.claimSha256 &&
|
|
139
|
+
String(state.claims[0].user?.id) === api.policy.claimActorId &&
|
|
140
|
+
state.claims[0].user?.type === "Bot",
|
|
141
|
+
"origin checkpoint current claim changed"
|
|
142
|
+
);
|
|
143
|
+
qualityGates(
|
|
144
|
+
{
|
|
145
|
+
...allocation.draft,
|
|
146
|
+
title: prior.issue.title,
|
|
147
|
+
body: prior.issue.body,
|
|
148
|
+
},
|
|
149
|
+
proposal,
|
|
150
|
+
policy,
|
|
151
|
+
config,
|
|
152
|
+
evidence
|
|
153
|
+
);
|
|
154
|
+
return { ...allocation, currentEvidence, origin: checkpoint };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Missing configured lifecycle labels are provisioned before the fresh fixed draft. */
|
|
160
|
+
async function ensureLabels({
|
|
161
|
+
api,
|
|
162
|
+
policy,
|
|
163
|
+
roles,
|
|
164
|
+
currentEvidence,
|
|
165
|
+
prior,
|
|
166
|
+
evidence,
|
|
167
|
+
}) {
|
|
168
|
+
for (const name of [
|
|
169
|
+
"type:Task",
|
|
170
|
+
"priority:medium",
|
|
171
|
+
roles.ready,
|
|
172
|
+
roles.claimed,
|
|
173
|
+
]) {
|
|
174
|
+
if (!currentEvidence.labels.includes(name)) {
|
|
175
|
+
await api.request(`repos/${policy.repository}/labels`, "POST", {
|
|
176
|
+
name,
|
|
177
|
+
color: "ededed",
|
|
178
|
+
description: "Managed dependency Task lifecycle",
|
|
179
|
+
});
|
|
180
|
+
currentEvidence.labels.push(name);
|
|
181
|
+
if (!prior.issue) evidence.labels = currentEvidence.labels;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The shipped claim and post-write quality check complete one canonical leaf. */
|
|
187
|
+
async function completeAllocation({
|
|
188
|
+
api,
|
|
189
|
+
proposal,
|
|
190
|
+
policy,
|
|
191
|
+
config,
|
|
192
|
+
prior,
|
|
193
|
+
evidence,
|
|
194
|
+
currentEvidence,
|
|
195
|
+
roles,
|
|
196
|
+
}) {
|
|
197
|
+
const draft = buildDraft(proposal, policy, config, evidence);
|
|
198
|
+
const before = qualityGates(draft, proposal, policy, config, evidence);
|
|
199
|
+
let issue =
|
|
200
|
+
prior.issue ??
|
|
201
|
+
(await api.request(`repos/${policy.repository}/issues`, "POST", {
|
|
202
|
+
title: draft.title,
|
|
203
|
+
body: draft.body,
|
|
204
|
+
labels: draft.labels,
|
|
205
|
+
assignees: draft.assignees,
|
|
206
|
+
}));
|
|
207
|
+
const claimed = await claimLeaf(
|
|
208
|
+
api,
|
|
209
|
+
issue,
|
|
210
|
+
draft,
|
|
211
|
+
proposal,
|
|
212
|
+
policy,
|
|
213
|
+
config,
|
|
214
|
+
evidence,
|
|
215
|
+
roles
|
|
216
|
+
);
|
|
217
|
+
issue = claimed.issue;
|
|
218
|
+
const state = claimed.state;
|
|
219
|
+
const after = qualityGates(
|
|
220
|
+
{ ...draft, title: issue.title, body: issue.body },
|
|
221
|
+
proposal,
|
|
222
|
+
policy,
|
|
223
|
+
config,
|
|
224
|
+
evidence
|
|
225
|
+
);
|
|
226
|
+
const announcement = `[${policy.repository.split("/")[1]}] Fresh dependency Task ${policy.repository}#${issue.number} is assigned to ${policy.maintainer}. Proposal key: ${proposal.key}. Pre/post writer quality gates completed; ordinary gates and runtime evidence remain required.`;
|
|
227
|
+
if (!issue.comments.some(comment => comment.body === announcement))
|
|
228
|
+
await api.request(
|
|
229
|
+
`repos/${policy.repository}/issues/${issue.number}/comments`,
|
|
230
|
+
"POST",
|
|
231
|
+
{ body: announcement }
|
|
232
|
+
);
|
|
233
|
+
return {
|
|
234
|
+
number: issue.number,
|
|
235
|
+
workItem: `${policy.repository}#${issue.number}`,
|
|
236
|
+
claimCommentId: String(state.claims[0].id),
|
|
237
|
+
claimSha256: sha256(CLAIM),
|
|
238
|
+
draft,
|
|
239
|
+
evidence,
|
|
240
|
+
quality: { before, after },
|
|
241
|
+
currentEvidence,
|
|
242
|
+
originalProposal: proposal,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Fixed writer pre/post quality plus real idempotent claim; no Skill RPC is invented. */
|
|
247
|
+
export async function allocateLeaf({
|
|
248
|
+
api,
|
|
249
|
+
proposal: suppliedProposal,
|
|
250
|
+
policy,
|
|
251
|
+
config,
|
|
252
|
+
cwd,
|
|
253
|
+
}) {
|
|
254
|
+
let proposal = suppliedProposal;
|
|
255
|
+
validateProposal(proposal, policy);
|
|
256
|
+
await api.main(proposal.parent);
|
|
257
|
+
const prior = await discoverPrior(api, proposal);
|
|
258
|
+
if (prior.status === "stale-outstanding")
|
|
259
|
+
return {
|
|
260
|
+
status: prior.status,
|
|
261
|
+
number: prior.issue.number,
|
|
262
|
+
workItem: `${policy.repository}#${prior.issue.number}`,
|
|
263
|
+
};
|
|
264
|
+
await api.assignable(policy.maintainer);
|
|
265
|
+
const currentEvidence = await filingEvidence(api, proposal, config, cwd);
|
|
266
|
+
const checkpoint = prior.issue
|
|
267
|
+
? locateCheckpoint(prior.issue.comments, proposal, policy)
|
|
268
|
+
: undefined;
|
|
269
|
+
if (checkpoint) proposal = checkpoint.payload.proposal;
|
|
270
|
+
const evidence = prior.issue
|
|
271
|
+
? historicalFiling(prior.issue)
|
|
272
|
+
: currentEvidence;
|
|
273
|
+
const roles = leafRoles(config, policy.repository);
|
|
274
|
+
const inputs = {
|
|
275
|
+
api,
|
|
276
|
+
proposal,
|
|
277
|
+
policy,
|
|
278
|
+
config,
|
|
279
|
+
prior,
|
|
280
|
+
checkpoint,
|
|
281
|
+
evidence,
|
|
282
|
+
currentEvidence,
|
|
283
|
+
roles,
|
|
284
|
+
};
|
|
285
|
+
const restored = await restoredAllocation(inputs);
|
|
286
|
+
if (restored) return restored;
|
|
287
|
+
await ensureLabels(inputs);
|
|
288
|
+
return completeAllocation(inputs);
|
|
289
|
+
}
|