@codyswann/lisa 4.51.0 → 4.52.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/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +2 -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 +5 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/copy-overwrite/scripts/check-e2e-coverage.mjs +117 -4
- 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-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-cursor/.claude-plugin/plugin.json +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/scripts/generate-two-channel-couplings.ts +215 -7
- package/scripts/two-channel-couplings.json +94 -0
|
@@ -228,9 +228,50 @@ export function buildDeliveryInventory(
|
|
|
228
228
|
);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/**
|
|
232
|
+
* The only part of a report these two functions read.
|
|
233
|
+
*
|
|
234
|
+
* Narrower than the full report on purpose: they need a key and a verdict per
|
|
235
|
+
* entry and nothing else, and a parameter that asked for the whole measurement
|
|
236
|
+
* would force every caller — including a test — to build one it does not use.
|
|
237
|
+
*/
|
|
238
|
+
interface CouplingKeySource {
|
|
239
|
+
readonly entries: readonly {
|
|
240
|
+
readonly key: string;
|
|
241
|
+
readonly verdict: string;
|
|
242
|
+
}[];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** One coupling's answer to "can this notice a STALE artifact?" (#3687). */
|
|
246
|
+
export interface StalenessClassification {
|
|
247
|
+
/**
|
|
248
|
+
* What the coupling CAN detect today — an observation, not a decision.
|
|
249
|
+
*
|
|
250
|
+
* `version-handshake` asks the artifact for a contract version and compares
|
|
251
|
+
* it. `capability-probe` asks for one subcommand or flag and infers age from
|
|
252
|
+
* whether it answers, which sees staleness at exactly one point and is blind
|
|
253
|
+
* above that floor — the CodySwannGT/lisa#3477 defect. `existence-only`
|
|
254
|
+
* cannot see age at all.
|
|
255
|
+
*/
|
|
256
|
+
readonly detects: "version-handshake" | "capability-probe" | "existence-only";
|
|
257
|
+
/**
|
|
258
|
+
* Why the current level is acceptable. Required unless a handshake exists.
|
|
259
|
+
*
|
|
260
|
+
* This is the DECISION half, kept separate from `detects` on purpose: a
|
|
261
|
+
* coupling that cannot see age and is fine that way (an advisory read, an
|
|
262
|
+
* artifact carrying no verdict-bearing logic) is a different fact from one
|
|
263
|
+
* that cannot see age and should. Collapsing them into a single
|
|
264
|
+
* "not-needed" token would lose exactly the distinction the sweep exists to
|
|
265
|
+
* record, and would let an unexamined default wear the same word as a
|
|
266
|
+
* considered exemption.
|
|
267
|
+
*/
|
|
268
|
+
readonly reason?: string;
|
|
269
|
+
}
|
|
270
|
+
|
|
231
271
|
/** The ledger's hand-authored half plus its derived half. */
|
|
232
272
|
interface Ledger {
|
|
233
273
|
readonly ratified: Readonly<Record<string, string>>;
|
|
274
|
+
readonly staleness: Readonly<Record<string, StalenessClassification>>;
|
|
234
275
|
readonly inspected: TwoChannelReport["inspected"];
|
|
235
276
|
readonly counts: TwoChannelReport["counts"];
|
|
236
277
|
readonly couplings: readonly {
|
|
@@ -259,6 +300,156 @@ function readRatified(ledgerPath: string): Readonly<Record<string, string>> {
|
|
|
259
300
|
return parsed?.ratified ?? {};
|
|
260
301
|
}
|
|
261
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Read the staleness classifications already recorded (#3687).
|
|
305
|
+
*
|
|
306
|
+
* Exactly the shape `readRatified` uses, and for the same reason: the ledger is
|
|
307
|
+
* a DERIVED artifact regenerated in-commit, so a hand-authored field survives
|
|
308
|
+
* only if the generator reads the previous version before rewriting it. This is
|
|
309
|
+
* the established local precedent for persisting a human decision inside a
|
|
310
|
+
* generated file, which is why the classification lives here rather than in a
|
|
311
|
+
* new sidecar nobody would remember to update.
|
|
312
|
+
* @param ledgerPath - Absolute path to the ledger
|
|
313
|
+
* @returns The classifications, keyed `<workflow>::<path>`
|
|
314
|
+
*/
|
|
315
|
+
function readStaleness(
|
|
316
|
+
ledgerPath: string
|
|
317
|
+
): Readonly<Record<string, StalenessClassification>> {
|
|
318
|
+
if (!existsSync(ledgerPath)) return {};
|
|
319
|
+
const parsed = JSON.parse(readFileSync(ledgerPath, "utf8")) as
|
|
320
|
+
| Partial<Ledger>
|
|
321
|
+
| undefined;
|
|
322
|
+
return parsed?.staleness ?? {};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The verdict for a coupling the package channel already covers.
|
|
327
|
+
*
|
|
328
|
+
* Those are omitted from the ledger, so every question this file asks about a
|
|
329
|
+
* live coupling has to exclude them — named once so the three sites cannot
|
|
330
|
+
* disagree about which verdict that is.
|
|
331
|
+
*/
|
|
332
|
+
const PACKAGE_BACKED = "package-backed";
|
|
333
|
+
|
|
334
|
+
/** The `detects` value meaning the coupling asks for a contract version. */
|
|
335
|
+
const VERSION_HANDSHAKE = "version-handshake";
|
|
336
|
+
|
|
337
|
+
/** The `detects` value meaning it infers age from one subcommand or flag. */
|
|
338
|
+
const CAPABILITY_PROBE = "capability-probe";
|
|
339
|
+
|
|
340
|
+
/** The `detects` value meaning it can see presence and nothing else. */
|
|
341
|
+
const EXISTENCE_ONLY = "existence-only";
|
|
342
|
+
|
|
343
|
+
/** The `detects` values a classification may carry. */
|
|
344
|
+
const DETECTS = new Set([VERSION_HANDSHAKE, CAPABILITY_PROBE, EXISTENCE_ONLY]);
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The floor a stated reason must clear to be a reason rather than a label.
|
|
348
|
+
*
|
|
349
|
+
* A floor, not a proof — no check tells a real argument from filler. What it
|
|
350
|
+
* buys is that "n/a", "ok" and an empty string cannot stand in for the decision
|
|
351
|
+
* this field exists to record.
|
|
352
|
+
*/
|
|
353
|
+
const MIN_REASON_CHARACTERS = 40;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Every problem with the staleness half, as operator-readable paragraphs.
|
|
357
|
+
*
|
|
358
|
+
* THREE refusals, and the first is the one that makes this a standing property
|
|
359
|
+
* rather than a one-time sweep: a coupling with no classification fails, so
|
|
360
|
+
* entry 25 cannot silently inherit "nobody looked". A sweep decays the moment
|
|
361
|
+
* somebody adds an entry; a required field does not.
|
|
362
|
+
* @param report - The measurement
|
|
363
|
+
* @param staleness - The recorded classifications
|
|
364
|
+
* @returns Problem paragraphs, empty when the staleness half is sound
|
|
365
|
+
*/
|
|
366
|
+
export function stalenessProblems(
|
|
367
|
+
report: CouplingKeySource,
|
|
368
|
+
staleness: Readonly<Record<string, StalenessClassification>>
|
|
369
|
+
): readonly string[] {
|
|
370
|
+
const live = new Set(
|
|
371
|
+
report.entries
|
|
372
|
+
.filter(entry => entry.verdict !== PACKAGE_BACKED)
|
|
373
|
+
.map(entry => entry.key)
|
|
374
|
+
);
|
|
375
|
+
const missing = [...live]
|
|
376
|
+
.filter(key => staleness[key] === undefined)
|
|
377
|
+
.map(
|
|
378
|
+
key =>
|
|
379
|
+
`\nUNCLASSIFIED COUPLING ${key}\n No staleness classification. Every ` +
|
|
380
|
+
`coupling must record whether it can detect a STALE artifact, not only ` +
|
|
381
|
+
`an absent one — absence skips and posts nothing, while present-and-old ` +
|
|
382
|
+
`posts a verdict from superseded logic that every reader believes. Add ` +
|
|
383
|
+
`a "staleness" entry with "detects" (version-handshake | ` +
|
|
384
|
+
`capability-probe | existence-only) and, unless it is a handshake, a ` +
|
|
385
|
+
`"reason" saying why that is acceptable here.`
|
|
386
|
+
);
|
|
387
|
+
const orphaned = Object.keys(staleness)
|
|
388
|
+
.filter(key => !live.has(key))
|
|
389
|
+
.map(
|
|
390
|
+
key =>
|
|
391
|
+
`\nSTALE CLASSIFICATION ${key}\n Nothing reads this path any more, so ` +
|
|
392
|
+
`the classification is an unexamined judgement the next matching path ` +
|
|
393
|
+
`would inherit. Delete it from ${LEDGER_PATH}.`
|
|
394
|
+
);
|
|
395
|
+
const malformed = Object.entries(staleness)
|
|
396
|
+
.filter(([key]) => live.has(key))
|
|
397
|
+
.flatMap(([key, value]) => describeMalformed(key, value));
|
|
398
|
+
return [...missing, ...orphaned, ...malformed];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Why one recorded classification is not usable, if it is not.
|
|
403
|
+
* @param key - The coupling key
|
|
404
|
+
* @param value - The recorded classification
|
|
405
|
+
* @returns Problem paragraphs for this entry
|
|
406
|
+
*/
|
|
407
|
+
function describeMalformed(
|
|
408
|
+
key: string,
|
|
409
|
+
value: StalenessClassification
|
|
410
|
+
): readonly string[] {
|
|
411
|
+
if (!DETECTS.has(value?.detects)) {
|
|
412
|
+
return [
|
|
413
|
+
`\nMALFORMED CLASSIFICATION ${key}\n "detects" is ` +
|
|
414
|
+
`${JSON.stringify(value?.detects)}; expected one of ` +
|
|
415
|
+
`${[...DETECTS].join(", ")}.`,
|
|
416
|
+
];
|
|
417
|
+
}
|
|
418
|
+
if (value.detects === VERSION_HANDSHAKE) return [];
|
|
419
|
+
const reason = typeof value.reason === "string" ? value.reason.trim() : "";
|
|
420
|
+
return reason.length >= MIN_REASON_CHARACTERS
|
|
421
|
+
? []
|
|
422
|
+
: [
|
|
423
|
+
`\nUNREASONED CLASSIFICATION ${key}\n "detects" is ` +
|
|
424
|
+
`"${value.detects}", which cannot see how old the artifact is, and no ` +
|
|
425
|
+
`"reason" of at least ${MIN_REASON_CHARACTERS} characters says why ` +
|
|
426
|
+
`that is acceptable here. A recorded decision and an unexamined ` +
|
|
427
|
+
`default must not look the same.`,
|
|
428
|
+
];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The one-line fleet coverage sentence (#3687).
|
|
433
|
+
* @param report - The measurement
|
|
434
|
+
* @param staleness - The recorded classifications
|
|
435
|
+
* @returns A sentence naming how much of the fleet can see age
|
|
436
|
+
*/
|
|
437
|
+
export function stalenessCoverage(
|
|
438
|
+
report: CouplingKeySource,
|
|
439
|
+
staleness: Readonly<Record<string, StalenessClassification>>
|
|
440
|
+
): string {
|
|
441
|
+
const live = report.entries.filter(entry => entry.verdict !== PACKAGE_BACKED);
|
|
442
|
+
const tally = (kind: string): number =>
|
|
443
|
+
live.filter(entry => staleness[entry.key]?.detects === kind).length;
|
|
444
|
+
const handshake = tally(VERSION_HANDSHAKE);
|
|
445
|
+
return (
|
|
446
|
+
` staleness detection: ${handshake} of ${live.length} coupling(s) can see ` +
|
|
447
|
+
`how old the artifact is (${VERSION_HANDSHAKE}); ${tally(CAPABILITY_PROBE)} ` +
|
|
448
|
+
`detect it at one floor only (${CAPABILITY_PROBE}); ${tally(EXISTENCE_ONLY)} ` +
|
|
449
|
+
`detect absence only (${EXISTENCE_ONLY}).`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
262
453
|
/**
|
|
263
454
|
* Render the ledger a report implies.
|
|
264
455
|
*
|
|
@@ -272,14 +463,16 @@ function readRatified(ledgerPath: string): Readonly<Record<string, string>> {
|
|
|
272
463
|
*/
|
|
273
464
|
function toLedger(
|
|
274
465
|
report: TwoChannelReport,
|
|
275
|
-
ratified: Readonly<Record<string, string
|
|
466
|
+
ratified: Readonly<Record<string, string>>,
|
|
467
|
+
staleness: Readonly<Record<string, StalenessClassification>>
|
|
276
468
|
): Ledger {
|
|
277
469
|
return {
|
|
278
470
|
ratified,
|
|
471
|
+
staleness,
|
|
279
472
|
inspected: report.inspected,
|
|
280
473
|
counts: report.counts,
|
|
281
474
|
couplings: report.entries
|
|
282
|
-
.filter(entry => entry.verdict !==
|
|
475
|
+
.filter(entry => entry.verdict !== PACKAGE_BACKED)
|
|
283
476
|
.map(entry => ({
|
|
284
477
|
key: entry.key,
|
|
285
478
|
workflow: entry.workflow,
|
|
@@ -302,6 +495,7 @@ function toLedger(
|
|
|
302
495
|
export function measure(root: string): {
|
|
303
496
|
readonly report: TwoChannelReport;
|
|
304
497
|
readonly ratified: Readonly<Record<string, string>>;
|
|
498
|
+
readonly staleness: Readonly<Record<string, StalenessClassification>>;
|
|
305
499
|
} {
|
|
306
500
|
const workflowsDir = path.join(root, WORKFLOWS_DIR);
|
|
307
501
|
const inventory = buildDeliveryInventory(root);
|
|
@@ -342,6 +536,7 @@ export function measure(root: string): {
|
|
|
342
536
|
ratified: readRatified(path.join(root, LEDGER_PATH)),
|
|
343
537
|
}),
|
|
344
538
|
ratified: readRatified(path.join(root, LEDGER_PATH)),
|
|
539
|
+
staleness: readStaleness(path.join(root, LEDGER_PATH)),
|
|
345
540
|
};
|
|
346
541
|
}
|
|
347
542
|
|
|
@@ -350,7 +545,10 @@ export function measure(root: string): {
|
|
|
350
545
|
* @param report - The measurement
|
|
351
546
|
* @returns The report text
|
|
352
547
|
*/
|
|
353
|
-
function humanReport(
|
|
548
|
+
function humanReport(
|
|
549
|
+
report: TwoChannelReport,
|
|
550
|
+
staleness: Readonly<Record<string, StalenessClassification>>
|
|
551
|
+
): string {
|
|
354
552
|
const counts = report.inspected;
|
|
355
553
|
const header =
|
|
356
554
|
`two-channel delivery: inspected ${counts.workflows} reusable workflow(s), ` +
|
|
@@ -370,7 +568,12 @@ function humanReport(report: TwoChannelReport): string {
|
|
|
370
568
|
`inherit. Delete it from ${LEDGER_PATH}.`
|
|
371
569
|
)
|
|
372
570
|
.join("");
|
|
373
|
-
|
|
571
|
+
// The coverage line answers the question the registry could not answer at
|
|
572
|
+
// all before this: "how many of our couplings can detect staleness?" — in one
|
|
573
|
+
// line, without opening 24 workflows.
|
|
574
|
+
const coverage = stalenessCoverage(report, staleness);
|
|
575
|
+
const unclassified = stalenessProblems(report, staleness).join("");
|
|
576
|
+
return `${header}\n${verdicts}\n${coverage}${findings}${stale}${unclassified}`;
|
|
374
577
|
}
|
|
375
578
|
|
|
376
579
|
/**
|
|
@@ -384,9 +587,9 @@ export function main(argv: readonly string[]): number {
|
|
|
384
587
|
process.stderr.write(`error: --root does not exist: ${options.root}\n`);
|
|
385
588
|
return EXIT_OPERATIONAL;
|
|
386
589
|
}
|
|
387
|
-
const { report, ratified } = measure(options.root);
|
|
590
|
+
const { report, ratified, staleness } = measure(options.root);
|
|
388
591
|
process.stdout.write(
|
|
389
|
-
`${options.json ? JSON.stringify(report, null, 2) : humanReport(report)}\n`
|
|
592
|
+
`${options.json ? JSON.stringify(report, null, 2) : humanReport(report, staleness)}\n`
|
|
390
593
|
);
|
|
391
594
|
if (!report.measured) {
|
|
392
595
|
process.stderr.write(
|
|
@@ -397,7 +600,7 @@ export function main(argv: readonly string[]): number {
|
|
|
397
600
|
return EXIT_OPERATIONAL;
|
|
398
601
|
}
|
|
399
602
|
const ledgerPath = path.join(options.root, LEDGER_PATH);
|
|
400
|
-
const rendered = `${JSON.stringify(toLedger(report, ratified), null, 2)}\n`;
|
|
603
|
+
const rendered = `${JSON.stringify(toLedger(report, ratified, staleness), null, 2)}\n`;
|
|
401
604
|
const current = existsSync(ledgerPath)
|
|
402
605
|
? readFileSync(ledgerPath, "utf8")
|
|
403
606
|
: "";
|
|
@@ -413,9 +616,14 @@ export function main(argv: readonly string[]): number {
|
|
|
413
616
|
mkdirSync(path.dirname(ledgerPath), { recursive: true });
|
|
414
617
|
writeFileSync(ledgerPath, rendered);
|
|
415
618
|
}
|
|
619
|
+
// The staleness half fails in BOTH modes, exactly as findings do. A gate you
|
|
620
|
+
// can regenerate your way past is not a gate: regenerating records a coupling,
|
|
621
|
+
// it does not classify one, and `toLedger` carries the map through untouched
|
|
622
|
+
// rather than inventing a default for a new entry (#3687).
|
|
416
623
|
const failed =
|
|
417
624
|
report.findings.length > 0 ||
|
|
418
625
|
report.staleRatifications.length > 0 ||
|
|
626
|
+
stalenessProblems(report, staleness).length > 0 ||
|
|
419
627
|
ledgerStale;
|
|
420
628
|
return failed ? 1 : 0;
|
|
421
629
|
}
|
|
@@ -3,6 +3,100 @@
|
|
|
3
3
|
"quality.yml::scripts/check-learnings-budget.ts": "Lisa-source-only branch, and the else arm is the delivery. The step tests for BOTH scripts/check-learnings-budget.ts and src/core/learnings-budget-check.ts, which co-exist only in this repository; every consumer takes the else arm and runs the prover as `bunx @codyswann/lisa@<their own declared version>`, which is the PACKAGE channel and not a caller-tree read at all. A trailing `grep -qE \"learnings budget (passed|saturated)|no learnings file\"` then refuses a run that printed no verdict, so the arm cannot pass vacuously. Ratified because the caller-tree read is unreachable outside this repository, not because the gap is tolerated.",
|
|
4
4
|
"quality.yml::scripts/generate-nightly-e2e-guard-certificate.mjs": "Lisa-source-only branch. Both reusable jobs require `github.repository == 'CodySwannGT/lisa'` before the step can start, and the run body repeats that exact repository guard before checking this artifact path. Ordinary and private host callers therefore skip the step without reading or fetching from their own tree. The scoped branch fetches only the immutable refs needed to behavior-test Lisa's own retained certificate inputs. Ratified because the caller-tree read is unreachable outside this repository, not because an undelivered host dependency is tolerated."
|
|
5
5
|
},
|
|
6
|
+
"staleness": {
|
|
7
|
+
"create-github-issue-on-failure.yml::scripts/nightly-e2e-provider-action.mjs": {
|
|
8
|
+
"detects": "existence-only",
|
|
9
|
+
"reason": "The read files a tracker issue after a nightly failure; it posts no required check and gates no merge. A stale copy files a slightly wrong issue body, which a human reads and corrects, rather than turning a merge green or red from superseded logic. The harm this classification exists to catch is a verdict nobody can see is old, and this coupling produces no verdict."
|
|
10
|
+
},
|
|
11
|
+
"create-jira-issue-on-failure.yml::scripts/nightly-e2e-provider-action.mjs": {
|
|
12
|
+
"detects": "existence-only",
|
|
13
|
+
"reason": "Same provider action as the GitHub arm, same reasoning: it files a tracker issue after a nightly failure, posts no required check, and gates no merge. A stale copy misformats a report a human reads; it cannot produce a merge verdict derived from superseded logic."
|
|
14
|
+
},
|
|
15
|
+
"create-linear-issue-on-failure.yml::scripts/nightly-e2e-provider-action.mjs": {
|
|
16
|
+
"detects": "existence-only",
|
|
17
|
+
"reason": "Same provider action as the GitHub and Jira arms, same reasoning: it files a tracker issue after a nightly failure, posts no required check, and gates no merge. A stale copy degrades a report rather than a verdict."
|
|
18
|
+
},
|
|
19
|
+
"create-sentry-issue-on-failure.yml::scripts/nightly-e2e-provider-action.mjs": {
|
|
20
|
+
"detects": "existence-only",
|
|
21
|
+
"reason": "Same provider action as the other three failure arms, same reasoning: it files an issue after a nightly failure, posts no required check, and gates no merge. A stale copy degrades a report rather than a verdict."
|
|
22
|
+
},
|
|
23
|
+
"maestro-native-e2e.yml::scripts/classify-maestro-failures.mjs": {
|
|
24
|
+
"detects": "existence-only",
|
|
25
|
+
"reason": "Classifies native E2E failures for the run summary so an operator can tell an infrastructure flake from a product regression. It labels a failure that has already been decided elsewhere; it does not decide one. A stale classifier mislabels a red the operator is already looking at, which is visible in the same output."
|
|
26
|
+
},
|
|
27
|
+
"nightly-e2e-health.yml::scripts/check-nightly-e2e-health.mjs": {
|
|
28
|
+
"detects": "version-handshake"
|
|
29
|
+
},
|
|
30
|
+
"nightly-e2e-health.yml::scripts/nightly-e2e-suites.schema.json": {
|
|
31
|
+
"detects": "existence-only",
|
|
32
|
+
"reason": "A JSON schema has no executable surface, so it cannot answer a contract-version call at all — no handshake is constructible here. It is covered transitively rather than left unguarded: the schema and check-nightly-e2e-health.mjs ship on the same typescript/copy-overwrite lane and are read by the same workflow, and that checker DOES handshake, so a tree old enough to carry a stale schema carries a stale checker and the existing handshake fails first."
|
|
33
|
+
},
|
|
34
|
+
"nightly-e2e-report.yml::scripts/check-nightly-e2e-health.mjs": {
|
|
35
|
+
"detects": "version-handshake"
|
|
36
|
+
},
|
|
37
|
+
"nightly-e2e-tracking.yml::scripts/reconcile-nightly-e2e-tracking.mjs": {
|
|
38
|
+
"detects": "existence-only",
|
|
39
|
+
"reason": "Reconciles the nightly tracking issue — opens, refreshes or closes it to match the latest health verdict. It consumes a verdict rather than producing one, so a stale copy mismanages a tracking issue a human reads and cannot make a merge gate green."
|
|
40
|
+
},
|
|
41
|
+
"publish-to-npm.yml::scripts/check-npm-publish-landed.mjs": {
|
|
42
|
+
"detects": "existence-only",
|
|
43
|
+
"reason": "A GENUINE GAP, recorded rather than waved through. It verifies a publish actually landed, so a stale copy can attest to a release that did not happen. It runs post-merge in the release workflow and posts no merge-blocking required context, which is why it is not the first handshake to build — but it is verdict-bearing and belongs at the front of the queue when this classification is next acted on."
|
|
44
|
+
},
|
|
45
|
+
"publish-to-npm.yml::scripts/check-release-package-identity.mjs": {
|
|
46
|
+
"detects": "existence-only",
|
|
47
|
+
"reason": "A GENUINE GAP, recorded rather than waved through, and the sibling of the publish-landed read: it attests that the published package is the one intended, so a stale copy can attest to an identity it no longer checks the same way. Post-merge and not merge-blocking, so it queues behind the merge gates rather than ahead of them."
|
|
48
|
+
},
|
|
49
|
+
"quality-rails.yml::scripts/check-threshold-ratchet.mjs": {
|
|
50
|
+
"detects": "existence-only",
|
|
51
|
+
"reason": "Same prover as the quality.yml read and the same GENUINE GAP, reached from the Rails caller tree. Recorded separately because the two callers can drift apart: a handshake added to one workflow does not reach the other, which is how a fleet ends up half-covered."
|
|
52
|
+
},
|
|
53
|
+
"quality-rails.yml::scripts/lisa-mutation.sh": {
|
|
54
|
+
"detects": "existence-only",
|
|
55
|
+
"reason": "A shell wrapper that shapes a mutation-testing invocation; the verdict comes from Stryker and its configured thresholds, not from this file. A stale wrapper passes older flags, which fails loudly as a runner usage error rather than producing a wrong mutation score."
|
|
56
|
+
},
|
|
57
|
+
"quality-rails.yml::scripts/lisa-scratch-run.sh": {
|
|
58
|
+
"detects": "existence-only",
|
|
59
|
+
"reason": "A shell wrapper that isolates a run's scratch namespace. It carries no verdict-bearing logic at all — it prepares a directory and execs the real command — so there is no superseded judgement for a stale copy to apply."
|
|
60
|
+
},
|
|
61
|
+
"quality-rails.yml::scripts/lisa-work-item.mjs": {
|
|
62
|
+
"detects": "version-handshake"
|
|
63
|
+
},
|
|
64
|
+
"quality.yml::scripts/check-e2e-coverage.mjs": {
|
|
65
|
+
"detects": "existence-only",
|
|
66
|
+
"reason": "Guarded by an existence test and scoped to Expo consumers. It reports route coverage against a declared threshold; a stale copy computes coverage by an older rule and says which rule it used in its own output, so the number it prints is interpretable rather than silently wrong."
|
|
67
|
+
},
|
|
68
|
+
"quality.yml::scripts/check-learnings-budget.ts": {
|
|
69
|
+
"detects": "existence-only",
|
|
70
|
+
"reason": "Already ratified as a Lisa-source-only branch: every consumer takes the else arm and runs the prover from the PACKAGE channel at their own declared version, which is a version pin rather than an unversioned copy. The two-channel skew this field is about does not arise on the path consumers actually take."
|
|
71
|
+
},
|
|
72
|
+
"quality.yml::scripts/check-skipped-required-checks.mjs": {
|
|
73
|
+
"detects": "existence-only",
|
|
74
|
+
"reason": "A GENUINE GAP and the sharpest one in this registry: it is the vacuity and review-evidence prover, so a stale copy posts a review verdict from superseded logic on a merge gate. Its own workflow resolves a second in-repo address when scripts/ has no copy, which bounds the absent case but says nothing about age. Highest-value handshake candidate here."
|
|
75
|
+
},
|
|
76
|
+
"quality.yml::scripts/check-state-classification.mjs": {
|
|
77
|
+
"detects": "existence-only",
|
|
78
|
+
"reason": "Guarded by an existence test and advisory in effect: it classifies demo/fixture state against a declared contract and reports what it found. A stale copy reports against an older contract version, which surfaces as a classification mismatch in its own output rather than as a green merge gate, so the failure is self-announcing."
|
|
79
|
+
},
|
|
80
|
+
"quality.yml::scripts/check-threshold-ratchet.mjs": {
|
|
81
|
+
"detects": "existence-only",
|
|
82
|
+
"reason": "A GENUINE GAP. The ratchet decides whether a threshold moved the wrong way, so a stale copy can pass a loosened threshold it does not yet know how to refuse. It is declared optional at push in this repository's own gate set, which lowers the blast radius but does not remove it."
|
|
83
|
+
},
|
|
84
|
+
"quality.yml::scripts/check-verification-coverage.mjs": {
|
|
85
|
+
"detects": "existence-only",
|
|
86
|
+
"reason": "Bespoke and conditional — it requires a verification-spec delta on feat/fix changes. A stale copy applies an older definition of what counts as a spec delta, which produces a visible disagreement with the spec in the pull request rather than a silent pass."
|
|
87
|
+
},
|
|
88
|
+
"quality.yml::scripts/generate-nightly-e2e-guard-certificate.mjs": {
|
|
89
|
+
"detects": "existence-only",
|
|
90
|
+
"reason": "Already ratified as undelivered: no lane ships this path, so no consumer tree carries a copy that could be stale. The staleness question is vacuous here for the same reason the delivery question was, and it is recorded rather than left blank so the entry is not mistaken for one nobody examined."
|
|
91
|
+
},
|
|
92
|
+
"quality.yml::scripts/lisa-floor-collisions.mjs": {
|
|
93
|
+
"detects": "existence-only",
|
|
94
|
+
"reason": "Reports collisions between declared security floors. A stale copy knows a smaller set of floors, so it under-reports rather than mis-decides, and the floors it does know it still checks correctly. Under-reporting is the benign direction for a collision detector."
|
|
95
|
+
},
|
|
96
|
+
"quality.yml::scripts/lisa-work-item.mjs": {
|
|
97
|
+
"detects": "version-handshake"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
6
100
|
"inspected": {
|
|
7
101
|
"workflows": 23,
|
|
8
102
|
"steps": 719,
|