From dc488302b822060cbef9c6c89a6cb1146de2b191 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Jul 2026 18:20:51 +0000 Subject: [PATCH] fix: expose follower closeout gates --- .../references/branch-follower.md | 2 + scripts/src/cicd-debug.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/.agents/skills/unidesk-cicd/references/branch-follower.md b/.agents/skills/unidesk-cicd/references/branch-follower.md index 489d7fd6..a7422ad7 100644 --- a/.agents/skills/unidesk-cicd/references/branch-follower.md +++ b/.agents/skills/unidesk-cicd/references/branch-follower.md @@ -36,6 +36,8 @@ When a repeated runtime pitfall or visibility defect is found during branch-foll `debug-step` output must stay bounded in both text and JSON modes. The default machine payload should include step result, compact state/status/decision/write summaries, target Job identity and short error/timing fields only. Full target Job logs, full target JSON and long stdout/stderr tails belong behind explicit drill-down, not in the default `--json` payload. +`status-read`, `events`, `logs` and debug summaries must expose compact closeout gate details when a follower is not aligned: git-mirror readiness, Tekton PipelineRun condition, Argo sync/health, runtime target sha/readiness and short errors. Repeating only phase/observed/target/message is a visibility defect and must be fixed before further rollout tuning. + ## Source Authority - Follower decisions must not read host source worktrees, target dev directories, `.worktree/*`, local git state, or direct GitHub branch refs. diff --git a/scripts/src/cicd-debug.ts b/scripts/src/cicd-debug.ts index 773185e5..cc9e87a3 100644 --- a/scripts/src/cicd-debug.ts +++ b/scripts/src/cicd-debug.ts @@ -188,6 +188,7 @@ function compactTargetDebugResult(parsed: Record | null): Recor aligned: status.aligned === true ? true : status.aligned === false ? false : null, pipelineRun: stringOrNull(status.pipelineRun), message: stringOrNull(status.message), + gates: asOptionalRecord(status.gates), }, decision: decision === null ? null : { phase: stringOrNull(decision.phase), @@ -269,6 +270,7 @@ function stateSnapshot(read: K8sStateRead, followerId: string): Record { + const payload = asOptionalRecord(live.payload); return { ok: live.ok, phase: live.phase, @@ -278,6 +280,48 @@ function compactAdapterStatus(live: AdapterSummary): Record { pipelineRun: live.pipelineRun, inFlightJob: live.inFlightJob, message: live.message, + gates: compactStatusGates(payload), + }; +} + +function compactStatusGates(payload: Record | null): Record | null { + if (payload === null) return null; + const gitMirror = asOptionalRecord(payload.gitMirror); + const tekton = asOptionalRecord(payload.tekton); + const argo = asOptionalRecord(payload.argo); + const runtime = asOptionalRecord(payload.runtime); + return { + gitMirror: gitMirror === null ? null : { + ok: gitMirror.ok === true, + sourceSnapshotReady: gitMirror.sourceSnapshotReady === true, + pendingFlush: gitMirror.pendingFlush === true, + githubInSync: gitMirror.githubInSync === true, + localSource: stringOrNull(gitMirror.localSource), + githubSource: stringOrNull(gitMirror.githubSource), + localGitops: stringOrNull(gitMirror.localGitops), + githubGitops: stringOrNull(gitMirror.githubGitops), + }, + tekton: tekton === null ? null : { + name: stringOrNull(tekton.name), + succeeded: tekton.succeeded === true ? true : tekton.succeeded === false ? false : null, + reason: stringOrNull(tekton.reason), + startTime: stringOrNull(tekton.startTime), + completionTime: stringOrNull(tekton.completionTime), + durationSeconds: numberOrNull(tekton.durationSeconds), + }, + argo: argo === null ? null : { + syncStatus: stringOrNull(argo.syncStatus), + healthStatus: stringOrNull(argo.healthStatus), + revision: stringOrNull(argo.revision), + ready: argo.ready === true, + }, + runtime: runtime === null ? null : { + ready: runtime.ready === true, + targetSha: stringOrNull(runtime.targetSha), + expectedSha: stringOrNull(runtime.expectedSha), + aligned: runtime.aligned === true ? true : runtime.aligned === false ? false : null, + }, + errors: Array.isArray(payload.errors) ? payload.errors.map(String).slice(0, 5) : [], }; }