ccqa 1.59.0 → 1.60.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/bin/ccqa.mjs +61 -43
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -14836,6 +14836,12 @@ function tail(output) {
|
|
|
14836
14836
|
const trimmed = output.trim();
|
|
14837
14837
|
return trimmed.length <= OUTPUT_TAIL_CHARS ? trimmed : trimmed.slice(-OUTPUT_TAIL_CHARS);
|
|
14838
14838
|
}
|
|
14839
|
+
const LOGGED_TAIL_LINES = 40;
|
|
14840
|
+
/** The end of an output tail that is already scrubbed, one log line per line. */
|
|
14841
|
+
function logOutputTail(scrubbed, write) {
|
|
14842
|
+
if (scrubbed === "") return;
|
|
14843
|
+
for (const line of scrubbed.split("\n").slice(-LOGGED_TAIL_LINES)) write(` | ${line}`);
|
|
14844
|
+
}
|
|
14839
14845
|
/**
|
|
14840
14846
|
* Where new support files may be created.
|
|
14841
14847
|
*
|
|
@@ -15027,16 +15033,19 @@ async function runVerificationLoop(p, state) {
|
|
|
15027
15033
|
failing = run;
|
|
15028
15034
|
}
|
|
15029
15035
|
}
|
|
15036
|
+
const outputTail = scrubEnvValues(tail(failing.output), outputScrub);
|
|
15030
15037
|
if (attempt >= maxRetries) {
|
|
15031
15038
|
warn(`verification still failing after ${maxRetries} fix attempt(s) — generated files kept`);
|
|
15039
|
+
logOutputTail(outputTail, warn);
|
|
15032
15040
|
await clearCaseRun(p.ctx.ref);
|
|
15033
15041
|
return finish(false);
|
|
15034
15042
|
}
|
|
15035
15043
|
fix(`verification failed (exit ${failing.exitCode}) — requesting a fix (${attempt + 1}/${maxRetries})`);
|
|
15044
|
+
logOutputTail(outputTail, fix);
|
|
15036
15045
|
const fixPrompt = buildLlmFixPrompt({
|
|
15037
15046
|
targetId: p.target,
|
|
15038
15047
|
command: failing.command,
|
|
15039
|
-
outputTail
|
|
15048
|
+
outputTail,
|
|
15040
15049
|
files: [...state.entries()].map(([path, f]) => ({
|
|
15041
15050
|
path,
|
|
15042
15051
|
contents: f.contents,
|
|
@@ -29038,12 +29047,12 @@ function releaseAll(locks, holder) {
|
|
|
29038
29047
|
}
|
|
29039
29048
|
//#endregion
|
|
29040
29049
|
//#region src/hub/core/audit-need.ts
|
|
29041
|
-
function auditNeed(drift,
|
|
29042
|
-
const entry = drift.specs[key];
|
|
29050
|
+
function auditNeed(drift, spec, range, deployTimes) {
|
|
29051
|
+
const entry = drift.specs[spec.key];
|
|
29043
29052
|
if (!entry) return { because: "neverAudited" };
|
|
29044
|
-
const since = freshness(entry.gitHead, key, range);
|
|
29053
|
+
const since = freshness(entry.gitHead, spec.key, range);
|
|
29045
29054
|
switch (since.kind) {
|
|
29046
|
-
case "current": return { because: "current" };
|
|
29055
|
+
case "current": return specMovedSince(spec.changedAt, entry.gitHead, entry.at, deployTimes) ? { because: "deployReached" } : { because: "current" };
|
|
29047
29056
|
case "touched": return { because: "deployReached" };
|
|
29048
29057
|
case "unanswerable": return {
|
|
29049
29058
|
because: "cannotTell",
|
|
@@ -29052,13 +29061,52 @@ function auditNeed(drift, key, range) {
|
|
|
29052
29061
|
default: throw new Error(`unhandled freshness: ${String(since)}`);
|
|
29053
29062
|
}
|
|
29054
29063
|
}
|
|
29064
|
+
/**
|
|
29065
|
+
* When each deployed commit reached the environment. A baseline read at that
|
|
29066
|
+
* commit cannot have seen anything committed after it was deployed.
|
|
29067
|
+
*/
|
|
29068
|
+
function deployedAt(log) {
|
|
29069
|
+
const out = /* @__PURE__ */ new Map();
|
|
29070
|
+
for (const entry of log.entries) out.set(entry.sha, entry.at);
|
|
29071
|
+
return out;
|
|
29072
|
+
}
|
|
29073
|
+
/**
|
|
29074
|
+
* Has the spec moved since the baseline was taken?
|
|
29075
|
+
*
|
|
29076
|
+
* A verdict is a claim about a (spec, product) pair, so either side moving
|
|
29077
|
+
* invalidates it. The deploy log covers the product side; this covers the
|
|
29078
|
+
* other one. Without it a spec repaired and merged stays `needsRepair` until
|
|
29079
|
+
* a deploy happens to reach it, and a run that passed against the previous
|
|
29080
|
+
* spec keeps answering `verified` for the new one.
|
|
29081
|
+
*
|
|
29082
|
+
* Compared against when the baseline commit was *deployed*, not when the audit
|
|
29083
|
+
* or run happened: the tree read at that commit predates its deployment, so an
|
|
29084
|
+
* edit after it is definitely not in it. Falls back to the baseline's own
|
|
29085
|
+
* timestamp when the log cannot place the commit.
|
|
29086
|
+
*
|
|
29087
|
+
* One-directional. A later edit time proves the baseline is stale; an earlier
|
|
29088
|
+
* one proves nothing, and this answers false rather than guessing.
|
|
29089
|
+
*/
|
|
29090
|
+
function specMovedSince(changedAt, baselineSha, baselineAt, deployTimes) {
|
|
29091
|
+
if (!changedAt) return null;
|
|
29092
|
+
return isLater(changedAt, baselineSha && deployTimes.get(baselineSha) || baselineAt) ? changedAt : null;
|
|
29093
|
+
}
|
|
29094
|
+
/**
|
|
29095
|
+
* Compared as instants, never as strings: a spec's edit time carries its
|
|
29096
|
+
* committer's offset, and `+09:00` sorts after `Z` for an earlier moment.
|
|
29097
|
+
* An unparseable side answers false, the same as a missing one.
|
|
29098
|
+
*/
|
|
29099
|
+
function isLater(a, b) {
|
|
29100
|
+
return Date.parse(a) > Date.parse(b);
|
|
29101
|
+
}
|
|
29055
29102
|
/** True for every answer but `current`. */
|
|
29056
29103
|
function needsAudit(need) {
|
|
29057
29104
|
return need.because !== "current";
|
|
29058
29105
|
}
|
|
29059
29106
|
function computeAuditNeed(input) {
|
|
29060
29107
|
const range = buildRange(input.log, input.touchIndex);
|
|
29061
|
-
|
|
29108
|
+
const deployTimes = deployedAt(input.log);
|
|
29109
|
+
return Object.fromEntries(input.specs.map((spec) => [spec.key, heldBy(input.locks, spec.key, input.now) ? { because: "held" } : auditNeed(input.drift, spec, range, deployTimes)]));
|
|
29062
29110
|
}
|
|
29063
29111
|
//#endregion
|
|
29064
29112
|
//#region src/drift/audit-selection.ts
|
|
@@ -29620,7 +29668,8 @@ async function readChunk(cwd, paths, byRelPath, out) {
|
|
|
29620
29668
|
let when = "";
|
|
29621
29669
|
for (const line of stdout.split("\n")) {
|
|
29622
29670
|
if (line.startsWith("\0")) {
|
|
29623
|
-
|
|
29671
|
+
const ms = Date.parse(line.slice(1).trim());
|
|
29672
|
+
when = Number.isNaN(ms) ? "" : new Date(ms).toISOString();
|
|
29624
29673
|
continue;
|
|
29625
29674
|
}
|
|
29626
29675
|
const abs = byRelPath.get(line.trim());
|
|
@@ -32244,36 +32293,6 @@ function createGetCoverageEdgesHandler(config) {
|
|
|
32244
32293
|
}
|
|
32245
32294
|
//#endregion
|
|
32246
32295
|
//#region src/hub/core/rerun.ts
|
|
32247
|
-
/**
|
|
32248
|
-
* When each deployed commit reached the environment. A baseline read at that
|
|
32249
|
-
* commit cannot have seen anything committed after it was deployed.
|
|
32250
|
-
*/
|
|
32251
|
-
function deployedAt(log) {
|
|
32252
|
-
const out = /* @__PURE__ */ new Map();
|
|
32253
|
-
for (const entry of log.entries) out.set(entry.sha, entry.at);
|
|
32254
|
-
return out;
|
|
32255
|
-
}
|
|
32256
|
-
/**
|
|
32257
|
-
* Has the spec moved since the baseline was taken?
|
|
32258
|
-
*
|
|
32259
|
-
* A verdict is a claim about a (spec, product) pair, so either side moving
|
|
32260
|
-
* invalidates it. The deploy log covers the product side; this covers the
|
|
32261
|
-
* other one. Without it a spec repaired and merged stays `needsRepair` until
|
|
32262
|
-
* a deploy happens to reach it, and a run that passed against the previous
|
|
32263
|
-
* spec keeps answering `verified` for the new one.
|
|
32264
|
-
*
|
|
32265
|
-
* Compared against when the baseline commit was *deployed*, not when the audit
|
|
32266
|
-
* or run happened: the tree read at that commit predates its deployment, so an
|
|
32267
|
-
* edit after it is definitely not in it. Falls back to the baseline's own
|
|
32268
|
-
* timestamp when the log cannot place the commit.
|
|
32269
|
-
*
|
|
32270
|
-
* One-directional. A later edit time proves the baseline is stale; an earlier
|
|
32271
|
-
* one proves nothing, and this answers false rather than guessing.
|
|
32272
|
-
*/
|
|
32273
|
-
function specMovedSince(changedAt, baselineSha, baselineAt, deployTimes) {
|
|
32274
|
-
if (!changedAt) return null;
|
|
32275
|
-
return changedAt > (baselineSha && deployTimes.get(baselineSha) || baselineAt) ? changedAt : null;
|
|
32276
|
-
}
|
|
32277
32296
|
function computeRerun(input) {
|
|
32278
32297
|
const { specs, ledger, log, touchIndex, drift, locks, attestations, dismissals, now } = input;
|
|
32279
32298
|
const range = buildRange(log, touchIndex);
|
|
@@ -32285,7 +32304,7 @@ function computeRerun(input) {
|
|
|
32285
32304
|
lastGreen: ledger.green[spec.key] ?? null,
|
|
32286
32305
|
lastRed: ledger.red[spec.key] ?? null
|
|
32287
32306
|
};
|
|
32288
|
-
let audit = auditState(drift, spec
|
|
32307
|
+
let audit = auditState(drift, spec, range, deployTimes);
|
|
32289
32308
|
let execution = executionState(coords, (sha) => freshness(sha, spec.key, range));
|
|
32290
32309
|
const driftEntry = drift.specs[spec.key];
|
|
32291
32310
|
const dismissal = dismissals.specs[spec.key];
|
|
@@ -32293,7 +32312,6 @@ function computeRerun(input) {
|
|
|
32293
32312
|
if (dismissed) audit = { audit: "clean" };
|
|
32294
32313
|
const auditMoved = specMovedSince(spec.changedAt, driftEntry?.gitHead ?? null, driftEntry?.at ?? "", deployTimes);
|
|
32295
32314
|
const runMoved = specMovedSince(spec.changedAt, coords.lastRun?.deployedSha ?? null, coords.lastRun?.at ?? "", deployTimes);
|
|
32296
|
-
if (auditMoved && audit.audit !== "due") audit = { audit: "due" };
|
|
32297
32315
|
if (runMoved && execution.execution === "passed") execution = { execution: "stale" };
|
|
32298
32316
|
const manualState = readAttestation(attestations.specs[spec.key], spec, coords, range, log, deployTimes);
|
|
32299
32317
|
const lapsedOverRed = manualState?.kind === "lapsed" && manualState.because !== "newerRed";
|
|
@@ -32338,7 +32356,7 @@ function computeRerun(input) {
|
|
|
32338
32356
|
*/
|
|
32339
32357
|
function readAttestation(attest, spec, { lastRun, lastRed }, range, log, deployTimes) {
|
|
32340
32358
|
if (!attest) return null;
|
|
32341
|
-
const newerRed = lastRed !== null && lastRed.at
|
|
32359
|
+
const newerRed = lastRed !== null && isLater(lastRed.at, attest.at);
|
|
32342
32360
|
if (newerRed && lastRed.runId === lastRun?.runId) return {
|
|
32343
32361
|
kind: "lapsed",
|
|
32344
32362
|
attest,
|
|
@@ -32384,8 +32402,8 @@ function readAttestation(attest, spec, { lastRun, lastRed }, range, log, deployT
|
|
|
32384
32402
|
* rule out is treated as having landed (ADR-0014). The hole is kept as an
|
|
32385
32403
|
* annotation so the answer stays explicable.
|
|
32386
32404
|
*/
|
|
32387
|
-
function auditState(drift,
|
|
32388
|
-
const need = auditNeed(drift,
|
|
32405
|
+
function auditState(drift, spec, range, deployTimes) {
|
|
32406
|
+
const need = auditNeed(drift, spec, range, deployTimes);
|
|
32389
32407
|
switch (need.because) {
|
|
32390
32408
|
case "neverAudited":
|
|
32391
32409
|
case "deployReached": return { audit: "due" };
|
|
@@ -32394,7 +32412,7 @@ function auditState(drift, key, range) {
|
|
|
32394
32412
|
...need.reason ? { auditAssumedReached: need.reason } : {}
|
|
32395
32413
|
};
|
|
32396
32414
|
case "current": {
|
|
32397
|
-
const label = drift.specs[key].label;
|
|
32415
|
+
const label = drift.specs[spec.key].label;
|
|
32398
32416
|
if (label === null) return { audit: "clean" };
|
|
32399
32417
|
if (label === "UNKNOWN") return { audit: "undecided" };
|
|
32400
32418
|
if (label === "PRODUCT_BUG" || label === "ENVIRONMENT") return { audit: "clean" };
|
package/dist/package.json
CHANGED