Files
pikasTech-unidesk/scripts/src/platform-infra-sub2api-codex/redaction.ts
T
2026-06-25 16:16:25 +00:00

708 lines
27 KiB
TypeScript

// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. redaction module for scripts/src/platform-infra-sub2api-codex.ts.
// Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:2035-2700 for #903.
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service";
import { shortSha256Fingerprint } from "../platform-infra-ops-library";
import {
codexPoolSentinelSummary,
codexPoolSentinelRuntimeImage,
readCodexPoolSentinelConfig,
renderCodexPoolSentinelManifest,
type CodexPoolSentinelConfig,
type CodexPoolSentinelProfileSecret,
} from "../platform-infra-sub2api-codex-sentinel";
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
import type { CodexPoolConfig, CodexPoolRuntimeTarget, CodexProfile, CodexTempUnschedulablePolicy } from "./types";
import { desiredAccountCapacityTotal } from "./accounts";
import { fingerprint, isRecord } from "./config-utils";
import { manualBindingSourcePlan, targetPublicExposureSummary } from "./public-exposure";
export function readAuthAPIKey(authPath: string): { apiKey: string | null; shape: string } {
if (!existsSync(authPath)) return { apiKey: null, shape: "missing" };
const parsed = JSON.parse(readFileSync(authPath, "utf8")) as unknown;
if (!isRecord(parsed)) return { apiKey: null, shape: "non-object" };
const value = parsed.OPENAI_API_KEY;
const shape = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
if (typeof value === "string" && value.length > 0) return { apiKey: value, shape };
return { apiKey: null, shape };
}
export function uniqueAccountName(profile: string, seen: Set<string>): string {
const normalized = profile
.toLowerCase()
.replace(/[^a-z0-9._-]+/gu, "-")
.replace(/^-+|-+$/gu, "") || "default";
let candidate = `unidesk-codex-${normalized}`;
let counter = 2;
while (seen.has(candidate)) {
candidate = `unidesk-codex-${normalized}-${counter}`;
counter += 1;
}
seen.add(candidate);
return candidate;
}
export function redactProfile(profile: CodexProfile): Record<string, unknown> {
return {
profile: profile.profile,
accountName: profile.accountName,
configFile: profile.configFile,
authFile: profile.authFile,
provider: profile.provider || null,
baseUrl: profile.baseUrl || null,
wireApi: profile.wireApi,
model: profile.model,
envKey: profile.envKey,
apiKeySource: profile.apiKeySource,
openaiResponsesWebSocketsV2Mode: profile.openaiResponsesWebSocketsV2Mode,
upstreamUserAgent: profile.upstreamUserAgent,
trustUpstream: profile.trustUpstream,
sentinelProtect: profile.sentinelProtect,
priority: profile.priority,
capacity: profile.capacity,
loadFactor: profile.loadFactor,
tempUnschedulable: tempUnschedulableSummary(profile.tempUnschedulable),
apiKeyPresent: profile.apiKey !== null && profile.apiKey.length > 0,
apiKeyBytes: profile.apiKey === null ? 0 : Buffer.byteLength(profile.apiKey, "utf8"),
apiKeyFingerprint: profile.apiKey === null ? null : fingerprint(profile.apiKey),
authOpenAIKeyShape: profile.authOpenAIKeyShape,
ok: profile.ok,
error: profile.error,
valuesPrinted: false,
};
}
export function compactProfile(profile: CodexProfile): Record<string, unknown> {
return {
profile: profile.profile,
accountName: profile.accountName,
provider: profile.provider || null,
model: profile.model,
priority: profile.priority,
trustUpstream: profile.trustUpstream,
sentinelProtectEnabled: profile.sentinelProtect.enabled,
sentinelProtectConsecutiveFailures: profile.sentinelProtect.enabled ? profile.sentinelProtect.consecutiveFailures : undefined,
capacity: profile.capacity,
loadFactor: profile.loadFactor,
tempUnschedulableEnabled: profile.tempUnschedulable.enabled,
tempUnschedulableRuleCount: profile.tempUnschedulable.rules.length,
apiKeyPresent: profile.apiKey !== null && profile.apiKey.length > 0,
ok: profile.ok,
error: profile.error,
valuesPrinted: false,
};
}
export function renderSub2ApiTempUnschedulableCredentials(policy: CodexTempUnschedulablePolicy): Record<string, unknown> {
if (!policy.enabled) return {};
return {
temp_unschedulable_enabled: policy.enabled,
temp_unschedulable_rules: policy.rules.map((rule) => ({
error_code: rule.statusCode,
keywords: [...rule.keywords],
duration_minutes: rule.durationMinutes,
description: rule.description ?? "",
})),
};
}
export function tempUnschedulableSummary(policy: CodexTempUnschedulablePolicy): Record<string, unknown> {
return {
enabled: policy.enabled,
ruleCount: policy.rules.length,
statusCodes: policy.rules.map((rule) => rule.statusCode),
};
}
export function codexPoolConfigSummary(pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): Record<string, unknown> {
const accountCapacityTotal = desiredAccountCapacityTotal(pool);
return {
version: pool.version,
kind: pool.kind,
metadata: pool.metadata,
groupName: pool.groupName,
groupDescription: pool.groupDescription,
apiKeyName: pool.apiKeyName,
apiKeySecretName: pool.apiKeySecretName,
apiKeySecretKey: pool.apiKeySecretKey,
adminEmailDefault: pool.adminEmailDefault,
minOwnerBalanceUsd: pool.minOwnerBalanceUsd,
minOwnerConcurrency: pool.minOwnerConcurrency,
minOwnerConcurrencySource: pool.minOwnerConcurrencySource,
accountCapacityTotal,
defaultAccountPriority: pool.defaultAccountPriority,
defaultAccountCapacity: pool.defaultAccountCapacity,
defaultAccountLoadFactor: pool.defaultAccountLoadFactor,
defaultTempUnschedulable: tempUnschedulableSummary(pool.defaultTempUnschedulable),
defaultSentinelProtect: pool.defaultSentinelProtect,
profileCount: pool.profiles.length,
manualAccounts: {
bindingSources: pool.manualAccounts.bindingSources.items.map(manualBindingSourcePlan),
protectedCount: pool.manualAccounts.protected.length,
protected: pool.manualAccounts.protected,
controlPolicy: "manual accounts are not created, credential-updated, pruned, probed, or frozen by UniDesk codex-pool sync/sentinel; optional proxy_id and pool group membership bindings are narrow YAML-controlled exceptions",
},
publicExposure: targetPublicExposureSummary(target),
localCodex: pool.localCodex,
sentinel: codexPoolSentinelSummary(pool.sentinel),
disclosure: {
full: "bun scripts/cli.ts platform-infra sub2api codex-pool plan --full",
},
valuesPrinted: false,
};
}
export function compactText(value: unknown, limit = 260): string {
if (typeof value !== "string") return "";
return value.length > limit ? `${value.slice(0, limit)}…` : value;
}
export function recordArray(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.filter(isRecord) : [];
}
export function pickSummaryFields(item: Record<string, unknown>, keys: string[]): Record<string, unknown> {
const result: Record<string, unknown> = {};
for (const key of keys) {
if (item[key] !== undefined) result[key] = item[key];
}
return result;
}
export function compactStatusBlock(block: unknown, keys: string[]): Record<string, unknown> | null {
if (!isRecord(block)) return null;
const items = recordArray(block.items);
const attentionItems = items
.filter((item) => item.ok === false)
.map((item) => pickSummaryFields(item, keys));
return {
ok: block.ok,
desired: block.desired,
missing: block.missing,
mismatched: block.mismatched,
totals: block.totals,
itemCount: items.length,
attentionItems,
valuesPrinted: false,
};
}
export function compactTempUnschedulableStatus(block: unknown): Record<string, unknown> | null {
if (!isRecord(block)) return null;
const items = recordArray(block.items);
const compactItems = items.map((item) => {
const result = pickSummaryFields(item, [
"accountName",
"accountId",
"status",
"schedulable",
"tempUnschedulableUntil",
"tempUnschedulableSet",
"ok",
]);
if (item.tempUnschedulableReasonPreview !== undefined) {
result.tempUnschedulableReason = tempUnschedulableReasonSummary(item.tempUnschedulableReasonPreview);
}
return result;
});
const frozenItems = compactItems.filter((item) => item.tempUnschedulableSet === true);
const focusedFrozenItems = uniqueByAccountName([
...frozenItems.filter((item) => isRecord(item.tempUnschedulableReason) && item.tempUnschedulableReason.statusCode === 400),
...frozenItems.filter((item) => item.schedulable === false),
...frozenItems,
]).slice(0, 6);
return {
ok: block.ok,
desired: block.desired,
enabledCount: Array.isArray(block.enabled) ? block.enabled.length : undefined,
missing: block.missing,
mismatched: block.mismatched,
itemCount: compactItems.length,
frozenCount: frozenItems.length,
frozenShown: focusedFrozenItems.length,
frozen: focusedFrozenItems.map((item) => pickSummaryFields(item, [
"accountName",
"accountId",
"schedulable",
"tempUnschedulableUntil",
"tempUnschedulableReason",
])),
manuallyUnschedulable: compactItems
.filter((item) => item.schedulable === false && item.tempUnschedulableSet !== true)
.map((item) => pickSummaryFields(item, ["accountName", "accountId", "status", "schedulable"])),
valuesPrinted: false,
};
}
export function compactManualAccounts(block: unknown): Record<string, unknown> | null {
if (!isRecord(block)) return null;
const items = recordArray(block.items).map((item) => pickSummaryFields(item, [
"accountName",
"reason",
"ok",
"exists",
"accountId",
"status",
"schedulable",
"inYamlProfiles",
"runtimeMarkedUnideskManaged",
"proxyBinding",
"groupBinding",
"controlPolicy",
]));
const proxySync = isRecord(block.proxySync)
? {
ok: block.proxySync.ok,
itemCount: block.proxySync.itemCount,
items: recordArray(block.proxySync.items).map((item) => pickSummaryFields(item, [
"accountName",
"accountId",
"enabled",
"ok",
"action",
"proxyAction",
"expectedProxyName",
"proxyId",
"runtimeProxyId",
"runtimeProxyName",
"bindingAligned",
"controlPolicy",
])),
valuesPrinted: false,
}
: undefined;
const groupSync = isRecord(block.groupSync)
? {
ok: block.groupSync.ok,
itemCount: block.groupSync.itemCount,
items: recordArray(block.groupSync.items).map((item) => pickSummaryFields(item, [
"accountName",
"accountId",
"enabled",
"ok",
"action",
"source",
"poolGroupName",
"poolGroupId",
"bindingAligned",
"controlPolicy",
])),
valuesPrinted: false,
}
: undefined;
return {
ok: block.ok,
protectedCount: block.protectedCount,
items,
proxySync,
groupSync,
valuesPrinted: false,
};
}
export function tempUnschedulableReasonSummary(value: unknown): Record<string, unknown> {
const reason = compactText(value, 180);
const statusCodeMatch = reason.match(/"status_code":(\d{3})/u) ?? reason.match(/OpenAI\s+(\d{3})/u) ?? reason.match(/\((\d{3})\)/u);
const keywordMatch = reason.match(/"matched_keyword":"([^"]+)"/u);
const summary: Record<string, unknown> = {
statusCode: statusCodeMatch ? Number(statusCodeMatch[1]) : undefined,
matchedKeyword: keywordMatch ? keywordMatch[1] : undefined,
};
if (!statusCodeMatch || !keywordMatch) summary.preview = compactText(reason, 120);
return summary;
}
export function uniqueByAccountName(items: Record<string, unknown>[]): Record<string, unknown>[] {
const seen = new Set<unknown>();
const result: Record<string, unknown>[] = [];
for (const item of items) {
const key = item.accountName ?? item.accountId;
if (seen.has(key)) continue;
seen.add(key);
result.push(item);
}
return result;
}
export function compactRuntimeCapability(block: unknown): unknown {
if (!isRecord(block)) return block;
const probe = isRecord(block.probe) ? block.probe : {};
const logEvidence = isRecord(probe.logEvidence) ? probe.logEvidence : {};
const accountState = isRecord(probe.accountState) ? probe.accountState : (isRecord(probe.badAccountState) ? probe.badAccountState : {});
const resources = isRecord(block.resources) ? block.resources : {};
const requirement = isRecord(block.requirement) ? block.requirement : {};
return {
ok: block.ok,
required: block.required,
capability: block.capability,
outcome: block.outcome,
requirement: Object.keys(requirement).length === 0 ? undefined : {
statusCode: requirement.statusCode,
representativeKeyword: requirement.representativeKeyword,
durationMinutes: requirement.durationMinutes,
sourceAccountName: requirement.sourceAccountName,
},
requestEvidence: Object.keys(probe).length === 0 ? undefined : {
requestId: probe.requestId,
durationMs: probe.durationMs,
httpStatus: probe.httpStatus,
responseOk: probe.responseOk,
accountState: Object.keys(accountState).length === 0 ? undefined : {
accountId: accountState.accountId,
status: accountState.status,
schedulable: accountState.schedulable,
tempUnschedulableUntil: accountState.tempUnschedulableUntil,
tempUnschedulableSet: accountState.tempUnschedulableSet,
tempUnschedulableReasonPreview: compactText(accountState.tempUnschedulableReasonPreview, 180),
},
logEvidence: Object.keys(logEvidence).length === 0 ? undefined : {
matchedLogLineCount: logEvidence.matchedLogLineCount,
failovers: logEvidence.failovers,
final: logEvidence.final,
},
bodyPreview: probe.responseOk === false ? compactText(probe.bodyPreview, 240) : undefined,
},
resources: Object.keys(resources).length === 0 ? undefined : {
groupId: resources.groupId,
accountId: resources.accountId,
badAccountId: resources.badAccountId,
goodAccountId: resources.goodAccountId,
apiKeyId: resources.apiKeyId,
valuesPrinted: false,
},
message: block.message,
valuesPrinted: false,
};
}
export function compactGatewayModels(block: unknown): unknown {
if (!isRecord(block)) return block;
return pickSummaryFields(block, [
"ok",
"httpStatus",
"modelCount",
"method",
"serviceDns",
"valuesPrinted",
]);
}
export function compactGatewayResponses(block: unknown): unknown {
if (!isRecord(block)) return block;
const evidence = isRecord(block.evidence) ? block.evidence : {};
return {
ok: block.ok,
degraded: block.degraded,
outcome: block.outcome,
httpStatus: block.httpStatus,
method: block.method,
model: block.model,
requestId: block.requestId,
durationMs: block.durationMs,
outputTextPreview: block.outputTextPreview,
evidence: Object.keys(evidence).length === 0 ? undefined : {
matchedLogLineCount: evidence.matchedLogLineCount,
failoverCount: Array.isArray(evidence.failovers) ? evidence.failovers.length : undefined,
failovers: Array.isArray(evidence.failovers) ? evidence.failovers.slice(-5) : undefined,
final: evidence.final,
},
valuesPrinted: false,
};
}
export function compactGatewayResponsesRecent(block: unknown): unknown {
if (!isRecord(block)) return block;
return {
ok: block.ok,
degraded: block.degraded,
window: block.window,
tailLines: block.tailLines,
failoverCount: block.failoverCount,
forwardFailureCount: block.forwardFailureCount,
finalErrorCount: block.finalErrorCount,
slowFinalErrorCount: block.slowFinalErrorCount,
contextCanceledCount: block.contextCanceledCount,
ignoredProbeNoiseCount: block.ignoredProbeNoiseCount,
failoverBudgetExhaustedCount: Array.isArray(block.failoverBudgetExhausted) ? block.failoverBudgetExhausted.length : undefined,
failoverBudgetExhausted: Array.isArray(block.failoverBudgetExhausted) ? block.failoverBudgetExhausted.slice(-3).reverse() : undefined,
recentFailovers: Array.isArray(block.recentFailovers) ? block.recentFailovers.slice(-4).reverse() : undefined,
recentForwardFailures: Array.isArray(block.recentForwardFailures) ? block.recentForwardFailures.slice(-4).reverse().map((item) => ({
...item,
errorPreview: compactText(item.errorPreview, 220),
})) : undefined,
recentFinalErrors: Array.isArray(block.recentFinalErrors) ? block.recentFinalErrors.slice(-3).reverse() : undefined,
recentSlowFinalErrors: Array.isArray(block.recentSlowFinalErrors) ? block.recentSlowFinalErrors.slice(-3).reverse() : undefined,
recentContextCanceled: Array.isArray(block.recentContextCanceled) ? block.recentContextCanceled.slice(-3).reverse() : undefined,
logsExitCode: block.logsExitCode,
valuesPrinted: false,
};
}
export function compactGatewayCompactRecent(block: unknown): unknown {
if (!isRecord(block)) return block;
return {
ok: block.ok,
degraded: block.degraded,
window: block.window,
tailLines: block.tailLines,
failureCount: block.failureCount,
successCount: block.successCount,
failoverCount: block.failoverCount,
finalErrorCount: block.finalErrorCount,
contextCanceledCount: block.contextCanceledCount,
recentFailures: Array.isArray(block.recentFailures) ? block.recentFailures.slice(-2).reverse() : undefined,
recentSuccesses: Array.isArray(block.recentSuccesses) ? block.recentSuccesses.slice(-2).reverse() : undefined,
recentFailovers: Array.isArray(block.recentFailovers) ? block.recentFailovers.slice(-4).reverse() : undefined,
recentFinalErrors: Array.isArray(block.recentFinalErrors) ? block.recentFinalErrors.slice(-2).reverse() : undefined,
recentContextCanceled: Array.isArray(block.recentContextCanceled) ? block.recentContextCanceled.slice(-2).reverse() : undefined,
logsExitCode: block.logsExitCode,
valuesPrinted: false,
};
}
export function compactSentinelErrorDetails(value: unknown): Record<string, unknown> | undefined {
if (!isRecord(value)) return undefined;
const body = isRecord(value.body) ? value.body : {};
const openaiError = isRecord(value.openaiError) ? value.openaiError : {};
return {
kind: value.kind,
statusCode: value.statusCode,
code: value.code ?? body.code ?? openaiError.code,
type: value.type ?? body.type ?? openaiError.type,
bodyHash: value.bodyHash,
valuesPrinted: false,
};
}
export function compactSentinelQuarantine(item: unknown): Record<string, unknown> {
if (!isRecord(item)) return {};
const protect = isRecord(item.sentinelProtect) ? item.sentinelProtect : {};
return {
accountName: item.accountName,
until: item.until,
applied: item.applied,
reason: item.reason,
failureKind: item.failureKind,
intervalMinutes: item.intervalMinutes,
sentinelProtect: Object.keys(protect).length > 0 ? {
enabled: protect.enabled,
decision: protect.decision,
failureCount: protect.failureCount,
threshold: protect.threshold,
} : undefined,
error: compactSentinelErrorDetails(item.errorDetails),
};
}
export function compactSentinelRecentAccount(item: unknown): Record<string, unknown> {
if (!isRecord(item)) return {};
const action = isRecord(item.action) ? item.action : {};
const error = compactSentinelErrorDetails(item.errorDetails);
const protect = isRecord(item.sentinelProtect) ? item.sentinelProtect : {};
return {
accountName: item.accountName,
lastProbeAt: item.lastProbeAt,
lastStatus: item.lastStatus,
nextProbeAfter: item.nextProbeAfter,
ok: item.ok,
purpose: item.purpose,
httpStatus: item.httpStatus,
durationMs: item.durationMs,
markerMatched: item.markerMatched,
sentinelProtect: Object.keys(protect).length > 0 ? {
enabled: protect.enabled,
decision: protect.decision,
protected: protect.protected,
failureCount: protect.failureCount,
threshold: protect.threshold,
} : undefined,
failureKind: item.failureKind,
requestShape: item.requestShape,
action: Object.keys(action).length > 0 ? {
taken: action.taken,
type: action.type,
} : undefined,
errorStatusCode: error?.statusCode,
errorCode: error?.code,
errorBodyHash: error?.bodyHash,
};
}
export function compactSentinelLastRun(value: unknown): Record<string, unknown> | undefined {
if (!isRecord(value)) return undefined;
return {
at: value.at,
monitorEnabled: value.monitorEnabled,
actionsEnabled: value.actionsEnabled,
profileCount: value.profileCount,
selected: value.selected,
okCount: value.okCount,
mismatchCount: value.mismatchCount,
markerMismatchCount: value.markerMismatchCount,
transportFailureCount: value.transportFailureCount,
actionsTaken: value.actionsTaken,
gatewayFailureMonitor: value.gatewayFailureMonitor,
selection: value.selection,
reconcileCount: Array.isArray(value.reconcile) ? value.reconcile.length : undefined,
};
}
export function compactSentinelStatus(block: unknown): unknown {
if (!isRecord(block)) return block;
const runtime = isRecord(block.runtime) ? block.runtime : block;
const desired = isRecord(runtime.desired) ? runtime.desired : {};
const cronJob = isRecord(runtime.cronJob) ? runtime.cronJob : {};
const secret = isRecord(runtime.secret) ? runtime.secret : {};
const configMap = isRecord(runtime.configMap) ? runtime.configMap : {};
const state = isRecord(runtime.state) ? runtime.state : {};
const freezeReassert = isRecord(block.freezeReassert) ? block.freezeReassert : {};
const qualityGatePrepare = isRecord(block.qualityGatePrepare) ? block.qualityGatePrepare : {};
const qualityGate = isRecord(block.qualityGate) ? block.qualityGate : {};
const quarantined = recordArray(state.quarantined).map(compactSentinelQuarantine);
const recentAccounts = recordArray(state.recentAccounts).map(compactSentinelRecentAccount);
const recentAttention = recentAccounts.filter((item) => item.ok === false || isRecord(item.action) && item.action.taken === true);
const recentHealthy = recentAccounts
.filter((item) => item.ok === true)
.slice(-3)
.map((item) => pickSummaryFields(item, ["accountName", "lastProbeAt", "lastStatus", "nextProbeAfter", "ok", "httpStatus", "markerMatched"]));
return {
ok: block.ok,
action: block.action,
desired: {
monitorEnabled: desired.monitorEnabled,
actionsEnabled: desired.actionsEnabled,
schedule: desired.schedule,
cronJobName: desired.cronJobName,
configMapName: desired.configMapName,
credentialsSecretName: desired.credentialsSecretName,
stateConfigMapName: desired.stateConfigMapName,
},
cronJob: {
exists: cronJob.exists,
schedule: cronJob.schedule,
suspend: cronJob.suspend,
lastScheduleTime: cronJob.lastScheduleTime,
active: cronJob.active,
error: cronJob.error,
},
secret: {
exists: secret.exists,
profileSecretPresent: secret.profileSecretPresent,
valuesPrinted: false,
error: secret.error,
},
configMap: {
exists: configMap.exists,
configPresent: configMap.configPresent,
runnerPresent: configMap.runnerPresent,
error: configMap.error,
},
state: {
exists: state.exists,
accountCount: state.accountCount,
quarantinedCount: state.quarantinedCount,
quarantinedShown: Math.min(quarantined.length, 3),
quarantined: quarantined.slice(-3),
recentAccountCount: recentAccounts.length,
recentAttention: uniqueByAccountName(recentAttention.slice(-3)),
recentHealthy,
lastRun: compactSentinelLastRun(state.lastRun),
error: state.error,
},
freezeReassert: Object.keys(freezeReassert).length > 0 ? {
ok: freezeReassert.ok,
skipped: freezeReassert.skipped,
reason: freezeReassert.reason,
itemCount: freezeReassert.itemCount,
attentionItems: freezeReassert.attentionItems,
} : undefined,
pendingProbePrepare: Object.keys(qualityGatePrepare).length > 0 ? {
ok: qualityGatePrepare.ok,
skipped: qualityGatePrepare.skipped,
reason: qualityGatePrepare.reason,
changedCount: qualityGatePrepare.changedCount,
fingerprintOnlyCount: qualityGatePrepare.fingerprintOnlyCount,
pendingOnly: qualityGatePrepare.pendingOnly,
items: qualityGatePrepare.items,
} : undefined,
pendingProbe: Object.keys(qualityGate).length > 0 ? {
ok: qualityGate.ok,
skipped: qualityGate.skipped,
reason: qualityGate.reason,
changedCount: qualityGate.changedCount,
fingerprintOnlyCount: qualityGate.fingerprintOnlyCount,
clampedCount: qualityGate.clampedCount,
items: qualityGate.items,
clampedItems: qualityGate.clampedItems,
} : undefined,
valuesPrinted: false,
};
}
export function compactSentinelProbeResult(parsed: Record<string, unknown> | null): Record<string, unknown> | null {
if (parsed === null) return null;
const probe = isRecord(parsed.probe) ? parsed.probe : {};
const summary = isRecord(probe.summary) ? probe.summary : {};
const state = isRecord(parsed.sentinelState) ? parsed.sentinelState : {};
return {
ok: parsed.ok,
mode: parsed.mode,
namespace: parsed.namespace,
job: parsed.job,
requestedAccounts: parsed.requestedAccounts,
summary: {
at: summary.at,
monitorEnabled: summary.monitorEnabled,
actionsEnabled: summary.actionsEnabled,
selected: summary.selected,
okCount: summary.okCount,
mismatchCount: summary.mismatchCount,
markerMismatchCount: summary.markerMismatchCount,
transportFailureCount: summary.transportFailureCount,
actionsTaken: summary.actionsTaken,
gatewayFailureMonitor: summary.gatewayFailureMonitor,
selection: summary.selection,
},
results: recordArray(probe.results).map((item) => pickSummaryFields(item, [
"accountName",
"purpose",
"ok",
"markerMatched",
"sentinelProtect",
"httpStatus",
"durationMs",
"usage",
"outputHash",
"outputPreview",
"responseBodyHash",
"responseBodyPreview",
"error",
"errorDetails",
"failureKind",
"sdk",
"requestShape",
])),
actions: recordArray(probe.actions).map((item) => pickSummaryFields(item, [
"accountName",
"taken",
"type",
"error",
])),
sentinelState: {
quarantined: state.quarantined,
recentAccounts: state.recentAccounts,
lastRun: state.lastRun,
},
valuesPrinted: false,
};
}