@agile-team/wl-skills-gate 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +5 -1
  3. package/bin/wl-skills-gate.js +21 -5
  4. package/docs/06-gitlab.md +21 -32
  5. package/docs/07-jenkins.md +48 -43
  6. package/docs/16-project-ci-integration.md +6 -6
  7. package/docs/17-onboarding.md +12 -0
  8. package/docs/19-nine-dimension-review.md +7 -1
  9. package/docs/README.md +1 -1
  10. package/fixtures/onboarding/managed-assets-0.10.0/java-unit.mjs +84 -0
  11. package/fixtures/onboarding/managed-assets-0.10.0/project-actions.mjs +266 -0
  12. package/lib/automation.js +64 -8
  13. package/lib/ci-prepare.js +91 -0
  14. package/lib/control-service.js +20 -7
  15. package/lib/deployment.js +4 -4
  16. package/lib/governance-notifications.js +7 -0
  17. package/lib/governance.js +27 -2
  18. package/lib/onboard-complete.js +35 -3
  19. package/lib/onboard-config.js +113 -13
  20. package/lib/onboard-registry.js +8 -2
  21. package/lib/onboard.js +1 -1
  22. package/lib/release-input.js +28 -7
  23. package/lib/release-review.js +120 -0
  24. package/lib/release-set.js +33 -6
  25. package/lib/setup-central.js +20 -2
  26. package/package.json +1 -1
  27. package/scripts/lib-tests/automation.test.mjs +7 -1
  28. package/scripts/lib-tests/ci-prepare.test.mjs +24 -0
  29. package/scripts/lib-tests/control-service.test.mjs +14 -0
  30. package/scripts/lib-tests/deployment.test.mjs +6 -4
  31. package/scripts/lib-tests/onboard-registry.test.mjs +33 -1
  32. package/scripts/lib-tests/prevention-ci-chain.test.mjs +139 -0
  33. package/scripts/lib-tests/prevention-full-system.test.mjs +133 -0
  34. package/scripts/lib-tests/prevention-governance-chain.test.mjs +198 -0
  35. package/scripts/lib-tests/prevention-project-chain.test.mjs +395 -0
  36. package/scripts/lib-tests/release-input.test.mjs +33 -9
  37. package/scripts/lib-tests/setup-central.test.mjs +11 -1
  38. package/scripts/test-support/release-review-fixture.mjs +32 -0
  39. package/templates/ci/Jenkinsfile +38 -1
  40. package/templates/ci/Jenkinsfile.release-sign +1 -0
  41. package/templates/ci/gitlab-backend.yml +6 -1
  42. package/templates/ci/gitlab-frontend.yml +6 -2
  43. package/templates/onboarding/automation/project-actions.mjs +43 -7
  44. package/templates/onboarding/backend/java-unit.mjs +1 -1
  45. package/templates/onboarding/backend/quality-tools.package.json +1 -1
  46. package/templates/onboarding/legacy/managed-assets.json +36 -0
  47. package/templates/platform/README.md +8 -2
  48. package/templates/platform/deployment-config.json +8 -2
  49. package/templates/platform/release-input-config.json +6 -3
  50. package/templates/platform/release-review-trust.json +46 -0
  51. package/templates/quality/rules.lock.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.11.0] — 2026-10-16
2
+
3
+ - 修复早期warning摘要未被常驻任务消费、首次来源失联/回流异常静默及旧环境复验可复用的断链,增加可靠执行异常与恢复提醒。
4
+ - 正式构建前自动按SHA选择中央最新九维Review并重查CI;组合签发与部署强制重验真实签名、独立授权及原件,旧组合需补证重新生成。
5
+ - CI按中央批准工作副本清单/锁准备依赖,禁任意生命周期脚本,仅用固定必要适配;质量报告与Review失败均保留原件。
6
+ - 已接项目按受管资产摘要批准升级,保留自定义策略、必测与POM;复用已提交自有版本锁并由实际首扫核验,修复当前Review草稿入口路径。
7
+
1
8
  ## [0.10.0] — 2026-10-15
2
9
 
3
10
  - 新增可启动的质量接入菜单和跨系统启动器,首次接入逐阶段展示、确认、执行、复扫与HTML反馈,不再要求人工传递计划摘要。
package/README.md CHANGED
@@ -47,7 +47,7 @@ Gate 是质量执行与裁决层,复用项目已有检查器和适用的 WL
47
47
  ```bash
48
48
  npx --yes --ignore-scripts --registry https://registry.npmjs.org/ \
49
49
  --@agile-team:registry=https://registry.npmjs.org/ \
50
- @agile-team/wl-skills-gate@0.10.0 setup project
50
+ @agile-team/wl-skills-gate@0.11.0 setup project
51
51
  ```
52
52
 
53
53
  | 项目 | 依赖装在哪里 | 业务构建 |
@@ -57,6 +57,8 @@ npx --yes --ignore-scripts --registry https://registry.npmjs.org/ \
57
57
 
58
58
  `quality-tools/` 是 Java 仓库内的 Node 工具依赖目录。内置后端配套针对 `wl-produce` PL;其他 Maven 项目先由负责人配置模块与必测。移动端、UniApp 不套用 PC 配套。
59
59
 
60
+ 已接项目升级仍选同一接入任务:识别旧受管脚本并展示差异,保留项目自定义策略、必测和 POM。
61
+
60
62
  首次接入反馈在 `quality-reports/setup/<sessionId>/result.html`,质量报告在 `quality-reports/quality-report.html`。只有真实首扫及 Hook 正反验证通过,才返回 `local-validated-awaiting-remote-acceptance`。GitLab/Jenkins 强制检查、独立 Review、真实到群及部署另做现场验收。
61
63
 
62
64
  接线配置、非交互任务参数与失败处理见 [接入指南](./docs/17-onboarding.md)。
@@ -86,6 +88,8 @@ npx --yes --ignore-scripts --registry https://registry.npmjs.org/ \
86
88
  | 真实 CI 证据、身份与 Review | [可信 CI 采集](./docs/18-ci-source.md) · [九维 Review](./docs/19-nine-dimension-review.md) | 旧报告、缺证据、自审与过期批准被拒绝 |
87
89
  | 飞书通知、认领、超时与复验 | [中央执行接线](./templates/platform/README.md) | 实际送达、失败重试、认领和独立关闭均留记录 |
88
90
 
91
+ 流水线按批准的工作副本清单和依赖锁准备环境,不运行任意生命周期脚本。构建前自动匹配本次 SHA 的正式 Review 并重查 CI;组合与部署再次核验签名及原件,缺失就阻断。普通警告按周期汇总,首次采集故障、持续异常和恢复也有提醒。
92
+
89
93
  项目检查作业与受控签发、部署、通知作业隔离。Hook 提供早反馈;服务端强制检查负责防绕过。
90
94
 
91
95
  ## 按需深入
@@ -18,7 +18,7 @@ function options(allowed) {
18
18
  const name = rest[i];
19
19
  if (!name.startsWith("--")) { out.positional.push(name); continue; }
20
20
  if (!allowed.includes(name) || Object.hasOwn(out, name)) throw new Error(`未知或重复参数: ${name}`);
21
- if (FLAGS.has(name)) { out[name] = true; continue; }
21
+ if (FLAGS.has(name) || command === "ci-prepare" && name === "--apply") { out[name] = true; continue; }
22
22
  const value = rest[++i];
23
23
  if (!value || value.startsWith("--")) throw new Error(`参数 ${name} 缺少值`);
24
24
  out[name] = value;
@@ -79,6 +79,22 @@ try {
79
79
  } else if (command === "setup") {
80
80
  const { runSetupCli } = await import("../lib/setup.js");
81
81
  process.exitCode = await runSetupCli(rest);
82
+ } else if (command === "ci-prepare") {
83
+ const o = options(["--root", "--apply", "--confirm", "--expect-workspace-hash", "--expect-lock-hash"]);
84
+ if (o.positional.length || !o["--apply"] && o["--confirm"]) throw new Error("ci-prepare 默认只读;执行需 --apply --confirm 及中央批准清单/锁摘要");
85
+ const { planCiPreparation, prepareCiDependencies } = await import("../lib/ci-prepare.js");
86
+ const result = o["--apply"] ? prepareCiDependencies({ root: o["--root"], confirm: !!o["--confirm"], expectWorkspaceHash: o["--expect-workspace-hash"], expectLockHash: o["--expect-lock-hash"] }) : planCiPreparation({ root: o["--root"] });
87
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
88
+ process.exitCode = result.status === "blocked" ? 2 : 0;
89
+ } else if (command === "verify-release-review") {
90
+ const o = options(["--config", "--project", "--sha", "--record", "--evidence-root", "--publication-dir", "--out-dir"]);
91
+ if (o.positional.length) throw new Error("verify-release-review 不接受位置参数");
92
+ for (const key of ["--config", "--project", "--sha"]) if (!o[key]) throw new Error(`verify-release-review 缺少 ${key}`);
93
+ if (o["--publication-dir"] && (o["--record"] || o["--evidence-root"]) || !o["--publication-dir"] && (!o["--record"] || !o["--evidence-root"])) throw new Error("选择中央 --publication-dir 自动匹配本次SHA;或维护者明确提供 --record 与 --evidence-root,两种方式不能混用");
94
+ const review = await import("../lib/release-review.js");
95
+ if (o["--out-dir"] && !o["--publication-dir"]) throw new Error("--out-dir 仅用于自动选择后的本次CI重查证据");
96
+ const result = o["--publication-dir"] ? await review.selectReleaseReview({ configFile: o["--config"], project: o["--project"], sha: o["--sha"], publicationDir: o["--publication-dir"], outDir: o["--out-dir"] }) : review.verifyReleaseReview({ configFile: o["--config"], project: o["--project"], sha: o["--sha"], recordFile: o["--record"], evidenceRoot: o["--evidence-root"] });
97
+ process.stdout.write(JSON.stringify(Object.fromEntries(["status", "project", "sha", "reviewId", "reviewerId", "authorId", "recordHash", "reviewDigest", "bodyDigest", "evidenceDigest", "sourceDigest", "trustHash", "liveVerified", "liveCheckDirectory"].filter((key) => result[key] !== undefined).map((key) => [key, result[key]])), null, 2) + "\n");
82
98
  } else if (command === "doctor") {
83
99
  const o = options(["--root", "--kind", "--out"]);
84
100
  if (o.positional.length) throw new Error("doctor 不接受位置参数");
@@ -129,11 +145,11 @@ try {
129
145
  const result = collectReviewEvidence({ configFile: o["--config"], sourceFile: o["--source"], inputFile: o["--input"], recordFile: o["--record"], evidenceRoot: o["--evidence-root"], outDir: o["--out"] });
130
146
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
131
147
  } else if (command === "release-set") {
132
- const operation = rest.shift(), o = options(["--input", "--evidence-root", "--expect-input-hash", "--private-key", "--issuer", "--out", "--release-set", "--public-key", "--expect-issuer", "--expect-release-set-hash", "--env"]);
148
+ const operation = rest.shift(), o = options(["--input", "--evidence-root", "--expect-input-hash", "--private-key", "--issuer", "--out", "--release-set", "--public-key", "--expect-issuer", "--expect-release-set-hash", "--env", "--review-config"]);
133
149
  if (o.positional.length) throw new Error("release-set 不接受位置参数");
134
150
  const { createReleaseSet, verifyReleaseSet } = await import("../lib/release-set.js"); let result;
135
- if (operation === "create") { protectOutput(o["--out"], [o["--input"], o["--private-key"]]); result = createReleaseSet({ inputFile: o["--input"], evidenceRoot: o["--evidence-root"], expectInputHash: o["--expect-input-hash"], privateKeyFile: o["--private-key"], issuer: o["--issuer"], outFile: o["--out"] }); }
136
- else if (operation === "verify") result = verifyReleaseSet({ releaseSetFile: o["--release-set"], publicKeyFile: o["--public-key"], expectIssuer: o["--expect-issuer"], expectReleaseSetHash: o["--expect-release-set-hash"], env: o["--env"] });
151
+ if (operation === "create") { protectOutput(o["--out"], [o["--input"], o["--private-key"], o["--review-config"]]); result = createReleaseSet({ inputFile: o["--input"], evidenceRoot: o["--evidence-root"], expectInputHash: o["--expect-input-hash"], privateKeyFile: o["--private-key"], issuer: o["--issuer"], outFile: o["--out"], reviewConfigFile: o["--review-config"] }); }
152
+ else if (operation === "verify") result = verifyReleaseSet({ releaseSetFile: o["--release-set"], publicKeyFile: o["--public-key"], expectIssuer: o["--expect-issuer"], expectReleaseSetHash: o["--expect-release-set-hash"], env: o["--env"], reviewConfigFile: o["--review-config"] });
137
153
  else throw new Error("release-set 子命令 create/verify");
138
154
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
139
155
  } else if (command === "deployment") {
@@ -296,5 +312,5 @@ try {
296
312
  if (rest.length) throw new Error("self-test 不接受额外参数");
297
313
  const { runAllFixtures } = await import("../scripts/run-fixtures.mjs");
298
314
  process.exitCode = await runAllFixtures();
299
- } else throw new Error("用法: wl-skills-gate <setup|onboard|run|doctor|automation|control|collect-ci|review-facts|review-ci|review-collect|review-publish|lifecycle-build|lifecycle-review|governance|inspect|cost|fix|validate-report|commit-message|manifest|attest|attest-collected|release-set|deployment|review-draft|review-sign|verify-artifact|promote|promote-apply|self-test>,详见 docs/README.md");
315
+ } else throw new Error("用法: wl-skills-gate <setup|onboard|ci-prepare|verify-release-review|run|doctor|automation|control|collect-ci|review-facts|review-ci|review-collect|review-publish|lifecycle-build|lifecycle-review|governance|inspect|cost|fix|validate-report|commit-message|manifest|attest|attest-collected|release-set|deployment|review-draft|review-sign|verify-artifact|promote|promote-apply|self-test>,详见 docs/README.md");
300
316
  } catch (e) { console.error(`[${command || "wl-skills-gate"}] ${e.message}`); process.exitCode = 2; }
package/docs/06-gitlab.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 06 GitLab 接线
2
2
 
3
- 对应 B1~B6;当前为接线说明/模板,平台权限和实际 Runner 尚待验收。本包配置为公共 npm 发布;使用须先取得 AGILE TEAM 书面授权,见 [LICENSE](../LICENSE)。以下 registry 是可选内网镜像/托管接线示例,不代表本包已发布到该地址;使用内网地址时须另行核验实际镜像版本与访问权限。
3
+ 包内候选、独立 Review、签发、条件更新和采集程序已交付;GitLab 权限与实际 Runner 另做现场验收。本包配置为公共 npm 发布;使用须先取得 AGILE TEAM 书面授权,见 [LICENSE](../LICENSE)。以下 registry 是可选内网镜像/托管接线示例,不代表本包已发布到该地址;使用内网地址时须另行核验实际镜像版本与访问权限。
4
4
 
5
5
  ## 发布到内网 registry
6
6
 
@@ -15,33 +15,22 @@
15
15
 
16
16
  ## 只读检查作业
17
17
 
18
- 前端中央模板示意;hash 由批准登记表下发,不能从候选文件现算后当批准:
19
-
20
- ```yaml
21
- wl-quality-check:
22
- stage: test
23
- image: node:22
24
- script:
25
- - corepack enable
26
- - pnpm install --frozen-lockfile --prod=false
27
- - >-
28
- pnpm exec wl-skills-gate run
29
- --profile standard --sha "$CI_COMMIT_SHA"
30
- --expect-policy-hash "$WL_APPROVED_POLICY_HASH"
31
- --expect-rules-hash "$WL_APPROVED_RULES_HASH"
32
- artifacts:
33
- when: always
34
- paths: [quality-reports/]
35
- reports:
36
- codequality: quality-reports/code-quality/gl-code-quality-report.json
37
- # junit: quality-reports/junit/*.xml # 真实测试生产者已接入后启用
38
- ```
18
+ 从固定 Gate 包提取 [前端模板](../templates/ci/gitlab-frontend.yml) 或 [后端模板](../templates/ci/gitlab-backend.yml),冻结到统一 CI 仓库;项目 include 固定 tag/commit,合并实际 stages/workflow/rules,保留所有业务必需作业。质量作业不得 allow_failure 或用 `|| true` 吞错。
19
+
20
+ | 受控项 | 用途 |
21
+ |---|---|
22
+ | `WL_APPROVED_QUALITY_IMAGE` / `WL_APPROVED_JAVA_QUALITY_IMAGE` | 固定前端 / Java 检查镜像,包含批准 Node ≥22.12、项目固定包管理器及 JDK/Maven |
23
+ | `WL_APPROVED_BOOTSTRAP_GATE_CLI` | 镜像预装的固定 Gate,先于项目依赖执行 `ci-prepare` |
24
+ | `WL_APPROVED_WORKSPACE_HASH`、`WL_APPROVED_DEPENDENCY_LOCK_HASH` | 已审阅提交的工作副本清单与对应依赖锁真实字节摘要 |
25
+ | `WL_APPROVED_POLICY_HASH`、`WL_APPROVED_RULES_HASH`、`WL_APPROVED_BASELINE_HASH`、`WL_APPROVED_WAIVERS_HASH` | 技术经理/运维批准登记的四类摘要,不能从候选现算后自封批准 |
26
+
27
+ 模板按锁安装并关闭任意生命周期,只运行固定 Gate 内的必要准备适配。包管理器不符、未知 prepare/postinstall、锁或源码漂移均阻断;不使用 corepack 隐式下载另一版本。准备成功后,前端执行根工程 Gate,后端执行 quality-tools 内 Gate 与实际 Maven 检查。模板自动生成 Review 事实并 always 归档报告,不把准备阶段当质量通过。
39
28
 
40
- 这个 job 假定项目已固定安装 Gate 0.9.0 并提交锁文件,不持有签发/晋级/部署/通知凭据。`node:22` 仅为版本系列示意,正式接线固定实际 Node 22.12+ 镜像版本/摘要与项目 pnpm 版本。Kit/UI/BD 当前要求 Node ≥22,不能沿用组合检查的 Node 20 示例。后端使用受控 Node+项目 JDK+Maven 镜像,先真实接线 wl-quality profile/检查器;若 Gate 由中央镜像提供,使用其固定 CLI,不与项目本地解析路径混用。详细接入见 [16 项目与 CI](16-project-ci-integration.md)。
29
+ 检查 Runner 不持有签发、晋级、部署或通知凭据。Gate 与项目清单固定同一批准版本;升级时重新审阅清单、依赖锁和批准摘要。详细入口见 [17 统一接入](17-onboarding.md)。
41
30
 
42
31
  上述采用全量 standard,不依赖 MR。若增加增量,分别处理 push/MR/候选/定时场景、首次 push 全零 SHA 与浅克隆缺历史,head 与 checkout 相同。MR 专属变量只在实际 MR pipeline 使用。[GitLab 变量说明](https://docs.gitlab.com/ci/variables/predefined_variables/#predefined-variables-for-merge-request-pipelines)。
43
32
 
44
- 普通 include 锁 tag 用于复用,项目仍能覆盖/删除,不能作为不可绕过证明。[include 合并规则](https://docs.gitlab.com/ci/yaml/includes/#override-included-configuration-values)。硬门来自平台强制策略或独立受控晋级作业与受保护共享分支。
33
+ 普通 include 锁 tag 用于复用,项目仍能覆盖/删除,不能作为不可绕过证明。[include 合并规则](https://docs.gitlab.com/ci/yaml/includes/#override-included-configuration-values)。硬门来自服务端强制策略或独立受控晋级作业与受保护共享分支。
45
34
 
46
35
  ## 候选检查:无特权 Runner
47
36
 
@@ -49,14 +38,14 @@ wl-quality-check:
49
38
  wl-skills-gate promote --source "$SOURCE_BRANCH" --target "$TARGET_BRANCH" \
50
39
  --expect-policy-hash "$WL_APPROVED_POLICY_HASH" \
51
40
  --expect-rules-hash "$WL_APPROVED_RULES_HASH" \
52
- --prepare-command 'pnpm install --frozen-lockfile'
41
+ --prepare-command "$WL_APPROVED_CANDIDATE_PREPARE_COMMAND"
53
42
  ```
54
43
 
55
- 后端改为其真实依赖准备命令。输出在 quality-reports/promotion-<id>/:候选检查、unsigned 结果、promotion-record.json、candidate.bundle。失败也保留记录/报告。这个作业只能读源/目标,不更新共享分支;归档 bundle 供控制器导入。
44
+ 候选工作树使用中央固定的 `WL_APPROVED_CANDIDATE_PREPARE_COMMAND`,从候选根调用同一 `ci-prepare`,带入批准清单/锁摘要;不让作者提供任意安装命令或生命周期。Java 业务准备和实际检查由批准适配执行。输出在 quality-reports/promotion-<id>/:候选检查、unsigned 结果、promotion-record.json、candidate.bundle。失败也保留记录/报告。这个作业只能读源/目标,不更新共享分支;归档 bundle 供控制器导入。
56
45
 
57
46
  ## 独立 Review、签发和更新
58
47
 
59
- 1. 平台保存候选 SHA、源/目标 HEAD 与作业 ID;授权非作者 Reviewer 检查候选,逐项处理阻断问题。
48
+ 1. 受控作业保存候选 SHA、源/目标 HEAD 与作业 ID;授权非作者 Reviewer 检查候选,逐项处理阻断问题。
60
49
  2. 自动生成九维草稿,补齐适用证据,非作者逐维确认;独立身份服务签发绑定正文/证据/候选的授权receipt。Review作业用固定身份公钥/issuer校验后review-sign,使用与身份服务分离的Review密钥;见[19 九维Review](19-nine-dimension-review.md)。
61
50
  3. 签发作业权威查询检查来源和成功状态,再 attest:standard 档、固定期望、源 job ID,使用 --promotion-record 绑定该候选记录。
62
51
  4. 受控更新作业执行:
@@ -75,9 +64,9 @@ wl-skills-gate promote-apply signed-proof.json \
75
64
  --out controller-audit
76
65
  ```
77
66
 
78
- 若有基线/例外,补其批准摘要。控制器在新 bare 仓库核验签名、Review、候选父提交、最新远端源/目标 HEAD,核验父提交后以批准的旧 HEAD 条件更新;不运行候选代码/项目 Hook。平台对同一目标分支串行化(例如 resource_group),配合保护分支和禁止普通写入;晚到候选需重新生成和 Review。
67
+ 若有基线/例外,补其批准摘要。控制器在新 bare 仓库核验签名、Review、候选父提交、最新远端源/目标 HEAD,核验父提交后以批准的旧 HEAD 条件更新;不运行候选代码/项目 Hook。运维对同一目标分支串行化(例如 resource_group),配合保护分支和禁止普通写入;晚到候选需重新生成和 Review。
79
68
 
80
- 更新前先落审计记录,审计目录不可写则拒绝更新;所有尝试保留控制器审计。若更新已成功但最终审计写入失败,返回 `promoted-audit-failed` 和非零退出码;平台必须核对远端实际 HEAD 并补齐记录,不能当作未更新而盲目重试。临时 bare 仓库在失败和成功后均清理。
69
+ 更新前先落审计记录,审计目录不可写则拒绝更新;所有尝试保留控制器审计。若更新已成功但最终审计写入失败,返回 `promoted-audit-failed` 和非零退出码;运维必须核对远端实际 HEAD 并补齐记录,不能当作未更新而盲目重试。临时 bare 仓库在失败和成功后均清理。
81
70
 
82
71
  B2/B3 分支保护与服务端接收控制仍由管理员配置,不能用本地 Hook 宣称“服务端拒绝接收”。
83
72
 
@@ -85,12 +74,12 @@ B2/B3 分支保护与服务端接收控制仍由管理员配置,不能用本
85
74
 
86
75
  B6/D1~D4 与样板同步建设:失败按项目/指纹幂等建单,分派主备,消息包含问题/报告/流水线认证链接,认领和复验回写主台账,定时升级无人认领/逾期。通知任务使用独立凭据并重试;通知失败不覆盖原门禁退出码。对账发现未运行或配置/权限漂移,不仅监听失败事件。
87
76
 
88
- ## 0.5.1 接线补充
77
+ ## 摘要、并发与失败处理
89
78
 
90
- 有 baseline/waivers 文件时,检查、promote、attest、promote-apply、release/verify-artifact 各环节都传同一批准摘要;它们来自平台审批登记,而不是在候选里自行计算并批准。候选依赖准备结果保存 dependency-prepare.json(退出码、耗时、超时/截断、脱敏输出),promotion-record.preparation 引用日志;prepare 失败不丢失网络/仓库错误上下文。日志脱敏是辅助措施,仍须限制访问与保留期,禁止把凭据直接写进命令。
79
+ 有 baseline/waivers 文件时,检查、promote、attest、promote-apply、release/verify-artifact 各环节都传同一批准摘要;它们来自中央审批登记,而不是在候选里自行计算并批准。候选依赖准备结果保存 dependency-prepare.json(退出码、耗时、超时/截断、脱敏输出),promotion-record.preparation 引用日志;prepare 失败不丢失网络/仓库错误上下文。日志脱敏是辅助措施,仍须限制访问与保留期,禁止把凭据直接写进命令。
91
80
 
92
81
  控制器使用 `--force-with-lease=refs/heads/<target>:<批准旧SHA>` 原子核对目标 HEAD,同时强制候选恰有批准的目标/源两个父提交,因此只允许批准旧 HEAD 的快进。此处不开放任意强推/回退功能。上线必须实测当前 GitLab 保护分支与受控账号接受这一条件更新,并保持禁止一般强推的保护策略;不能为了适配而放开任意强推。[Git push 显式 lease 语义](https://git-scm.com/docs/git-push)。
93
82
 
94
- 该条件更新保护目标分支;源分支最后核对与目标更新不是多引用原子事务。如果团队要求“更新瞬间源分支也必须最新”,由服务端接收校验、固定源引用或平台锁补充实现,不能将本地 HEAD 检查写成全局原子保证。
83
+ 该条件更新保护目标分支;源分支最后核对与目标更新不是多引用原子事务。如果团队要求“更新瞬间源分支也必须最新”,由服务端接收校验、固定源引用或受控锁补充实现,不能将本地 HEAD 检查写成全局原子保证。
95
84
 
96
85
  台账准入不得简单使用“项目只要有未关闭问题就禁止所有发布”。修复发布可凭绑定本次修复 SHA/范围的有效质量、Review 和回归证据,在授权范围内进入目标环境复验,再关闭;原事故的恢复与根因整改分开记录。具体准入参见 12 与落地执行附录 C。
@@ -1,62 +1,67 @@
1
1
  # 07 Jenkins 构建、签发与部署
2
2
 
3
- 对应 C1~C5;命令代码已测,真实作业/权限/制品和双仓组合待验收。
3
+ 使用包内 [CI 模板](../templates/ci/),由米传汇智和运维冻结实际作业、SCM/ref、镜像、脚本、凭据与修改权限。项目作者不能替换或跳过受控作业;真实环境另做验收。
4
4
 
5
- ## 作业拆分
5
+ ## 一次接好五类作业
6
6
 
7
- - 检查/构建作业可执行项目源码,不能挂签发/部署凭据。
8
- - 签发作业仅下载权威查询核验的报告和 manifest,在无项目代码的受控工作区执行固定版本 Gate;持有签发私钥。
9
- - 部署作业固定公钥、目标环境、来源作业和批准策略,验证真实制品后部署;不重新构建。
7
+ | 顺序 | 模板 | 自动执行与成功条件 |
8
+ |---:|---|---|
9
+ | 1 | [Jenkinsfile](../templates/ci/Jenkinsfile) | checkout → 按实际 SHA 选择正式 Review、重查当前 CI → 固定依赖准备 → release 全量检查 → 实际构建 → manifest |
10
+ | 2 | [Jenkinsfile.attest](../templates/ci/Jenkinsfile.attest) | 独立查询成功作业、下载原始报告/清单/制品,再签组件证明;不执行项目代码 |
11
+ | 3 | [Jenkinsfile.release-sign](../templates/ci/Jenkinsfile.release-sign) | 消费助手生成且已另行批准的组合输入;核验两仓正式 Review、组件证明、实际制品与兼容依据,签固定组合 |
12
+ | 4 | [Jenkinsfile.deployment](../templates/ci/Jenkinsfile.deployment) | 核验组合、当前环境与固定适配器 → 自动登记计划 → 登录后独立授权 deploy → 执行并交付原始记录 |
13
+ | 5 | 同一部署模板的 smoke / recover | 各自生成新计划、取得对应动作授权;独立验证人授权 smoke,恢复依据已批准的旧组合和恢复约束 |
10
14
 
11
- 先盘点 Jenkins 实际触发、脚本维护位置、作业修改/启动权限和凭据;受控共享库/配置版本化。不把作者可改 Jenkinsfile 当最终权限边界。
15
+ [Review 签发模板](../templates/ci/Jenkinsfile.review-sign)独立消费认证页面的九维确认授权,产生正式记录与交付 manifest。它与构建、组件/组合签发、部署分别运行,不把私钥授给项目检查 Runner。
12
16
 
13
- ## 检查与构建
17
+ ## 构建前检查什么
14
18
 
15
- 在确定 checkout HEAD、安装真实依赖之后:
19
+ `verify-release-review --publication-dir` 从中央固定目录自动选择本次 checkout SHA 的当前正式记录,不要求人复制动态报告路径。固定信任配置核验 Review 与身份授权公钥、issuer、复核名单、九维/机器策略和原始证据;随后重新查询当前 CI。缺证据、自审、错误 SHA、被替代的 Review、重试后的失败/新作业均阻断。
16
20
 
17
- ```bash
18
- wl-skills-gate run --profile release --sha "$CHECKED_SHA" --env "$TARGET_ENV" \
19
- --expect-policy-hash "$WL_APPROVED_POLICY_HASH" --expect-rules-hash "$WL_APPROVED_RULES_HASH"
20
- # 在同一不可变源码/受控环境中执行真实构建,成功后:
21
- wl-skills-gate manifest --project "$WL_PROJECT" --env "$TARGET_ENV" \
22
- --artifacts dist --out quality-reports/artifact-manifest.json
23
- ```
21
+ 中央 Review 原件可只读挂载。当前 CI 复验记录写入本机 `quality-reports/release-review-live/<uuid>/`,结果和失败原因写入 `release-review-verification.json/.log`,随 `post always` 归档。
24
22
 
25
- 后端 artifacts 指向实际 JAR/制品;配置、迁移脚本按实际分发路径用 --configs 纳入清单。post always 归档报告和对应工件。多个环境重新构建时重新出清单/证明,旧证明不跨环境使用。
23
+ `ci-prepare` 使用中央预装固定 Gate,核对已提交 `quality/workspace.json` 与依赖锁的批准字节摘要,按锁安装且关闭任意生命周期;必要准备仅用固定适配。未知脚本、版本不符、锁或源码漂移均停止。它不在 CI 激活 Hook,也不代替后续质量检查。
26
24
 
27
- ## 受控签发
25
+ 实际构建脚本来自批准作业配置。前端 manifest 纳入真实 dist,后端纳入真实 JAR;分发配置和数据脚本用 `WL_CONFIG_PATHS` 纳入。部署只消费这些不可变制品,不重新构建。
28
26
 
29
- 平台先查询批准的具体质量/构建作业,核验模板/Runner/身份、真实 SHA、成功状态、工具/覆盖、工件完整性,再在独立签发工作区:
27
+ ## 组合与授权
30
28
 
31
- ```bash
32
- wl-skills-gate attest --summary evidence/quality-summary.json \
33
- --profile release --project "$WL_PROJECT" --sha "$CHECKED_SHA" --env "$TARGET_ENV" \
34
- --expect-policy-hash "$WL_APPROVED_POLICY_HASH" --expect-rules-hash "$WL_APPROVED_RULES_HASH" \
35
- --manifest evidence/artifact-manifest.json \
36
- --private-key "$WL_SIGNING_KEY_FILE" --issuer "$WL_GATE_ISSUER" \
37
- --pipeline-id "$WL_VERIFIED_PIPELINE_ID" --job-id "$WL_VERIFIED_JOB_ID" \
38
- --out evidence/signed-proof.json
39
- ```
29
+ 发布负责人从接入助手选择“生成组合发布输入”。固定 [输入配置](../templates/platform/release-input-config.json)引用真实采集/签发原件与 `reviewPublicationDir`,程序按两仓实际 SHA 自动选 Review,展示作者、复核人、作业、部署顺序和 schema。确认只授权生成输入;组合签发与部署另行批准。
40
30
 
41
- 有基线/例外时必须补其批准摘要。签名认证签发者和绑定内容,不能替代平台来源查询;issuer/jobId 字符串本身不是查询。这个查询连接需按真实 GitLab/Jenkins API、身份与授权实施,当前包不自动配置。
31
+ [release-review-trust.json](../templates/platform/release-review-trust.json)固定中央公钥、人员、摘要、证据根、collector 和 catalog。[deployment-config.json](../templates/platform/deployment-config.json)通过 `reviewConfigFile` 绑定同份信任。`release-set create/verify --review-config` 与部署消费都会重验正式 Review 及归档原件;直接给旧 formal 文件也不能绕过当前版本。旧组合缺 Review 归档须重新生成。
42
32
 
43
- ## 部署前验证
33
+ 普通新发布要求当前有效 Review。smoke/recover 对已签组合按其签发时点核验历史 Review,仍须核验原签名、实际制品/环境和新的独立动作授权,不因此取得新发布或关闭权限。
44
34
 
45
- ```bash
46
- wl-skills-gate verify-artifact evidence/signed-proof.json \
47
- --summary evidence/quality-summary.json --project "$WL_PROJECT" \
48
- --sha "$CHECKED_SHA" --env "$TARGET_ENV" \
49
- --public-key "$WL_GATE_PUBLIC_KEY" --expect-issuer "$WL_GATE_ISSUER" \
50
- --expect-pipeline-id "$WL_VERIFIED_PIPELINE_ID" --expect-job-id "$WL_VERIFIED_JOB_ID" \
51
- --expect-policy-hash "$WL_APPROVED_POLICY_HASH" --expect-rules-hash "$WL_APPROVED_RULES_HASH" \
52
- --manifest evidence/artifact-manifest.json --artifact-root artifact-staging \
53
- --max-age-minutes 1440
54
- ```
35
+ `run release`、`attest`、`verify-artifact` 是机器事实接口,单独通过不能代替正式 Review、组合准入、部署授权或问题关闭。构建成功也不关闭问题;必要环境独立复验后,再由认证处理页执行关闭。
55
36
 
56
- artifact-staging 中保持 manifest 相对路径。任何非零退出停止 Deploy。固定公钥/期望值/来源 ID 从平台控制配置和查询取得,不接受作者上传的公钥、passed 字符串或自行计算的批准值。
37
+ ## 固定变量与凭据
57
38
 
58
- 验证后部署只读不可变工件,发布记录写入证明、制品、配置和实际环境。冒烟失败停止后续环境晋级、按恢复方案处理并建单;扫描或部署成功不自动关闭线上缺陷。
39
+ 下表配置在受控作业/凭据库,不能由作者临时提交或覆盖。路径/摘要由批准资产与真实采集结果交接,生成值由作业传递,发布操作者不逐次抄填。
59
40
 
60
- ## 保留待接入
41
+ | 作业 | 固定变量或输入 |
42
+ |---|---|
43
+ | 全部 Jenkins 模板 | `WL_APPROVED_GATE_CLI`:批准镜像/安装包中的固定 CLI;Node、项目包管理器、JDK/Maven 和镜像摘要另行固定 |
44
+ | 检查/构建 | `WL_PROJECT`、`WL_TARGET_ENV`;`WL_APPROVED_REVIEW_TRUST_CONFIG`、`WL_APPROVED_REVIEW_PUBLICATION_DIR`;`WL_APPROVED_WORKSPACE_HASH`、`WL_APPROVED_DEPENDENCY_LOCK_HASH`;`WL_APPROVED_POLICY_HASH`、`WL_APPROVED_RULES_HASH`、`WL_APPROVED_BASELINE_HASH`、`WL_APPROVED_WAIVERS_HASH`;`WL_APPROVED_BUILD_SCRIPT`、`WL_ARTIFACT_PATHS` |
45
+ | 构建可选项 | `WL_CONFIG_PATHS`:实际分发配置/脚本;`WL_APPROVED_REVIEW_BASE_SHA`:批准的差异基点 |
46
+ | 组件签发 | `WL_APPROVED_GATE_ROOT`、`WL_FIXED_CI_SOURCE_CONFIG`、`WL_CENTRAL_CI_REQUEST`、`WL_NEW_COLLECTED_DIRECTORY`、`WL_FIXED_COMPONENT_SIGN_REQUEST`;请求绑定本次实际来源、候选/环境、清单、四类策略摘要和输出 |
47
+ | 正式 Review 签发 | `WL_REVIEW_PUBLISH_CONFIG`:隔离签发配置 |
48
+ | 组合签发 | `WL_APPROVED_RELEASE_INPUT`、`WL_APPROVED_RELEASE_INPUT_HASH`、`WL_CENTRAL_EVIDENCE_ROOT`、`WL_APPROVED_REVIEW_TRUST_CONFIG`、`WL_RELEASE_ISSUER`、`WL_NEW_RELEASE_SET_FILE` |
49
+ | 部署/复验 | `WL_DEPLOYMENT_CONFIG`、`WL_DEPLOYMENT_CONFIG_ID`、`WL_SELECTED_RELEASE_SET`、`WL_SELECTED_RELEASE_SET_HASH`、`WL_FIXED_ENVIRONMENT_STATE`、`WL_CONTROL_DATA_DIR`、`WL_CONTROL_ORIGIN` |
50
+ | 中央调度 | `WL_CENTRAL_AUTOMATION_CONFIG`;Jenkins 定时作业与 systemd/常驻循环择一 |
61
51
 
62
- 双仓 release-manifest 组合核验(前后端 SHA/契约/脚本/批准)、生产恢复演练、现场冒烟、紧急通道及事后补检仍须平台实现和留证,不能因为单仓 verify-artifact 通过而标为完成。
52
+ | 凭据 ID / 环境变量 | 只授给谁 |
53
+ |---|---|
54
+ | `wl-ci-review-readonly-token` → `WL_GITLAB_READ_TOKEN` | 构建前 Review 重查与独立 Review 签发;构建模板仅在校验阶段绑定 |
55
+ | 来源配置的 `tokenEnv` / `userEnv` | 对应查询作业;Jenkins 来源另按配置注入 `WL_JENKINS_READ_USER`、`WL_JENKINS_READ_TOKEN`,不授源码执行阶段 |
56
+ | `wl-component-proof-signing-key` → `WL_COMPONENT_KEY` | 独立组件签发 |
57
+ | `wl-review-signing-key` → `WL_REVIEW_PRIVATE_KEY_FILE` | 独立正式 Review 签发 |
58
+ | `wl-release-set-signing-key` → `WL_RELEASE_KEY` | 独立组合签发 |
59
+ | `wl-deployment-execution-key` → `WL_EXECUTOR_KEY` | 受控部署/复验执行;实际部署 API 凭据由固定适配配置的 `secretEnv` 注入 |
60
+
61
+ `WL_CHECKED_SHA` 来自实际 checkout。`ACTION` 是 deploy/smoke/recover 选项;`WL_CURRENT_PLAN_ID`、`WL_NEW_DEPLOYMENT_PLAN`、`WL_NEW_EXECUTION_DIRECTORY` 由模板生成。Jenkins 提供的 `JOB_NAME/BUILD_NUMBER/BUILD_URL` 必须与部署配置匹配;不以人工 SHA 参数代替真实 SCM revision。
62
+
63
+ 中央飞书、身份与事件签名凭据由[中央接线配置](../templates/platform/README.md)引用并隔离,不放入检查/构建作业。运行前创建受控持久目录、输入队列和归档目录;通知失败保留原始检查/部署结果,并由待发箱与独立监控反馈。
64
+
65
+ ## 验收
66
+
67
+ 至少演练:正常完整组合、缺/旧 Review、当前 CI 失败或重试、错 SHA、替换制品/配置、错环境、无授权/重放、冒烟失败与显式恢复。成功与失败都保留原始报告和作业 ID;真实 Jenkins 权限、实际部署/冒烟与群投递另留现场证据。
@@ -19,8 +19,8 @@
19
19
  | 层 | 完成条件 |
20
20
  |---|---|
21
21
  | 项目 | 固定执行器/锁/受管资产,正确根目录,统一命令,轻量 Hook,检查不改源码 |
22
- | 流水线 | 对真实 SHA 执行批准范围、实际测试与构建,保留完整原生报告;blocked/invalid 均不晋级 |
23
- | 统一质量服务 | 共享分支保护、批准策略、真实候选、独立 Review/签发/制品验证;删 Hook/job 也不能绕过 |
22
+ | 流水线 | 按批准工作副本清单/锁准备环境,关闭任意生命周期;对真实 SHA 检查/测试;构建前正式 Review 与当前 CI 重验;失败留原件并阻断 |
23
+ | 统一质量服务 | 共享分支保护、真实候选、九维独立授权/签发;组合与部署重验当前 Review、实际制品和对应动作授权 |
24
24
  | 治理 | 可信事件、幂等主台账、主备/复核、认证跳转、可靠待发箱、SLA/对账、独立关闭 |
25
25
 
26
26
  “共享实现已验证”“业务项目已接入”“已接项目准入已验收”“业务问题已关闭”分别登记。模拟和隔离验证不能代替真实接线证据。本轮只完善共享资产,业务项目/test-qa 不改动,不发送真实消息。
@@ -33,12 +33,12 @@
33
33
 
34
34
  BD 的 verify:quality-codecc 等属于源码包维护者夹具验证。业务项目执行自己的 POM、源码和真实测试。PMD 7 与 P3C/PMD 6 分开调用和保存输出。
35
35
 
36
- ## 本轮新增共享资产
36
+ ## 共享入口与资产
37
37
 
38
38
  | 资产 | 使用位置与边界 |
39
39
  |---|---|
40
- | onboard complete | 包内统一识别、差异确认、固定安装、规则/POM/锁与Hook首扫;项目差异和远端验收独立留证 |
41
- | 九维Review | 标准/发布检查后自动草稿,逐维证据、正文/摘要/候选和独立身份授权;正式晋级重验 |
40
+ | setup project / workspace | 首次接入编排差异确认、固定安装、规则/POM/锁与 Hook 首扫;新克隆按已提交清单恢复,不重写策略 |
41
+ | 九维Review | 自动草稿、非作者逐维授权、独立签发;构建按 SHA 自动选择当前正式版本并重查 CI,组合/部署再验原件 |
42
42
  | collect-ci与分类卡片 | 真实GitLab/Jenkins API与原始工件、构建/Review事实、同源卡片/待发箱,默认不真实发送 |
43
43
  | doctor | 只读盘点前后端的依赖/资产/策略/POM 准备度,不执行项目测试或安装;CI/Jenkins 作业另由统一质量服务核验 |
44
44
  | JUnit 多报告与共享 producer | 显式 files/reportDirs/requiredSuites/requiredTests,真实同轮生产,全量 XML 与 testcase 校验 |
@@ -61,4 +61,4 @@ BD 的 verify:quality-codecc 等属于源码包维护者夹具验证。业务项
61
61
  - [14 低 token 执行](14-low-token-execution.md):唯一生产者、摘要阅读与成本。
62
62
  - [质量服务配置与样例](../templates/platform/README.md):可信 receipt、事件/动作/对账及消息预览。
63
63
 
64
- 实际文件名以包目录为准。团队操作手册只呈现一次接入、自动检查、确认后脚本修复、复扫反馈、构建部署与关闭验收;资产、CLI/POM/CI参数和来源移入明细附录。Gate 的 templates/onboarding 已纳入固定 npm 包,统一入口为 onboard;工作区 quality-onboarding-examples 保留历史适配与兼容入口。已复制脚本仍须经升级计划审核同步。先验前后端各一个模块与一条真实告警,再扩面;正式准入从开始就拒绝确定缺陷、必测失败和证据失效。
64
+ 实际文件名以包目录为准。团队操作手册只呈现一次接入、自动检查、确认后脚本修复、复扫反馈、构建部署与关闭验收;资产、CLI/POM/CI参数和来源移入明细附录。Gate 的 templates/onboarding 已纳入固定 npm 包,统一操作入口为 setup,底层首次接入契约为 onboard --complete;工作区 quality-onboarding-examples 保留历史适配与兼容入口。已复制脚本仍须经升级计划审核同步。先验前后端各一个模块与一条真实告警,再扩面;正式准入从开始就拒绝确定缺陷、必测失败和证据失效。
@@ -30,8 +30,20 @@
30
30
 
31
31
  **wl-quality:打开项目检查接入状态** 在允许自动任务的受信工作区打开时执行,只读、不联网、不安装。IDE 的 Workspace Trust 与自动任务许可由用户环境控制;Git clone 自身不会执行仓库脚本。首次初始化成功后,日常仍由 Hook 自动检查。
32
32
 
33
+ ### 已接项目升级
34
+
35
+ 先提交已批准的配置、清单和受管脚本。同一项目接入任务展示固定依赖和已登记受管资产的升级差异,确认后继续;0.10.0旧脚本通过包内固定摘要识别。自定义policy、九维适用范围、必测与POM原文保留;未提交策略、自改脚本或未登记旧版先审阅适配。升级后审阅提交供新clone使用,不删文件绕过冲突。
36
+
33
37
  ### 运维与发布输入
34
38
 
39
+ GitLab/Jenkins模板用中央预装固定Gate调用`ci-prepare`,中央登记`quality/workspace.json`和依赖锁的真实字节摘要;按锁安装时关闭任意生命周期,必要准备仅用内置固定适配,不激活CI Hook。准备通过不等于质量检查通过。
40
+
41
+ pnpm预检和安装同时禁用pnpmfile执行;发现项目或全局pnpmfile声明会停止,由工程工具维护者提供固定摘要、允许目录和幂等验证适配。`--ignore-scripts`本身不覆盖pnpmfile,[pnpm说明](https://pnpm.io/id/11.x/pnpmfile)可供维护者核对。
42
+
43
+ Jenkins在准备和构建前调用`verify-release-review --publication-dir`,以checkout SHA自动选最新正式Review并重查当前CI,记录放本机quality-reports,不写只读中央原件。发布输入配置固定`reviewConfigFile`和组件`reviewPublicationDir`;组合及部署强制重验同候选Review签名、独立授权和原件。旧组合缺该证据需重新生成,不接受布尔批准。
44
+
45
+ 中央输入队列(正式Review交付、部署回流)首次由运维在受控持久卷创建;运行中缺失视为异常并提醒,不能自动造空目录。普通warning当周期汇总,首次/持续采集故障与恢复通过同一可靠待发箱反馈;配置或台账本身不可用由独立监控兜底。
46
+
35
47
  中央交互先询问配置目录及独立Review签发是否分机,默认分机,不读取独立私钥。只预检时使用 `setup central`;经批准启动时使用 `setup central --start --watch`,消息仍只预览。真实发送另加 `--send`,启动确认会明确显示发送范围。非交互启动要求 `--config-dir DIR --start --watch --confirm --plan-hash HASH`;分机时加 `--separate-review-signer`。服务以前台受控任务运行,SIGINT/SIGTERM 清理本任务启动的服务,不安装系统服务、不改GitLab/Jenkins权限或密钥、不启动独立签发。
36
48
 
37
49
  发布负责人维护 `templates/platform/release-input-config.json` 的项目副本。`setup release-input --config FILE` 展示实际组件、SHA、作业和输出,确认后生成输入。非交互生成需 `--confirm --config-hash HASH --input-hash HASH`;原件变化会使旧确认失效。作者映射、兼容性结论、恢复方案仍由对应负责人批准。生成后的原件摘要在签发前再次复核。
@@ -11,10 +11,16 @@
11
11
  | 汇总草稿 | `buildReviewInput` 自动取本轮原始 Gate 报告;其他采集器补提交、分支、diff、交互、测试、CI 和责任证据 | 固定九维 `review.md`、`review-draft.json`;永远 `pending-confirmation` |
12
12
  | 人工确认 | 受控身份服务核对实际登录身份、非作者、候选和九维结论,再签发 `review-authorization` receipt | 仅 `review-publish`,绑定当前正文/证据/候选;不是修复或发布权限 |
13
13
  | 正式签发 | `signReviewRecord` / `review-sign`:独立身份公钥、固定 issuer、受控规则摘要与 Review 私钥 | 严格 v2 `review-record`,签名覆盖正文和证据摘要、逐维确认及授权 receipt |
14
- | 使用前重验 | `verifyReview`;`promote-apply` 已实际调用此验证 | 重验原始文件、正文、九维完整性、当前 Gate 报告、授权签名/时效与候选,任一不符即拒绝 |
14
+ | 候选使用前重验 | `verifyReview`;`promote-apply` 实际调用 | 重验原始文件、正文、九维完整性、Gate 报告、授权签名/时效与候选;源/目标 HEAD 另由晋级控制器核对 |
15
+ | 构建与新发布 | `verify-release-review --publication-dir`;`release-set create/verify --review-config`;`deployment` | 固定信任核验当前正式版本;构建前另重查真实 CI;组合归档 Review 签名/正文/必要原件,部署再验 |
16
+ | 历史组合复验/恢复 | `deployment` 的 smoke / recover | 按原组合签发时点核验历史 Review,仍验证原件、制品、环境和新的独立动作授权;不授予新发布或关闭权限 |
15
17
 
16
18
  草稿不能放行。只上传扫描报告只能覆盖部分编码/扫描事实,其余维度明确显示 `pending-evidence`。正式签发必须九维齐全;不适用项须已批准的策略写明事实,再由非作者逐项确认。Code Review 和扫描门禁不能标成 N/A。
17
19
 
20
+ 生产发布信任使用固定 [release-review-trust.json](../templates/platform/release-review-trust.json),绑定公钥、授权 issuer、复核名单、九维/机器策略、证据根、collector 和 catalog。中央登记新 Review 时更新当前版本索引;旧入口重复登记不回滚索引。同 SHA 新增待确认补证后,旧正式记录也不能用于构建、组合签发或新部署;直接传旧 formal 文件同样被拒绝。旧目录没有当前版本索引时,触发批准质量流水线的新运行并登记新入口,不修改旧记录或伪造批准。
21
+
22
+ 构建任务从固定正式交付目录自动匹配本次 SHA,再用固定只读 CI 凭据重新查询必需作业和原件,重试/失败/变化即停止。中央原件可以只读;本轮复验与失败记录写入本机构建报告目录。实际接线、变量与凭据分工见 [Jenkins 指南](07-jenkins.md),开发者无需抄报告路径或编写授权 JSON。
23
+
18
24
  ## 文件契约
19
25
 
20
26
  采集作业把本轮所需文件下载到独立的 `evidenceRoot`,从真实来源 API 核对项目、候选、作业终态和访问权限。每项证据写明 `id/type/source/project/candidateSha/file/dimensionIds`;文件仅接受根目录内的明确相对路径,不接受链接或越界路径。
package/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # wl-skills-gate 技术手册索引
2
2
 
3
- 当前版本 0.10.0;对照团队落地执行 V3.4。技术宣贯基线仍为 V3.1;团队具体操作单源见 [16 项目与运维入口](16-project-ci-integration.md),本索引提供工具契约与参数参考。
3
+ 当前版本 0.11.0;对照团队落地执行 V3.4。技术宣贯基线仍为 V3.1;团队具体操作单源见 [16 项目与运维入口](16-project-ci-integration.md),本索引提供工具契约与参数参考。
4
4
 
5
5
  授权声明见 [LICENSE](../LICENSE);公开获取不授予使用、复制、修改或再分发授权,申请入口为 [仓库 Issues](https://github.com/ChenyCHENYU/wl-skills-gate/issues)。
6
6
 
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ // wl-produce 接入示例:基于 Gate 0.10.0 模板,显式使用华新 settings 并解除父 POM 跳测。
3
+ // Copy to scripts/quality/java-unit.mjs. All paths are relative to the Java repo root.
4
+ // The plan is approved configuration. Maven failures never become success.
5
+ import path from "node:path";
6
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, lstatSync, readdirSync, rmSync } from "node:fs";
7
+ import { spawnSync } from "node:child_process";
8
+ import { createHash } from "node:crypto";
9
+
10
+ const hash = (input) => createHash("sha256").update(input).digest("hex");
11
+ const root = process.cwd();
12
+ function inside(rel) {
13
+ if (typeof rel !== "string" || !/^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(rel) || rel.split("/").some((p) => !p || p === "." || p === "..")) throw new Error("计划路径须为无别名的明确仓库内相对路径");
14
+ const file = path.resolve(root, rel);
15
+ if (!file.startsWith(root + path.sep)) throw new Error("计划路径越出仓库");
16
+ let current = root;
17
+ for (const part of path.relative(root, file).split(path.sep)) { current = path.join(current, part); let stat; try { stat = lstatSync(current); } catch (error) { if (error.code !== "ENOENT") throw error; } if (stat?.isSymbolicLink()) throw new Error("计划路径不接受符号链接"); }
18
+ return file;
19
+ }
20
+ const argv = process.argv.slice(2);
21
+ if (argv.length !== 2 || argv[0] !== "--plan") { process.stderr.write("用法: node scripts/quality/java-unit.mjs --plan quality/java-unit.plan.json\n"); process.exit(2); }
22
+ let plan, planText, staleReports = [];
23
+ const tracked = new Set();
24
+ const listing = spawnSync("git", ["ls-files", "-z"], { cwd: root, encoding: "utf8", shell: false });
25
+ if (listing.status === 0) for (const file of listing.stdout.split("\0").filter(Boolean)) tracked.add(file);
26
+ function output(rel) {
27
+ const file = inside(rel);
28
+ if (tracked.has(rel)) throw new Error("单元报告/日志不能覆盖已跟踪输入");
29
+ if (existsSync(file) && !lstatSync(file).isFile()) throw new Error("单元报告/日志不是普通文件");
30
+ return file;
31
+ }
32
+ try {
33
+ planText = readFileSync(inside(argv[1]), "utf8"); plan = JSON.parse(planText);
34
+ if (plan.schemaVersion !== 1 || !Array.isArray(plan.modules) || !plan.modules.length || typeof plan.prepareDependencies !== "boolean") throw new Error("单元计划结构无效");
35
+ if (new Set(plan.modules.map((m) => m.path)).size !== plan.modules.length) throw new Error("单元计划模块重复");
36
+ const reports = new Set();
37
+ for (const m of plan.modules) {
38
+ const module = inside(m.path);
39
+ if (!existsSync(inside(`${m.path}/pom.xml`)) || !Array.isArray(m.reportFiles) || !m.reportFiles.length) throw new Error("模块/POM/批准报告缺失");
40
+ for (const rel of m.reportFiles) { const file = output(rel); if (path.dirname(file) !== path.join(module, "target", "surefire-reports") || !/^TEST-.+\.xml$/.test(path.basename(file)) || reports.has(rel)) throw new Error("报告须为模块明确 Surefire XML,不能遗漏/重复"); reports.add(rel); }
41
+ const directory = inside(`${m.path}/target/surefire-reports`);
42
+ if (existsSync(directory)) for (const name of readdirSync(directory)) if (name.endsWith(".xml")) staleReports.push(output(`${m.path}/target/surefire-reports/${name}`));
43
+ }
44
+ if (plan.offline !== undefined && typeof plan.offline !== "boolean") throw new Error("offline 只能为布尔值");
45
+ inside("quality-reports/java-unit");
46
+ for (const name of ["prepare.log", "unit.log", "unit-execution.json"]) output(`quality-reports/java-unit/${name}`);
47
+ } catch (error) { process.stderr.write(`[java-unit] ${error.message}\n`); process.exit(2); }
48
+
49
+ const out = inside("quality-reports/java-unit"); mkdirSync(out, { recursive: true });
50
+ const execution = { schemaVersion: 1, eventId: process.env.WL_GATE_EVENT_ID || null, sha: process.env.WL_GATE_COMMIT_SHA || null,
51
+ planHash: hash(planText), modules: plan.modules.map((m) => m.path), startedAt: new Date().toISOString(), phases: [], status: "running", reports: [] };
52
+ const command = process.env.WL_MAVEN_COMMAND || (process.platform === "win32" ? "mvn.cmd" : "mvn");
53
+ const common = ["-B", "-ntp", "-s", ".mvn/settings-hx.xml", ...(plan.offline ? ["-o"] : []), "-pl", plan.modules.map((m) => m.path).join(",")];
54
+ function run(phase, args) {
55
+ const started = Date.now();
56
+ const result = spawnSync(command, args, { cwd: root, encoding: "utf8", timeout: 600000, maxBuffer: 32 * 1024 * 1024, shell: false });
57
+ // CI Runner must keep secrets out of commands/project output; logs have restricted artifact access.
58
+ let text = (result.stdout || "") + (result.stderr || "");
59
+ for (const [name, value] of Object.entries(process.env)) if (/TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE_KEY|WEBHOOK/i.test(name) && value.length >= 4) text = text.split(value).join("[REDACTED]");
60
+ text = text.replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/gi, "$1[REDACTED]@")
61
+ .replace(/(\b(?:authorization\s*[:=]\s*(?:bearer\s+)?|(?:token|password|passwd|secret|_authToken)\s*[:=]\s*))[^\s,;]+/gi, "$1[REDACTED]");
62
+ const log = output(`quality-reports/java-unit/${phase}.log`); writeFileSync(log, text);
63
+ execution.phases.push({ phase, exitCode: result.status, durationMs: Date.now() - started, log: path.relative(root, log), error: result.error ? result.error.code || "execution-error" : null });
64
+ if (result.status !== 0 || result.error) throw new Error(`${phase} 失败,检查 ${path.relative(root, log)}`);
65
+ }
66
+ try {
67
+ // All modules and output paths were preflighted before any removal or Maven call.
68
+ for (const file of staleReports) rmSync(file);
69
+ // Preparation does not count as test evidence. unit does not use -am on empty dependency modules.
70
+ if (plan.prepareDependencies) run("prepare", [...common, "-am", "install", "-Dmaven.test.skip=true"]);
71
+ run("unit", [...common, "-Pwl-quality-unit", "verify", "-Dmaven.test.skip=false", "-DskipTests=false"]);
72
+ const expected = new Set(plan.modules.flatMap((m) => m.reportFiles));
73
+ const actual = new Set();
74
+ for (const m of plan.modules) {
75
+ const dir = inside(`${m.path}/target/surefire-reports`);
76
+ for (const name of readdirSync(dir)) if (name.endsWith(".xml")) {
77
+ const rel = `${m.path}/target/surefire-reports/${name}`;
78
+ const file = inside(rel); if (!lstatSync(file).isFile()) throw new Error("Surefire 报告不是普通文件"); actual.add(rel);
79
+ }
80
+ }
81
+ if (expected.size !== actual.size || [...expected].some((rel) => !actual.has(rel))) throw new Error("实际 Surefire XML 与批准全集不同,拒绝漏测/未登记用例");
82
+ execution.reports = [...actual].sort().map((rel) => ({ file: rel, sha256: hash(readFileSync(inside(rel))) })); execution.status = "completed";
83
+ } catch (error) { execution.status = "invalid"; execution.reason = error.message; process.stderr.write(`[java-unit] ${error.message}\n`); process.exitCode = 2; }
84
+ finally { execution.completedAt = new Date().toISOString(); writeFileSync(output("quality-reports/java-unit/unit-execution.json"), JSON.stringify(execution, null, 2) + "\n"); }