feat: add JD01 YAML-first deployment support

This commit is contained in:
Codex
2026-06-29 08:13:34 +00:00
parent fe917bec4a
commit 076c4b643d
49 changed files with 10909 additions and 5223 deletions
+21 -33
View File
@@ -1,9 +1,7 @@
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-25-p0-web-probe-sentinel.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-26-p9-multi-web-probe-sentinel.
// Responsibility: Redacted YAML configRef graph for web-probe sentinel plan/status.
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { rootPath } from "./config";
import { readWebProbeSentinelConfigRef } from "./hwlab-node-web-sentinel-config-ref";
import { HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS, type HwlabRuntimeLaneSpec, type HwlabRuntimeWebProbeSentinelConfigRefKey } from "./hwlab-node-lanes";
import { effectiveWebProbeSentinelPublicExposure, resolveWebProbeSentinel, webProbeSentinelRegistryRows, type WebProbeSentinelRegistryRow } from "./hwlab-node-web-sentinel-resolver";
import type { RenderedCliResult } from "./output";
@@ -212,7 +210,7 @@ export function webProbeSentinelConfigPlan(spec: HwlabRuntimeLaneSpec, action: W
const selected = resolveWebProbeSentinel(spec, sentinelId);
const refs = HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS
.map((key) => readSentinelConfigRef(key, selected.configRefs[key]))
.map((key) => readSentinelConfigRef(spec, key, selected.configRefs[key]))
.map((ref) => effectiveConfigRefStatus(spec, selected.id, ref));
const conflicts = selected.enabled ? crossReferenceConflicts(spec, refs) : [];
const refBlocked = refs.some((ref) => !ref.present || !ref.targetPresent || ref.missingFields.length > 0 || ref.conflicts.length > 0 || ref.error !== null);
@@ -255,28 +253,22 @@ export function withWebProbeSentinelConfigRendered(value: WebProbeSentinelConfig
};
}
function readSentinelConfigRef(key: HwlabRuntimeWebProbeSentinelConfigRefKey, ref: string): InternalConfigRefStatus {
const parsed = parseConfigRef(ref);
if (parsed.error !== null) return emptyRefStatus(key, ref, parsed.file, parsed.path, parsed.error);
const absPath = rootPath(parsed.file);
if (!existsSync(absPath)) return emptyRefStatus(key, ref, parsed.file, parsed.path, `${parsed.file} does not exist`);
function readSentinelConfigRef(spec: HwlabRuntimeLaneSpec, key: HwlabRuntimeWebProbeSentinelConfigRefKey, ref: string): InternalConfigRefStatus {
try {
const text = readFileSync(absPath, "utf8");
const sha256 = `sha256:${createHash("sha256").update(text).digest("hex")}`;
const doc = Bun.YAML.parse(text) as unknown;
const target = valueAtPath(doc, parsed.path);
const resolved = readWebProbeSentinelConfigRef(spec, ref);
const target = resolved.target;
const targetKind = target === undefined ? "missing" : targetKindOf(target);
const missingFields = target === undefined ? ["target"] : missingFieldsForTarget(key, target);
return {
key,
ref,
file: parsed.file,
path: parsed.path,
file: resolved.file,
path: resolved.path,
present: true,
targetPresent: target !== undefined,
targetKind,
sha256,
byteCount: Buffer.byteLength(text),
sha256: resolved.sha256,
byteCount: resolved.byteCount,
missingFields,
conflicts: [],
summary: summarizeTarget(key, target),
@@ -285,21 +277,10 @@ function readSentinelConfigRef(key: HwlabRuntimeWebProbeSentinelConfigRefKey, re
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return emptyRefStatus(key, ref, parsed.file, parsed.path, message);
return emptyRefStatus(key, ref, "", "", message);
}
}
function parseConfigRef(ref: string): { readonly file: string; readonly path: string; readonly error: string | null } {
const [file, path, extra] = ref.split("#");
if (extra !== undefined || file === undefined || path === undefined || file.length === 0 || path.length === 0) {
return { file: file ?? "", path: path ?? "", error: "configRef must use path/to/file.yaml#object.path" };
}
if (file.startsWith("/") || file.includes("\0") || file.includes("..") || !file.startsWith("config/") || !file.endsWith(".yaml")) {
return { file, path, error: "configRef file must be repo-relative config/*.yaml without .." };
}
return { file, path, error: null };
}
function emptyRefStatus(key: HwlabRuntimeWebProbeSentinelConfigRefKey, ref: string, file: string, path: string, error: string): InternalConfigRefStatus {
return {
key,
@@ -348,8 +329,6 @@ function crossReferenceConflicts(spec: HwlabRuntimeLaneSpec, refs: readonly Inte
requireEquals(conflicts, byKey.get("runtime"), "namespace", spec.runtimeNamespace, `selected namespace ${spec.runtimeNamespace}`);
}
const promptSetRef = byKey.get("promptSet")?.ref ?? null;
const reportViewsRef = byKey.get("reportViews")?.ref ?? null;
const scenarioIds = new Set<string>();
const scenarioProviders = new Set<string>();
for (const [index, scenario] of scenarios.entries()) {
@@ -357,8 +336,8 @@ function crossReferenceConflicts(spec: HwlabRuntimeLaneSpec, refs: readonly Inte
if (id !== null) scenarioIds.add(id);
const provider = stringAt(scenario, "providerProfile");
if (provider !== null) scenarioProviders.add(provider);
if (promptSetRef !== null) requireEquals(conflicts, byKey.get("scenarios"), `[${index}].promptSetRef`, promptSetRef, "promptSet configRef");
if (reportViewsRef !== null) requireEquals(conflicts, byKey.get("scenarios"), `[${index}].reportViewRef`, reportViewsRef, "reportViews configRef");
requireReadableConfigRef(spec, conflicts, byKey.get("scenarios"), `[${index}].promptSetRef`, scenario.promptSetRef);
requireReadableConfigRef(spec, conflicts, byKey.get("scenarios"), `[${index}].reportViewRef`, scenario.reportViewRef);
}
const promptProvider = promptSet === null ? null : stringAt(promptSet, "providerProfile");
@@ -384,6 +363,15 @@ function crossReferenceConflicts(spec: HwlabRuntimeLaneSpec, refs: readonly Inte
return conflicts;
}
function requireReadableConfigRef(spec: HwlabRuntimeLaneSpec, conflicts: string[], ref: InternalConfigRefStatus | undefined, path: string, value: unknown): void {
if (typeof value !== "string" || value.length === 0) return;
try {
readWebProbeSentinelConfigRef(spec, value);
} catch (error) {
conflicts.push(`${ref?.file ?? "-"}#${ref?.path ?? "-"}.${path}=${value} is not readable: ${error instanceof Error ? error.message : String(error)}`);
}
}
function requireEquals(conflicts: string[], ref: InternalConfigRefStatus | undefined, path: string, expected: string, expectedLabel: string): void {
if (ref === undefined) return;
const actual = stringAt(ref.target, path);