fix: 完成只读状态预算验证
This commit is contained in:
@@ -500,6 +500,9 @@ controlPlane:
|
||||
gitopsPath: deploy/gitops/node/nc01/runtime-v02
|
||||
argoApplication: agentrun-nc01-v02
|
||||
nc01-release:
|
||||
status:
|
||||
readOnlyCapture:
|
||||
timeoutMs: 20000
|
||||
deployment:
|
||||
format: unidesk-yaml-only
|
||||
gitopsRoot: deploy/gitops/node/nc01
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
- 首轮修复已建立稳定的 typed observation,使 PaC `status/history/debug-step` 与 AgentRun `control-plane status` 在 timeout、remote failure、empty response 和 parse failure 时不再以空 stdout 结束。
|
||||
- 首轮仍把 PaC 只读 capture 预算 `25000ms` 与 AgentRun status capture 预算 `20000ms` 写成 TypeScript 常量。
|
||||
- 这些预算属于可调运维事实;源码常量绕过 owning YAML,无法披露来源路径,也无法在选中对象缺失预算时 fail-closed。
|
||||
- `18d0bd89` 已把预算迁入 YAML,但只给默认 `nc01-v02` lane 声明 AgentRun 预算;parser 会校验 `controlPlane.lanes` 的全部对象,因此 `nc01-release` 缺失 `status.readOnlyCapture` 时,`agentrun control-plane status` 在任何目标选择前即失败。
|
||||
- AgentRun 默认文本只显示嵌套 PaC timeout,未披露选中 lane 的 budget path;PaC observation 实际返回 `configPath`,其 TypeScript 接口却漏声明该字段。
|
||||
|
||||
## 官方最佳实践
|
||||
|
||||
@@ -25,19 +27,20 @@
|
||||
## Current Control Flow
|
||||
|
||||
1. CLI 读取 PaC 或 AgentRun YAML。
|
||||
2. parser 解析目标、consumer 或 lane。
|
||||
3. capture 调用使用 TypeScript 文件内固定的 timeout 常量。
|
||||
4. typed observation 披露实际 timeout 数值,但无法证明该数值的 owning config path。
|
||||
5. YAML 缺失预算时仍可继续执行,不能按当前选中对象 fail-closed。
|
||||
2. parser 解析目标、consumer 或全部 lane。
|
||||
3. `18d0bd89` 后 capture 已使用 YAML timeout,但 AgentRun 只有一个 lane 声明预算,导致 parser 在发起只读 capture 前失败。
|
||||
4. PaC text/JSON 已披露 timeout 与 config path;AgentRun JSON 有 `readCaptureBudget`,默认文本未披露该字段。
|
||||
5. 嵌套 PaC 与 runtime capture 串行执行;当前有效预算为 `25000ms + 20000ms`,需要用原入口实测其是否与外层 `trans` 60 秒短连接竞争。
|
||||
|
||||
## Desired Control Flow
|
||||
|
||||
1. PaC parser 必填读取 `config/platform-infra/pipelines-as-code.yaml#observability.readOnlyCapture.timeoutMs`。
|
||||
2. AgentRun lane parser 必填读取 `config/agentrun.yaml#controlPlane.lanes.<lane>.status.readOnlyCapture.timeoutMs`。
|
||||
2. AgentRun 每个 owning lane 都必须声明 `config/agentrun.yaml#controlPlane.lanes.<lane>.status.readOnlyCapture.timeoutMs`,parser 对全部 lane 必填校验且不补默认值。
|
||||
3. parser 将预算值与精确 config path 放入 typed spec,不提供源码默认值。
|
||||
4. capture 只使用已验证的 YAML 预算。
|
||||
5. PaC text/JSON observation 与 AgentRun status text/JSON 摘要同时披露 config path 和实际 timeout。
|
||||
6. 缺失字段时 parser 在发起远端读取前失败,并指出缺失 YAML 路径。
|
||||
7. status JSON 显式声明 `mutation: false`;失败保持非空 typed 结果,不触发 PipelineRun、Argo、mirror 或 runtime 写操作。
|
||||
|
||||
## 修改
|
||||
|
||||
@@ -49,22 +52,38 @@
|
||||
- `status/history/debug-step` capture 使用 YAML 预算。
|
||||
- typed observation 与默认 text 输出披露预算路径和实际数值。
|
||||
- `config/agentrun.yaml`
|
||||
- 在 `controlPlane.lanes.nc01-v02.status.readOnlyCapture.timeoutMs` 声明 `20000`。
|
||||
- 在 `controlPlane.lanes.nc01-v02.status.readOnlyCapture.timeoutMs` 与 `controlPlane.lanes.nc01-release.status.readOnlyCapture.timeoutMs` 分别声明 `20000`,保证所有 owning lane 通过必填 parser。
|
||||
- `scripts/src/agentrun-lanes.ts`
|
||||
- lane parser 必填解析 status capture 预算与精确 config path。
|
||||
- `scripts/src/agentrun/control-plane.ts`
|
||||
- 删除 `agentRunStatusCaptureTimeoutMs` 源码常量。
|
||||
- source、runtime 和 git-mirror 只读 capture 使用选中 lane 的 YAML 预算。
|
||||
- status 结果披露 `readCaptureBudget`。
|
||||
- status 结果显式披露 `mutation: false`。
|
||||
- `scripts/src/agentrun/public-exposure.ts`
|
||||
- 默认文本新增 `AGENTRUN READ CAPTURE BUDGET`,披露选中 lane 的实际 timeout 与 config path。
|
||||
- 嵌套 `PAC READ OBSERVATION` 同时披露 PaC budget config path。
|
||||
- `scripts/src/platform-infra-pipelines-as-code.ts`
|
||||
- 补齐 `PacReadOnlyCaptureObservation.configPath` 类型声明,使 typed JSON 与 TypeScript 合同一致。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向 Bun 测试验证 PaC typed observation 保留 timeout、config path、首断点与只读 Next。
|
||||
- TypeScript 语法检查覆盖修改的 parser、PaC 与 AgentRun control-plane 文件。
|
||||
- YAML 读取和原 CLI 入口验证证明预算由选中配置解析,并在 text/JSON 中可见。
|
||||
- 定向 Bun 测试:`7 pass / 0 fail`,覆盖 PaC typed observation 与 AgentRun text 两层 YAML budget path。
|
||||
- `bun --check` 覆盖修改的 parser、PaC、AgentRun control-plane、renderer 与 capture helper。
|
||||
- PaC status JSON:约 `28s` 返回 `status=timeout`、`timeoutMs=25000`、PaC config path、`mutation=false`,exit `1`,stdout 非空。
|
||||
- PaC history text:约 `29s` 返回 `pac-read-timeout`、PaC config path 与只读 Next,exit `1`,stdout 非空。
|
||||
- PaC debug-step text:约 `3s` 成功,`34/34` fixtures,通过同一 YAML budget path,exit `0`。
|
||||
- AgentRun status text:内部 PaC `25609ms`、runtime `20150ms`、total `45759ms`;外层 `trans` 总耗时约 `47.273s`,在 60 秒短连接预算内稳定返回,披露 PaC `25000ms` 与 lane `20000ms` 两层 config path,exit `1`,stdout 非空。
|
||||
- AgentRun `--raw` JSON:约 `45.242s` 返回;完整 JSON 的 `readCaptureBudget` 与 `summary.pac.readObservation` 分别披露 AgentRun/PaC config path,且 status 结果声明 `mutation=false`。
|
||||
- `git diff --check` 验证补丁格式。
|
||||
- 所有目标源码、Git、验证和原入口观察均通过 `trans NC01:/root/unidesk/.worktree/2165-pac-status-visible ...` 执行。
|
||||
|
||||
## 取消与超时结论
|
||||
|
||||
- 上次任务取消不能归因于当前 CLI 嵌套预算:修正 YAML 完整性后,最慢 AgentRun 原入口内部总耗时约 `45.759s`,外层 `trans` 约 `47.273s`,未触发 60 秒短连接 timeout。
|
||||
- 当前链路确实串行消费 PaC `25000ms` 与 runtime `20000ms`,但仍保留约 14 秒外层余量;本轮没有证据支持新增 job、锁、租约、第二状态库或额外 authority。
|
||||
- 取消更符合 Artificer/session 外部终止,而不是 CLI 预算嵌套 first break;本报告只记录可复现证据,不猜测未观测的调度原因。
|
||||
|
||||
## 权限与变更边界
|
||||
|
||||
- 保留现有 typed observation、即时 progress、只读 Next 和非 mutation 行为。
|
||||
|
||||
@@ -527,6 +527,7 @@ export async function statusPipelinesAsCodeLane(config: UniDeskConfig, options:
|
||||
ok: runtimeProbe.value.exitCode === 0 && pacStatus.ok === true && blockers.length === 0,
|
||||
command: "agentrun control-plane status",
|
||||
mode: "pipelines-as-code-migrated-lane",
|
||||
mutation: false,
|
||||
configPath: target.configPath,
|
||||
readCaptureBudget: spec.status.readOnlyCapture,
|
||||
target: options.full || options.raw ? agentRunLaneSummary(spec) : compactAgentRunLaneStatusTarget(spec),
|
||||
@@ -902,6 +903,7 @@ export async function statusYamlLane(config: UniDeskConfig, options: StatusOptio
|
||||
ok: sourceProbe.value.exitCode === 0 && runtimeProbe.value.exitCode === 0 && mirrorProbe.value.exitCode === 0 && blockers.length === 0,
|
||||
command: "agentrun control-plane status",
|
||||
mode: "yaml-declared-node-lane",
|
||||
mutation: false,
|
||||
configPath: target.configPath,
|
||||
readCaptureBudget: spec.status.readOnlyCapture,
|
||||
target: options.full || options.raw ? agentRunLaneSummary(spec) : compactAgentRunLaneStatusTarget(spec),
|
||||
|
||||
@@ -75,6 +75,7 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
||||
const next = record(result.next);
|
||||
const disclosure = record(result.disclosure);
|
||||
const timings = record(result.timings);
|
||||
const readCaptureBudget = record(result.readCaptureBudget);
|
||||
const blockers = Array.isArray(summary.blockers) ? summary.blockers.map(String) : [];
|
||||
const warnings = Array.isArray(summary.warnings) ? summary.warnings.map(String) : [];
|
||||
const migration = record(summary.migration);
|
||||
@@ -130,8 +131,15 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
||||
displayValue(pacRead.exitCode),
|
||||
displayValue(pacFirstBreak.code),
|
||||
]]),
|
||||
` budget-config: ${displayValue(pacRead.configPath)}`,
|
||||
` next-read-only: ${displayValue(pacRead.next)}`,
|
||||
"",
|
||||
"AGENTRUN READ CAPTURE BUDGET",
|
||||
renderTable(["TIMEOUT_MS", "CONFIG_PATH"], [[
|
||||
displayValue(readCaptureBudget.timeoutMs),
|
||||
displayValue(readCaptureBudget.configPath),
|
||||
]]),
|
||||
"",
|
||||
warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((warning) => `- ${warning}`)].join("\n"),
|
||||
"",
|
||||
"NEXT",
|
||||
@@ -176,6 +184,12 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
||||
],
|
||||
),
|
||||
"",
|
||||
"AGENTRUN READ CAPTURE BUDGET",
|
||||
renderTable(["TIMEOUT_MS", "CONFIG_PATH"], [[
|
||||
displayValue(readCaptureBudget.timeoutMs),
|
||||
displayValue(readCaptureBudget.configPath),
|
||||
]]),
|
||||
"",
|
||||
warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((warning) => `- ${warning}`)].join("\n"),
|
||||
"",
|
||||
"NEXT",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { resolve } from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
import { sentinelPublishImageBuildShell } from "./hwlab-node-web-sentinel-cicd-jobs";
|
||||
import type { SentinelCicdState } from "./hwlab-node-web-sentinel-cicd-shared";
|
||||
import { renderAgentRunControlPlaneStatusSummary } from "./agentrun/public-exposure";
|
||||
import { compactHistoryJson, compactStatusSummary, pacReadOnlyCaptureObservation, renderDebugStep, renderHistory, renderStatus } from "./platform-infra-pipelines-as-code";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -87,6 +88,41 @@ describe("PaC 失败证据合同", () => {
|
||||
expect(output).toContain("next-read-only:");
|
||||
});
|
||||
|
||||
test("AgentRun status 文本披露 lane 与 PaC 两层 YAML capture 预算", () => {
|
||||
const output = renderAgentRunControlPlaneStatusSummary({
|
||||
ok: false,
|
||||
target: { node: { id: "NC01" }, lane: "nc01-v02" },
|
||||
summary: {
|
||||
migration: { migrated: true, sourceAuthority: "gitea-pipelines-as-code" },
|
||||
pac: {
|
||||
readObservation: {
|
||||
stage: "status-remote-observe",
|
||||
status: "timeout",
|
||||
elapsedMs: 25001,
|
||||
timeoutMs: 25000,
|
||||
configPath: "config/platform-infra/pipelines-as-code.yaml#observability.readOnlyCapture.timeoutMs",
|
||||
exitCode: 124,
|
||||
firstBreak: { code: "pac-read-timeout" },
|
||||
next: "bun scripts/cli.ts platform-infra pipelines-as-code status --full",
|
||||
},
|
||||
},
|
||||
blockers: ["pac-not-ready"],
|
||||
warnings: [],
|
||||
nextAction: {},
|
||||
},
|
||||
readCaptureBudget: {
|
||||
timeoutMs: 20000,
|
||||
configPath: "config/agentrun.yaml.controlPlane.lanes.nc01-v02.status.readOnlyCapture.timeoutMs",
|
||||
},
|
||||
next: {},
|
||||
timings: {},
|
||||
}).renderedText;
|
||||
expect(output).toContain("PAC READ OBSERVATION");
|
||||
expect(output).toContain("config/platform-infra/pipelines-as-code.yaml#observability.readOnlyCapture.timeoutMs");
|
||||
expect(output).toContain("AGENTRUN READ CAPTURE BUDGET");
|
||||
expect(output).toContain("config/agentrun.yaml.controlPlane.lanes.nc01-v02.status.readOnlyCapture.timeoutMs");
|
||||
});
|
||||
|
||||
test("feature-config warning 在 status、history、debug-step 同构有界投影", () => {
|
||||
const observation = extractPacSourceObservation([
|
||||
{
|
||||
|
||||
@@ -307,6 +307,7 @@ interface PacReadOnlyCaptureObservation {
|
||||
readonly stage: string;
|
||||
readonly elapsedMs: number;
|
||||
readonly timeoutMs: number;
|
||||
readonly configPath: string;
|
||||
readonly exitCode: number;
|
||||
readonly stdoutBytes: number;
|
||||
readonly stderrBytes: number;
|
||||
|
||||
Reference in New Issue
Block a user