From 2197de73360089e024aaf24810a3555a2019776e Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 07:48:19 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E7=A8=B3=E5=AE=9A=20PaC=20source=20?= =?UTF-8?q?artifact=20YAML=20=E7=A9=BA=E7=99=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../R1.1_Task_Report.md | 6 +++++ .../R1.2_Task_Report.md | 6 +++++ .../R1.3_Task_Report.md | 7 ++++++ .../R1.4_Task_Report.md | 7 ++++++ .../R1_Task_Report.md | 6 +++++ .../pac-source-artifact-yaml-whitespace.md | 25 +++++++++++++++++++ ...-pipelines-as-code-source-artifact.test.ts | 14 +++++++++++ ...infra-pipelines-as-code-source-artifact.ts | 7 +++++- 8 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md create mode 100644 docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md create mode 100644 docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md create mode 100644 docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.4_Task_Report.md create mode 100644 docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md create mode 100644 docs/MDTODO/pac-source-artifact-yaml-whitespace.md diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md new file mode 100644 index 00000000..35e2ad85 --- /dev/null +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md @@ -0,0 +1,6 @@ +# R1.1 报告 + +- 复现输入包含空 `metadata`、空数组和嵌套空数组。 +- Bun YAML 输出 `metadata: `、`spec: ` 和 `items: ` 等尾随空格。 +- `git diff --no-index --check` 稳定以非零状态报告 trailing whitespace。 + diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md new file mode 100644 index 00000000..deddebc3 --- /dev/null +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md @@ -0,0 +1,6 @@ +# R1.2 报告 + +- 在通用 `pacSourceArtifactYaml()` 中逐行移除水平尾随空白。 +- 未修改 AgentRun 或 HWLAB consumer 文件及渲染职责。 +- 序列化后重新解析与原对象完全相等。 + diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md new file mode 100644 index 00000000..18393e3d --- /dev/null +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md @@ -0,0 +1,7 @@ +# R1.3 报告 + +- 定向测试:24 项通过,0 项失败,共 287 个断言。 +- AgentRun 与 HWLAB 首次生成均成功,第二次写入均为 `mutation=false`。 +- 三个真实生成文件均可解析、无行尾空格、EOF 恰好一个换行。 +- 两个 consumer 的 `source-artifact check` 均返回 aligned。 + diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.4_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.4_Task_Report.md new file mode 100644 index 00000000..ae378dec --- /dev/null +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.4_Task_Report.md @@ -0,0 +1,7 @@ +# R1.4 报告 + +- 交付分支:`fix/1753-pac-yaml-trailing-space`。 +- 关联 issue:https://github.com/pikasTech/unidesk/issues/1753 +- PR:https://github.com/pikasTech/unidesk/pull/1754 +- 受控 preflight:`MERGEABLE`、`CLEAN`、`readyForCommanderMerge=true`。 +- 不执行合并或部署。 diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md new file mode 100644 index 00000000..7ffcfbf1 --- /dev/null +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md @@ -0,0 +1,6 @@ +# R1 总报告 + +- 通用 PaC source artifact YAML serializer 已消除 Bun 空 mapping/sequence 产生的行尾空格。 +- YAML 结构、一个 EOF 换行和重复生成幂等均已验证。 +- AgentRun 与 HWLAB consumer 无需手工修补。 + diff --git a/docs/MDTODO/pac-source-artifact-yaml-whitespace.md b/docs/MDTODO/pac-source-artifact-yaml-whitespace.md new file mode 100644 index 00000000..33f49bb3 --- /dev/null +++ b/docs/MDTODO/pac-source-artifact-yaml-whitespace.md @@ -0,0 +1,25 @@ +# PaC Source Artifact YAML 空白稳定性 + +- 来源:https://github.com/pikasTech/unidesk/issues/1753 +- 范围:通用 PaC source artifact serializer;禁止 consumer 手工修补。 + + +## R1 [completed] + +修复 PaC source artifact YAML serializer 的行尾空格,关联 https://github.com/pikasTech/unidesk/issues/1753,完成任务后将详细报告写入[任务报告](./details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md)。 + +### R1.1 [completed] + +复现空 mapping 行尾空格和 git diff --check 失败,关联 https://github.com/pikasTech/unidesk/issues/1753,完成任务后将详细报告写入[任务报告](./details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md)。 + +### R1.2 [completed] + +在通用 serializer 修复稳定 YAML 输出并保持结构不变,完成任务后将详细报告写入[任务报告](./details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md)。 + +### R1.3 [completed] + +验证 AgentRun/HWLAB 生成、EOF 和二次写入幂等,完成任务后将详细报告写入[任务报告](./details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md)。 + +### R1.4 [completed] + +提交非 draft PR、运行受控 preflight 并留下审查证据,完成任务后将详细报告写入[任务报告](./details/pac-source-artifact-yaml-whitespace/R1.4_Task_Report.md)。 diff --git a/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts b/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts index 4ca363e1..1b1a2b02 100644 --- a/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts +++ b/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts @@ -270,6 +270,20 @@ describe("source artifact serialization", () => { expect(first).toContain("apiVersion: tekton.dev/v1\nkind: Pipeline\n"); expect(Bun.YAML.parse(first)).toEqual(manifest); }); + + test("empty mappings and sequences have stable whitespace without changing structure", () => { + const manifest = { + apiVersion: "tekton.dev/v1", + kind: "PipelineRun", + metadata: {}, + spec: { params: [], workspaces: [], pipelineSpec: { tasks: [] } }, + }; + const serialized = pacSourceArtifactYaml(manifest); + expect(serialized).not.toMatch(/[\t ]+$/m); + expect(serialized.endsWith("\n")).toBe(true); + expect(serialized.endsWith("\n\n")).toBe(false); + expect(Bun.YAML.parse(serialized)).toEqual(manifest); + }); }); describe("runtime source-commit selection", () => { diff --git a/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts b/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts index ab099f76..bc77a0e4 100644 --- a/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts +++ b/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts @@ -1032,7 +1032,12 @@ function parseYamlRecord(text: string, label: string): Record { } export function pacSourceArtifactYaml(value: Record): string { - return `${Bun.YAML.stringify(value, null, 2).trim()}\n`; + const serialized = Bun.YAML.stringify(value, null, 2) + .split("\n") + .map((line) => line.replace(/[\t ]+$/u, "")) + .join("\n") + .trim(); + return `${serialized}\n`; } function requiredParam(binding: PacSourceArtifactBinding, key: string): string { From 7adcb9f09cfd465d8983348c249c58cbf1bbc929 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 08:09:33 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=9D=E7=95=99=20PaC=20YAML=20?= =?UTF-8?q?=E6=A0=87=E9=87=8F=E7=A9=BA=E7=99=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 仅规范结构性 mapping 容器头,并以完整 YAML round-trip 校验失败闭合。\n\nCo-Authored-By: Claude Opus 4.7 --- ...-pipelines-as-code-source-artifact.test.ts | 56 +++++++++++++++++++ ...infra-pipelines-as-code-source-artifact.ts | 33 +++++++++-- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts b/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts index 1b1a2b02..6891b0c6 100644 --- a/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts +++ b/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts @@ -11,6 +11,7 @@ import { canonicalSha256, canonicalizePacPipelineSpec, firstPacSourceArtifactDrift, + pacSourceArtifactSafeError, pacValueEvidence, pacSourceArtifactYaml, parsePacSourceArtifactOptions, @@ -284,6 +285,61 @@ describe("source artifact serialization", () => { expect(serialized.endsWith("\n\n")).toBe(false); expect(Bun.YAML.parse(serialized)).toEqual(manifest); }); + + test("multi-line script scalar preserves trailing spaces and tabs exactly", () => { + const script = [ + "#!/bin/sh ", + "printf 'mapping-looking scalar: ' ", + "metadata: ", + " {}", + "printf 'tab follows'\t", + "", + ].join("\n"); + const manifest = { + apiVersion: "tekton.dev/v1", + kind: "Pipeline", + metadata: { name: "scalar-round-trip" }, + spec: { tasks: [{ name: "build", taskSpec: { steps: [{ name: "build", script }] } }] }, + }; + const serialized = pacSourceArtifactYaml(manifest); + const parsed = Bun.YAML.parse(serialized) as typeof manifest; + expect(parsed).toEqual(manifest); + expect(parsed.spec.tasks[0]?.taskSpec.steps[0]?.script).toBe(script); + expect(Buffer.from(parsed.spec.tasks[0]?.taskSpec.steps[0]?.script ?? "")).toEqual(Buffer.from(script)); + expect(serialized.endsWith("\n")).toBe(true); + expect(serialized.endsWith("\n\n")).toBe(false); + }); + + test("round-trip failure exposes only secret-safe drift evidence", () => { + const secret = "ROUND_TRIP_SCRIPT_SECRET_DO_NOT_PRINT"; + let reads = 0; + const spec: Record = {}; + Object.defineProperty(spec, "script", { + enumerable: true, + get: () => { + reads += 1; + return reads <= 2 ? secret : "changed-after-serialization"; + }, + }); + + let thrown: unknown; + try { + pacSourceArtifactYaml({ apiVersion: "tekton.dev/v1", kind: "Pipeline", spec }); + } catch (error) { + thrown = error; + } + expect(thrown).toBeInstanceOf(Error); + const safe = pacSourceArtifactSafeError(thrown); + const serializedError = JSON.stringify({ + message: thrown instanceof Error ? thrown.message : String(thrown), + direct: thrown, + safe, + }); + expect(safe.code).toBe("source-artifact-yaml-round-trip-failed"); + expect(serializedError).not.toContain(secret); + expect(serializedError).not.toContain("changed-after-serialization"); + expect(serializedError).toContain("sha256"); + }); }); describe("runtime source-commit selection", () => { diff --git a/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts b/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts index bc77a0e4..1f81253d 100644 --- a/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts +++ b/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts @@ -1032,12 +1032,33 @@ function parseYamlRecord(text: string, label: string): Record { } export function pacSourceArtifactYaml(value: Record): string { - const serialized = Bun.YAML.stringify(value, null, 2) - .split("\n") - .map((line) => line.replace(/[\t ]+$/u, "")) - .join("\n") - .trim(); - return `${serialized}\n`; + const lines = Bun.YAML.stringify(value, null, 2).split("\n"); + let blockScalarHeaderIndent: number | null = null; + const normalized = lines.map((line) => { + const content = line.trim(); + const indent = line.length - line.trimStart().length; + if (blockScalarHeaderIndent !== null) { + if (content.length === 0 || indent > blockScalarHeaderIndent) return line; + blockScalarHeaderIndent = null; + } + + const structuralLine = line.replace(/:[\t ]+$/u, ":"); + if (/(?:^|:[\t ]+|-[\t ]+)[|>](?:[1-9][+-]?|[+-][1-9]?|[+-])?[\t ]*(?:#.*)?$/u.test(structuralLine)) { + blockScalarHeaderIndent = indent; + } + return structuralLine; + }).join("\n"); + const withSingleEofNewline = `${normalized.replace(/\n+$/u, "")}\n`; + const roundTrip = parseYamlRecord(withSingleEofNewline, "serialized source artifact"); + const roundTripDrift = firstCanonicalDrift(value, roundTrip, "$"); + if (roundTripDrift !== null) { + throw new PacSourceArtifactError("source-artifact-yaml-round-trip-failed", { + driftPath: roundTripDrift.path, + expectedEvidence: roundTripDrift.expected, + actualEvidence: roundTripDrift.actual, + }); + } + return withSingleEofNewline; } function requiredParam(binding: PacSourceArtifactBinding, key: string): string { From 7e212e938a89ace0914fa296622cfa06a4731174 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 08:11:26 +0200 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20PaC=20YAML=20?= =?UTF-8?q?=E7=A9=BA=E7=99=BD=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同步标量 round-trip、脱敏与最新定向测试证据,并统一报告 EOF。\n\nCo-Authored-By: Claude Opus 4.7 --- .../pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md | 1 - .../pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md | 5 ++--- .../pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md | 4 ++-- .../pac-source-artifact-yaml-whitespace/R1_Task_Report.md | 5 ++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md index 35e2ad85..a192a0c5 100644 --- a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.1_Task_Report.md @@ -3,4 +3,3 @@ - 复现输入包含空 `metadata`、空数组和嵌套空数组。 - Bun YAML 输出 `metadata: `、`spec: ` 和 `items: ` 等尾随空格。 - `git diff --no-index --check` 稳定以非零状态报告 trailing whitespace。 - diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md index deddebc3..33661729 100644 --- a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.2_Task_Report.md @@ -1,6 +1,5 @@ # R1.2 报告 -- 在通用 `pacSourceArtifactYaml()` 中逐行移除水平尾随空白。 +- 在通用 `pacSourceArtifactYaml()` 中仅规范 block scalar 外的 mapping 容器头空白。 - 未修改 AgentRun 或 HWLAB consumer 文件及渲染职责。 -- 序列化后重新解析与原对象完全相等。 - +- 输出前重新解析并逐字段对比原对象;任何漂移均以脱敏摘要失败闭合。 diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md index 18393e3d..e79c8fa3 100644 --- a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1.3_Task_Report.md @@ -1,7 +1,7 @@ # R1.3 报告 -- 定向测试:24 项通过,0 项失败,共 287 个断言。 +- 定向测试:26 项通过,0 项失败,共 297 个断言。 - AgentRun 与 HWLAB 首次生成均成功,第二次写入均为 `mutation=false`。 - 三个真实生成文件均可解析、无行尾空格、EOF 恰好一个换行。 - 两个 consumer 的 `source-artifact check` 均返回 aligned。 - +- 多行 script 的行尾双空格、tab 与伪结构文本保持字节一致。 diff --git a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md index 7ffcfbf1..29763313 100644 --- a/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md +++ b/docs/MDTODO/details/pac-source-artifact-yaml-whitespace/R1_Task_Report.md @@ -1,6 +1,5 @@ # R1 总报告 -- 通用 PaC source artifact YAML serializer 已消除 Bun 空 mapping/sequence 产生的行尾空格。 -- YAML 结构、一个 EOF 换行和重复生成幂等均已验证。 +- 通用 PaC source artifact YAML serializer 已仅在结构表示层消除 Bun 容器头行尾空格。 +- 多行 scalar 内容、YAML 结构、一个 EOF 换行和重复生成幂等均已验证。 - AgentRun 与 HWLAB consumer 无需手工修补。 -