@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.
Files changed (143) hide show
  1. package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +174 -0
  2. package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
  3. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
  4. package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
  5. package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
  6. package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
  7. package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +188 -0
  8. package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +271 -0
  9. package/all/copy-overwrite/scripts/lib/history-secret-scanner.mjs +6 -296
  10. package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
  11. package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
  12. package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
  13. package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
  14. package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
  15. package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
  16. package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
  17. package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
  18. package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
  19. package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
  20. package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
  21. package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
  22. package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
  23. package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
  24. package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
  25. package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
  26. package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
  27. package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
  28. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
  29. package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
  30. package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
  31. package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
  32. package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
  33. package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
  34. package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +272 -0
  35. package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
  36. package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
  37. package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
  38. package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
  39. package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
  40. package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
  41. package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
  42. package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
  43. package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
  44. package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
  45. package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
  46. package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
  47. package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
  48. package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
  49. package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +258 -0
  50. package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
  51. package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
  52. package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
  53. package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
  54. package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
  55. package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
  56. package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
  57. package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
  58. package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
  59. package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
  60. package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
  61. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
  62. package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
  63. package/all/copy-overwrite/scripts/lisa-history-secrets.mjs +8 -1
  64. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
  65. package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
  66. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
  67. package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
  68. package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
  69. package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
  70. package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
  71. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
  72. package/dist/codex/scripts/block-no-verify.sh +89 -5
  73. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  74. package/dist/core/lisa-owned-hash-ledger.js +620 -0
  75. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  76. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  77. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  78. package/dist/core/upstream-evidence-manifest.js +165 -13
  79. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  80. package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
  81. package/package.json +4 -4
  82. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa/hooks/block-no-verify.agy.sh +89 -5
  85. package/plugins/lisa/hooks/block-no-verify.sh +89 -5
  86. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
  87. package/plugins/lisa-agy/plugin.json +1 -1
  88. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  91. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-copilot/hooks/block-no-verify.sh +89 -5
  95. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
  97. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  99. package/plugins/lisa-expo-agy/plugin.json +1 -1
  100. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  104. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  105. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  109. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  110. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  114. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  115. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  119. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  120. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  124. package/plugins/lisa-rails-agy/plugin.json +1 -1
  125. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  126. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  129. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  130. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  131. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  133. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  134. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  135. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  136. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  137. package/plugins/src/base/hooks/block-no-verify.agy.sh +89 -5
  138. package/plugins/src/base/hooks/block-no-verify.sh +89 -5
  139. package/scripts/check-shipped-surface-removals.mjs +96 -24
  140. package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
  141. package/scripts/two-channel-couplings.json +31 -7
  142. package/typescript/copy-contents/.husky/commit-msg +18 -0
  143. 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
- const EVIDENCE_BUDGET = 64 * 1024 * 1024;
226
- const NO_REPLACEMENTS = "--no-replace-objects";
227
- const TAXONOMY = "Disk/missing/corrupt";
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
- "Git blob bytes cannot be verified. Repair complete Git objects and retry; no raw source is published."
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
+ }