Skip to content

Commit e13798b

Browse files
committed
fix: stabilize validation entrypoints and profile refresh
1 parent 86da1f4 commit e13798b

10 files changed

Lines changed: 289 additions & 28 deletions

‎changelogs/unreleased.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- **恢复与验证**:未结算操作的 Stop 保留诊断,owner 转换传播本次提交 fence,正式终止后仅收缩会话投影而保留完整任务历史。新增隔离自然任务入口,业务结果以实际文件与计算核验,未完成观察保持 UNVERIFIED;实际发布和安装状态以发行证据为准。
1313

1414
- **本地候选修复 — 派生资产新鲜度与默认验证漏检**:刷新 `content/skills/portfolio.json`,并将 `skill-portfolio-current` 纳入 changed/iterative 默认验证不变量,使 clean-tree `npm test` 计划覆盖 portfolio freshness;同步 validation DAG 断言与 Windows 中文文件名负向测试夹具,避免默认验证对派生资产 stale false-green。本项仅为本地源码候选,未 tag、未 publish、未刷新 MCP。
15+
- **本地候选修复 — 验证入口、产物状态投影与 Profile 当前真相刷新**:收紧 active-root 产物 stale CP 状态检测,避免把“待用户确认是否提交”等非 CP 文案误判为 CP1/CP2 阻断;默认 `test:fast/full/delivery/boundary/profile-deploy/package-release` 改为 `human-cli --plan` 入口并补充 `:ai` 严格变体,避免 Codex/AI 环境缺 `contextEpoch` 时本地 plan 命令误红;新增 `profile-current:refresh` 标准命令,用于更新 active Profile `ProfileCurrentTruthV1` 的当前源码 HEAD 与 source candidate 身份。本项仅为本地源码候选,未 tag、未 publish、未刷新 MCP。
1516

1617
- **v1.20.0 发布候选 — 任务连续性与工程控制面升级**:汇总 Stage A/B 的任务写入代际、合法 CP 演进续代、taskless 恢复、项目优先 Profile、多语言连续性、分层 SkillRoute、宿主原生多 Agent 编排合同、验证执行提速与修复批次收敛。完整说明见 [`changelogs/releases/v1.20.0.md`](./releases/v1.20.0.md)。正式发布事实仍只由 tag、registry、GitHub Release 与发布后安装回读共同成立。
1718
- **两阶段精确制品发布防护**:tag 资格阶段只生成一个正式精确 tarball,对同一制品完成隔离 consumer/HOME/prefix/cache/workspace 安装并保存;本机复用该制品完成真实 Codex G1→G2 后,才通过 `publish-qualified` 续发 npm 与 GitHub Release,避免 tag 触发后未经真实宿主验收即发生不可逆发布。

‎package.json‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
"global-adapters:remove:dry-run": "node index.js global-adapters remove --dry-run",
1515
"test:global-adapters-cli": "node scripts/test-global-adapters-cli.js",
1616
"test": "node scripts/run-validation.js --route changed --actor human-cli --plan",
17-
"test:fast": "node scripts/run-validation.js --route fast",
18-
"test:full": "node scripts/run-validation.js --route full",
17+
"test:fast": "node scripts/run-validation.js --route fast --actor human-cli --plan",
18+
"test:fast:ai": "node scripts/run-validation.js --route fast --actor ai-hook",
19+
"test:full": "node scripts/run-validation.js --route full --actor human-cli --plan",
20+
"test:full:ai": "node scripts/run-validation.js --route full --actor ai-hook",
1921
"test:all": "npm run test:full",
2022
"test:core": "node scripts/validate-versions.js && node scripts/validate.js",
2123
"test:supported-runtime-control-plane": "npm run test:core && npm run test:context-read && npm run test:context-binding && npm run test:task-checkpoint-epoch && npm run test:task-continuity-stage-b && npm run test:profile-materialization-stage-b && npm run test:task-recovery && npm run test:runtime-generation-retention && npm run test:context-delivery && node scripts/test-mcp-servers.js && npm run test:memory-index && npm run test:skill-route && npm run test:cli-json-contract",
@@ -152,10 +154,14 @@
152154
"test:artifact-authority": "node scripts/test-artifact-mutation-authority.js && npm run test:artifact-slot-template-coverage",
153155
"test:changed": "node scripts/run-validation.js --route changed --actor human-cli --plan",
154156
"test:changed:ai": "node scripts/run-validation.js --route changed --actor ai-hook",
155-
"test:delivery": "node scripts/run-validation.js --route delivery",
156-
"test:boundary": "node scripts/run-validation.js --route boundary",
157-
"test:profile-deploy": "node scripts/run-validation.js --route profile-deploy",
158-
"test:package-release": "node scripts/run-validation.js --route package-release",
157+
"test:delivery": "node scripts/run-validation.js --route delivery --actor human-cli --plan",
158+
"test:delivery:ai": "node scripts/run-validation.js --route delivery --actor ai-hook",
159+
"test:boundary": "node scripts/run-validation.js --route boundary --actor human-cli --plan",
160+
"test:boundary:ai": "node scripts/run-validation.js --route boundary --actor ai-hook",
161+
"test:profile-deploy": "node scripts/run-validation.js --route profile-deploy --actor human-cli --plan",
162+
"test:profile-deploy:ai": "node scripts/run-validation.js --route profile-deploy --actor ai-hook",
163+
"test:package-release": "node scripts/run-validation.js --route package-release --actor human-cli --plan",
164+
"test:package-release:ai": "node scripts/run-validation.js --route package-release --actor ai-hook",
159165
"test:control-plane-module-contracts": "node scripts/test-control-plane-module-contracts.js",
160166
"test:closure-evidence": "node scripts/test-closure-evidence-controls.js",
161167
"test:control-plane": "npm run test:probe-registry && npm run test:cli-command-registry && npm run test:cli-json-contract && npm run test:governance-status-summary && npm run test:always-on-governance && npm run test:intent-consistency && npm run test:local-probe && npm run test:context-read && npm run test:context-binding && npm run test:context-read-controls && npm run test:tenant-selection && npm run test:workflow-capabilities && npm run test:api-verification-contracts && npm run test:git-index-isolation && npm run test:git-execution-context && npm run test:evolution-target-decision && npm run test:workspace-provisioning && npm run test:worktree-lifecycle && npm run test:control-plane-contracts && npm run test:markdown-structure && npm run test:control-plane-module-contracts && npm run test:consumer-evolution-controls && npm run test:residual-absorption-controls && npm run test:brand-visual-quality && npm run test:turn-liveness && npm run test:closure-evidence && npm run test:workflow-completion && npm run test:public-text-integrity && npm run test:external-review-claim && npm run test:candidate-review-bundle && npm run test:capability-surface-decision && npm run test:host-capability-routing && npm run test:requirement-parallel-orchestration && npm run test:evidence-freshness && npm run test:optimization-backlog-evidence && npm run test:discipline-execution && npm run test:skill-resolve && npm run test:process-enforcement-e2e && npm run test:docs-surface-inventory && npm run test:delivery-honesty && npm run test:ecr-closure",
@@ -185,6 +191,7 @@
185191
"release:dry-run:github": "node scripts/publish-dry-run.js --registry github",
186192
"release:dry-run:all": "node scripts/publish-dry-run.js --registry all",
187193
"validate": "node scripts/validate.js",
194+
"profile-current:refresh": "node scripts/refresh-profile-current-truth.js",
188195
"prepack": "node scripts/prepack-control-content.js",
189196
"postpack": "node scripts/postpack-control-content.js",
190197
"cleanup:package-projection": "node scripts/cleanup-package-projection.js",
@@ -267,6 +274,7 @@
267274
"scripts/generate-public-site-data.js",
268275
"scripts/generate-skill-intents.js",
269276
"scripts/generate-workflow-root-registry.js",
277+
"scripts/refresh-profile-current-truth.js",
270278
"scripts/generate-host-instruction-projections.js",
271279
"scripts/host-instruction-projection.json",
272280
"scripts/postinstall.js",

‎scripts/lib/requirement-artifact-check.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,15 @@ const STATUS_PROJECTION_LINE_RE = /(?:状态|status|当前\s*CP|CP\s*状态|next
4444
const STALE_PHASE_STATUS_PATTERNS = Object.freeze({
4545
cp1: [
4646
/CP1\s*(?:未确认|待确认|待用户确认)/i,
47-
/(?:待|等待)(?:用户)?确认\s*(?:CP1|需求确认|问题确认)?/i
47+
/(?:待|等待)(?:用户)?确认\s*(?:CP1|需求确认|问题确认)/i,
48+
/(?:待|等待)\s*(?:CP1|需求确认|问题确认)/i
4849
],
4950
cp2: [
5051
/待\s*CP2/i,
5152
/等待\s*CP2/i,
5253
/CP2\s*(?:未确认|待确认|待用户确认|待用户决策|候选|未进入)/i,
53-
/(?:待|等待)用户确认\s*(?:CP2|技术方案|修复方案)?/i
54+
/(?:待|等待)用户确认\s*(?:CP2|技术方案|修复方案)/i,
55+
/(?:技术方案|修复方案)\s*(?:未确认|待确认|待用户确认)/i
5456
],
5557
cp3: [
5658
/待\s*CP3/i,

‎scripts/lib/validate-governance-intake.js‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,11 @@ function buildGovernanceIntakeChecks(ctx) {
288288
const testRouter = read(path.join(ROOT, 'content', 'skills', 'test-router', 'SKILL.md'))
289289

290290
const scriptExpectations = [
291-
['test', 'node scripts/run-validation.js --route changed'],
292-
['test:fast', 'node scripts/run-validation.js --route fast'],
293-
['test:full', 'node scripts/run-validation.js --route full'],
294-
['test:delivery', 'node scripts/run-validation.js --route delivery'],
295-
['test:boundary', 'node scripts/run-validation.js --route boundary'],
291+
['test', 'node scripts/run-validation.js --route changed --actor human-cli --plan'],
292+
['test:fast', 'node scripts/run-validation.js --route fast --actor human-cli --plan'],
293+
['test:full', 'node scripts/run-validation.js --route full --actor human-cli --plan'],
294+
['test:delivery', 'node scripts/run-validation.js --route delivery --actor human-cli --plan'],
295+
['test:boundary', 'node scripts/run-validation.js --route boundary --actor human-cli --plan'],
296296
['test:validation-dag', 'node scripts/test-validation-dag.js'],
297297
['test:all', 'npm run test:full'],
298298
['test:all:with-audit', 'npm run test:audit'],
@@ -303,6 +303,12 @@ function buildGovernanceIntakeChecks(ctx) {
303303
const value = scripts[scriptName] || ''
304304
if (!value.includes(needle)) err(`[V42] package.json script ${scriptName} missing "${needle}"`)
305305
}
306+
for (const route of ['fast', 'full', 'changed', 'delivery', 'boundary', 'profile-deploy', 'package-release']) {
307+
const scriptName = `test:${route}:ai`
308+
if (!scripts[scriptName]?.includes(`--route ${route}`) || !scripts[scriptName]?.includes('--actor ai-hook')) {
309+
err(`[V42] package.json script ${scriptName} missing strict AI validation actor`)
310+
}
311+
}
306312
const validationManifest = JSON.parse(read(path.join(ROOT, 'scripts', 'validation-manifest.json')))
307313
for (const route of ['fast', 'full', 'changed', 'delivery', 'boundary', 'profile-deploy', 'package-release']) {
308314
if (!validationManifest.routes?.[route]) err(`[V42] validation manifest missing route ${route}`)

‎scripts/lib/validate-optimization-controls.js‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,22 @@ function buildOptimizationControlChecks(ctx) {
9191
]) {
9292
if (!pkg.scripts?.[script]) err(`[V92] missing package script: ${script}`)
9393
}
94-
if (pkg.scripts?.test !== 'node scripts/run-validation.js --route changed' ||
95-
pkg.scripts?.['test:fast'] !== 'node scripts/run-validation.js --route fast' ||
96-
pkg.scripts?.['test:full'] !== 'node scripts/run-validation.js --route full' ||
97-
pkg.scripts?.['test:delivery'] !== 'node scripts/run-validation.js --route delivery' ||
98-
pkg.scripts?.['test:boundary'] !== 'node scripts/run-validation.js --route boundary') {
99-
err('[V92] stable test entry points must route through the canonical validation manifest')
94+
if (pkg.scripts?.test !== 'node scripts/run-validation.js --route changed --actor human-cli --plan' ||
95+
pkg.scripts?.['test:fast'] !== 'node scripts/run-validation.js --route fast --actor human-cli --plan' ||
96+
pkg.scripts?.['test:full'] !== 'node scripts/run-validation.js --route full --actor human-cli --plan' ||
97+
pkg.scripts?.['test:delivery'] !== 'node scripts/run-validation.js --route delivery --actor human-cli --plan' ||
98+
pkg.scripts?.['test:boundary'] !== 'node scripts/run-validation.js --route boundary --actor human-cli --plan') {
99+
err('[V92] stable test entry points must route through the canonical validation manifest as human-safe plan entries')
100+
}
101+
for (const route of ['fast', 'full', 'changed', 'delivery', 'boundary', 'profile-deploy', 'package-release']) {
102+
const aiScript = `test:${route}:ai`
103+
if (route === 'changed') {
104+
if (pkg.scripts?.[aiScript] !== 'node scripts/run-validation.js --route changed --actor ai-hook') {
105+
err(`[V92] missing strict AI validation entry point: ${aiScript}`)
106+
}
107+
} else if (pkg.scripts?.[aiScript] !== `node scripts/run-validation.js --route ${route} --actor ai-hook`) {
108+
err(`[V92] missing strict AI validation entry point: ${aiScript}`)
109+
}
100110
}
101111
if (pkg.scripts?.['test:skill-portfolio:staged'] !== 'node scripts/generate-skill-portfolio.js --check-staged') {
102112
err('[V92] staged Skill portfolio command must target the Git index candidate')
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env node
2+
'use strict'
3+
4+
const fs = require('fs')
5+
const path = require('path')
6+
const { execFileSync } = require('child_process')
7+
const { resolveProfileDir } = require('../hooks/_runtime/workspace-layout.cjs')
8+
const {
9+
extractWorkflowCurrentTruth,
10+
parseProfileCurrentTruth,
11+
validateDevCodexCurrentTruth
12+
} = require('./lib/profile-current-truth')
13+
const { buildCandidateIdentity } = require('./lib/validation-dag')
14+
15+
const ROOT = path.resolve(__dirname, '..')
16+
const TRUTH_HEADING_RE = /^## ProfileCurrentTruthV1[ \t]*\n```json\n[\s\S]*?\n```(?:\n|$)/m
17+
18+
function argValue(args, name) {
19+
const index = args.indexOf(name)
20+
if (index === -1 || index + 1 >= args.length) return ''
21+
return args[index + 1]
22+
}
23+
24+
function readJson(filePath) {
25+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
26+
}
27+
28+
function currentGitHead(repoRoot) {
29+
return execFileSync('git', ['rev-parse', 'HEAD'], {
30+
cwd: repoRoot,
31+
encoding: 'utf8',
32+
windowsHide: true,
33+
stdio: ['ignore', 'pipe', 'pipe']
34+
}).trim()
35+
}
36+
37+
function isoNoMillis(date = new Date()) {
38+
return date.toISOString().replace(/\.\d{3}Z$/, 'Z')
39+
}
40+
41+
function buildRefreshedCurrentTruthRecord(record, input) {
42+
const packageVersion = input.packageVersion || record.sourceVersion
43+
const npmLatest = record.npmLatest || packageVersion
44+
const gitHead = input.gitHead || record.gitHead
45+
const candidateId = input.candidateId || record.sourceCandidate?.candidateId || ''
46+
const ciMatrix = input.ciMatrix || record.ciMatrix
47+
const identityMatches = record.sourceVersion === packageVersion &&
48+
record.npmLatest === npmLatest &&
49+
record.gitHead === gitHead &&
50+
record.sourceCandidate?.candidateId === candidateId &&
51+
JSON.stringify(record.ciMatrix || null) === JSON.stringify(ciMatrix || null)
52+
const now = input.now || (identityMatches && record.asOf ? record.asOf : isoNoMillis())
53+
const releaseCommit = record.releaseCommit || record.gitHead
54+
const exactReleasedHead = gitHead === releaseCommit && packageVersion === npmLatest
55+
56+
const next = {
57+
...record,
58+
sourceVersion: packageVersion,
59+
npmLatest,
60+
gitHead,
61+
releaseCommit,
62+
ciMatrix,
63+
asOf: now
64+
}
65+
66+
if (exactReleasedHead && record.sourceCandidate?.status === 'RELEASED') {
67+
next.releaseState = record.releaseState || `${packageVersion} released`
68+
next.sourceCandidate = {
69+
...record.sourceCandidate,
70+
candidateId,
71+
remoteCi: {
72+
...record.sourceCandidate.remoteCi,
73+
head: gitHead
74+
}
75+
}
76+
return next
77+
}
78+
79+
delete next.candidate
80+
next.releaseState = `${npmLatest}发行事实保持有效;当前HEAD及工作区为尚未选定下一版本的本地工作态,资格与远端CI待验证`
81+
next.sourceCandidate = {
82+
schemaVersion: 'SourceCandidateTruthV1',
83+
...(record.sourceCandidate || {}),
84+
candidateId,
85+
status: 'LOCAL_PENDING',
86+
localQualification: {
87+
status: 'UNVERIFIED',
88+
runId: 'not-qualified-working-source',
89+
observedAt: identityMatches
90+
? (record.sourceCandidate?.localQualification?.observedAt || now)
91+
: now
92+
},
93+
remoteCi: {
94+
status: 'UNVERIFIED',
95+
runId: 'not-pushed',
96+
head: gitHead,
97+
observedAt: identityMatches
98+
? (record.sourceCandidate?.remoteCi?.observedAt || now)
99+
: now
100+
},
101+
releaseAuthorized: false
102+
}
103+
return next
104+
}
105+
106+
function replaceCurrentTruthBlock(markdown, record) {
107+
const nextBlock = `## ProfileCurrentTruthV1\n\`\`\`json\n${JSON.stringify(record, null, 2)}\n\`\`\`\n`
108+
if (!TRUTH_HEADING_RE.test(markdown)) {
109+
throw new Error('ProfileCurrentTruthV1 block not found or not a strict json fence')
110+
}
111+
return markdown.replace(TRUTH_HEADING_RE, nextBlock)
112+
}
113+
114+
function refreshProfileCurrentTruth(options = {}) {
115+
const repoRoot = path.resolve(options.repoRoot || ROOT)
116+
const profileDir = path.resolve(options.profileDir || resolveProfileDir(repoRoot))
117+
const releaseProfilePath = path.join(profileDir, '05-发布规范.md')
118+
const releaseProfileText = fs.readFileSync(releaseProfilePath, 'utf8')
119+
const parsed = parseProfileCurrentTruth(releaseProfileText, { required: true })
120+
if (!parsed.valid || !parsed.record) {
121+
const error = new Error(`current Profile truth is invalid: ${parsed.errors.join('; ')}`)
122+
error.errors = parsed.errors
123+
throw error
124+
}
125+
126+
const packageVersion = readJson(path.join(repoRoot, 'package.json')).version
127+
const gitHead = currentGitHead(repoRoot)
128+
const candidateId = buildCandidateIdentity({ repoRoot }).candidateId
129+
const workflowText = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'ci.yml'), 'utf8')
130+
const validationManifest = readJson(path.join(repoRoot, 'scripts', 'validation-manifest.json'))
131+
const nextRecord = buildRefreshedCurrentTruthRecord(parsed.record, {
132+
packageVersion,
133+
gitHead,
134+
candidateId,
135+
ciMatrix: extractWorkflowCurrentTruth(workflowText, validationManifest),
136+
now: options.now
137+
})
138+
const nextText = replaceCurrentTruthBlock(releaseProfileText, nextRecord)
139+
const changed = nextText !== releaseProfileText
140+
141+
const validation = validateDevCodexCurrentTruth({
142+
releaseProfileText: nextText,
143+
overviewProfileText: fs.readFileSync(path.join(profileDir, '01-项目信息.md'), 'utf8'),
144+
testProfileText: fs.readFileSync(path.join(profileDir, '04-测试规范.md'), 'utf8'),
145+
docsProfileText: fs.readFileSync(path.join(profileDir, '07-用户文档与契约规范.md'), 'utf8'),
146+
packageVersion,
147+
gitHead,
148+
candidateId,
149+
requireSourceCandidate: true,
150+
workflowText,
151+
validationManifest
152+
})
153+
if (!validation.valid) {
154+
const error = new Error(`refreshed ProfileCurrentTruthV1 is invalid: ${validation.errors.join('; ')}`)
155+
error.errors = validation.errors
156+
throw error
157+
}
158+
159+
if (!options.check && changed) fs.writeFileSync(releaseProfilePath, nextText, 'utf8')
160+
return { releaseProfilePath, changed, record: nextRecord }
161+
}
162+
163+
function main() {
164+
const args = process.argv.slice(2)
165+
const result = refreshProfileCurrentTruth({
166+
repoRoot: argValue(args, '--project-root') || ROOT,
167+
profileDir: argValue(args, '--profile-dir') || '',
168+
check: args.includes('--check')
169+
})
170+
const action = result.changed ? (args.includes('--check') ? 'needs-refresh' : 'refreshed') : 'already-current'
171+
console.log(`[profile-current:refresh] ${action}: ${result.releaseProfilePath}`)
172+
if (args.includes('--check') && result.changed) process.exitCode = 1
173+
}
174+
175+
if (require.main === module) {
176+
try {
177+
main()
178+
} catch (error) {
179+
console.error(`[profile-current:refresh] ${error.message}`)
180+
process.exitCode = 1
181+
}
182+
}
183+
184+
module.exports = {
185+
buildRefreshedCurrentTruthRecord,
186+
refreshProfileCurrentTruth,
187+
replaceCurrentTruthBlock
188+
}

‎scripts/test-readme-governance.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const readmeGovernanceNode = validationManifest.nodes.find(node => node.id === '
141141
const fullRouteNodes = validationManifest.routes.full && validationManifest.routes.full.nodes
142142
const readmeGovernanceCovered = Boolean(
143143
testAllScript.trim() === 'npm run test:full' &&
144-
testScript === 'node scripts/run-validation.js --route changed' &&
144+
testScript === 'node scripts/run-validation.js --route changed --actor human-cli --plan' &&
145145
readmeGovernanceNode &&
146146
readmeGovernanceNode.command === 'node' &&
147147
Array.isArray(readmeGovernanceNode.args) &&

0 commit comments

Comments
 (0)