diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml
index 2c68519b..c51ad449 100644
--- a/config/hwlab-node-lanes.yaml
+++ b/config/hwlab-node-lanes.yaml
@@ -299,7 +299,7 @@ lanes:
install:
stateDir: /tmp/hwlab-release-source-workspace-bootstrap
sourceAuthority:
- giteaMirrorRepoKey: hwlab-nc01-v03
+ giteaMirrorRepoKey: hwlab-nc01-production
cicdRepo: /root/hwlab-release-cicd.git
cicdRepoLock: /tmp/hwlab-release-cicd-repo.lock
app: hwlab-node-production
diff --git a/config/platform-infra/gitea.yaml b/config/platform-infra/gitea.yaml
index d6dd21be..9f0eaa78 100644
--- a/config/platform-infra/gitea.yaml
+++ b/config/platform-infra/gitea.yaml
@@ -375,6 +375,28 @@ sourceAuthority:
readUrl: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
configRef: config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01.git.readUrl
disposition: replaced-by-gitea
+ - key: hwlab-nc01-production
+ targetId: NC01
+ upstream:
+ repository: pikasTech/HWLAB
+ cloneUrl: https://github.com/pikasTech/HWLAB.git
+ branch: release
+ gitea:
+ owner: mirrors
+ name: pikasTech-HWLAB
+ mirrorMode: controlled-push
+ publicRead: true
+ readUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
+ gitops:
+ branch: release-gitops
+ flushDisposition: retained-for-gitops-flush
+ snapshot:
+ naming: gitea-actions-immutable-source
+ prefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/release
+ legacyGitMirror:
+ readUrl: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
+ configRef: config/hwlab-node-lanes.yaml#lanes.production.targets.NC01.git.readUrl
+ disposition: replaced-by-gitea
- key: selfmedia-nc01
targetId: NC01
credentialOverride:
diff --git a/config/platform-infra/pipelines-as-code.yaml b/config/platform-infra/pipelines-as-code.yaml
index e2572e57..37d36daf 100644
--- a/config/platform-infra/pipelines-as-code.yaml
+++ b/config/platform-infra/pipelines-as-code.yaml
@@ -214,6 +214,10 @@ repositories:
variables:
NODE: NC01
LANE: v03
+ - extends: templates.repositories.hwlabProduction
+ variables:
+ NODE: NC01
+ LANE: production
- id: selfmedia-nc01
name: selfmedia-nc01
namespace: selfmedia-ci
@@ -434,20 +438,11 @@ consumers:
NODE: NC01
LANE: production
id: hwlab-nc01-production
- repositoryRef: hwlab-nc01-v03
+ repositoryRef: hwlab-nc01-production
lane: production
pipeline: hwlab-nc01-production-ci-image-publish
pipelineRunPrefix: hwlab-nc01-production-ci-poll
argoApplication: hwlab-node-production
- params:
- source_branch: release
- gitops_branch: release-gitops
- source_snapshot_prefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/release
- pipeline_name: hwlab-nc01-production-ci-image-publish
- pipeline_run_prefix: hwlab-nc01-production-ci-poll
- service_account: hwlab-nc01-production-tekton-runner
- lane: production
- runtime_namespace: hwlab-production
sourceArtifact:
mode: remote-pipeline-annotation
renderer: hwlab-runtime-lane
@@ -758,6 +753,34 @@ templates:
node: "${NODE}"
lane: v03
runtime_namespace: hwlab-v03
+ hwlabProduction:
+ id: "hwlab-${nodeLower}-production"
+ name: "hwlab-${nodeLower}-production"
+ namespace: hwlab-ci
+ providerType: gitea
+ url: https://gitea.pikapython.com/mirrors/pikasTech-HWLAB
+ cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
+ owner: mirrors
+ repo: pikasTech-HWLAB
+ secretName: "pac-gitea-hwlab-${nodeLower}-production"
+ tokenKey: token
+ webhookSecretKey: webhook.secret
+ concurrencyLimit: 1
+ params:
+ git_read_url: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
+ git_write_url: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
+ source_branch: release
+ gitops_branch: release-gitops
+ source_snapshot_prefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/release
+ pipeline_name: "hwlab-${nodeLower}-production-ci-image-publish"
+ pipeline_run_prefix: "hwlab-${nodeLower}-production-ci-poll"
+ service_account: "hwlab-${nodeLower}-production-tekton-runner"
+ workspace_pvc_size: 8Gi
+ git_ssh_secret: hwlab-git-ssh
+ pipeline_timeout: 1h0m0s
+ node: "${NODE}"
+ lane: production
+ runtime_namespace: hwlab-production
consumers:
agentrunV02:
id: "agentrun-${nodeLower}-v02"
diff --git a/docs/MDTODO/details/pr-merge-driven-automatic-delivery/R7.2_Task_Report.md b/docs/MDTODO/details/pr-merge-driven-automatic-delivery/R7.2_Task_Report.md
index 18aeab92..05b7d0a4 100644
--- a/docs/MDTODO/details/pr-merge-driven-automatic-delivery/R7.2_Task_Report.md
+++ b/docs/MDTODO/details/pr-merge-driven-automatic-delivery/R7.2_Task_Report.md
@@ -19,23 +19,48 @@
- `https://hwlab.pikapython.com` YAML publicExposure;
- PostgreSQL database、role、Secret consumer 和 migration ledger;
- Kafka client、direct publish、refresh replay、projector 和 live SSE consumer group。
-- production 与 development 共享同一个 HWLAB Gitea PaC Repository CR;两个 lane 使用独立 consumer,并由 consumer params 覆盖 source branch、snapshot、Pipeline 和 runtime identity。
-- 共享 Repository 避免同一镜像仓库重复声明 PaC Repository;独立 consumer 仍保持 production 自动交付路径与 development 完全隔离。
-- `lanes.production.targets.NC01.sourceAuthority.giteaMirrorRepoKey` 已统一为共享 authority `hwlab-nc01-v03`,不再引用不存在的 production repo key。
-- delivery authority parser 以 consumer params 解析 branch 与 snapshot:
- - 显式 consumer override 优先;
- - 未显式覆盖时继承 Repository 默认;
- - 多分支镜像优先匹配同 branch authority;
- - 唯一共享镜像允许 production consumer 复用同一 Repository。
+- production 与 development 复用同一个 Gitea 物理仓库
+ `mirrors/pikasTech-HWLAB`,但分别声明独立 YAML source identity:
+ - development:`hwlab-nc01-v03` / `v0.3` / `v0.3-gitops`;
+ - production:`hwlab-nc01-production` / `release` / `release-gitops`。
+- production PaC consumer 精确引用 `hwlab-nc01-production` repository identity;
+ branch、snapshot 和 GitOps revision 由该 repository identity 声明,
+ 不再由 consumer params 覆盖 development identity。
+- `lanes.production.targets.NC01.sourceAuthority.giteaMirrorRepoKey`
+ 精确引用 `hwlab-nc01-production`。
+- delivery authority parser 已删除同 target/owner/repo 的跨 branch fallback:
+ - 必须同时精确匹配 target、Gitea owner/name 和 source branch;
+ - 缺少或冲突时 fail-closed;
+ - 禁止把 `v0.3` identity 猜作 `release` authority。
- 通用 HWLAB lane parser 和 plan 已投影 migration ledger,并从 owning YAML 动态生成合法 lane 提示。
- `config/platform-infra/kafka.yaml` 已声明 production Cloud API Kafka ACL,未改变 topic、实时和回放架构。
+## 方案依据
+
+- OpenGitOps 原则要求 desired state 声明式、版本化且不可变,并由软件代理自动拉取和持续 reconcile;因此 branch identity 与 immutable snapshot 必须由 owning YAML 明确声明,不能依赖运行时猜测。
+- Tekton Pipelines 将 Git revision 作为显式输入:
+ - source branch 只负责选择受控镜像入口;
+ - 实际构建继续使用受控 snapshot/commit revision;
+ - mutable branch 名不直接充当构建 provenance。
+- Argo CD 的 `targetRevision` 明确选择 Git branch、tag 或 commit;production 使用独立 `release-gitops`,避免与 `v0.3-gitops` 共用可变 desired state。
+- Kubernetes 建议以不可变 image digest 或等价内容寻址 revision
+ 固定工作负载版本:
+ - Pipeline 继续产出 commit-pinned artifact;
+ - GitOps/Argo 只消费已固定 revision;
+ - 禁止从运行面或脏 workspace 构建。
+- 官方参考:
+ - OpenGitOps principles:;
+ - Tekton Git resolver revision:;
+ - Argo CD tracking strategies:;
+ - Kubernetes image digest:。
+
## Target 原入口证据
以下命令均在 `NC01:/root/unidesk/.worktree/2010-hwlab-release-production-lane` 执行:
- `git diff --check`:通过。
- `bun --check`:
+ - `scripts/src/cicd-delivery-authority.ts`:通过;
- `scripts/src/hwlab-fake-model-provider.ts`:通过;
- `scripts/src/hwlab-node-lanes.ts`:通过;
- `scripts/src/hwlab-node/entry.ts`:通过;
@@ -50,19 +75,22 @@
- namespace 为 `hwlab-v03`;
- development 配置继续可解析。
- `bun scripts/cli.ts platform-infra pipelines-as-code plan --target NC01 --consumer hwlab-nc01-production`:
- - Repository 为共享的 `hwlab-nc01-v03`;
+ - Repository 为独立 identity `hwlab-nc01-production`;
- consumer 为独立的 `hwlab-nc01-production`;
- Tekton Pipeline 为 `hwlab-nc01-production-ci-image-publish`;
- Argo Application 为 `hwlab-node-production`;
- `single-path`、`yaml-source-of-truth`、`public-repository-url`、`gitea-internal-source` 和 `runtime-zero-docker` 均为 `true`。
- `bun scripts/cli.ts platform-infra pipelines-as-code plan --target NC01 --consumer hwlab-nc01-v03`:通过,development consumer 保持独立。
- delivery authority 与 renderer 定向断言通过:
- - NC01 只声明一个 HWLAB Gitea repository,key 为 `hwlab-nc01-v03`;
- - production 与 v03 的 `repositoryRef` 均为 `hwlab-nc01-v03`;
- - production consumer 保持 `hwlab-nc01-production`;
- - production branch 为 `release`,snapshot prefix 为 release 独立前缀;
- - authority 解析结果为 `pac-pr-merge`;
- - feature config renderer 使用 production consumer。
+ - NC01 声明两个 HWLAB source identity,分别为
+ `hwlab-nc01-v03` 与 `hwlab-nc01-production`;
+ - 两个 identity 指向同一 Gitea 物理仓库;
+ - production `repositoryRef` 为 `hwlab-nc01-production`,branch 为 `release`;
+ - development `repositoryRef` 为 `hwlab-nc01-v03`,branch 为 `v0.3`;
+ - 两个 lane 的 snapshot prefix 与 GitOps branch 独立;
+ - production consumer 携带 `v0.3` source branch 查询时返回
+ `authority-identity-mismatch`,证明跨 branch fail-closed;
+ - parser 源码不再包含 `branchAuthorityRepositories` fallback。
- 纯 Kafka 定向断言通过:
- `directPublish`、`liveKafkaSse`、`kafkaRefreshReplay` 为 `true`;
- `transactionalProjector`、`projectionOutboxRelay`、`projectionRealtime` 为 `false`;
@@ -72,7 +100,18 @@
- GitOps branch、runtime namespace、Argo Application、public origin;
- PostgreSQL database、role、Secret、migration ledger;
- Kafka direct publish、refresh replay 和 live SSE group;
- - shared Repository、independent consumer 和各 lane branch 参数。
+ - shared physical Gitea repository、independent source/repository/consumer identity。
+
+## 验证边界
+
+- `platform-infra gitea validate --target NC01` 未能进入本任务新增 identity 的运行面校验:
+ - 命令先被既有 `agentrun-nc01-release` 的
+ `legacyGitMirror.configRef` 缺失阻断;
+ - 该缺失存在于 `origin/master`,不属于本任务改动;
+ - 本轮未扩大范围修复该独立问题。
+- 因用户明确禁止人工 mirror、PipelineRun、Argo 和创建 `release` branch,
+ 本轮证据证明的是 YAML 声明、controlled-push 选择、PaC binding 与 fail-closed 隔离;
+ 合并后的真实自动链仍由后续授权阶段通过正常 GitHub release 提交触发。
## Primary Workspace 边界
diff --git a/docs/reference/hwlab.md b/docs/reference/hwlab.md
index ba78202f..c41ab1fe 100644
--- a/docs/reference/hwlab.md
+++ b/docs/reference/hwlab.md
@@ -21,6 +21,16 @@
- patch 不得改变交付 authority 或冒充终态;
- 结论必须写回 owning YAML/源码并由正常 PR 自动交付;
- 临时 patch 随后撤销或由声明式交付覆盖。
+- HWLAB production release delivery authority:
+ - development 与 production 复用同一个
+ `mirrors/pikasTech-HWLAB` Gitea 物理仓库;
+ - owning YAML 必须分别声明 `hwlab-nc01-v03` 与
+ `hwlab-nc01-production` source identity;
+ - production identity 固定选择 `release`、独立 immutable snapshot prefix
+ 与 `release-gitops`;
+ - production PaC consumer 必须精确引用 production repository identity;
+ - delivery authority parser 必须精确匹配 target、owner/name 与 branch,
+ 缺少 production identity 时 fail-closed,禁止回退到 `v0.3`。
- 进入任何 HWLAB 工作前,按 `NC01/v03` 解析 route/workspace/sourceBranch/kubeRoute/runtime namespace 做预检、快进和验证:工作面是 `NC01:/root/hwlab-v03`、source branch 是 `v0.3`、k3s route 是 `NC01:k3s`、runtime namespace 是 `hwlab-v03`、公网入口由 YAML 的 NC01 target 声明。
- NC01 的 node-local registry 是 k3s workload/PVC,不是 host Docker registry。`hwlab nodes control-plane infra status --node NC01 --lane v03` 应显示 registry workload ready、PVC bound 和 endpoint ready;必须通过受控 `runtime-image preload` 和 `infra tools-image build/status` 补齐 BuildKit、runtime/base 和 tools image,再把 HWLAB/AgentRun status 与 web-probe smoke 作为可用性证据。
- HWLAB 项目内长期规则入口仍以目标 repo 的 `AGENTS.md` 为准。进入已解析的目标 workspace 后,必须重新读取该 workspace 的规则文件;不能只凭主 server 的压缩上下文继续操作。
diff --git a/scripts/src/cicd-delivery-authority.ts b/scripts/src/cicd-delivery-authority.ts
index ce3d5793..79790a70 100644
--- a/scripts/src/cicd-delivery-authority.ts
+++ b/scripts/src/cicd-delivery-authority.ts
@@ -149,20 +149,12 @@ export function readCicdDeliveryAuthorityCatalog(): CicdDeliveryAuthorityCatalog
pacRepositories.filter((candidate) => same(candidate.id, repositoryRef)),
`${path}.repositoryRef=${repositoryRef}`,
);
- const params = item.params === undefined ? null : record(item.params, `${path}.params`);
- const sourceBranch = params === null
- ? pacRepository.sourceBranch
- : requiredString(params.source_branch ?? pacRepository.sourceBranch, `${path}.params.source_branch`);
- const sourceSnapshotPrefix = params === null
- ? pacRepository.sourceSnapshotPrefix
- : requiredString(params.source_snapshot_prefix ?? pacRepository.sourceSnapshotPrefix, `${path}.params.source_snapshot_prefix`);
- const authorityRepositories = giteaRepositories.filter((candidate) => same(candidate.targetId, node)
- && same(candidate.owner, pacRepository.owner)
- && same(candidate.repo, pacRepository.repo));
- const branchAuthorityRepositories = authorityRepositories.filter((candidate) => same(candidate.sourceBranch, sourceBranch));
const authorityRepository = exactlyOne(
- branchAuthorityRepositories.length > 0 ? branchAuthorityRepositories : authorityRepositories,
- `${path} source authority ${node}/${pacRepository.owner}/${pacRepository.repo}@${sourceBranch}`,
+ giteaRepositories.filter((candidate) => same(candidate.targetId, node)
+ && same(candidate.owner, pacRepository.owner)
+ && same(candidate.repo, pacRepository.repo)
+ && same(candidate.sourceBranch, pacRepository.sourceBranch)),
+ `${path} source authority ${node}/${pacRepository.owner}/${pacRepository.repo}@${pacRepository.sourceBranch}`,
);
return {
consumerId,
@@ -172,8 +164,8 @@ export function readCicdDeliveryAuthorityCatalog(): CicdDeliveryAuthorityCatalog
pipelineRunPrefix,
repositoryRef,
sourceRepository: authorityRepository.sourceRepository,
- sourceBranch,
- sourceSnapshotPrefix,
+ sourceBranch: authorityRepository.sourceBranch,
+ sourceSnapshotPrefix: pacRepository.sourceSnapshotPrefix,
giteaOwner: pacRepository.owner,
giteaRepository: pacRepository.repo,
giteaRepositoryUrl: pacRepository.url,