387 lines
16 KiB
TypeScript
387 lines
16 KiB
TypeScript
export interface MirrorWebhookDisclosureSelection {
|
|
repoKey: string | null;
|
|
deliveryId: string | null;
|
|
}
|
|
|
|
export function rawMirrorWebhookStatus(result: Record<string, unknown>): Record<string, unknown> {
|
|
return {
|
|
ok: result.ok === true,
|
|
action: "platform-infra-gitea-mirror-webhook-status",
|
|
mutation: false,
|
|
view: "raw",
|
|
target: result.target,
|
|
raw: record(result.remote),
|
|
safety: {
|
|
explicitRawRequired: true,
|
|
valuesPrinted: false,
|
|
stdoutGuard: "The YAML-configured stdout guard remains active; oversized raw evidence is written once to the protected dump path.",
|
|
},
|
|
};
|
|
}
|
|
|
|
export function buildMirrorWebhookStatusDisclosure(
|
|
result: Record<string, unknown>,
|
|
selection: MirrorWebhookDisclosureSelection,
|
|
): Record<string, unknown> {
|
|
const target = record(result.target);
|
|
const targetId = stringValue(target.id, "<node>");
|
|
const webhook = record(result.webhook);
|
|
const webhookRetry = record(webhook.ingressRetry);
|
|
const webhookHookReconcile = record(webhook.hookReconcile);
|
|
const webhookBridge = record(webhook.bridge);
|
|
const bridge = record(result.bridge);
|
|
const durableInbox = record(bridge.durableInbox);
|
|
const failedDeliveryRecovery = record(durableInbox.failedDeliveryRecovery);
|
|
const allRecoveryEntries = arrayRecords(failedDeliveryRecovery.entries);
|
|
const allRepositories = arrayRecords(result.repositories);
|
|
const inboxDeliveries = arrayRecords(durableInbox.deliveries);
|
|
const bridgeLogs = record(result.bridgeLogs);
|
|
const allLogEvents = arrayRecords(bridgeLogs.events);
|
|
const aggregateView = selection.repoKey === null && selection.deliveryId === null;
|
|
|
|
let selectedRepoKey = selection.repoKey;
|
|
let effectiveDeliveryId = selection.deliveryId;
|
|
let selectedInbox = selection.deliveryId === null
|
|
? undefined
|
|
: inboxDeliveries.find((item) => stringValue(item.deliveryId, "") === selection.deliveryId
|
|
&& (selection.repoKey === null || stringValue(item.repo, "") === selection.repoKey));
|
|
|
|
if (selectedRepoKey === null && selectedInbox !== undefined) selectedRepoKey = stringValue(selectedInbox.repo, "") || null;
|
|
if (selectedRepoKey === null && selection.deliveryId !== null) {
|
|
const event = allLogEvents.find((item) => stringValue(item.deliveryId, "") === selection.deliveryId);
|
|
selectedRepoKey = event === undefined ? null : stringValue(event.repo, "") || null;
|
|
}
|
|
|
|
let selectedRepository = selectedRepoKey === null
|
|
? undefined
|
|
: allRepositories.find((item) => stringValue(item.key, "") === selectedRepoKey);
|
|
if (selectedRepository === undefined && selection.deliveryId !== null) {
|
|
selectedRepository = allRepositories.find((item) => stringValue(item.deliveryId, "") === selection.deliveryId);
|
|
if (selectedRepoKey === null && selectedRepository !== undefined) selectedRepoKey = stringValue(selectedRepository.key, "") || null;
|
|
}
|
|
if (effectiveDeliveryId === null && selectedRepository !== undefined) effectiveDeliveryId = stringValue(selectedRepository.deliveryId, "") || null;
|
|
if (selectedInbox === undefined && effectiveDeliveryId !== null) {
|
|
selectedInbox = inboxDeliveries.find((item) => stringValue(item.deliveryId, "") === effectiveDeliveryId
|
|
&& (selectedRepoKey === null || stringValue(item.repo, "") === selectedRepoKey));
|
|
}
|
|
if (selectedInbox === undefined && selectedRepository !== undefined) {
|
|
const repositoryInbox = record(selectedRepository.durableInboxRecord);
|
|
if (stringValue(repositoryInbox.deliveryId, "") === (effectiveDeliveryId ?? "")) selectedInbox = repositoryInbox;
|
|
}
|
|
|
|
const repositories = selectedRepoKey === null
|
|
? allRepositories
|
|
: allRepositories.filter((item) => stringValue(item.key, "") === selectedRepoKey);
|
|
const selectedRepositoryName = selectedRepository === undefined ? null : stringValue(selectedRepository.repository, "") || null;
|
|
const recoveryEntries = allRecoveryEntries.filter((item) => {
|
|
if (selectedRepositoryName !== null && stringValue(item.repository, "") !== selectedRepositoryName) return false;
|
|
if (effectiveDeliveryId !== null && stringValue(item.deliveryId, "") !== effectiveDeliveryId) return false;
|
|
return true;
|
|
});
|
|
const matchingLogEvents = allLogEvents.filter((item) => {
|
|
if (selectedRepoKey !== null && stringValue(item.repo, "") !== selectedRepoKey) return false;
|
|
if (effectiveDeliveryId !== null && stringValue(item.deliveryId, "") !== effectiveDeliveryId) return false;
|
|
return true;
|
|
});
|
|
const selectedLogEvents = matchingLogEvents.slice(aggregateView ? -3 : -4).map(webhookLogEventSummary);
|
|
const deliveryFound = effectiveDeliveryId !== null && (
|
|
selectedInbox !== undefined
|
|
|| (selectedRepository !== undefined && stringValue(selectedRepository.deliveryId, "") === effectiveDeliveryId)
|
|
|| matchingLogEvents.length > 0
|
|
);
|
|
const selectionError = selection.repoKey !== null && selectedRepository === undefined
|
|
? "repository-not-observed"
|
|
: selection.deliveryId !== null && !deliveryFound
|
|
? "delivery-not-observed-in-retained-evidence"
|
|
: null;
|
|
const selectedDelivery = selectedInbox === undefined ? null : webhookDeliverySummary(selectedInbox);
|
|
const currentDeliveries = repositories
|
|
.map((repo) => record(repo.durableInboxRecord))
|
|
.filter((item) => stringValue(item.deliveryId, "") !== "")
|
|
.map(webhookDeliveryAggregateSummary);
|
|
const firstError = firstWebhookStatusError({
|
|
repository: selectedRepository ?? allRepositories.find((repo) => compactErrorValue(repo.error) !== null),
|
|
delivery: selectedInbox,
|
|
logEvents: matchingLogEvents,
|
|
durableInbox,
|
|
bridgeLogs,
|
|
mirrorStatus: record(result.mirrorStatus),
|
|
});
|
|
const selectorArgs = [
|
|
selectedRepoKey === null ? "" : ` --repo ${selectedRepoKey}`,
|
|
effectiveDeliveryId === null ? "" : ` --delivery-id ${effectiveDeliveryId}`,
|
|
].join("");
|
|
const commandBase = `bun scripts/cli.ts platform-infra gitea mirror webhook status --target ${targetId}${selectorArgs}`;
|
|
const repoCommands = allRepositories.slice(0, 8).map((repo) => {
|
|
const key = stringValue(repo.key, "");
|
|
return {
|
|
key,
|
|
deliveryId: stringValue(repo.deliveryId, "") || null,
|
|
command: `bun scripts/cli.ts platform-infra gitea mirror webhook status --target ${targetId} --repo ${key}`,
|
|
};
|
|
});
|
|
|
|
return {
|
|
ok: result.ok === true && selectionError === null,
|
|
action: "platform-infra-gitea-mirror-webhook-status",
|
|
mutation: false,
|
|
view: selection.deliveryId !== null ? "delivery-drill-down" : selection.repoKey !== null ? "repository-drill-down" : "domain-aggregate",
|
|
target: {
|
|
id: target.id,
|
|
route: target.route,
|
|
namespace: target.namespace,
|
|
role: target.role,
|
|
},
|
|
selection: {
|
|
requestedRepoKey: selection.repoKey,
|
|
selectedRepoKey,
|
|
requestedDeliveryId: selection.deliveryId,
|
|
effectiveDeliveryId,
|
|
repositoryFound: selection.repoKey === null || selectedRepository !== undefined,
|
|
deliveryFound: selection.deliveryId === null || deliveryFound,
|
|
error: selectionError,
|
|
},
|
|
webhook: {
|
|
enabled: webhook.enabled === true,
|
|
direction: webhook.direction,
|
|
publicUrlSha256: webhookHookReconcile.desiredUrlSha256,
|
|
events: webhook.events,
|
|
responseBudgetMs: webhook.responseBudgetMs,
|
|
ingressAttemptBudgetMs: webhook.ingressAttemptBudgetMs,
|
|
ingressResponseHeaderTimeoutMs: webhook.ingressResponseHeaderTimeoutMs,
|
|
retry: {
|
|
enabled: webhookRetry.enabled === true,
|
|
maxAttempts: webhookRetry.maxAttempts,
|
|
initialDelayMs: webhookRetry.initialDelayMs,
|
|
maxDelayMs: webhookRetry.maxDelayMs,
|
|
},
|
|
bridge: {
|
|
deploymentName: webhookBridge.deploymentName,
|
|
serviceName: webhookBridge.serviceName,
|
|
},
|
|
hookReconcile: {
|
|
enabled: webhookHookReconcile.enabled === true,
|
|
ownerTargetId: webhookHookReconcile.ownerTargetId,
|
|
runningOnTarget: webhookHookReconcile.runningOnTarget === true,
|
|
intervalMs: webhookHookReconcile.intervalMs,
|
|
requestTimeoutMs: webhookHookReconcile.requestTimeoutMs,
|
|
topologySha256: webhookHookReconcile.topologySha256,
|
|
},
|
|
},
|
|
bridge: {
|
|
deployment: bridge.deployment,
|
|
ready: bridge.ready === true,
|
|
readyReplicas: bridge.readyReplicas,
|
|
serviceExists: bridge.serviceExists === true,
|
|
durableInbox: {
|
|
ready: durableInbox.ready === true,
|
|
storageReady: durableInbox.storageReady === true,
|
|
capacityAvailable: durableInbox.capacityAvailable === true,
|
|
counts: durableInbox.counts,
|
|
retainedDeliveryCount: inboxDeliveries.length,
|
|
totalBytes: durableInbox.totalBytes,
|
|
maxBytes: durableInbox.maxBytes,
|
|
errorType: durableInbox.errorType ?? null,
|
|
journal: durableInbox.journal,
|
|
failedDeliveryRecovery: {
|
|
enabled: failedDeliveryRecovery.enabled === true,
|
|
ready: failedDeliveryRecovery.ready === true,
|
|
state: failedDeliveryRecovery.state,
|
|
counts: failedDeliveryRecovery.counts,
|
|
entries: recoveryEntries.map(failedDeliveryRecoveryEntrySummary),
|
|
},
|
|
},
|
|
},
|
|
repositories: repositories.map(webhookRepositorySummary),
|
|
deliveries: selection.repoKey === null && selection.deliveryId === null ? currentDeliveries : selectedDelivery === null ? [] : [selectedDelivery],
|
|
retry: selectedInbox === undefined ? null : {
|
|
state: selectedInbox.state,
|
|
totalAttempts: selectedInbox.totalAttempts,
|
|
cycleAttempt: selectedInbox.cycleAttempt,
|
|
nextAttemptAt: selectedInbox.nextAttemptAt ?? null,
|
|
lastError: compactErrorValue(selectedInbox.lastError),
|
|
},
|
|
logs: {
|
|
exitCode: bridgeLogs.exitCode,
|
|
matched: matchingLogEvents.length,
|
|
returned: selectedLogEvents.length,
|
|
omitted: Math.max(0, matchingLogEvents.length - selectedLogEvents.length),
|
|
events: selectedLogEvents,
|
|
errorTail: compactErrorValue(bridgeLogs.errorTail),
|
|
},
|
|
diagnostics: {
|
|
mirrorStatus: {
|
|
exitCode: record(result.mirrorStatus).exitCode,
|
|
errorTail: compactErrorValue(record(result.mirrorStatus).errorTail),
|
|
},
|
|
firstError,
|
|
},
|
|
disclosure: {
|
|
rawOmitted: true,
|
|
retainedInboxRecords: inboxDeliveries.length,
|
|
rawRequiresExplicitFlag: true,
|
|
valuesPrinted: false,
|
|
},
|
|
next: {
|
|
repositories: repoCommands,
|
|
full: `${commandBase} --full`,
|
|
raw: `${commandBase} --raw`,
|
|
},
|
|
};
|
|
}
|
|
|
|
function failedDeliveryRecoveryEntrySummary(item: Record<string, unknown>): Record<string, unknown> {
|
|
return {
|
|
repository: item.repository,
|
|
hookId: item.hookId,
|
|
deliveryId: item.deliveryId,
|
|
state: item.state,
|
|
attempts: item.attempts,
|
|
cooldownMs: item.cooldownMs,
|
|
nextAt: item.nextAt ?? null,
|
|
updatedAt: item.updatedAt,
|
|
lastStatusCode: item.lastStatusCode ?? null,
|
|
completionReason: item.completionReason ?? null,
|
|
};
|
|
}
|
|
|
|
function webhookRepositorySummary(repo: Record<string, unknown>): Record<string, unknown> {
|
|
const selected = record(repo.selectedPushDelivery);
|
|
const topology = record(repo.hookTopology);
|
|
return {
|
|
key: repo.key,
|
|
repository: repo.repository,
|
|
branch: repo.branch,
|
|
hookReady: repo.hookReady === true,
|
|
githubHead: repo.githubHead,
|
|
branchCommit: repo.branchCommit,
|
|
snapshotCommit: repo.snapshotCommit,
|
|
authorityMatches: repo.authorityMatches === true,
|
|
stale: repo.bridgeEventStale === true,
|
|
staleReason: repo.staleReason ?? null,
|
|
deliveryId: selected.deliveryId ?? null,
|
|
deliveryAccepted: repo.deliveryAccepted === true,
|
|
selectedDelivery: {
|
|
deliveryId: selected.deliveryId ?? null,
|
|
hookId: selected.hookId ?? null,
|
|
status: selected.status ?? null,
|
|
statusCode: selected.statusCode ?? null,
|
|
deliveredAt: selected.deliveredAt ?? null,
|
|
accepted: repo.deliveryAccepted === true,
|
|
},
|
|
hookTopology: {
|
|
ready: topology.ready === true,
|
|
desiredUrlSha256: topology.desiredUrlSha256 ?? null,
|
|
desiredTopologyUrlSha256: Array.isArray(topology.desiredTopologyUrlSha256) ? topology.desiredTopologyUrlSha256 : [],
|
|
observedManagedUrlSha256: Array.isArray(topology.observedManagedUrlSha256) ? topology.observedManagedUrlSha256 : [],
|
|
driftTypes: Array.isArray(topology.driftTypes) ? topology.driftTypes : [],
|
|
},
|
|
durableState: repo.durableInboxState,
|
|
durableCommitted: repo.durableInboxCommitted === true,
|
|
error: compactErrorValue(repo.error),
|
|
};
|
|
}
|
|
|
|
function webhookDeliverySummary(delivery: Record<string, unknown>): Record<string, unknown> {
|
|
const result = record(delivery.result);
|
|
return {
|
|
deliveryId: delivery.deliveryId,
|
|
repo: delivery.repo,
|
|
repository: delivery.repository,
|
|
ref: delivery.ref,
|
|
requestedCommit: delivery.requestedCommit,
|
|
state: delivery.state,
|
|
acceptedSequence: delivery.acceptedSequence,
|
|
totalAttempts: delivery.totalAttempts,
|
|
cycleAttempt: delivery.cycleAttempt,
|
|
nextAttemptAt: delivery.nextAttemptAt ?? null,
|
|
updatedAt: delivery.updatedAt,
|
|
result: {
|
|
sourceCommit: result.sourceCommit,
|
|
authorityCommit: result.authorityCommit,
|
|
snapshotRef: result.snapshotRef,
|
|
disposition: result.disposition,
|
|
},
|
|
lastError: compactErrorValue(delivery.lastError),
|
|
};
|
|
}
|
|
|
|
function webhookDeliveryAggregateSummary(delivery: Record<string, unknown>): Record<string, unknown> {
|
|
const result = record(delivery.result);
|
|
return {
|
|
deliveryId: delivery.deliveryId,
|
|
repo: delivery.repo,
|
|
state: delivery.state,
|
|
totalAttempts: delivery.totalAttempts,
|
|
updatedAt: delivery.updatedAt,
|
|
requestedCommit: delivery.requestedCommit,
|
|
sourceCommit: result.sourceCommit,
|
|
disposition: result.disposition,
|
|
lastError: compactErrorValue(delivery.lastError),
|
|
};
|
|
}
|
|
|
|
function webhookLogEventSummary(event: Record<string, unknown>): Record<string, unknown> {
|
|
return {
|
|
event: event.event,
|
|
repo: event.repo,
|
|
deliveryId: event.deliveryId,
|
|
ok: event.ok === true,
|
|
terminal: event.terminal === true,
|
|
syncAttempt: event.syncAttempt,
|
|
disposition: event.disposition,
|
|
sourceCommit: event.sourceCommit,
|
|
authorityCommit: event.authorityCommit,
|
|
elapsedMs: event.elapsedMs,
|
|
errorType: event.errorType ?? null,
|
|
error: compactErrorValue(event.error),
|
|
};
|
|
}
|
|
|
|
function firstWebhookStatusError(input: {
|
|
repository?: Record<string, unknown>;
|
|
delivery?: Record<string, unknown>;
|
|
logEvents: Record<string, unknown>[];
|
|
durableInbox: Record<string, unknown>;
|
|
bridgeLogs: Record<string, unknown>;
|
|
mirrorStatus: Record<string, unknown>;
|
|
}): Record<string, unknown> | null {
|
|
const candidates: Array<[string, unknown]> = [
|
|
["repository", input.repository?.error],
|
|
["durable-inbox-record", input.delivery?.lastError],
|
|
["bridge-event", input.logEvents.find((event) => compactErrorValue(event.error) !== null)?.error],
|
|
["durable-inbox", input.durableInbox.errorType],
|
|
["bridge-logs", Number(input.bridgeLogs.exitCode) === 0 ? null : input.bridgeLogs.errorTail],
|
|
["mirror-status", Number(input.mirrorStatus.exitCode) === 0 ? null : input.mirrorStatus.errorTail],
|
|
];
|
|
for (const [domain, value] of candidates) {
|
|
const message = compactErrorValue(value);
|
|
if (message !== null) return { domain, message };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function compactErrorValue(value: unknown): string | null {
|
|
if (value === null || value === undefined || value === "") return null;
|
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
const compact = text.replace(/\s+/gu, " ").trim();
|
|
if (compact === "" || compact === "null" || compact === "{}") return null;
|
|
return compact.length > 240 ? `${compact.slice(0, 237)}...` : compact;
|
|
}
|
|
|
|
function record(value: unknown): Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
}
|
|
|
|
function arrayRecords(value: unknown): Record<string, unknown>[] {
|
|
return Array.isArray(value)
|
|
? value.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)) as Record<string, unknown>[]
|
|
: [];
|
|
}
|
|
|
|
function stringValue(value: unknown, fallback = "-"): string {
|
|
if (typeof value === "string" && value.length > 0) return value;
|
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
return fallback;
|
|
}
|