@deftai/directive-core 0.99.0 → 0.101.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/authz/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +241 -30
- package/dist/authz/decompose-apply.d.ts +81 -0
- package/dist/authz/decompose-apply.js +377 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/check/cached-orchestrator.d.ts +26 -5
- package/dist/check/cached-orchestrator.js +228 -6
- package/dist/check/gate-lists.d.ts +10 -4
- package/dist/check/gate-lists.js +19 -5
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +2 -1
- package/dist/check/named-cause.d.ts +45 -0
- package/dist/check/named-cause.js +121 -0
- package/dist/check/orchestrator.js +6 -1
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/help.d.ts +5 -0
- package/dist/doctor/help.js +31 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +130 -79
- package/dist/doctor/session-coda.d.ts +82 -0
- package/dist/doctor/session-coda.js +151 -0
- package/dist/doctor/types.d.ts +16 -0
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/init-deposit/gitignore.js +2 -0
- package/dist/intake/issue-ingest.js +36 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/policy/ac-pass-banking.d.ts +58 -0
- package/dist/policy/ac-pass-banking.js +150 -0
- package/dist/policy/ceremony-dial.d.ts +96 -11
- package/dist/policy/ceremony-dial.js +250 -23
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +16 -1
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/preflight-cache/evaluate.d.ts +3 -0
- package/dist/preflight-cache/evaluate.js +3 -0
- package/dist/product-first-done-gate/acceptance.d.ts +25 -0
- package/dist/product-first-done-gate/acceptance.js +247 -0
- package/dist/product-first-done-gate/check-mode.d.ts +45 -0
- package/dist/product-first-done-gate/check-mode.js +148 -0
- package/dist/product-first-done-gate/evaluate.d.ts +50 -0
- package/dist/product-first-done-gate/evaluate.js +255 -0
- package/dist/product-first-done-gate/index.d.ts +11 -0
- package/dist/product-first-done-gate/index.js +11 -0
- package/dist/product-first-done-gate/types.d.ts +64 -0
- package/dist/product-first-done-gate/types.js +61 -0
- package/dist/render/framework-commands.js +4 -0
- package/dist/run-summary/emit.d.ts +56 -0
- package/dist/run-summary/emit.js +171 -0
- package/dist/run-summary/index.d.ts +4 -0
- package/dist/run-summary/index.js +4 -0
- package/dist/run-summary/path.d.ts +25 -0
- package/dist/run-summary/path.js +78 -0
- package/dist/run-summary/types.d.ts +76 -0
- package/dist/run-summary/types.js +21 -0
- package/dist/scope/acceptance-evidence.d.ts +110 -0
- package/dist/scope/acceptance-evidence.js +422 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.d.ts +5 -0
- package/dist/scope/decompose.js +134 -11
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.js +3 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +61 -2
- package/dist/session/ac-pass-banking.d.ts +221 -0
- package/dist/session/ac-pass-banking.js +761 -0
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/deposit-sha.d.ts +49 -0
- package/dist/session/deposit-sha.js +121 -0
- package/dist/session/effort-budget.d.ts +136 -0
- package/dist/session/effort-budget.js +352 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +7 -0
- package/dist/session/orientation-compression.d.ts +127 -0
- package/dist/session/orientation-compression.js +425 -0
- package/dist/session/orientation-state.d.ts +29 -0
- package/dist/session/orientation-state.js +85 -0
- package/dist/session/session-start.d.ts +31 -0
- package/dist/session/session-start.js +228 -2
- package/dist/session/toolchain-preflight.d.ts +64 -0
- package/dist/session/toolchain-preflight.js +151 -0
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +8 -2
- package/dist/triage/bootstrap/index.js +15 -3
- package/dist/triage/classify/index.d.ts +1 -0
- package/dist/triage/classify/index.js +2 -0
- package/dist/triage/classify/label-mirror.js +16 -0
- package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
- package/dist/triage/classify/mirror-discovery-tip.js +234 -0
- package/dist/triage/reconcile/reconcile.js +28 -6
- package/dist/triage/summary/index.js +12 -4
- package/dist/triage/welcome/default-mode.js +16 -0
- package/dist/ts-check-lane/run-lane.js +21 -4
- package/dist/vbrief-validate/conformance.js +7 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +15 -3
package/dist/authz/store.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { existsSync, readdirSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
6
6
|
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
-
import { containedWrite } from "../fs/contained-write.js";
|
|
7
|
+
import { ContainedWriteError, containedWrite } from "../fs/contained-write.js";
|
|
8
8
|
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
9
9
|
import { isHumanOrigin } from "./origin.js";
|
|
10
10
|
import { authzAuditPath, authzGrantPath, authzGrantsDir, authzStatePath } from "./paths.js";
|
|
@@ -105,6 +105,13 @@ function parseScope(raw) {
|
|
|
105
105
|
storyIds: readStringArray(rec.storyIds ?? rec.story_ids),
|
|
106
106
|
issueIds: readNumberArray(rec.issueIds ?? rec.issue_ids),
|
|
107
107
|
cohortId: readString(rec, "cohortId") ?? readString(rec, "cohort_id"),
|
|
108
|
+
// #3239 structural decompose apply bindings (snake_case accepted on load).
|
|
109
|
+
contentDigest: readString(rec, "contentDigest") ??
|
|
110
|
+
readString(rec, "content_digest") ??
|
|
111
|
+
readString(rec, "draftDigest") ??
|
|
112
|
+
readString(rec, "draft_digest"),
|
|
113
|
+
parentPath: readString(rec, "parentPath") ?? readString(rec, "parent_path"),
|
|
114
|
+
targetPath: readString(rec, "targetPath") ?? readString(rec, "target_path"),
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
117
|
function parseSemantics(raw) {
|
|
@@ -233,6 +240,217 @@ export function markGrantUsed(projectRoot, grantId, now = new Date()) {
|
|
|
233
240
|
saveGrant(projectRoot, used);
|
|
234
241
|
return used;
|
|
235
242
|
}
|
|
243
|
+
function isProcessAlive(pid) {
|
|
244
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
245
|
+
return false;
|
|
246
|
+
try {
|
|
247
|
+
process.kill(pid, 0);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
const code = err.code;
|
|
252
|
+
// ESRCH → dead. EPERM → exists but unsignalable — treat as alive (never reclaim).
|
|
253
|
+
if (code === "EPERM")
|
|
254
|
+
return true;
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Whether a claim lock may be reclaimed after a crashed holder (#3239).
|
|
260
|
+
* Live owner PIDs are never reclaimed (no mtime-only steal of a live critical section).
|
|
261
|
+
* Corrupt / unreadable records are reclaimable; PID-reuse residual needs manual delete.
|
|
262
|
+
*/
|
|
263
|
+
export function isGrantClaimLockReclaimable(rec) {
|
|
264
|
+
if (rec === null)
|
|
265
|
+
return true;
|
|
266
|
+
return !isProcessAlive(rec.pid);
|
|
267
|
+
}
|
|
268
|
+
function readGrantClaimLockRecord(lockPath) {
|
|
269
|
+
try {
|
|
270
|
+
const raw = readFileSync(lockPath, "utf8").trim();
|
|
271
|
+
// JSON form (current). Legacy: "pid\niso\n" lines from earlier #3239 revisions.
|
|
272
|
+
if (raw.startsWith("{")) {
|
|
273
|
+
const parsed = JSON.parse(raw);
|
|
274
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
275
|
+
return null;
|
|
276
|
+
const o = parsed;
|
|
277
|
+
const pid = typeof o.pid === "number" ? o.pid : Number(o.pid);
|
|
278
|
+
const startedAt = typeof o.startedAt === "string" ? o.startedAt : "";
|
|
279
|
+
const token = typeof o.token === "string" ? o.token : "";
|
|
280
|
+
if (!Number.isFinite(pid) || token.length === 0)
|
|
281
|
+
return null;
|
|
282
|
+
return { pid, startedAt, token };
|
|
283
|
+
}
|
|
284
|
+
const lines = raw.split(/\r?\n/);
|
|
285
|
+
const pid = Number(lines[0]);
|
|
286
|
+
if (!Number.isFinite(pid))
|
|
287
|
+
return null;
|
|
288
|
+
return {
|
|
289
|
+
pid,
|
|
290
|
+
startedAt: typeof lines[1] === "string" ? lines[1] : "",
|
|
291
|
+
token: "legacy",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Claim a single-use grant for structural apply (#3239).
|
|
300
|
+
*
|
|
301
|
+
* Concurrent-safe + failure-safe order when `apply` is provided:
|
|
302
|
+
* exclusive lock → re-load → revalidate → mark single-use usedAt → apply (writes)
|
|
303
|
+
* → on apply throw: rollback usedAt + release lock (grant reusable for retry).
|
|
304
|
+
* Multi-use grants run apply (if any) without mutating usedAt.
|
|
305
|
+
* Without `apply`, single-use is marked under the lock (claim-only / test path).
|
|
306
|
+
*
|
|
307
|
+
* Crash after mark + before rollback: grant stays spent (no double-apply); operator remints.
|
|
308
|
+
* Dead-PID / corrupt locks reclaim via **rename-away** of the old lock path (only one
|
|
309
|
+
* renamer wins) then exclusive create — never blind rmSync of a live winner's lock.
|
|
310
|
+
* Live PIDs are never reclaimed. PID-reuse residual: operator deletes the lock file.
|
|
311
|
+
*/
|
|
312
|
+
export function claimSingleUseGrantForApply(projectRoot, grantId, options = {}) {
|
|
313
|
+
// Back-compat: second arg was `now: Date` in the first #3239 revision.
|
|
314
|
+
const opts = options instanceof Date ? { now: options } : options;
|
|
315
|
+
const now = opts.now ?? new Date();
|
|
316
|
+
const usedAtIso = utcIso(now);
|
|
317
|
+
const safe = grantId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
318
|
+
const root = resolve(projectRoot);
|
|
319
|
+
const lockRel = join(".deft", "authz", "locks", `${safe}.lock`);
|
|
320
|
+
const lockPath = join(root, lockRel);
|
|
321
|
+
const lockToken = randomBytes(8).toString("hex");
|
|
322
|
+
const lockBody = `${JSON.stringify({
|
|
323
|
+
pid: process.pid,
|
|
324
|
+
startedAt: usedAtIso,
|
|
325
|
+
token: lockToken,
|
|
326
|
+
})}\n`;
|
|
327
|
+
const tryCreateLock = () => {
|
|
328
|
+
try {
|
|
329
|
+
containedWrite({
|
|
330
|
+
root,
|
|
331
|
+
target: lockPath,
|
|
332
|
+
data: lockBody,
|
|
333
|
+
mode: "create",
|
|
334
|
+
});
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
if (err instanceof ContainedWriteError && err.code === "CONTAINED_WRITE_EXISTS") {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
// Other containment/IO failures fail closed as reservation denial.
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
/** True when the on-disk lock still carries our token (not stolen mid-section). */
|
|
346
|
+
const stillOwnLock = () => {
|
|
347
|
+
const rec = readGrantClaimLockRecord(lockPath);
|
|
348
|
+
return rec !== null && rec.token === lockToken && rec.pid === process.pid;
|
|
349
|
+
};
|
|
350
|
+
let locked = tryCreateLock();
|
|
351
|
+
if (!locked) {
|
|
352
|
+
// Dead-PID / corrupt reclaim: rename the old lock aside (atomic contention) then create.
|
|
353
|
+
// Blind rmSync is forbidden — a second reclaimer must not delete a winner's new lock.
|
|
354
|
+
const existing = readGrantClaimLockRecord(lockPath);
|
|
355
|
+
if (isGrantClaimLockReclaimable(existing)) {
|
|
356
|
+
const side = `${lockPath}.reclaim.${randomBytes(6).toString("hex")}`;
|
|
357
|
+
try {
|
|
358
|
+
renameSync(lockPath, side);
|
|
359
|
+
try {
|
|
360
|
+
rmSync(side, { force: true });
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
/* best-effort side cleanup */
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// Lost rename race or lock already gone — fall through to exclusive create.
|
|
368
|
+
}
|
|
369
|
+
locked = tryCreateLock();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (!locked) {
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
reason: `Directive denied scope:decompose apply: grant ${grantId} is already reserved ` +
|
|
376
|
+
"or spent by a concurrent apply. Human action required: remint if the prior apply failed " +
|
|
377
|
+
"(or remove a leftover `.deft/authz/locks/<id>.lock` after a dead-holder crash if reclaim fails).",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
let markedUsedAt = null;
|
|
381
|
+
try {
|
|
382
|
+
const grant = loadGrant(projectRoot, grantId);
|
|
383
|
+
if (grant === null) {
|
|
384
|
+
return {
|
|
385
|
+
ok: false,
|
|
386
|
+
reason: `Directive denied scope:decompose apply: grant ${grantId} missing.`,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (opts.revalidate !== undefined) {
|
|
390
|
+
const check = opts.revalidate(grant);
|
|
391
|
+
if (!check.ok) {
|
|
392
|
+
return { ok: false, reason: check.reason };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (!grant.semantics.singleUse) {
|
|
396
|
+
if (opts.apply !== undefined) {
|
|
397
|
+
opts.apply(grant);
|
|
398
|
+
}
|
|
399
|
+
return { ok: true, grant };
|
|
400
|
+
}
|
|
401
|
+
if (grant.semantics.usedAt !== null) {
|
|
402
|
+
return {
|
|
403
|
+
ok: false,
|
|
404
|
+
reason: `Directive denied scope:decompose apply: single-use grant ${grantId} already spent at ` +
|
|
405
|
+
`${grant.semantics.usedAt}.`,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
// Spend under lock before protected writes so a post-write mark failure cannot
|
|
409
|
+
// leave completed mutations behind an unspent single-use grant (#3239 Greptile).
|
|
410
|
+
// Apply throw rolls usedAt back while the lock is still held (retry-safe).
|
|
411
|
+
const used = {
|
|
412
|
+
...grant,
|
|
413
|
+
semantics: {
|
|
414
|
+
...grant.semantics,
|
|
415
|
+
usedAt: usedAtIso,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
saveGrant(projectRoot, used);
|
|
419
|
+
markedUsedAt = usedAtIso;
|
|
420
|
+
if (opts.apply !== undefined) {
|
|
421
|
+
try {
|
|
422
|
+
opts.apply(used);
|
|
423
|
+
}
|
|
424
|
+
catch (applyErr) {
|
|
425
|
+
// Rollback spend so a failed multi-file apply does not strand the approval.
|
|
426
|
+
const current = loadGrant(projectRoot, grantId);
|
|
427
|
+
if (current?.semantics.singleUse && current.semantics.usedAt === markedUsedAt) {
|
|
428
|
+
const restored = {
|
|
429
|
+
...current,
|
|
430
|
+
semantics: { ...current.semantics, usedAt: null },
|
|
431
|
+
};
|
|
432
|
+
saveGrant(projectRoot, restored);
|
|
433
|
+
markedUsedAt = null;
|
|
434
|
+
}
|
|
435
|
+
throw applyErr;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (!stillOwnLock()) {
|
|
439
|
+
return {
|
|
440
|
+
ok: false,
|
|
441
|
+
reason: `Directive denied scope:decompose apply: grant ${grantId} lock ownership lost mid-claim. ` +
|
|
442
|
+
"Human action required: inspect partial outputs and remint if needed.",
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
return { ok: true, grant: used };
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
// Only remove the lock when we still own it — never delete a successor's claim.
|
|
449
|
+
if (stillOwnLock()) {
|
|
450
|
+
rmSync(lockPath, { force: true });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
236
454
|
export function saveAuthzState(projectRoot, state) {
|
|
237
455
|
writeJsonContained(projectRoot, authzStatePath(projectRoot), state);
|
|
238
456
|
}
|
package/dist/authz/types.d.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* agent authors must not independently satisfy implementation-approval gates.
|
|
6
6
|
* Local-file forgery by a credential-compromised agent remains #983-class OOS.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
9
|
-
export declare const
|
|
8
|
+
/** Structural apply op for scope:decompose (#3239 / epic #3237 Q2). Always gated. */
|
|
9
|
+
export declare const SCOPE_DECOMPOSE_APPLY_STRUCTURAL: "scope.decompose.apply.structural";
|
|
10
|
+
/** Operation classes bound on a grant (Wave 1 structural binding + Wave 4 closed verbs + #3239). */
|
|
11
|
+
export declare const AUTHZ_OPERATIONS: readonly ["edit", "push", "pr", "merge", "settings", "deployment", "issue_mutation", "release-cut", "release-publish", "release-rollback", "scope.decompose.apply.structural"];
|
|
10
12
|
export type AuthzOperation = (typeof AUTHZ_OPERATIONS)[number];
|
|
11
13
|
/**
|
|
12
14
|
* Human-origin kinds accepted as approval provenance.
|
|
@@ -41,6 +43,21 @@ export interface GrantScope {
|
|
|
41
43
|
* for product mutations; approving one cohort does not clear UAT.
|
|
42
44
|
*/
|
|
43
45
|
readonly cohortId: string | null;
|
|
46
|
+
/**
|
|
47
|
+
* SHA-256 hex of exact bound artifact bytes (decompose draft for #3239).
|
|
48
|
+
* Unset/null = unbound (cannot satisfy structural decompose apply).
|
|
49
|
+
*/
|
|
50
|
+
readonly contentDigest?: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Project-relative parent artifact path for structural decompose apply (#3239).
|
|
53
|
+
* Unset/null = unbound.
|
|
54
|
+
*/
|
|
55
|
+
readonly parentPath?: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Project-relative draft/target path for structural decompose apply (#3239).
|
|
58
|
+
* Unset/null = unbound.
|
|
59
|
+
*/
|
|
60
|
+
readonly targetPath?: string | null;
|
|
44
61
|
}
|
|
45
62
|
export interface GrantSemantics {
|
|
46
63
|
readonly expiresAt: string | null;
|
package/dist/authz/types.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* agent authors must not independently satisfy implementation-approval gates.
|
|
6
6
|
* Local-file forgery by a credential-compromised agent remains #983-class OOS.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
8
|
+
/** Structural apply op for scope:decompose (#3239 / epic #3237 Q2). Always gated. */
|
|
9
|
+
export const SCOPE_DECOMPOSE_APPLY_STRUCTURAL = "scope.decompose.apply.structural";
|
|
10
|
+
/** Operation classes bound on a grant (Wave 1 structural binding + Wave 4 closed verbs + #3239). */
|
|
9
11
|
export const AUTHZ_OPERATIONS = [
|
|
10
12
|
"edit",
|
|
11
13
|
"push",
|
|
@@ -18,6 +20,8 @@ export const AUTHZ_OPERATIONS = [
|
|
|
18
20
|
"release-cut",
|
|
19
21
|
"release-publish",
|
|
20
22
|
"release-rollback",
|
|
23
|
+
/** Human-origin exact-draft-digest gate for scope:decompose apply (#3239). */
|
|
24
|
+
SCOPE_DECOMPOSE_APPLY_STRUCTURAL,
|
|
21
25
|
];
|
|
22
26
|
/**
|
|
23
27
|
* Human-origin kinds accepted as approval provenance.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TaskRunResult } from "../cache/task-cache/types.js";
|
|
2
|
+
import { type ToolchainPreflightResult } from "../session/toolchain-preflight.js";
|
|
2
3
|
import { type CheckOrchestratorSeams } from "./context.js";
|
|
3
4
|
export interface CachedCheckOptions extends CheckOrchestratorSeams {
|
|
4
5
|
readonly onGateStart?: (gateId: string) => void;
|
|
@@ -6,16 +7,36 @@ export interface CachedCheckOptions extends CheckOrchestratorSeams {
|
|
|
6
7
|
readonly gateSpawnFn?: (gateId: string, taskBin: string, taskArgs: string[], opts: {
|
|
7
8
|
cwd: string;
|
|
8
9
|
env?: NodeJS.ProcessEnv;
|
|
9
|
-
}) => Pick<TaskRunResult, "exitCode" | "stdout" | "stderr"
|
|
10
|
+
}) => Pick<TaskRunResult, "exitCode" | "stdout" | "stderr"> & {
|
|
11
|
+
spawnError?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* #3282: inject preflight (tests). When omitted, runs live toolchain preflight.
|
|
15
|
+
* Pass `null` to skip preflight entirely (legacy test paths).
|
|
16
|
+
*/
|
|
17
|
+
readonly preflight?: ToolchainPreflightResult | null;
|
|
18
|
+
/** #3282: session id for run-summary lines (default: new UUID). */
|
|
19
|
+
readonly sessionId?: string;
|
|
20
|
+
/** #3282: disable run-summary emission (tests). */
|
|
21
|
+
readonly emitRunSummary?: boolean;
|
|
10
22
|
}
|
|
11
23
|
/**
|
|
12
24
|
* Run check gates sequentially with content-hash caching (#1713).
|
|
13
25
|
* Falls back to fail-open execution for undeclared / non-cacheable gates.
|
|
14
26
|
*
|
|
15
|
-
* Gate order
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
27
|
+
* Gate order (#3284 / #3188):
|
|
28
|
+
* 1. Product AC (`verify:ac`) first — fail-fast; never skippable when commands exist
|
|
29
|
+
* 2. Hygiene preflight (may be advisory under pressure / degraded)
|
|
30
|
+
* 3. Suite last (`ts:check-lane`)
|
|
31
|
+
*
|
|
32
|
+
* Modes (env / ceremony dial / hard budget — see resolveProductFirstCheckMode):
|
|
33
|
+
* - full: AC hard → hygiene hard → suite
|
|
34
|
+
* - pressure: AC hard → hygiene advisory → suite
|
|
35
|
+
* - rapid: AC only (ceremony dial rapid/minimal positive content)
|
|
36
|
+
*
|
|
37
|
+
* #3282: toolchain preflight enables degraded skip report when go-task/pnpm
|
|
38
|
+
* are missing; gate failures print named cause + remedy; run-summary JSONL
|
|
39
|
+
* is appended when DEFT_RUN_SUMMARY_PATH is set (fail-open).
|
|
19
40
|
*/
|
|
20
41
|
export declare function dispatchCachedTaskCheck(frameworkRoot: string, projectRoot: string, options?: CachedCheckOptions): number;
|
|
21
42
|
//# sourceMappingURL=cached-orchestrator.d.ts.map
|
|
@@ -1,30 +1,60 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { join, resolve } from "node:path";
|
|
3
4
|
import { lintShippedRegistry, resolveTaskContract, runWithCache, } from "../cache/task-cache/index.js";
|
|
4
5
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
6
|
+
import { applyProductFirstGateMode, isHygieneGate, isProductAcGate, resolveProductFirstCheckMode, } from "../product-first-done-gate/index.js";
|
|
7
|
+
import { RunSummaryEmitter } from "../run-summary/emit.js";
|
|
8
|
+
import { runToolchainPreflight, SKIP_ALL_GATES, } from "../session/toolchain-preflight.js";
|
|
5
9
|
import { evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, } from "./consumer-gate-integrity.js";
|
|
6
10
|
import { resolveCheckTarget } from "./context.js";
|
|
7
11
|
import { checkGateId, checkGateSpawnArgs, gatesForCheckTarget, isSuiteCheckGate, } from "./gate-lists.js";
|
|
12
|
+
import { formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate } from "./named-cause.js";
|
|
8
13
|
function captureSpawn(taskBin, args, opts) {
|
|
9
14
|
const result = spawnSync(taskBin, args, {
|
|
10
15
|
cwd: opts.cwd,
|
|
11
16
|
encoding: "utf8",
|
|
12
17
|
env: opts.env ?? process.env,
|
|
13
18
|
});
|
|
19
|
+
if (result.error !== undefined) {
|
|
20
|
+
return {
|
|
21
|
+
exitCode: result.status ?? 1,
|
|
22
|
+
stdout: result.stdout ?? "",
|
|
23
|
+
stderr: result.stderr ?? "",
|
|
24
|
+
spawnError: result.error.message,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
14
27
|
return {
|
|
15
28
|
exitCode: result.status ?? 1,
|
|
16
29
|
stdout: result.stdout ?? "",
|
|
17
30
|
stderr: result.stderr ?? "",
|
|
18
31
|
};
|
|
19
32
|
}
|
|
33
|
+
function writeLines(lines, stream = "stderr") {
|
|
34
|
+
const write = stream === "stdout"
|
|
35
|
+
? process.stdout.write.bind(process.stdout)
|
|
36
|
+
: process.stderr.write.bind(process.stderr);
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
write(`${line}\n`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
20
41
|
/**
|
|
21
42
|
* Run check gates sequentially with content-hash caching (#1713).
|
|
22
43
|
* Falls back to fail-open execution for undeclared / non-cacheable gates.
|
|
23
44
|
*
|
|
24
|
-
* Gate order
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
45
|
+
* Gate order (#3284 / #3188):
|
|
46
|
+
* 1. Product AC (`verify:ac`) first — fail-fast; never skippable when commands exist
|
|
47
|
+
* 2. Hygiene preflight (may be advisory under pressure / degraded)
|
|
48
|
+
* 3. Suite last (`ts:check-lane`)
|
|
49
|
+
*
|
|
50
|
+
* Modes (env / ceremony dial / hard budget — see resolveProductFirstCheckMode):
|
|
51
|
+
* - full: AC hard → hygiene hard → suite
|
|
52
|
+
* - pressure: AC hard → hygiene advisory → suite
|
|
53
|
+
* - rapid: AC only (ceremony dial rapid/minimal positive content)
|
|
54
|
+
*
|
|
55
|
+
* #3282: toolchain preflight enables degraded skip report when go-task/pnpm
|
|
56
|
+
* are missing; gate failures print named cause + remedy; run-summary JSONL
|
|
57
|
+
* is appended when DEFT_RUN_SUMMARY_PATH is set (fail-open).
|
|
28
58
|
*/
|
|
29
59
|
export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}) {
|
|
30
60
|
const resolvedFramework = resolve(frameworkRoot);
|
|
@@ -33,8 +63,41 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
33
63
|
const taskBin = options.taskBin ?? "task";
|
|
34
64
|
const target = resolveCheckTarget(resolvedFramework, resolvedProject);
|
|
35
65
|
const cwd = target === "check:framework-source" ? resolvedFramework : resolvedProject;
|
|
36
|
-
const
|
|
66
|
+
const modeResolution = resolveProductFirstCheckMode({
|
|
67
|
+
environ: options.env ?? process.env,
|
|
68
|
+
projectRoot: resolvedProject,
|
|
69
|
+
});
|
|
70
|
+
const baseGates = gatesForCheckTarget(target);
|
|
71
|
+
const gates = applyProductFirstGateMode(baseGates, modeResolution.mode, checkGateId);
|
|
37
72
|
const codeVersion = readCorePackageVersion();
|
|
73
|
+
const sessionId = options.sessionId ?? randomUUID();
|
|
74
|
+
const gateOutcomes = [];
|
|
75
|
+
const emitSummary = (exitCode, degraded) => {
|
|
76
|
+
if (options.emitRunSummary === false)
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
const emitter = new RunSummaryEmitter({
|
|
80
|
+
projectRoot: resolvedProject,
|
|
81
|
+
sessionId,
|
|
82
|
+
frameworkVersion: codeVersion,
|
|
83
|
+
env: options.env,
|
|
84
|
+
});
|
|
85
|
+
emitter.emitCheckInvocation({
|
|
86
|
+
target,
|
|
87
|
+
exit_code: exitCode,
|
|
88
|
+
degraded,
|
|
89
|
+
gates: gateOutcomes,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// fail-open
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
if (modeResolution.mode !== "full") {
|
|
97
|
+
process.stderr.write(`check: product-first mode=${modeResolution.mode} ` +
|
|
98
|
+
`(sources=${modeResolution.sources.join(",")}; ` +
|
|
99
|
+
`hygieneAdvisory=${modeResolution.hygieneAdvisory}; acOnly=${modeResolution.acOnly}) (#3284)\n`);
|
|
100
|
+
}
|
|
38
101
|
// #3070: fail loud with deposit-repair guidance when consumer check-graph
|
|
39
102
|
// includes (e.g. tasks/verify.yml for verify:orphan-active) are missing,
|
|
40
103
|
// instead of opaque go-task "Task does not exist" exit 200/201.
|
|
@@ -42,6 +105,7 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
42
105
|
const integrity = evaluateConsumerGateIntegrity(resolvedFramework);
|
|
43
106
|
if (!integrity.ok) {
|
|
44
107
|
process.stderr.write(formatConsumerGateIntegrityFailure(integrity));
|
|
108
|
+
emitSummary(2, false);
|
|
45
109
|
return 2;
|
|
46
110
|
}
|
|
47
111
|
}
|
|
@@ -50,22 +114,86 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
50
114
|
for (const finding of registryLint.findings.filter((f) => f.kind === "under-declared-input")) {
|
|
51
115
|
process.stderr.write(`check: task registry lint failed for ${finding.taskId}: ${finding.detail}\n`);
|
|
52
116
|
}
|
|
117
|
+
emitSummary(2, false);
|
|
53
118
|
return 2;
|
|
54
119
|
}
|
|
55
120
|
if (gates.length === 0) {
|
|
56
121
|
process.stderr.write(`check: no gate list for target ${target}\n`);
|
|
122
|
+
emitSummary(2, false);
|
|
57
123
|
return 2;
|
|
58
124
|
}
|
|
125
|
+
// #3282: toolchain preflight — degraded skip when framework tools missing.
|
|
126
|
+
const preflight = options.preflight === undefined
|
|
127
|
+
? runToolchainPreflight({
|
|
128
|
+
projectRoot: resolvedProject,
|
|
129
|
+
frameworkRoot: resolvedFramework,
|
|
130
|
+
})
|
|
131
|
+
: options.preflight;
|
|
132
|
+
// Expand SKIP_ALL_GATES sentinel to the live composition (avoids hardcode drift).
|
|
133
|
+
const skipSet = new Set();
|
|
134
|
+
for (const id of preflight?.skipGateIds ?? []) {
|
|
135
|
+
if (id === SKIP_ALL_GATES) {
|
|
136
|
+
for (const g of gates) {
|
|
137
|
+
skipSet.add(checkGateId(g));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
skipSet.add(id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const degraded = preflight?.degraded === true;
|
|
145
|
+
if (preflight?.degraded) {
|
|
146
|
+
for (const line of preflight.lines) {
|
|
147
|
+
process.stderr.write(`${line}\n`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// When task/node are missing, every gate is skipped — report named skips and
|
|
151
|
+
// exit 2 (config/environment), never exit 0 green (#3282 Greptile P1).
|
|
152
|
+
// Missing framework tooling must not look like a clean product pass.
|
|
153
|
+
if (degraded && skipSet.size > 0) {
|
|
154
|
+
const allSkipped = gates.every((g) => skipSet.has(checkGateId(g)));
|
|
155
|
+
if (allSkipped) {
|
|
156
|
+
const skipped = gates.map((g) => {
|
|
157
|
+
const id = checkGateId(g);
|
|
158
|
+
const cause = preflight?.findings.find((f) => !f.present)?.cause ?? "toolchain preflight degraded";
|
|
159
|
+
const remedy = preflight?.findings.find((f) => !f.present)?.remedy ?? remedyForGate(id, cause);
|
|
160
|
+
gateOutcomes.push({ id, status: "skipped", cause, remedy });
|
|
161
|
+
return { id, cause, remedy };
|
|
162
|
+
});
|
|
163
|
+
writeLines(formatDegradedSkipReport({
|
|
164
|
+
reason: "done-gate toolchain incomplete at check start",
|
|
165
|
+
skipped,
|
|
166
|
+
exitCode: 2,
|
|
167
|
+
}));
|
|
168
|
+
emitSummary(2, true);
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
59
172
|
for (const gateSpec of gates) {
|
|
60
173
|
const gateId = checkGateId(gateSpec);
|
|
174
|
+
// #3282: skip gates that require missing tools (e.g. pnpm-only suite).
|
|
175
|
+
if (skipSet.has(gateId)) {
|
|
176
|
+
const missing = preflight?.findings.find((f) => !f.present);
|
|
177
|
+
const cause = missing?.cause ?? "toolchain preflight marked gate skippable";
|
|
178
|
+
const remedy = missing?.remedy ?? remedyForGate(gateId, cause);
|
|
179
|
+
process.stderr.write(`check: skipping gate ${gateId} (degraded) — cause: ${cause}; remedy: ${remedy}\n`);
|
|
180
|
+
gateOutcomes.push({ id: gateId, status: "skipped", cause, remedy });
|
|
181
|
+
options.onGateStart?.(gateId);
|
|
182
|
+
options.onGateComplete?.(gateId, 0, false);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
61
185
|
// #3188: log suite entry so operators/tests can prove fast failures never
|
|
62
186
|
// reach vitest+coverage (suite gates are ordered last in gate-lists).
|
|
63
187
|
if (isSuiteCheckGate(gateSpec)) {
|
|
64
188
|
process.stderr.write(`check: starting suite gate ${gateId} after fast preflight (#3188)\n`);
|
|
65
189
|
}
|
|
190
|
+
if (isProductAcGate(gateId)) {
|
|
191
|
+
process.stderr.write(`check: product AC gate ${gateId} first (#3284)\n`);
|
|
192
|
+
}
|
|
66
193
|
options.onGateStart?.(gateId);
|
|
67
194
|
const contract = resolveTaskContract(gateId);
|
|
68
195
|
const taskArgs = checkGateSpawnArgs(gateSpec, taskfilePath);
|
|
196
|
+
let lastSpawn = { exitCode: 0, stdout: "", stderr: "" };
|
|
69
197
|
const result = runWithCache({
|
|
70
198
|
projectRoot: cwd,
|
|
71
199
|
contract,
|
|
@@ -78,6 +206,12 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
78
206
|
env: options.env,
|
|
79
207
|
})
|
|
80
208
|
: captureSpawn(taskBin, taskArgs, { cwd, env: options.env });
|
|
209
|
+
lastSpawn = {
|
|
210
|
+
exitCode: spawned.exitCode,
|
|
211
|
+
stdout: spawned.stdout,
|
|
212
|
+
stderr: spawned.stderr,
|
|
213
|
+
spawnError: "spawnError" in spawned ? spawned.spawnError : undefined,
|
|
214
|
+
};
|
|
81
215
|
if (spawned.stdout.length > 0) {
|
|
82
216
|
process.stdout.write(spawned.stdout);
|
|
83
217
|
}
|
|
@@ -88,17 +222,105 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
88
222
|
},
|
|
89
223
|
});
|
|
90
224
|
options.onGateComplete?.(gateId, result.exitCode, result.fromCache);
|
|
91
|
-
// Fail-fast: do not start later gates (including suite) after a failure
|
|
225
|
+
// Fail-fast: do not start later gates (including suite) after a failure —
|
|
226
|
+
// unless this is a hygiene gate under pressure mode (advisory only, #3284).
|
|
92
227
|
if (result.exitCode !== 0) {
|
|
228
|
+
if (modeResolution.hygieneAdvisory && isHygieneGate(gateId) && !isProductAcGate(gateId)) {
|
|
229
|
+
process.stderr.write(`check: hygiene gate ${gateId} failed (exit ${result.exitCode}) but is ADVISORY ` +
|
|
230
|
+
`under ${modeResolution.mode} mode — continuing (#3284)\n`);
|
|
231
|
+
const named = formatNamedCauseFailure({
|
|
232
|
+
gateId,
|
|
233
|
+
exitCode: result.exitCode,
|
|
234
|
+
stdout: lastSpawn.stdout,
|
|
235
|
+
stderr: lastSpawn.stderr,
|
|
236
|
+
spawnError: lastSpawn.spawnError,
|
|
237
|
+
});
|
|
238
|
+
writeLines(named.lines);
|
|
239
|
+
gateOutcomes.push({
|
|
240
|
+
id: gateId,
|
|
241
|
+
status: "run",
|
|
242
|
+
exit_code: result.exitCode,
|
|
243
|
+
cause: `advisory hygiene failure: ${named.cause}`,
|
|
244
|
+
remedy: named.remedy,
|
|
245
|
+
from_cache: result.fromCache,
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const named = formatNamedCauseFailure({
|
|
250
|
+
gateId,
|
|
251
|
+
exitCode: result.exitCode,
|
|
252
|
+
stdout: lastSpawn.stdout,
|
|
253
|
+
stderr: lastSpawn.stderr,
|
|
254
|
+
spawnError: lastSpawn.spawnError,
|
|
255
|
+
});
|
|
256
|
+
writeLines(named.lines);
|
|
257
|
+
gateOutcomes.push({
|
|
258
|
+
id: gateId,
|
|
259
|
+
status: "failed",
|
|
260
|
+
exit_code: result.exitCode,
|
|
261
|
+
cause: named.cause,
|
|
262
|
+
remedy: named.remedy,
|
|
263
|
+
from_cache: result.fromCache,
|
|
264
|
+
});
|
|
265
|
+
// Mark remaining as skipped for the summary (not run).
|
|
266
|
+
let sawCurrent = false;
|
|
267
|
+
for (const later of gates) {
|
|
268
|
+
const id = checkGateId(later);
|
|
269
|
+
if (!sawCurrent) {
|
|
270
|
+
if (id === gateId)
|
|
271
|
+
sawCurrent = true;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (!gateOutcomes.some((o) => o.id === id)) {
|
|
275
|
+
gateOutcomes.push({
|
|
276
|
+
id,
|
|
277
|
+
status: "skipped",
|
|
278
|
+
cause: `skipped after ${gateId} failed`,
|
|
279
|
+
remedy: "Fix the failed gate above, then re-run task check",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (isProductAcGate(gateId)) {
|
|
284
|
+
process.stderr.write(`check: product AC gate ${gateId} failed (exit ${result.exitCode}); ` +
|
|
285
|
+
`failing closed before hygiene (#3284)\n`);
|
|
286
|
+
emitSummary(result.exitCode, degraded);
|
|
287
|
+
return result.exitCode;
|
|
288
|
+
}
|
|
93
289
|
if (!isSuiteCheckGate(gateSpec)) {
|
|
94
290
|
const remaining = gates.some(isSuiteCheckGate)
|
|
95
291
|
? "skipping remaining gates including suite"
|
|
96
292
|
: "skipping remaining gates";
|
|
97
293
|
process.stderr.write(`check: fast gate ${gateId} failed (exit ${result.exitCode}); ${remaining} (#3188)\n`);
|
|
98
294
|
}
|
|
295
|
+
emitSummary(result.exitCode, degraded);
|
|
99
296
|
return result.exitCode;
|
|
100
297
|
}
|
|
298
|
+
gateOutcomes.push({
|
|
299
|
+
id: gateId,
|
|
300
|
+
status: "run",
|
|
301
|
+
exit_code: 0,
|
|
302
|
+
from_cache: result.fromCache,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// Partial degraded (some gates skipped for pnpm, others ran): never report a
|
|
306
|
+
// green pass when required suite/toolchain gates were skipped (#3282 Greptile P1).
|
|
307
|
+
const skippedOutcomes = gateOutcomes.filter((o) => o.status === "skipped");
|
|
308
|
+
if (degraded && skippedOutcomes.length > 0) {
|
|
309
|
+
const skipped = skippedOutcomes.map((o) => ({
|
|
310
|
+
id: o.id,
|
|
311
|
+
cause: o.cause ?? "degraded",
|
|
312
|
+
remedy: o.remedy ?? remedyForGate(o.id, o.cause ?? "degraded"),
|
|
313
|
+
}));
|
|
314
|
+
writeLines(formatDegradedSkipReport({
|
|
315
|
+
reason: "partial toolchain; skipped gates did not run",
|
|
316
|
+
skipped,
|
|
317
|
+
ran: gateOutcomes.filter((o) => o.status === "run").map((o) => o.id),
|
|
318
|
+
exitCode: 2,
|
|
319
|
+
}));
|
|
320
|
+
emitSummary(2, true);
|
|
321
|
+
return 2;
|
|
101
322
|
}
|
|
323
|
+
emitSummary(0, degraded);
|
|
102
324
|
return 0;
|
|
103
325
|
}
|
|
104
326
|
//# sourceMappingURL=cached-orchestrator.js.map
|