Skip to content

Commit 71e550e

Browse files
committed
fix: make release validation checkout-independent
1 parent 60dcdaa commit 71e550e

6 files changed

Lines changed: 99 additions & 42 deletions

File tree

‎changelogs/releases/v1.19.0.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ v1.19.0 修复真实工程任务在会话、Hook、工具调用和恢复过程
4242
- Auto 新根只允许父运行已终态、无 live lease、当前 committed HEAD 为严格后继且节点/边界/heavy/副作用/预算不扩大;新根保留父 root/terminal 摘要,plan-only 与执行共用同一拒绝路径,底层 evidence store 也禁止带 live lease/runner 换根。
4343
- 普通 changed edit 不再静默升级 V3/full;只有用户明确发布授权或受信 CI/release 角色绑定同一 BudgetCard 时才运行发布级全量验证。
4444
- `PF-342` 摘要稳定化、`PF-343` 稳定终态存储和 actual-candidate evidence gate 同批闭环。
45+
- Git ancestry 回归不再借用被测仓的 `HEAD^`/`HEAD^^`,而是在测试自有临时仓建立三提交 lineage;浅克隆与完整仓都验证同一真实 `merge-base --is-ancestor` 合同。
46+
- runtime contract closure 变化会使旧宿主 capability evidence 立即失效;发布候选必须重跑当前源码 Codex CLI S15,并同步 raw evidence、可移植投影、runtime/adapter 与 capability 文件摘要,不能把旧 PASS 继承到新 runtime。
4547

4648
### 宿主与文档边界
4749

‎hooks/_runtime/evidence/codex-skill-route-pass.v1.json‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@
55
"hostVariant": "codex-cli/exec-user-global-local-stdio",
66
"testedVersion": "codex-cli 0.145.0",
77
"protocol": "MCP 2024-11-05",
8-
"runtimeContractDigest": "c441afd6202dcb4632a49b4599a4c28bf471535ef5ebf3dac3d7d5167b584ba8",
9-
"hostAdapterDigest": "fba6e1f387cb132a334b4f8b94b340b9f05b94e2836582b12b3ffceedac2739c",
10-
"sourceEvidenceDigest": "3c41dbeeb38d52eed8edcff9dfc6ca3fc699f766ffa479f2188ad94a2c104751",
8+
"runtimeContractDigest": "74bb7e731875754835245c147d0df6737b4f20b0d119e215daa1a7914169550a",
9+
"hostAdapterDigest": "f7b5072195add6fcbfa22b284ba7c8503ac5985bde203311422afe53738ebf8c",
10+
"sourceEvidenceDigest": "7e5b3ed0ee349244d3b57447709c01afb8181f251a1141aac4412fd964c9804f",
1111
"sourceProbe": {
1212
"schemaVersion": "SkillRouteS15EvidenceV1",
13-
"probeRunId": "s15-codex-probe-5c20b537-08de-48a8-9031-d54f8154e75c",
13+
"probeRunId": "s15-codex-probe-13f73501-8ccb-4ff5-b117-a9921d6f86c3",
1414
"authorizationSource": "isolated-probe-authority",
1515
"contextSource": "host-hooks",
1616
"observationMode": "hook-post-history",
1717
"receiptStatus": "relevant-complete",
18-
"completedAt": "2026-08-26T00:23:47.235Z"
18+
"completedAt": "2026-08-26T09:33:36.728Z"
1919
},
2020
"runtimeBinding": {
2121
"source": "isolated-source-candidate",
22-
"expectedDigest": "c441afd6202dcb4632a49b4599a4c28bf471535ef5ebf3dac3d7d5167b584ba8",
23-
"generationDigest": "c441afd6202dcb4632a49b4599a4c28bf471535ef5ebf3dac3d7d5167b584ba8",
24-
"modeReceiptDigest": "c441afd6202dcb4632a49b4599a4c28bf471535ef5ebf3dac3d7d5167b584ba8",
25-
"routeEnvelopeDigest": "c441afd6202dcb4632a49b4599a4c28bf471535ef5ebf3dac3d7d5167b584ba8"
22+
"expectedDigest": "74bb7e731875754835245c147d0df6737b4f20b0d119e215daa1a7914169550a",
23+
"generationDigest": "74bb7e731875754835245c147d0df6737b4f20b0d119e215daa1a7914169550a",
24+
"modeReceiptDigest": "74bb7e731875754835245c147d0df6737b4f20b0d119e215daa1a7914169550a",
25+
"routeEnvelopeDigest": "74bb7e731875754835245c147d0df6737b4f20b0d119e215daa1a7914169550a"
2626
},
2727
"probe": {
2828
"schemaVersion": "SkillRouteProbeSummaryV1",

‎hooks/_runtime/host-skill-route-capabilities.v1.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
"status": "PASS",
1414
"testedVersion": "codex-cli 0.145.0",
1515
"protocol": "MCP 2024-11-05",
16-
"runtimeContractDigest": "c441afd6202dcb4632a49b4599a4c28bf471535ef5ebf3dac3d7d5167b584ba8",
17-
"hostAdapterDigest": "fba6e1f387cb132a334b4f8b94b340b9f05b94e2836582b12b3ffceedac2739c",
16+
"runtimeContractDigest": "74bb7e731875754835245c147d0df6737b4f20b0d119e215daa1a7914169550a",
17+
"hostAdapterDigest": "f7b5072195add6fcbfa22b284ba7c8503ac5985bde203311422afe53738ebf8c",
1818
"evidenceRef": "hooks/_runtime/evidence/codex-skill-route-pass.v1.json",
19-
"evidenceDigest": "0f280854a903a4109e18ad5259404b2a823d9eefc589d44bc57c0013aee4aa70",
19+
"evidenceDigest": "a8e1756ac47612bbd9f44555dd8239375a9e92d9f2e60470c4c692ecd8aae403",
2020
"entrySurface": "codex exec --ephemeral",
2121
"bootstrapDelivery": "stable user-global Hook launcher UserPromptSubmit or profile_context_plan fallback",
2222
"defaultEligible": true

‎scripts/lib/skill-route-test-fixture.js‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,18 @@ function createSkillRouteFixture (options = {}) {
202202
globalRuntime: fixture.globalRuntime,
203203
env: {}
204204
}
205-
fixture.cleanup = () => fs.rmSync(root, {
206-
recursive: true,
207-
force: true,
208-
maxRetries: 10,
209-
retryDelay: 250
210-
})
205+
fixture.cleanup = () => {
206+
if (process.env.DEVCODEX_KEEP_TEST_ARTIFACTS === '1') {
207+
process.stderr.write(`[skill-route-test-fixture] retained ${root}\n`)
208+
return
209+
}
210+
fs.rmSync(root, {
211+
recursive: true,
212+
force: true,
213+
maxRetries: 10,
214+
retryDelay: 250
215+
})
216+
}
211217
if (options.workspaceSkill !== false) {
212218
writeWorkspaceSkill(root, options.skillId || 'workspace-probe')
213219
}

‎scripts/run-validation.js‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,16 @@ function tryResolveAutoContinuation({ plan, candidate, authorityContext, store,
668668
}
669669
}
670670

671-
function resolveAiBudgetAuthority({ options, plan, candidate, authorityContext, activeRoot, execute, manifest = null }) {
671+
function resolveAiBudgetAuthority({
672+
options,
673+
plan,
674+
candidate,
675+
authorityContext,
676+
activeRoot,
677+
execute,
678+
manifest = null,
679+
gitRepoRoot = ROOT
680+
}) {
672681
if (!authorityContext.taskIdentity || !authorityContext.sessionKey || !authorityContext.taskRecoveryKey) {
673682
throw new ValidationDagError('VALIDATION_AI_TASK_BINDING_REQUIRED', 'AI BudgetCard authority requires one current formal task session')
674683
}
@@ -702,7 +711,8 @@ function resolveAiBudgetAuthority({ options, plan, candidate, authorityContext,
702711
plan,
703712
candidate,
704713
control,
705-
authorityContext
714+
authorityContext,
715+
repoRoot: gitRepoRoot
706716
})
707717
if (!controlValidation.valid && !expiredRootContinuation) {
708718
if (!execute) {

‎scripts/test-validation-budget-control.js‎

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,44 @@ function persistRepairCloseout({ activeRoot, metaDir, identity, sessionKey, repa
262262
return read.state
263263
}
264264

265+
function createGitLineageFixture(fixtureRoot) {
266+
const repoRoot = path.join(fixtureRoot, 'git-lineage')
267+
fs.mkdirSync(repoRoot, { recursive: true })
268+
execFileSync('git', ['init', '--quiet'], {
269+
cwd: repoRoot,
270+
windowsHide: true
271+
})
272+
const heads = []
273+
for (let index = 0; index < 3; index += 1) {
274+
const timestamp = `2020-01-01T00:00:0${index}Z`
275+
execFileSync('git', [
276+
'-c', 'user.name=DevCodex Test',
277+
'-c', 'user.email=devcodex-test@example.invalid',
278+
'-c', 'commit.gpgSign=false',
279+
'commit', '--allow-empty', '--quiet', '--no-gpg-sign', '-m', `lineage-${index}`
280+
], {
281+
cwd: repoRoot,
282+
windowsHide: true,
283+
env: {
284+
...process.env,
285+
GIT_AUTHOR_DATE: timestamp,
286+
GIT_COMMITTER_DATE: timestamp
287+
}
288+
})
289+
heads.push(execFileSync('git', ['rev-parse', 'HEAD'], {
290+
cwd: repoRoot,
291+
encoding: 'utf8',
292+
windowsHide: true
293+
}).trim())
294+
}
295+
return {
296+
repoRoot,
297+
ancestorHead: heads[0],
298+
previousHead: heads[1],
299+
currentHead: heads[2]
300+
}
301+
}
302+
265303
function main() {
266304
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'devcodex-validation-budget-control-'))
267305
const activeRoot = path.join(fixtureRoot, '.devcodex', 'devcodex')
@@ -604,21 +642,12 @@ function main() {
604642
sessionKey: rolloverSession,
605643
control: rolloverControl
606644
})
607-
const currentHead = execFileSync('git', ['rev-parse', 'HEAD'], {
608-
cwd: REPO_ROOT,
609-
encoding: 'utf8',
610-
windowsHide: true
611-
}).trim()
612-
const previousHead = execFileSync('git', ['rev-parse', 'HEAD^'], {
613-
cwd: REPO_ROOT,
614-
encoding: 'utf8',
615-
windowsHide: true
616-
}).trim()
617-
const ancestorHead = execFileSync('git', ['rev-parse', 'HEAD^^'], {
618-
cwd: REPO_ROOT,
619-
encoding: 'utf8',
620-
windowsHide: true
621-
}).trim()
645+
const {
646+
repoRoot: rolloverGitRoot,
647+
currentHead,
648+
previousHead,
649+
ancestorHead
650+
} = createGitLineageFixture(fixtureRoot)
622651
const rolloverRootPlan = fixturePlan(rolloverTaskId, contextEpoch, 'root-rollover-parent')
623652
const rolloverRootCandidate = { ...fixtureCandidate('root-rollover-parent'), head: ancestorHead }
624653
const rolloverContext = authorityContext({
@@ -633,7 +662,8 @@ function main() {
633662
candidate: rolloverRootCandidate,
634663
authorityContext: rolloverContext,
635664
activeRoot,
636-
execute: true
665+
execute: true,
666+
gitRepoRoot: rolloverGitRoot
637667
})
638668
const rolloverStore = createValidationEvidenceStore({
639669
activeRoot,
@@ -679,7 +709,8 @@ function main() {
679709
candidate: rolloverNextCandidate,
680710
authorityContext: rolloverNextContext,
681711
activeRoot,
682-
execute
712+
execute,
713+
gitRepoRoot: rolloverGitRoot
683714
}), 'VALIDATION_CONTINUATION_SCOPE_WIDENED')
684715
}
685716
const rolloverPreview = resolveAiBudgetAuthority({
@@ -688,7 +719,8 @@ function main() {
688719
candidate: rolloverNextCandidate,
689720
authorityContext: rolloverNextContext,
690721
activeRoot,
691-
execute: false
722+
execute: false,
723+
gitRepoRoot: rolloverGitRoot
692724
})
693725
assert.strictEqual(rolloverPreview.decision, 'auto-root-rollover-plan-only')
694726
const rolloverExecution = resolveAiBudgetAuthority({
@@ -697,7 +729,8 @@ function main() {
697729
candidate: rolloverNextCandidate,
698730
authorityContext: rolloverNextContext,
699731
activeRoot,
700-
execute: true
732+
execute: true,
733+
gitRepoRoot: rolloverGitRoot
701734
})
702735
assert.strictEqual(rolloverExecution.decision, 'auto-root-rollover-authorized')
703736
assert.strictEqual(rolloverExecution.authority.parentRootReceiptDigest, rolloverRoot.authority.receiptDigest)
@@ -738,7 +771,8 @@ function main() {
738771
candidate: completedRolloverCandidate,
739772
authorityContext: completedRolloverContext,
740773
activeRoot,
741-
execute: false
774+
execute: false,
775+
gitRepoRoot: rolloverGitRoot
742776
})
743777
assert.strictEqual(completedRolloverPreview.decision, 'auto-root-rollover-plan-only')
744778
const completedRolloverExecution = resolveAiBudgetAuthority({
@@ -747,7 +781,8 @@ function main() {
747781
candidate: completedRolloverCandidate,
748782
authorityContext: completedRolloverContext,
749783
activeRoot,
750-
execute: true
784+
execute: true,
785+
gitRepoRoot: rolloverGitRoot
751786
})
752787
assert.strictEqual(completedRolloverExecution.decision, 'auto-root-rollover-authorized')
753788
assert.strictEqual(completedRolloverExecution.authority.parentRootReceiptDigest,
@@ -1054,7 +1089,11 @@ function main() {
10541089
currentPending.bindingDigest, 'failed pending CAS must preserve the unique current binding')
10551090
process.stdout.write('test-validation-budget-control: ok\n')
10561091
} finally {
1057-
fs.rmSync(fixtureRoot, { recursive: true, force: true })
1092+
if (process.env.DEVCODEX_KEEP_TEST_ARTIFACTS === '1') {
1093+
process.stderr.write(`[test-validation-budget-control] retained ${fixtureRoot}\n`)
1094+
} else {
1095+
fs.rmSync(fixtureRoot, { recursive: true, force: true })
1096+
}
10581097
}
10591098
}
10601099

0 commit comments

Comments
 (0)