feat(sentinel): restore JD01 cadence cronjob visibility
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-27-p11-monitor-web-observability-dashboard.
|
||||
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-07-01-p15-cadence-otel.
|
||||
// Responsibility: Quick-verify observe orchestration and artifact interpretation for web-probe sentinel P5 validation.
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
text,
|
||||
withWarnings,
|
||||
} from "./hwlab-node-web-sentinel-cicd";
|
||||
import { emitWebProbeSentinelSpan } from "./hwlab-node-web-sentinel-otel";
|
||||
|
||||
function printQuickVerifyProgress(state: SentinelCicdState, runId: string | null, phase: string, status: string, extra: Record<string, unknown> = {}): void {
|
||||
const compactExtra = Object.fromEntries(Object.entries(extra).map(([key, value]) => {
|
||||
@@ -60,12 +62,30 @@ export function runSentinelQuickVerify(state: SentinelCicdState, reason: string,
|
||||
const maxSeconds = numberAt(state.cicd, "targetValidation.maxSeconds");
|
||||
const scenario = findScenario(state, scenarioId);
|
||||
if (scenario === null) return { ok: false, status: "blocked", reason: "scenario-not-found", scenarioId, valuesRedacted: true };
|
||||
const runId = `sentinel-run-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
||||
emitWebProbeSentinelSpan(sentinelOtelContext(state), "web_probe_sentinel.quick_verify.job_start", {
|
||||
scenarioId,
|
||||
runId,
|
||||
cadence: stringAtNullable(scenario, "cadence"),
|
||||
status: "running",
|
||||
valuesRedacted: true,
|
||||
});
|
||||
const commandSequence = arrayAt(scenario, "commandSequence").map(record);
|
||||
const needsPromptSet = commandSequence.some((item) => stringAt(item, "type") === "sendPrompt" && inlinePromptText(item) === null);
|
||||
const prompts = needsPromptSet
|
||||
? readPromptSetForScenario(state, scenario)
|
||||
: { ok: true as const, prompts: [], summary: { source: "not-required", promptCount: 0, valuesRedacted: true } };
|
||||
if (!prompts.ok) return { ok: false, status: "blocked", reason: "prompt-source-unavailable", promptSource: prompts, valuesRedacted: true };
|
||||
if (!prompts.ok) {
|
||||
emitWebProbeSentinelSpan(sentinelOtelContext(state), "web_probe_sentinel.quick_verify.job_finish", {
|
||||
scenarioId,
|
||||
runId,
|
||||
status: "blocked",
|
||||
exitCode: 1,
|
||||
failureKind: "prompt-source-unavailable",
|
||||
valuesRedacted: true,
|
||||
}, false);
|
||||
return { ok: false, status: "blocked", reason: "prompt-source-unavailable", promptSource: prompts, valuesRedacted: true };
|
||||
}
|
||||
const accountEnv = quickVerifyAccountEnv(state);
|
||||
if (!accountEnv.ok) {
|
||||
const findings = [{
|
||||
@@ -78,7 +98,7 @@ export function runSentinelQuickVerify(state: SentinelCicdState, reason: string,
|
||||
}];
|
||||
return recordQuickVerify(state, {
|
||||
ok: false,
|
||||
runId: `sentinel-run-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`,
|
||||
runId,
|
||||
scenarioId,
|
||||
reason,
|
||||
status: "blocked",
|
||||
@@ -104,7 +124,6 @@ export function runSentinelQuickVerify(state: SentinelCicdState, reason: string,
|
||||
const hardBudgetSeconds = Math.min(timeoutSeconds, Math.max(maxSeconds, numberAt(scenario, "maxRunSeconds")));
|
||||
const elapsedWarnings = () => targetValidationElapsedWarnings(elapsedMs(), "quick verify confirm-wait", warningBudgetSeconds);
|
||||
const deadline = Date.now() + hardBudgetSeconds * 1000;
|
||||
const runId = `sentinel-run-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
||||
printQuickVerifyProgress(state, runId, "start", "running", { scenarioId, reason, warningBudgetSeconds, hardBudgetSeconds, timeoutSeconds });
|
||||
const steps: Record<string, unknown>[] = [];
|
||||
const startArgs = [
|
||||
@@ -659,9 +678,29 @@ function recordQuickVerify(state: SentinelCicdState, payload: Record<string, unk
|
||||
maintenance: payload.reason === "maintenance-stop",
|
||||
valuesRedacted: true,
|
||||
}, 60);
|
||||
emitWebProbeSentinelSpan(sentinelOtelContext(state), "web_probe_sentinel.quick_verify.job_finish", {
|
||||
scenarioId: payload.scenarioId,
|
||||
runId: payload.runId,
|
||||
observerId: payload.observerId,
|
||||
status: payload.status,
|
||||
exitCode: payload.ok === true && recordResult.ok === true ? 0 : 1,
|
||||
failureKind: payload.failure ?? (recordResult.ok === true ? null : "record-run-failed"),
|
||||
valuesRedacted: true,
|
||||
}, payload.ok === true && recordResult.ok === true);
|
||||
return withWarnings({ ...payload, views, recordResult, valuesRedacted: true }, recordResult.ok === true ? [] : ["quick verify completed but sentinel report index record failed; report/dashboard may lag until record payload is reduced or retried."]);
|
||||
}
|
||||
|
||||
function sentinelOtelContext(state: SentinelCicdState): { readonly node: string; readonly lane: string; readonly sentinelId: string; readonly namespace: string | null; readonly runtime: Record<string, unknown>; readonly cicd: Record<string, unknown> } {
|
||||
return {
|
||||
node: state.spec.nodeId,
|
||||
lane: state.spec.lane,
|
||||
sentinelId: state.sentinelId,
|
||||
namespace: stringAtNullable(state.runtime, "namespace"),
|
||||
runtime: state.runtime,
|
||||
cicd: state.cicd,
|
||||
};
|
||||
}
|
||||
|
||||
function compactQuickVerifyRecordViews(views: Record<string, unknown>): Record<string, unknown> {
|
||||
const compacted: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(views)) {
|
||||
|
||||
Reference in New Issue
Block a user