Skip to content

Commit 3e2d138

Browse files
committed
feat: add local observability and schema contracts
1 parent 8b56575 commit 3e2d138

61 files changed

Lines changed: 6363 additions & 192 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ DevCodex 通过 `.github/`(Copilot)、`CLAUDE.md + .claude/ + .mcp.json`(C
4040
- **自动报告**: 每次会话自动写入报告,从不询问 — 直接执行
4141
- **安全底线**: S01~S07 七条不可覆盖的安全规则
4242
- **宿主生命周期护栏**: Claude Code 与 OpenAI Codex 在已支持的 Hook 事件上提供 runtime 护栏;Copilot / JetBrains / Cursor 等无等价本地 Hook 时降级为 instruction-fallback;默认 `safety-only` 仅对危险命令硬拦,流程项提醒放行,`strict` 模式才升级可阻断事件
43-
- **长任务 Turn Liveness**: `TurnLivenessRecoveryGate` 记录 `running / awaiting-continuation / suspect / stalled-recoverable / terminal` 状态、工具租约、continuation ACK 与恢复检查点;Hook 只能在事件到达时判断历史停滞,不能自行唤醒宿主、重放写操作或把 `PostToolUse` 当成任务完成
43+
- **长任务 Turn Liveness**: `TurnLivenessRecoveryGate` 记录 `running / awaiting-continuation / suspect / stalled-recoverable / terminal` 状态、工具租约、continuation ACK、双阶段 checkpoint 与当前 turn 的 `LocalTaskTraceV1`;Hook 只能在事件到达时判断历史停滞,trace replay 只返回数据,不能自行唤醒宿主、执行 payload、重放写操作或把 `PostToolUse` 当成任务完成
4444
- **全模式入口检查**: 所有模式在实质任务前显示 PC0~PC7;dev 模式额外执行 PC4 规范雷达与完整合规链
4545
- **项目现实扩展**: 先做语义意图初判,再结合目标项目 Profile、目录与当前任务上下文修正最终路由、产物落点和验证方式
4646
- **可配置并发策略**: Profile `config.json` 可配置 `extensions.devcodex.concurrency`;默认 `auto` 表示只读准备和隔离验证可并行、共享状态写入保持单写者,保守项目可设为 `serial`
@@ -64,7 +64,7 @@ DevCodex 通过 `.github/`(Copilot)、`CLAUDE.md + .claude/ + .mcp.json`(C
6464
- **授权本地安全审查呈现**: `AuthorizedLocalSecurityAuditPresentationGate``security-threat-modeling` 承接,分离用户可见最小证据和隔离本地探针;内容不可见或额外安全检查发生时保存 `SafetyInterruptionCard` 并从文件真相/审查状态恢复,不把表达调整描述成绕过平台控制。V89 验证授权、证据预算与恢复链。
6565
- **发布凭据拓扑**: `PublisherCredentialTopologyGate``release-verification` 承接;首次发布或 publisher/repository/package/registry/auth topology 变化时核对发布身份、secret scope/access/inheritance、workflow permission、package ownership 和最近成功 run。只验证拓扑,不读取或输出 secret value;普通 patch 可记录 unchanged evidence。V90 与 R0~R7 共同守门。
6666
- **scoped registry 目标解析**: `ScopedRegistryResolutionGate``release-verification` 承接;scoped package 双仓发布必须同时冻结 global registry、`@scope:registry`、userconfig 与命令级 override,用隔离配置或显式 scope override 证明两通道独立解析。V92 与 targeted fixture 防止相同 scope 路由制造双仓假阳性。
67-
- **Profile 生成与三档闭环校验**: `ProfileGenerationContractGate` 统一 `profile-lite` / `profile-standard` / `profile-closed-loop` 的生成、加载、状态和校验契约;`FeatureInventorySchemaGate` 要求规范功能清单使用 `FeatureInventorySchemaV1``ProfileTierMigrationSafetyGate` 保证 plan/dry-run 零写入、升级保留正文、降档显式授权;`ProfileTierStandardGate``ProfileLifecycleClassificationGate``AllDevCodexProfileValidationGate` 继续负责档位、生命周期和全工作区校验
67+
- **Profile 生成与三档闭环校验**: `ProfileGenerationContractGate` 统一 `profile-lite` / `profile-standard` / `profile-closed-loop` 的生成、加载、状态和校验契约;`FeatureInventorySchemaGate` 要求新生成的规范功能清单使用 `FeatureInventorySchemaV2` 并兼容读取 V1,分离生命周期、证据状态、日期与引用;`ProfileTierMigrationSafetyGate` 保证 plan/dry-run 零写入、升级保留正文、降档显式授权;`ProfileTierStandardGate``ProfileLifecycleClassificationGate``AllDevCodexProfileValidationGate` 继续负责档位、生命周期和全工作区校验
6868
- **项目工程泄漏审查**: 项目工程 / 代码质量审查执行 `PE-12 资源生命周期与泄漏风险`,必须检查内存泄露、资源泄漏、监听器/定时器/连接/流未释放、缓存无界增长和组件卸载清理缺失
6969
- **泄漏风险稳定性压测**: 写测试用例或回归验证时先执行 `LeakRiskStabilityPressureTest` 条件判定;命中长运行、高并发、缓存/连接/监听器/定时器/流/socket/worker/订阅/组件生命周期或 `PE-12` 风险时,TestRoute 纳入场景/负载/稳定性压测并记录基线、冷却后回落和资源指标前后对比;低风险任务写 `N/A + skipReason`
7070
- **coverage 与外部 runtime 生命周期验证**: 项目存在 coverage 阈值、CI coverage 或发布覆盖率要求时执行 `CoverageGateDecision`,区分断言通过与覆盖率门禁通过;外部 runtime/plugin/registry/adapter/provider、injected runtime、owner mutation 或 function source fingerprint 风险执行 `ExternalRuntimePluginLifecycleGate``ExternalRegistryLifecycleMatrixGate``FunctionSourceFingerprintMatrixGate``ClusterEscalationGate``RiskBasedValidationLadder`
@@ -279,8 +279,10 @@ AGENTS.md ← 与 instructions.md / copilot-instructions.md / CL
279279
| `devcodex migrate-layout plan` | 生成 `.devcodex` 工作区集中布局迁移清单 |
280280
| `devcodex migrate-layout apply --manifest <path>` | 按 manifest 执行集中布局切换 |
281281
| `devcodex migrate-layout rollback --manifest <path>` | 回滚集中布局迁移 |
282-
| `devcodex status` | 状态:检查已安装的组件 |
283-
| `devcodex doctor` | 诊断当前宿主、Agent、Hook、Profile 与记忆状态 |
282+
| `devcodex status [--json]` | 状态:检查已安装组件;JSON 模式返回 `StatusDiagnosticV1` |
283+
| `devcodex doctor [--json]` | 诊断当前宿主、Agent、Hook、Profile 与记忆状态;JSON 模式返回 `DoctorDiagnosticV1` |
284+
| `devcodex probe [id ...] [--json]` | 运行同步、local-only、只读 typed probes;默认包含 host/workspace/profile |
285+
| `devcodex trace show\|replay [--state <file>] [--json]` | 查看或校验重放当前 turn trace 的只读数据投影;不执行 payload 或 mutation |
284286
| `devcodex help` | 查看 CLI 子命令与选项帮助 |
285287
| `devcodex init --dry-run` | 预览模式:仅显示将复制的文件 |
286288

@@ -319,10 +321,15 @@ devcodex profile init --tier profile-standard
319321
devcodex status
320322
```
321323

324+
自动化脚本可使用 `devcodex status --json` / `devcodex doctor --json`。两者只输出一个 `DevCodexCliEnvelopeV1` JSON 文档;非法参数返回稳定 `CLI_INVALID_OPTION` 和退出码 2,默认人读输出保持兼容。
325+
326+
本地维护者还可运行 `devcodex probe --json` 获取 host/workspace/profile 的 typed 只读结果,或用 `devcodex trace show|replay --state <lifecycle-state.json> --json` 检查 `LocalTaskTraceV1`。probe 不联网、不监听、不写状态;trace replay 不执行事件 payload,输出会携带源文件 SHA-256 便于 zero-write 对账。
327+
322328
- 首次创建默认目标是 `profile-lite`,命令会另外显示基于 package、脚本和目录证据得出的推荐档位;只有显式 `--tier` 才升级。
323329
- 已有 Profile 默认继承当前档位;升级只补缺失文件并保留原正文。显式降档必须追加 `--allow-downgrade`,高档文件仍会保留。
324330
- `profile plan` 等价于安全预览,和 `profile init --dry-run` 一样不会创建目录、文件或备份;`--force` 会在覆盖前备份。
325-
- 三档默认生成矩阵为 **5 / 8 / 9**:lite 生成 README、01~03、config;standard 再生成 04/05/06;closed-loop 再生成 07。规范清单采用 `FeatureInventorySchemaV1` 十字段表,扫描无法证明的事实保持 `unverified`,不会伪装成已发布能力。
331+
- 三档默认生成矩阵为 **5 / 8 / 9**:lite 生成 README、01~03、config;standard 再生成 04/05/06;closed-loop 再生成 07。规范清单采用 `FeatureInventorySchemaV2` 十四字段表并兼容读取 V1;扫描无法证明的事实保持 `unverified`,不会因文档存在伪装成 implemented/validated/released。
332+
- Skill portfolio 使用 schema v2:每项保留确定性 `SkillIndexV2` 投影;`BundleDecisionV1` 只读输出 selected/ignored/conflict/budget/exit,不修改 `plugin.json` lifecycle。
326333

327334
完整命令、三档文件矩阵、迁移和排错见 [Profile 使用指南](./website/docs/guide/profile.md)
328335

@@ -501,6 +508,8 @@ DevCodex Hook runtime 不再把所有拦截都等同为“停止”。拦截会
501508
8. **长任务在工具输出后看起来一直挂着**
502509
- `PostToolUse` 只表示工具返回,Turn Liveness 会先进入 `awaiting-continuation`;120 秒后记为 `suspect`,300 秒后记为 `stalled-recoverable`
503510
- 有后续 Hook 事件时,runtime 会基于 checkpoint 生成一次性 `TurnRecoveryCard`;宿主没有继续派发事件时,Hook 本身无法主动唤醒任务
511+
- `CheckpointValidationResultV1` 分开记录 response-time 与 post-execution;PostToolUse/PreCompact 或缺失证据不能让 post-execution 通过,只有实际 Stop terminal evidence 才能完成
512+
- `devcodex trace show|replay --state <lifecycle-state.json> --json` 可检查当前 turn 的 sequence/duplicate/terminal;replay 只读且不会执行 payload
504513
- 工具或 Agent 仍持有有效长租约时不会按 120/300 秒误判;任何恢复都不得自动重放未知副作用的写操作
505514
- gray sidecar 可执行 `npm run check:turn-liveness -- --state <lifecycle-state.json> --json`;安装包消费者可直接运行 `node node_modules/@vextjs/devcodex/scripts/check-turn-liveness.js --state <lifecycle-state.json> --json`
506515
- sidecar 只做一次读取和分类,不 watch、不写状态、不唤醒宿主、不重放操作、不控制进程;输出 `sidecar-observed` 不能冒充 `host-native-verified`

changelogs/unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- `RuntimeStateTransitionProjectionGate` 将 append-only 历史迁移、当前权威投影、低权威 consumer drift 和真正 `CONFLICTING_CURRENT_STATE` 分离,保留 schema v1/read-only 兼容并降低 strict conflict 噪声。
1717
- `ProfileReleaseTruthAuthorityMatrixGate` 以 package/plugin 为 DevCodex release authority,对账 project 01/05/07 与 workspace current claim;历史 release/versioned docs 不被批量误判。
1818
- Skill portfolio 更新为当前源码 77(74 active + 3 gray),同步 gate registry、CP/TestRoute/report/prompts、README/website/Profile、V92/V95/V97 与 package closure;版本仍为 1.14.0,本批不 tag、不 publish。
19+
- 吸纳 v2 规划的 A/B 本地薄切片:`status/doctor --json` 统一诊断 envelope、IntentConsistencyGuard-lite、Skill Portfolio schema v2/BundleDecisionV1、FeatureInventorySchemaV2 状态与证据投影,以及 typed local probe、双阶段 CheckpointValidation、`LocalTaskTraceV1` 与只读 `probe/trace` CLI;保留 CLI 人读、Profile V1 兼容、77/74/3 lifecycle 和 Hook 无事件不自唤醒边界,版本仍为 1.14.0。
1920

2021
## 记录提示
2122

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
'use strict'
2+
3+
const CHECKPOINT_VALIDATION_SCHEMA_VERSION = 'CheckpointValidationResultV1'
4+
const CHECKPOINT_MODES = new Set(['response-time', 'post-execution'])
5+
6+
function nowIso(options = {}) {
7+
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now()
8+
return { nowMs, observedAt: new Date(nowMs).toISOString() }
9+
}
10+
11+
function stringList(value) {
12+
return [...new Set((Array.isArray(value) ? value : [])
13+
.map(item => String(item || '').trim())
14+
.filter(Boolean))]
15+
}
16+
17+
function evidenceList(value) {
18+
return (Array.isArray(value) ? value : [])
19+
.filter(item => item !== null && item !== undefined)
20+
.map(item => (item && typeof item === 'object' ? { ...item } : { value: String(item) }))
21+
}
22+
23+
function normalizeDeadline(value) {
24+
const text = String(value || '')
25+
return Number.isFinite(Date.parse(text)) ? new Date(Date.parse(text)).toISOString() : null
26+
}
27+
28+
/** Evaluate response-time or post-execution evidence without inferring missing host results. */
29+
function validateCheckpointEvidence(input = {}, options = {}) {
30+
const { nowMs, observedAt } = nowIso(options)
31+
const mode = String(input.mode || '')
32+
const evidence = evidenceList(input.evidence)
33+
const blockingIssues = stringList(input.blockingIssues)
34+
const suppliedActions = stringList(input.requiredActions)
35+
const deadlineAt = normalizeDeadline(input.deadlineAt)
36+
37+
if (!CHECKPOINT_MODES.has(mode)) {
38+
return {
39+
schemaVersion: CHECKPOINT_VALIDATION_SCHEMA_VERSION,
40+
mode,
41+
status: 'blocked',
42+
blocking: true,
43+
requiredActions: suppliedActions.length ? suppliedActions : ['select-response-time-or-post-execution-mode'],
44+
evidenceState: 'unverified',
45+
evidence,
46+
blockingIssues,
47+
observedAt,
48+
errorCode: 'CHECKPOINT_MODE_INVALID',
49+
deadlineAt
50+
}
51+
}
52+
53+
if (blockingIssues.length) {
54+
return {
55+
schemaVersion: CHECKPOINT_VALIDATION_SCHEMA_VERSION,
56+
mode,
57+
status: 'blocked',
58+
blocking: true,
59+
requiredActions: suppliedActions.length ? suppliedActions : blockingIssues,
60+
evidenceState: evidence.length ? 'source-backed' : 'unverified',
61+
evidence,
62+
blockingIssues,
63+
observedAt,
64+
errorCode: null,
65+
deadlineAt
66+
}
67+
}
68+
69+
if (!evidence.length) {
70+
const timedOut = mode === 'post-execution' && deadlineAt && nowMs >= Date.parse(deadlineAt)
71+
return {
72+
schemaVersion: CHECKPOINT_VALIDATION_SCHEMA_VERSION,
73+
mode,
74+
status: timedOut ? 'incomplete-timeout' : 'unverified',
75+
blocking: mode === 'post-execution',
76+
requiredActions: suppliedActions.length
77+
? suppliedActions
78+
: [timedOut ? 'record-timeout-and-recover' : `collect-${mode}-evidence`],
79+
evidenceState: 'unverified',
80+
evidence,
81+
blockingIssues: [],
82+
observedAt,
83+
errorCode: timedOut ? 'TRACE_COMPLETION_TIMEOUT' : 'HOST_RESULT_INCOMPLETE',
84+
deadlineAt
85+
}
86+
}
87+
88+
return {
89+
schemaVersion: CHECKPOINT_VALIDATION_SCHEMA_VERSION,
90+
mode,
91+
status: 'pass',
92+
blocking: false,
93+
requiredActions: suppliedActions,
94+
evidenceState: 'verified',
95+
evidence,
96+
blockingIssues: [],
97+
observedAt,
98+
errorCode: null,
99+
deadlineAt
100+
}
101+
}
102+
103+
function normalizeCheckpointValidationResult(raw, mode, options = {}) {
104+
const source = raw && typeof raw === 'object' ? raw : {}
105+
const evidence = evidenceList(source.evidence)
106+
if (!evidence.length || source.status === 'unverified' || source.status === 'incomplete-timeout') {
107+
return validateCheckpointEvidence({
108+
mode,
109+
evidence,
110+
blockingIssues: source.blockingIssues,
111+
requiredActions: source.requiredActions,
112+
deadlineAt: source.deadlineAt
113+
}, options)
114+
}
115+
if (source.status === 'blocked') {
116+
return validateCheckpointEvidence({
117+
mode,
118+
evidence,
119+
blockingIssues: stringList(source.blockingIssues).length ? source.blockingIssues : ['persisted-blocking-checkpoint'],
120+
requiredActions: source.requiredActions,
121+
deadlineAt: source.deadlineAt
122+
}, options)
123+
}
124+
return validateCheckpointEvidence({ mode, evidence, deadlineAt: source.deadlineAt }, options)
125+
}
126+
127+
function createCheckpointValidationSet(options = {}) {
128+
return {
129+
responseTime: validateCheckpointEvidence({ mode: 'response-time' }, options),
130+
postExecution: validateCheckpointEvidence({ mode: 'post-execution' }, options)
131+
}
132+
}
133+
134+
function normalizeCheckpointValidationSet(raw, options = {}) {
135+
const source = raw && typeof raw === 'object' ? raw : {}
136+
return {
137+
responseTime: normalizeCheckpointValidationResult(source.responseTime, 'response-time', options),
138+
postExecution: normalizeCheckpointValidationResult(source.postExecution, 'post-execution', options)
139+
}
140+
}
141+
142+
module.exports = {
143+
CHECKPOINT_MODES,
144+
CHECKPOINT_VALIDATION_SCHEMA_VERSION,
145+
createCheckpointValidationSet,
146+
normalizeCheckpointValidationResult,
147+
normalizeCheckpointValidationSet,
148+
validateCheckpointEvidence
149+
}

0 commit comments

Comments
 (0)