release: add one-click sole reviewer signing
This commit is contained in:
@@ -73,4 +73,6 @@ Do not reintroduce the old root `apps/`、`services/`、`packages/`、`sdks/`、
|
||||
8. **提速不能绕过底线。** 身份与租户隔离、合同事实完整性、版本保护、备份恢复、迁移兼容、TLS/密钥、失败接管、正式政策及最终公众切流授权不得为赶工而取消。
|
||||
9. **单一人工发布审核人。** 本轮唯一人工发布审核人为陈钰麒(总经理);技术门禁、数据库/备份/恢复核验和证据比对由系统自动完成,不再追加第二名发布审核人。60 资产+14 Prompt 只作一次批次签名批准,系统生成 74 项应用证据;Provider、公开政策、恢复与回退也只接受该审核人的具名决定,其中每次恢复或回退只接受一条 `release_owner` 批准。离线根密钥与日常审批密钥必须用途隔离且不得复用。两名普通 UAT 账号只是租户隔离测试身份,不是审核人;租户自己的业务审批策略不受本条改变。
|
||||
|
||||
RC-06 审核人只使用两个无复杂参数入口:`yuqei-infra/scripts/Initialize-ReleaseSigner.cmd` 完成一次性加密密钥初始化,候选和 RC-05 材料齐备后使用 `yuqei-infra/scripts/Approve-ReleaseCandidate.cmd` 核对一页摘要并签署一次;不得手工编辑 JSON、hash 或 pin。详见 [`project-docs/RC06-single-reviewer-one-click-signing-runbook-2026-09-27.md`](project-docs/RC06-single-reviewer-one-click-signing-runbook-2026-09-27.md)。工具代码存在不代表真实密钥或批准已经形成。
|
||||
|
||||
本轮收口唯一执行顺序是 `FREEZE → BUILD → RC05-RUN → RC06-UAT → RC07-RECOVERY → RC08-PRODUCTION`。在真实证据形成前,内部受控试点和公众开放均保持 `NO_GO`;不得把本地测试、AI 自动审核或模拟证据描述为法律批准、第三方存证或公众生产放行。效率规则不构成生产操作的长期授权;生产部署、迁移、回退、切流、真实凭据或数据使用、风险接受及法律签署仍须具名授权人明确批准。
|
||||
|
||||
@@ -67,21 +67,21 @@
|
||||
- 每场景三个自动质量字段必须显式为 true,human/legal 字段必须明确保持未完成,`not_legal_approval=true`。
|
||||
- 历史 `release_eligible` 仅接受 provider succeeded、非 timeout、business succeeded/resolved。
|
||||
- 冻结源码和数据传递闭包先以安全文件描述符固定到私有临时树,再从固定字节加载执行;不再执行校验后的可变工作树模块。
|
||||
- v3 只接受陈钰麒对绑定真实 60 资产、14 Prompt、31 项处置及候选/RC-05 身份的一个批次作一次 Ed25519 签名批准;审批 keyring 必须由冻结 pin 对应的离线根密钥签发,发布执行过程须由冻结 pin 对应的服务密钥签名 74 项 apply result 和在线 hash。验证器逐项核验,旧 schema、无根信任、无服务签名或其他审核人均硬拒绝。
|
||||
- approval envelope v4 / payload v3 只接受陈钰麒对绑定真实 60 资产、14 Prompt、31 项处置及候选/RC-05 身份的一个批次作一次 Ed25519 签名批准;签名同时绑定 manifest 原始文件 SHA、payload SHA、确定性单页审阅摘要 SHA、候选精确 keyring epoch 和 root 签 keyring payload SHA。审批 keyring 必须由冻结 pin 对应的离线根密钥签发,并在根签 payload 中授权该候选的精确 manifest 原始 SHA;源码只固定长期 root/apply pin 和候选精确 epoch,不把候选专属 keyring payload 回写源码形成哈希循环。任何 keyring 轮换或撤销都必须提升 epoch、作废原候选并重新冻结。发布执行过程须由冻结 pin 对应的服务密钥签名 74 项 apply result 和在线 hash。验证器逐项核验,旧 schema、无根信任、跨候选 keyring、无服务签名或其他审核人均硬拒绝。
|
||||
- 单审核人本机工具提供两个无复杂参数入口:`Initialize-ReleaseSigner.cmd` 仅首次生成加密 PKCS#8 审批密钥,`Approve-ReleaseCandidate.cmd` 自动发现唯一待审请求、展示候选/60+14+31/RC-05 摘要、要求 6 位批准码和本人口令,签后立即调用正式验证器自验。入口只选取带 Python Software Foundation 有效签名并通过密码库预检的本机离线运行时,以 `-I -S -B` 隔离启动;20 字符长句口令、私钥及锁文件 owner/DACL、原子禁止覆盖写入、过期请求自动归档、签后请求归档及重复点击复验均由工具处理。该脚本工具不宣称抵御已控制当前 Windows 账户的恶意代码;正式操作仍以干净离线主机和固定 Python/Python DLL/cryptography 闭包 digest 为外部运行环境门禁。私钥、口令、真实签名和收据不进入源码树;根密钥和 apply 服务密钥仍是用途隔离的技术设施,不是额外审核人。操作说明见 `project-docs/RC06-single-reviewer-one-click-signing-runbook-2026-09-27.md`。
|
||||
|
||||
### 验证
|
||||
|
||||
- Windows RC-06/RC-07 联合套件:64 passed,1 skipped,0 failed。
|
||||
- Linux `192.168.3.62`:RC-06 安全读取器符号链接回归 1/1 passed。
|
||||
- Linux 使用的最终文件 SHA-256:
|
||||
- `build-rc06-publication-batch.py`: `92d1138478c16c6ea2da57715848d0727992bf922ce2c495bc36206fc0b8efa8`
|
||||
- `test_rc06_publication_batch.py`: `2bccb26b9a7fd3509b1c6550eea426ac7e940db3b90c3b923204cfa57221a2e6`
|
||||
- 当前开发态 Windows 定向回归:RC-06+release manifest 共 101 passed,1 个 Windows 不支持符号链接的条件用例 skipped,0 failed;这不是冻结候选的正式证据。
|
||||
- Linux `192.168.3.62` 的既有 RC-06 安全读取器符号链接回归曾为 1/1 passed;新候选形成后必须对同一不可变文件重新执行并记录正式 evidence。
|
||||
- 当前仍在 BUILD-05 源码收口,按强制交付规则不记录“最终文件 SHA”或生成不可变证据;只在唯一候选重新冻结后生成。
|
||||
|
||||
### 未完成
|
||||
|
||||
- RC-05 正式真实矩阵终态不存在。
|
||||
- 陈钰麒对 60 资产+14 Prompt 精确 hash 的一次批次签名批准尚不存在,系统据此生成的 74 项 apply receipt 和在线 hash 快照也不存在。
|
||||
- RC-06 离线根公钥、当前审批 keyring payload 和 apply 服务公钥的冻结 pin 尚未配置;在新候选冻结前验证器会保持 fail-closed。
|
||||
- RC-06 离线根公钥、候选精确 keyring epoch 和 apply 服务公钥的冻结配置,以及根签候选专属 keyring,均尚未形成;在新候选冻结和 manifest 形成前验证器会保持 fail-closed。任何 keyring 轮换或撤销都必须变更精确 epoch、作废原候选并重新冻结,旧 epoch 不可重放。
|
||||
- 一键工具已具备初始化、系统准备待审请求、单页摘要、交互签署与自验能力,但本轮未运行真实初始化或签署;当前不存在可签的正式待审请求。
|
||||
- 1 个起草 Prompt+8 个复核 Prompt 的真实运行时可执行快照不存在。
|
||||
- 管理员+2 名普通成员、2 个工作区的完整 API/UI UAT 未执行。
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# RC-06 单审核人一键初始化与签署操作说明
|
||||
|
||||
更新时间:2026-09-27
|
||||
|
||||
## 1. 适用范围与当前状态
|
||||
|
||||
本工具只处理 RC-06 的一个精确发布批次:60 个合同资产、14 个 Prompt 模块和 31 项处置结论。唯一人工审核人为陈钰麒(总经理)。离线根密钥和 apply 服务密钥由系统按用途隔离管理,它们不是人工审核人。
|
||||
|
||||
当前没有生成真实审核私钥、正式签名、候选 tag、发布收据或生产授权。内部试点、UAT、生产及公众开放继续 `NO_GO`。一次 RC-06 签名不等于资产已经发布,也不等于授权公众生产切流。
|
||||
|
||||
## 2. 审核人只需要执行的两个入口
|
||||
|
||||
### 2.1 首次初始化
|
||||
|
||||
候选冻结前,由陈钰麒在受控 Windows 电脑上双击:
|
||||
|
||||
`yuqei-infra\scripts\Initialize-ReleaseSigner.cmd`
|
||||
|
||||
工具会:
|
||||
|
||||
1. 将加密 Ed25519 审批私钥保存在当前 Windows 用户的 `%LOCALAPPDATA%\Yuqei\ReleaseSigner\keys`;
|
||||
2. 要求输入并再次确认不少于 20 个字符、非纯数字且不可过于简单的长句口令;
|
||||
3. 生成可交给系统使用的公钥注册材料和公钥指纹;
|
||||
4. 拒绝把私钥写入源码仓库、OneDrive、网络目录、符号链接或不属于当前账户的目录,并将目录、私钥和操作锁的 owner/DACL 收紧到当前账户与 SYSTEM;
|
||||
5. 重复运行时验证已有密钥,不覆盖或偷偷换钥。
|
||||
|
||||
口令只在本机遮罩输入,不通过命令行、环境变量、密码文件、聊天或日志传递。私钥不得上传仓库、服务器、邮件、聊天、共享盘或 CI artifact。
|
||||
|
||||
双击入口只选择本机已安装、带 Python Software Foundation 有效签名且具备所需密码库的离线 Python 运行时;以 `-I -S -B` 隔离模式启动,忽略 `PYTHONPATH`、user-site、`sitecustomize` 和安装器别名,不联网安装或更新运行时。若不存在合格运行时,工具会在输入口令前停止。
|
||||
|
||||
### 2.2 最终批次签署
|
||||
|
||||
只有在系统已配置长期 root/apply-service 公钥指纹和候选精确 keyring epoch、形成唯一 commit/tag/source manifest、由离线根签发绑定该 manifest 原始 SHA 的候选专属 keyring,并完成同一候选 BUILD 与 RC-05 后,系统才会在本机 signer inbox 放入唯一待审请求。届时由陈钰麒双击:
|
||||
|
||||
`yuqei-infra\scripts\Approve-ReleaseCandidate.cmd`
|
||||
|
||||
工具会自动校验候选、root 签名 keyring、keyring 对精确 manifest 原始 SHA 的授权、冻结 root/apply pin、epoch、时窗、60/14/31 清单及 RC-05 终态,然后显示一页中文摘要。陈钰麒只需:
|
||||
|
||||
1. 核对候选 commit/tag、manifest 原始 SHA、payload SHA、60/14/31 数量和 RC-05 状态;
|
||||
2. 输入摘要显示的 6 位批准码;
|
||||
3. 输入本人审核密钥口令一次。
|
||||
|
||||
工具在批准码和口令输入完成后重新取得当前时间并重读全部材料,生成最长 24 小时的批准,再自动裁剪到请求、审批密钥、keyring 和全部风险接受的最早到期时间;用户不能手工填写审批时间或身份。签名写出前会调用正式 RC-06 验证器自验,并以完整 staging+禁止覆盖的原子发布写出收据。系统会先认证并归档不同候选留下的过期请求;失败、取消、错误口令、文件漂移、多个仍有效请求或 pin 未配置都不会留下正式收据。
|
||||
|
||||
成功后待审请求自动移入 `processed`,不会阻塞下一候选。重复双击会自动复验已有收据并提示“此前已签署,无需重复操作”,不会再次签名或覆盖文件。
|
||||
|
||||
## 3. 系统自动完成的工作
|
||||
|
||||
- 生成并管理与审核密钥不同的离线根密钥和 apply 服务密钥;
|
||||
- 在源码冻结前固定长期 root 公钥、候选精确 keyring epoch 和 apply 服务公钥指纹;任何 keyring 轮换或撤销都必须使原候选失效并重新冻结;
|
||||
- 在 manifest 形成后,用根密钥签发只包含陈钰麒审批公钥、并授权该 manifest 原始 SHA 的候选专属 trusted keyring;keyring payload 不写回源码,避免候选 commit 与 keyring 形成哈希循环;
|
||||
- 生成不可变 RC-06 manifest 和待审请求;
|
||||
- 用户签名后验证 approval v4 / payload v3;payload 同时绑定候选精确 keyring epoch 和 root 签 keyring payload SHA;
|
||||
- 后续通过业务 API 应用 74 项内容,并由服务密钥签署 74 项机器结果和在线 hash;
|
||||
- 继续验证 1 个起草+8 个审查运行时 Prompt 快照以及 RC-06 UAT。
|
||||
|
||||
## 4. 不得执行的操作
|
||||
|
||||
- 不得手工编辑 manifest、request、keyring、approval、SHA 或签名 JSON;
|
||||
- 不得把 payload SHA 当作 manifest 原始文件 SHA;
|
||||
- 不得在候选冻结后修改 pin 或源码;
|
||||
- 不得复用审核私钥作为离线根密钥或 apply 服务密钥;
|
||||
- 不得把本地初始化、自动测试或一次 RC-06 签名描述为正式政策批准、第三方存证、司法效力证明或公众生产放行。
|
||||
|
||||
## 5. 运行环境信任边界
|
||||
|
||||
`-I -S -B`、Python Software Foundation 签名检查和本机 ACL 用于减少错误运行时、环境变量注入、跨账户读取及普通文件漂移;它们不等同于受操作系统保护的代码签名发布程序,也不能抵御已经控制当前 Windows 账户、能够键盘注入或篡改用户可写 Python/密码库的恶意进程。
|
||||
|
||||
因此,正式初始化和签署必须在发布负责人确认的干净离线 Windows 主机上执行,并在候选证据中记录实际 Python、Python DLL 和 cryptography 完整导入闭包的固定 digest。若要把“当前账户已失陷”纳入防护范围,必须另行交付代码签名 EXE/MSI 并安装到受保护目录;本 CMD/PowerShell/Python 工具不得被描述为具备该防护能力。
|
||||
|
||||
## 6. 正确顺序
|
||||
|
||||
`审核密钥初始化 → 固定长期 root/apply pin 与候选精确 epoch → 唯一 commit/tag 冻结 → BUILD → RC05-RUN → manifest 形成 → 系统根签候选专属 keyring 与待审请求 → 陈钰麒一次签署 → 自动验签和 74 项应用证据 → RC06-UAT`
|
||||
|
||||
若 inbox 没有请求,工具不会生成批准;若存在不同候选的旧过期请求,工具会认证后自动归档;若仍存在多个有效请求,工具必须拒绝猜测。陈钰麒不需要手工配置或删除文件。
|
||||
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
setlocal
|
||||
chcp 65001 >nul
|
||||
set "launcher=%~dp0Launch-ReleaseSigner.ps1"
|
||||
set "powershell=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
if not exist "%powershell%" (
|
||||
echo 启动失败:系统缺少受信任的 Windows PowerShell。
|
||||
echo 未创建候选批准,请将此窗口截图交给系统处理。
|
||||
pause
|
||||
exit /b 2
|
||||
)
|
||||
"%powershell%" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%launcher%" sign
|
||||
set "result=%ERRORLEVEL%"
|
||||
echo.
|
||||
if not "%result%"=="0" echo 签署未完成,未创建新的候选批准。
|
||||
pause
|
||||
exit /b %result%
|
||||
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
setlocal
|
||||
chcp 65001 >nul
|
||||
set "launcher=%~dp0Launch-ReleaseSigner.ps1"
|
||||
set "powershell=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
if not exist "%powershell%" (
|
||||
echo 启动失败:系统缺少受信任的 Windows PowerShell。
|
||||
echo 未创建审核密钥,请将此窗口截图交给系统处理。
|
||||
pause
|
||||
exit /b 2
|
||||
)
|
||||
"%powershell%" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%launcher%" initialize
|
||||
set "result=%ERRORLEVEL%"
|
||||
echo.
|
||||
if not "%result%"=="0" echo 初始化未完成,未创建审核密钥或候选批准。
|
||||
pause
|
||||
exit /b %result%
|
||||
@@ -0,0 +1,90 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[ValidateSet("initialize", "sign", "runtime-check")]
|
||||
[string]$Command
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$env:PSModulePath = @(
|
||||
(Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\Modules")
|
||||
(Join-Path $env:ProgramFiles "WindowsPowerShell\Modules")
|
||||
) -join ";"
|
||||
|
||||
function Get-TrustedPythonCandidates {
|
||||
$candidatePaths = [System.Collections.Generic.List[string]]::new()
|
||||
foreach ($pattern in @(
|
||||
(Join-Path $env:LOCALAPPDATA "Python\pythoncore-*\python.exe")
|
||||
(Join-Path $env:LOCALAPPDATA "Programs\Python\Python*\python.exe")
|
||||
(Join-Path $env:ProgramFiles "Python*\python.exe")
|
||||
)) {
|
||||
Get-ChildItem -Path $pattern -File -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { $candidatePaths.Add($_.FullName) }
|
||||
}
|
||||
foreach ($registryRoot in @(
|
||||
"Registry::HKEY_CURRENT_USER\Software\Python\PythonCore"
|
||||
"Registry::HKEY_LOCAL_MACHINE\Software\Python\PythonCore"
|
||||
)) {
|
||||
Get-ChildItem -LiteralPath $registryRoot -ErrorAction SilentlyContinue |
|
||||
ForEach-Object {
|
||||
$properties = Get-ItemProperty -LiteralPath (Join-Path $_.PSPath "InstallPath") -ErrorAction SilentlyContinue
|
||||
if ($properties.ExecutablePath) {
|
||||
$candidatePaths.Add([string]$properties.ExecutablePath)
|
||||
} elseif ($properties.'(default)') {
|
||||
$candidatePaths.Add((Join-Path ([string]$properties.'(default)') "python.exe"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$trusted = foreach ($candidatePath in $candidatePaths | Sort-Object -Unique) {
|
||||
if (-not (Test-Path -LiteralPath $candidatePath -PathType Leaf)) { continue }
|
||||
$item = Get-Item -LiteralPath $candidatePath -Force
|
||||
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { continue }
|
||||
if ($item.FullName -like "*\Microsoft\WindowsApps\*") { continue }
|
||||
$version = [version]::new(
|
||||
[Math]::Max(0, $item.VersionInfo.FileMajorPart),
|
||||
[Math]::Max(0, $item.VersionInfo.FileMinorPart),
|
||||
[Math]::Max(0, $item.VersionInfo.FileBuildPart),
|
||||
[Math]::Max(0, $item.VersionInfo.FilePrivatePart)
|
||||
)
|
||||
if ($version -lt [version]"3.11") { continue }
|
||||
$signature = Get-AuthenticodeSignature -LiteralPath $item.FullName
|
||||
if (
|
||||
$signature.Status -ne [System.Management.Automation.SignatureStatus]::Valid -or
|
||||
$null -eq $signature.SignerCertificate -or
|
||||
$signature.SignerCertificate.Subject -notlike "*CN=Python Software Foundation*"
|
||||
) { continue }
|
||||
[pscustomobject]@{ Path = $item.FullName; Version = $version }
|
||||
}
|
||||
return $trusted | Sort-Object Version -Descending
|
||||
}
|
||||
|
||||
try {
|
||||
$signerScript = Join-Path $PSScriptRoot "rc06-single-reviewer-signing.py"
|
||||
if (-not (Test-Path -LiteralPath $signerScript -PathType Leaf)) {
|
||||
throw "The release signer program is missing"
|
||||
}
|
||||
Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:PYTHONHOME -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:PYTHONSTARTUP -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:PYTHONUSERBASE -ErrorAction SilentlyContinue
|
||||
$runtime = $null
|
||||
$ErrorActionPreference = "Continue"
|
||||
foreach ($candidate in @(Get-TrustedPythonCandidates)) {
|
||||
$preflightOutput = @(& $candidate.Path -I -S -B $signerScript runtime-check 2>&1)
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$runtime = $candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($null -eq $runtime) {
|
||||
$ErrorActionPreference = "Stop"
|
||||
throw "No trusted offline Python runtime has the required cryptography dependency"
|
||||
}
|
||||
& $runtime.Path -I -S -B $signerScript $Command
|
||||
exit $LASTEXITCODE
|
||||
} catch {
|
||||
Write-Host ("Signer startup failed: " + $_.Exception.Message) -ForegroundColor Red
|
||||
Write-Host "No reviewer key or candidate approval was created."
|
||||
exit 2
|
||||
}
|
||||
@@ -31,20 +31,22 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
||||
SCHEMA_VERSION = "yuqei.rc06-publication-batch.v3"
|
||||
DISPOSITION_SCHEMA_VERSION = "yuqei.rc06-golden-dispositions.v2"
|
||||
IMAGE_SCHEMA_VERSION = "yuqei.rc06-image-identities.v1"
|
||||
APPROVAL_SCHEMA_VERSION = "yuqei.rc06-publication-batch-approval.v3"
|
||||
APPROVAL_PAYLOAD_SCHEMA_VERSION = "yuqei.rc06-publication-batch-approval-payload.v1"
|
||||
APPROVAL_KEYRING_SCHEMA_VERSION = "yuqei.rc06-publication-approval-keyring.v2"
|
||||
APPROVAL_SCHEMA_VERSION = "yuqei.rc06-publication-batch-approval.v4"
|
||||
APPROVAL_PAYLOAD_SCHEMA_VERSION = "yuqei.rc06-publication-batch-approval-payload.v3"
|
||||
APPROVAL_REVIEW_SUMMARY_SCHEMA_VERSION = "yuqei.rc06-single-reviewer-summary.v1"
|
||||
APPROVAL_KEYRING_SCHEMA_VERSION = "yuqei.rc06-publication-approval-keyring.v3"
|
||||
APPLY_SCHEMA_VERSION = "yuqei.rc06-publication-system-item-apply-receipt.v3"
|
||||
APPLY_PAYLOAD_SCHEMA_VERSION = "yuqei.rc06-publication-system-item-apply-payload.v1"
|
||||
APPROVAL_SIGNATURE_DOMAIN = b"YUQEI_RC06_PUBLICATION_BATCH_APPROVAL_V1\0"
|
||||
APPROVAL_KEYRING_SIGNATURE_DOMAIN = b"YUQEI_RC06_APPROVAL_KEYRING_V1\0"
|
||||
APPLY_SIGNATURE_DOMAIN = b"YUQEI_RC06_SYSTEM_APPLY_RECEIPT_V1\0"
|
||||
TRUSTED_APPROVAL_ROOT_KEY_ID = "yuqei-rc06-approval-root-v1"
|
||||
# Provision these immutable pins only when the source candidate is frozen. Empty
|
||||
# values deliberately keep approval/apply validation fail-closed in development.
|
||||
# Provision the long-lived root/apply pins and exact candidate keyring epoch
|
||||
# before source freeze. A root-signed keyring then authorizes the exact frozen
|
||||
# manifest raw SHA without creating a source/keyring hash cycle. Rotation or
|
||||
# revocation requires a new epoch and therefore a new frozen candidate.
|
||||
TRUSTED_APPROVAL_ROOT_PUBLIC_KEY_SHA256 = ""
|
||||
TRUSTED_APPROVAL_KEYRING_PAYLOAD_SHA256 = ""
|
||||
MIN_APPROVAL_KEYRING_EPOCH = 0
|
||||
FROZEN_APPROVAL_KEYRING_EPOCH = 0
|
||||
TRUSTED_APPLY_SERVICE_PUBLIC_KEY_SHA256 = ""
|
||||
RC05_SUMMARY_SCHEMA_VERSION = "multiscene-evaluation-run.v1"
|
||||
RC05_LEDGER_SCHEMA_VERSION = "multiscene-call-ledger.v1"
|
||||
@@ -131,6 +133,7 @@ GOLDEN_INVENTORY_PATH = (
|
||||
)
|
||||
FROZEN_IMPLEMENTATION_PATHS = (
|
||||
"yuqei-infra/scripts/build-rc06-publication-batch.py",
|
||||
"yuqei-infra/scripts/rc06-single-reviewer-signing.py",
|
||||
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/contract_templates.py",
|
||||
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/contract_scene_assets.py",
|
||||
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/template_inputs.py",
|
||||
@@ -2376,16 +2379,198 @@ def validate_manifest(payload: Any) -> dict[str, Any]:
|
||||
return manifest
|
||||
|
||||
|
||||
def build_approval_review_summary(
|
||||
payload: Any,
|
||||
*,
|
||||
manifest_raw_sha256: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the exact, human-readable projection approved by the sole reviewer."""
|
||||
|
||||
manifest = validate_manifest(payload)
|
||||
candidate = manifest["candidate"]
|
||||
terminal = candidate["rc05_terminal_evidence"]
|
||||
manifest_raw_sha256 = _require_sha(
|
||||
manifest_raw_sha256,
|
||||
"publication batch raw SHA-256",
|
||||
)
|
||||
summary = {
|
||||
"schema_version": APPROVAL_REVIEW_SUMMARY_SCHEMA_VERSION,
|
||||
"decision_scope": "approve_exact_rc06_publication_batch",
|
||||
"reviewer": dict(SOLE_RELEASE_REVIEWER),
|
||||
"candidate": {
|
||||
"commit": candidate["commit"],
|
||||
"tag": candidate["tag"],
|
||||
"source_manifest_sha256": candidate["source_manifest_sha256"],
|
||||
"source_manifest_payload_sha256": candidate["source_manifest_payload_sha256"],
|
||||
"image_identities_sha256": candidate["image_identities_sha256"],
|
||||
"image_identity_set_sha256": candidate["image_identity_set_sha256"],
|
||||
"rc05_plan_sha256": candidate["rc05_plan_sha256"],
|
||||
"rc05_summary_raw_sha256": terminal["summary_raw_sha256"],
|
||||
"rc05_ledger_identity_raw_sha256": terminal["ledger_identity_raw_sha256"],
|
||||
"rc05_campaign_identity_raw_sha256": terminal["campaign_identity_raw_sha256"],
|
||||
"rc05_complete_real_green": terminal["complete_real_green"],
|
||||
},
|
||||
"batch": {
|
||||
"asset_count": manifest["asset_batch"]["count"],
|
||||
"asset_inventory_sha256": manifest["asset_batch"]["inventory_sha256"],
|
||||
"prompt_module_count": manifest["prompt_module_batch"]["count"],
|
||||
"prompt_module_inventory_sha256": manifest["prompt_module_batch"]["inventory_sha256"],
|
||||
"golden_disposition_count": manifest["golden_dispositions"]["count"],
|
||||
"golden_disposition_counts_by_status": manifest["golden_dispositions"]["counts_by_status"],
|
||||
"golden_disposition_inventory_sha256": manifest["golden_dispositions"]["inventory_sha256"],
|
||||
},
|
||||
"manifest_raw_sha256": manifest_raw_sha256,
|
||||
"manifest_payload_sha256": manifest["manifest_payload_sha256"],
|
||||
"approval_validity_max_hours": int(MAX_APPROVAL_WINDOW.total_seconds() // 3600),
|
||||
"remaining_controls_after_approval": list(BLOCKED_CONTROLS[1:]),
|
||||
"claims_not_made": [
|
||||
"This approval does not itself publish any asset or Prompt module.",
|
||||
"This approval does not authorize public production traffic cutover.",
|
||||
"System apply evidence and executable runtime Prompt snapshots remain separate gates.",
|
||||
],
|
||||
}
|
||||
_assert_no_secret_material(summary, "approval review summary")
|
||||
return summary
|
||||
|
||||
|
||||
def _approval_risk_acceptance_deadline(manifest: Mapping[str, Any]) -> datetime | None:
|
||||
"""Return the earliest expiry across scoped Golden risk acceptances."""
|
||||
|
||||
deadlines = [
|
||||
_parse_time(
|
||||
item["risk_acceptance"]["expires_at"],
|
||||
f"Golden disposition {item['sample_id']} risk acceptance expires_at",
|
||||
)
|
||||
for item in manifest["golden_dispositions"]["items"]
|
||||
if item["status"] == "scoped_internal_risk_acceptance"
|
||||
]
|
||||
return min(deadlines) if deadlines else None
|
||||
|
||||
|
||||
def _validate_disposition_approval_window(
|
||||
manifest: Mapping[str, Any],
|
||||
*,
|
||||
approved_at: datetime,
|
||||
expires_at: datetime,
|
||||
) -> None:
|
||||
"""Ensure every disposition decision and exception covers the approval window."""
|
||||
|
||||
for item in manifest["golden_dispositions"]["items"]:
|
||||
if item["status"] == "unresolved":
|
||||
raise PublicationBatchError(
|
||||
"approval cannot release a batch with unresolved Golden dispositions"
|
||||
)
|
||||
decided_at = _parse_time(
|
||||
item["decided_at"],
|
||||
f"Golden disposition {item['sample_id']} decided_at",
|
||||
)
|
||||
if decided_at > approved_at:
|
||||
raise PublicationBatchError(
|
||||
f"Golden disposition {item['sample_id']} was decided after the approval time"
|
||||
)
|
||||
if item["status"] != "scoped_internal_risk_acceptance":
|
||||
continue
|
||||
risk_expires_at = _parse_time(
|
||||
item["risk_acceptance"]["expires_at"],
|
||||
f"Golden disposition {item['sample_id']} risk acceptance expires_at",
|
||||
)
|
||||
if approved_at >= risk_expires_at:
|
||||
raise PublicationBatchError(
|
||||
f"Golden disposition {item['sample_id']} risk acceptance expired before approval"
|
||||
)
|
||||
if expires_at > risk_expires_at:
|
||||
raise PublicationBatchError(
|
||||
f"approval validity exceeds Golden disposition {item['sample_id']} risk acceptance"
|
||||
)
|
||||
|
||||
|
||||
def build_approval_payload(
|
||||
manifest: Any,
|
||||
*,
|
||||
manifest_raw_sha256: str,
|
||||
key_id: str,
|
||||
approval_keyring_epoch: int,
|
||||
approval_keyring_payload_sha256: str,
|
||||
approved_at: datetime,
|
||||
expires_at: datetime,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the only approval payload accepted for the exact validated batch."""
|
||||
|
||||
validated = validate_manifest(manifest)
|
||||
normalized_key_id = _require_string(key_id, "approval key_id", max_length=128)
|
||||
if not STABLE_ID_RE.fullmatch(normalized_key_id):
|
||||
raise PublicationBatchError("approval key_id is invalid")
|
||||
if (
|
||||
not isinstance(approval_keyring_epoch, int)
|
||||
or isinstance(approval_keyring_epoch, bool)
|
||||
or approval_keyring_epoch < 1
|
||||
):
|
||||
raise PublicationBatchError("approval keyring epoch is invalid")
|
||||
normalized_keyring_payload_sha256 = _require_sha(
|
||||
approval_keyring_payload_sha256,
|
||||
"approval keyring payload SHA-256",
|
||||
)
|
||||
if approved_at.tzinfo is None or approved_at.utcoffset() is None:
|
||||
raise PublicationBatchError("approval approved_at must include a timezone")
|
||||
if expires_at.tzinfo is None or expires_at.utcoffset() is None:
|
||||
raise PublicationBatchError("approval expires_at must include a timezone")
|
||||
if expires_at <= approved_at or expires_at - approved_at > MAX_APPROVAL_WINDOW:
|
||||
raise PublicationBatchError(
|
||||
"approval validity must be greater than zero and no longer than seven days"
|
||||
)
|
||||
if not validated["publication_control"]["golden_dispositions_complete"]:
|
||||
raise PublicationBatchError("approval cannot release a batch with unresolved Golden dispositions")
|
||||
_validate_disposition_approval_window(
|
||||
validated,
|
||||
approved_at=approved_at,
|
||||
expires_at=expires_at,
|
||||
)
|
||||
manifest_raw_sha256 = _require_sha(
|
||||
manifest_raw_sha256,
|
||||
"publication batch raw SHA-256",
|
||||
)
|
||||
review_summary = build_approval_review_summary(
|
||||
validated,
|
||||
manifest_raw_sha256=manifest_raw_sha256,
|
||||
)
|
||||
return {
|
||||
"schema_version": APPROVAL_PAYLOAD_SCHEMA_VERSION,
|
||||
"batch_manifest_raw_sha256": manifest_raw_sha256,
|
||||
"batch_manifest_payload_sha256": validated["manifest_payload_sha256"],
|
||||
"decision": "approved",
|
||||
"approved_at": approved_at.isoformat(timespec="seconds"),
|
||||
"expires_at": expires_at.isoformat(timespec="seconds"),
|
||||
"key_id": normalized_key_id,
|
||||
"approval_keyring_epoch": approval_keyring_epoch,
|
||||
"approval_keyring_payload_sha256": normalized_keyring_payload_sha256,
|
||||
"approver": {**SOLE_RELEASE_REVIEWER, "human_confirmed": True},
|
||||
"asset_payload_sha256s": {
|
||||
item["template_id"]: item["asset_payload_sha256"]
|
||||
for item in validated["asset_batch"]["items"]
|
||||
},
|
||||
"prompt_module_payload_sha256s": {
|
||||
item["key"]: item["module_payload_sha256"]
|
||||
for item in validated["prompt_module_batch"]["items"]
|
||||
},
|
||||
"golden_disposition_inventory_sha256": validated["golden_dispositions"][
|
||||
"inventory_sha256"
|
||||
],
|
||||
"review_summary_sha256": _sha256_json(review_summary),
|
||||
}
|
||||
|
||||
|
||||
def _require_sole_release_reviewer(value: Any, label: str) -> dict[str, Any]:
|
||||
reviewer = _require_keys(
|
||||
value,
|
||||
label,
|
||||
("approver_id", "name", "role", "organization", "human_confirmed"),
|
||||
)
|
||||
actual = {
|
||||
field: _require_string(reviewer[field], f"{label} {field}", max_length=256)
|
||||
for field in ("approver_id", "name", "role", "organization")
|
||||
}
|
||||
actual = {}
|
||||
for field in ("approver_id", "name", "role", "organization"):
|
||||
normalized = _require_string(reviewer[field], f"{label} {field}", max_length=256)
|
||||
if reviewer[field] != normalized:
|
||||
raise PublicationBatchError(f"{label} {field} must be exact canonical text")
|
||||
actual[field] = normalized
|
||||
if actual != SOLE_RELEASE_REVIEWER:
|
||||
raise PublicationBatchError(f"{label} must be the configured sole release reviewer")
|
||||
if reviewer["human_confirmed"] is not True:
|
||||
@@ -2422,7 +2607,8 @@ def _validate_approval_keyring(
|
||||
keyring,
|
||||
"approval keyring",
|
||||
(
|
||||
"schema_version", "root_key_id", "epoch", "issued_at", "expires_at", "keys",
|
||||
"schema_version", "root_key_id", "epoch", "issued_at", "expires_at",
|
||||
"authorized_batch_manifest_raw_sha256", "keys",
|
||||
"keyring_payload_sha256", "root_signature_base64",
|
||||
),
|
||||
)
|
||||
@@ -2431,9 +2617,14 @@ def _validate_approval_keyring(
|
||||
unsigned = {
|
||||
field: trusted[field]
|
||||
for field in (
|
||||
"schema_version", "root_key_id", "epoch", "issued_at", "expires_at", "keys"
|
||||
"schema_version", "root_key_id", "epoch", "issued_at", "expires_at",
|
||||
"authorized_batch_manifest_raw_sha256", "keys",
|
||||
)
|
||||
}
|
||||
_require_sha(
|
||||
trusted["authorized_batch_manifest_raw_sha256"],
|
||||
"approval keyring authorized batch manifest raw SHA-256",
|
||||
)
|
||||
payload_sha256 = _require_sha(
|
||||
trusted["keyring_payload_sha256"],
|
||||
"approval keyring payload SHA-256",
|
||||
@@ -2444,10 +2635,6 @@ def _validate_approval_keyring(
|
||||
raise PublicationBatchError("approval root public key pin is not provisioned")
|
||||
if _sha256_bytes(_public_key_raw(root_public_key)) != TRUSTED_APPROVAL_ROOT_PUBLIC_KEY_SHA256:
|
||||
raise PublicationBatchError("approval root public key does not match the frozen pin")
|
||||
if not HEX64_RE.fullmatch(TRUSTED_APPROVAL_KEYRING_PAYLOAD_SHA256):
|
||||
raise PublicationBatchError("approval keyring payload pin is not provisioned")
|
||||
if payload_sha256 != TRUSTED_APPROVAL_KEYRING_PAYLOAD_SHA256:
|
||||
raise PublicationBatchError("approval keyring is not the currently trusted revocation state")
|
||||
if trusted["root_key_id"] != TRUSTED_APPROVAL_ROOT_KEY_ID:
|
||||
raise PublicationBatchError("approval keyring uses the wrong root key")
|
||||
epoch = trusted["epoch"]
|
||||
@@ -2455,13 +2642,13 @@ def _validate_approval_keyring(
|
||||
not isinstance(epoch, int)
|
||||
or isinstance(epoch, bool)
|
||||
or epoch < 1
|
||||
or not isinstance(MIN_APPROVAL_KEYRING_EPOCH, int)
|
||||
or isinstance(MIN_APPROVAL_KEYRING_EPOCH, bool)
|
||||
or MIN_APPROVAL_KEYRING_EPOCH < 1
|
||||
or not isinstance(FROZEN_APPROVAL_KEYRING_EPOCH, int)
|
||||
or isinstance(FROZEN_APPROVAL_KEYRING_EPOCH, bool)
|
||||
or FROZEN_APPROVAL_KEYRING_EPOCH < 1
|
||||
):
|
||||
raise PublicationBatchError("approval keyring epoch policy is not provisioned")
|
||||
if epoch < MIN_APPROVAL_KEYRING_EPOCH:
|
||||
raise PublicationBatchError("approval keyring epoch is older than the trusted minimum")
|
||||
if epoch != FROZEN_APPROVAL_KEYRING_EPOCH:
|
||||
raise PublicationBatchError("approval keyring epoch does not match the frozen candidate")
|
||||
issued_at = _parse_time(trusted["issued_at"], "approval keyring issued_at")
|
||||
expires_at = _parse_time(trusted["expires_at"], "approval keyring expires_at")
|
||||
if expires_at <= issued_at or approved_at < issued_at or approved_at >= expires_at:
|
||||
@@ -2535,6 +2722,7 @@ def _validate_approval_keyring(
|
||||
|
||||
def validate_approval_receipt(
|
||||
manifest: Mapping[str, Any],
|
||||
manifest_raw_sha256: str,
|
||||
receipt: Any,
|
||||
keyring: Any,
|
||||
root_public_key: Ed25519PublicKey,
|
||||
@@ -2550,16 +2738,24 @@ def validate_approval_receipt(
|
||||
envelope["payload"],
|
||||
"approval payload",
|
||||
(
|
||||
"schema_version", "batch_manifest_payload_sha256", "decision", "approved_at",
|
||||
"expires_at", "key_id", "approver", "asset_payload_sha256s",
|
||||
"schema_version", "batch_manifest_raw_sha256", "batch_manifest_payload_sha256",
|
||||
"decision", "approved_at", "expires_at", "key_id",
|
||||
"approval_keyring_epoch", "approval_keyring_payload_sha256", "approver",
|
||||
"asset_payload_sha256s",
|
||||
"prompt_module_payload_sha256s", "golden_disposition_inventory_sha256",
|
||||
"evidence_sha256",
|
||||
"review_summary_sha256",
|
||||
),
|
||||
)
|
||||
if payload["schema_version"] != APPROVAL_PAYLOAD_SCHEMA_VERSION or payload["decision"] != "approved":
|
||||
raise PublicationBatchError("approval receipt is not an approved RC-06 batch receipt")
|
||||
if payload["batch_manifest_payload_sha256"] != manifest["manifest_payload_sha256"]:
|
||||
raise PublicationBatchError("approval receipt is not bound to this batch")
|
||||
normalized_manifest_raw_sha256 = _require_sha(
|
||||
manifest_raw_sha256,
|
||||
"publication batch raw SHA-256",
|
||||
)
|
||||
if payload["batch_manifest_raw_sha256"] != normalized_manifest_raw_sha256:
|
||||
raise PublicationBatchError("approval receipt is not bound to the exact manifest file")
|
||||
approved_at = _parse_time(payload["approved_at"], "approval approved_at")
|
||||
expires_at = _parse_time(payload["expires_at"], "approval expires_at")
|
||||
if expires_at <= approved_at or expires_at - approved_at > MAX_APPROVAL_WINDOW:
|
||||
@@ -2579,15 +2775,43 @@ def validate_approval_receipt(
|
||||
raise PublicationBatchError("approval receipt Prompt identities drift")
|
||||
if payload["golden_disposition_inventory_sha256"] != manifest["golden_dispositions"]["inventory_sha256"]:
|
||||
raise PublicationBatchError("approval receipt Golden disposition identity drift")
|
||||
_require_sha(payload["evidence_sha256"], "approval evidence SHA-256")
|
||||
review_summary_sha256 = _require_sha(
|
||||
payload["review_summary_sha256"],
|
||||
"approval review summary SHA-256",
|
||||
)
|
||||
expected_review_summary_sha256 = _sha256_json(
|
||||
build_approval_review_summary(
|
||||
manifest,
|
||||
manifest_raw_sha256=normalized_manifest_raw_sha256,
|
||||
)
|
||||
)
|
||||
if review_summary_sha256 != expected_review_summary_sha256:
|
||||
raise PublicationBatchError("approval receipt does not match the exact review summary")
|
||||
if not manifest["publication_control"]["golden_dispositions_complete"]:
|
||||
raise PublicationBatchError("approval cannot release a batch with unresolved Golden dispositions")
|
||||
_validate_disposition_approval_window(
|
||||
manifest,
|
||||
approved_at=approved_at,
|
||||
expires_at=expires_at,
|
||||
)
|
||||
|
||||
trusted_keys = _validate_approval_keyring(
|
||||
keyring,
|
||||
root_public_key=root_public_key,
|
||||
approved_at=approved_at,
|
||||
)
|
||||
if payload["approval_keyring_epoch"] != keyring["epoch"]:
|
||||
raise PublicationBatchError("approval receipt keyring epoch drift")
|
||||
if _require_sha(
|
||||
payload["approval_keyring_payload_sha256"],
|
||||
"approval receipt keyring payload SHA-256",
|
||||
) != keyring["keyring_payload_sha256"]:
|
||||
raise PublicationBatchError("approval receipt keyring payload identity drift")
|
||||
if keyring["authorized_batch_manifest_raw_sha256"] != normalized_manifest_raw_sha256:
|
||||
raise PublicationBatchError("approval keyring does not authorize the exact batch manifest")
|
||||
keyring_expires_at = _parse_time(keyring["expires_at"], "approval keyring expires_at")
|
||||
if expires_at > keyring_expires_at:
|
||||
raise PublicationBatchError("approval validity exceeds the trusted keyring window")
|
||||
key_id = _require_string(payload["key_id"], "approval key_id", max_length=128)
|
||||
matches = [
|
||||
item
|
||||
@@ -2617,6 +2841,7 @@ def validate_approval_receipt(
|
||||
|
||||
def validate_apply_receipt(
|
||||
manifest: Mapping[str, Any],
|
||||
manifest_raw_sha256: str,
|
||||
approval: Mapping[str, Any],
|
||||
approval_keyring: Mapping[str, Any],
|
||||
approval_root_public_key: Ed25519PublicKey,
|
||||
@@ -2626,6 +2851,7 @@ def validate_apply_receipt(
|
||||
) -> dict[str, Any]:
|
||||
validate_approval_receipt(
|
||||
manifest,
|
||||
manifest_raw_sha256,
|
||||
approval,
|
||||
approval_keyring,
|
||||
approval_root_public_key,
|
||||
@@ -2655,7 +2881,7 @@ def validate_apply_receipt(
|
||||
_require_string(row["environment_id"], "apply environment_id", max_length=256)
|
||||
applied_at = _parse_time(row["applied_at"], "apply applied_at")
|
||||
approval_payload = approval["payload"]
|
||||
if applied_at < _parse_time(approval_payload["approved_at"], "approval approved_at") or applied_at > _parse_time(
|
||||
if applied_at < _parse_time(approval_payload["approved_at"], "approval approved_at") or applied_at >= _parse_time(
|
||||
approval_payload["expires_at"], "approval expires_at"
|
||||
):
|
||||
raise PublicationBatchError("apply receipt was generated outside the approval window")
|
||||
@@ -2871,7 +3097,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
approval_parser = subparsers.add_parser("validate-approval", help="validate a separate approval receipt")
|
||||
approval_parser.add_argument("--manifest", type=Path, required=True)
|
||||
approval_parser.add_argument("--manifest-sha256")
|
||||
approval_parser.add_argument("--manifest-sha256", required=True)
|
||||
approval_parser.add_argument("--approval", type=Path, required=True)
|
||||
approval_parser.add_argument("--approval-sha256", required=True)
|
||||
approval_parser.add_argument("--approval-keyring", type=Path, required=True)
|
||||
@@ -2880,7 +3106,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
apply_parser = subparsers.add_parser("validate-apply", help="validate a separate apply receipt")
|
||||
apply_parser.add_argument("--manifest", type=Path, required=True)
|
||||
apply_parser.add_argument("--manifest-sha256")
|
||||
apply_parser.add_argument("--manifest-sha256", required=True)
|
||||
apply_parser.add_argument("--approval", type=Path, required=True)
|
||||
apply_parser.add_argument("--approval-sha256", required=True)
|
||||
apply_parser.add_argument("--approval-keyring", type=Path, required=True)
|
||||
@@ -2906,7 +3132,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
"service_writes": 0,
|
||||
}
|
||||
elif args.command == "validate-approval":
|
||||
manifest, _ = _load_manifest(args.manifest, args.manifest_sha256)
|
||||
manifest, manifest_raw_sha = _load_manifest(args.manifest, args.manifest_sha256)
|
||||
approval, raw_sha = secure_read_json(args.approval, "approval receipt", args.approval_sha256)
|
||||
approval_keyring, _ = secure_read_json(
|
||||
args.approval_keyring,
|
||||
@@ -2920,6 +3146,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
)
|
||||
validate_approval_receipt(
|
||||
manifest,
|
||||
manifest_raw_sha,
|
||||
approval,
|
||||
approval_keyring,
|
||||
approval_root_public_key,
|
||||
@@ -2933,7 +3160,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
"service_writes": 0,
|
||||
}
|
||||
else:
|
||||
manifest, _ = _load_manifest(args.manifest, args.manifest_sha256)
|
||||
manifest, manifest_raw_sha = _load_manifest(args.manifest, args.manifest_sha256)
|
||||
approval, approval_raw = secure_read_json(
|
||||
args.approval, "approval receipt", args.approval_sha256
|
||||
)
|
||||
@@ -2954,6 +3181,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
)
|
||||
validate_approval_receipt(
|
||||
manifest,
|
||||
manifest_raw_sha,
|
||||
approval,
|
||||
approval_keyring,
|
||||
approval_root_public_key,
|
||||
@@ -2963,6 +3191,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
)
|
||||
validate_apply_receipt(
|
||||
manifest,
|
||||
manifest_raw_sha,
|
||||
approval,
|
||||
approval_keyring,
|
||||
approval_root_public_key,
|
||||
|
||||
@@ -66,6 +66,11 @@ RELEASE_CONTROL_PATHS = (
|
||||
"yuqei-infra/scripts/build-installable-release.py",
|
||||
"yuqei-infra/scripts/build-infrastructure-image-bundle.py",
|
||||
"yuqei-infra/scripts/build-third-party-materials.py",
|
||||
"yuqei-infra/scripts/build-rc06-publication-batch.py",
|
||||
"yuqei-infra/scripts/rc06-single-reviewer-signing.py",
|
||||
"yuqei-infra/scripts/Launch-ReleaseSigner.ps1",
|
||||
"yuqei-infra/scripts/Initialize-ReleaseSigner.cmd",
|
||||
"yuqei-infra/scripts/Approve-ReleaseCandidate.cmd",
|
||||
"yuqei-infra/tests/test_production_compose_preflight.py",
|
||||
"yuqei-infra/tests/test_production_compose_merge_gate.py",
|
||||
"yuqei-infra/tests/test_release_package_executor.py",
|
||||
@@ -76,6 +81,7 @@ RELEASE_CONTROL_PATHS = (
|
||||
"yuqei-infra/tests/test_rollback_rehearsal_planner.py",
|
||||
"yuqei-infra/tests/test_third_party_materials.py",
|
||||
"yuqei-infra/tests/test_release_candidate_manifest_tools.py",
|
||||
"yuqei-infra/tests/test_rc06_publication_batch.py",
|
||||
)
|
||||
CONTRACT_LOCK_PATH = REQUIREMENT_LOCK_PATHS[0]
|
||||
CONTRACT_DOCKERFILE_PATH = "yuqei-infra/docker/contract-api.Dockerfile"
|
||||
|
||||
@@ -31,6 +31,7 @@ CLOSEOUT_PREFIX = "docs/qa/multiscene-closeout-20260910/"
|
||||
MAIN_PLAN = "project-docs/AI合同平台-多场景完整交付与开发收口方案-2026-09-10.md"
|
||||
CURRENT_RELEASE_CONTEXT_DOCS = {
|
||||
"project-docs/上线收口-执行约束-2026-09-10.md",
|
||||
"project-docs/RC06-single-reviewer-one-click-signing-runbook-2026-09-27.md",
|
||||
}
|
||||
HISTORICAL_PROJECT_DOCS = {
|
||||
"project-docs/V4-website-shell-redesign-2026-08-28.md",
|
||||
@@ -94,6 +95,11 @@ RUNTIME_COMPONENTS = {
|
||||
"yuqei-infra/scripts/build-installable-release.py",
|
||||
"yuqei-infra/scripts/build-infrastructure-image-bundle.py",
|
||||
"yuqei-infra/scripts/build-third-party-materials.py",
|
||||
"yuqei-infra/scripts/build-rc06-publication-batch.py",
|
||||
"yuqei-infra/scripts/rc06-single-reviewer-signing.py",
|
||||
"yuqei-infra/scripts/Launch-ReleaseSigner.ps1",
|
||||
"yuqei-infra/scripts/Initialize-ReleaseSigner.cmd",
|
||||
"yuqei-infra/scripts/Approve-ReleaseCandidate.cmd",
|
||||
"yuqei-infra/scripts/build-release-candidate-manifest.py",
|
||||
"yuqei-infra/scripts/build-release-worktree-ownership.py",
|
||||
"yuqei-infra/tests/test_installable_release_assembler_hardening.py",
|
||||
@@ -103,6 +109,7 @@ RUNTIME_COMPONENTS = {
|
||||
"yuqei-infra/tests/test_third_party_materials.py",
|
||||
"yuqei-infra/tests/test_release_package_executor.py",
|
||||
"yuqei-infra/tests/test_release_candidate_manifest_tools.py",
|
||||
"yuqei-infra/tests/test_rc06_publication_batch.py",
|
||||
"yuqei-infra/tests/test_production_compose_preflight.py",
|
||||
"yuqei-infra/tests/test_production_compose_merge_gate.py",
|
||||
"yuqei-infra/tests/test_linux_release_build_tools.py",
|
||||
@@ -150,6 +157,11 @@ REQUIRED_WORKTREE_ANCHORS = (
|
||||
"yuqei-infra/scripts/build-installable-release.py",
|
||||
"yuqei-infra/scripts/build-infrastructure-image-bundle.py",
|
||||
"yuqei-infra/scripts/build-third-party-materials.py",
|
||||
"yuqei-infra/scripts/build-rc06-publication-batch.py",
|
||||
"yuqei-infra/scripts/rc06-single-reviewer-signing.py",
|
||||
"yuqei-infra/scripts/Launch-ReleaseSigner.ps1",
|
||||
"yuqei-infra/scripts/Initialize-ReleaseSigner.cmd",
|
||||
"yuqei-infra/scripts/Approve-ReleaseCandidate.cmd",
|
||||
"yuqei-infra/tests/test_production_compose_preflight.py",
|
||||
"yuqei-infra/tests/test_production_compose_merge_gate.py",
|
||||
"yuqei-infra/tests/test_release_package_executor.py",
|
||||
@@ -160,6 +172,7 @@ REQUIRED_WORKTREE_ANCHORS = (
|
||||
"yuqei-infra/tests/test_rollback_rehearsal_planner.py",
|
||||
"yuqei-infra/tests/test_third_party_materials.py",
|
||||
"yuqei-infra/tests/test_release_candidate_manifest_tools.py",
|
||||
"yuqei-infra/tests/test_rc06_publication_batch.py",
|
||||
)
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -300,7 +300,15 @@ RUN python -c "import platform; name, version = platform.libc_ver(); minimum=(2,
|
||||
("project-docs/report-TP03-run.json", "generated_evidence_retain_exclude"),
|
||||
("project-docs/V4-website-shell-redesign-2026-08-28.md", "generated_evidence_retain_exclude"),
|
||||
("project-docs/上线收口-执行约束-2026-09-10.md", "release_evidence_compact_include"),
|
||||
(
|
||||
"project-docs/RC06-single-reviewer-one-click-signing-runbook-2026-09-27.md",
|
||||
"release_evidence_compact_include",
|
||||
),
|
||||
("project-docs/unreviewed-plan.md", "needs_manual_review"),
|
||||
("yuqei-infra/scripts/rc06-single-reviewer-signing.py", "release_source_include"),
|
||||
("yuqei-infra/scripts/Launch-ReleaseSigner.ps1", "release_source_include"),
|
||||
("yuqei-infra/scripts/Initialize-ReleaseSigner.cmd", "release_source_include"),
|
||||
("yuqei-infra/scripts/Approve-ReleaseCandidate.cmd", "release_source_include"),
|
||||
("yuqei-infra/config/.env.example", "release_source_include"),
|
||||
(
|
||||
"yuqei-infra/release-trust/third-party-materials-baseline.json",
|
||||
|
||||
Reference in New Issue
Block a user